opus-agent-base 0.1.0__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.
- opus_agent_base-0.1.0/.gitignore +2 -0
- opus_agent_base-0.1.0/.python-version +1 -0
- opus_agent_base-0.1.0/PKG-INFO +20 -0
- opus_agent_base-0.1.0/pyproject.toml +34 -0
- opus_agent_base-0.1.0/src/opus_agent_base/__init__.py +21 -0
- opus_agent_base-0.1.0/src/opus_agent_base/agent/__init__.py +1 -0
- opus_agent_base-0.1.0/src/opus_agent_base/agent/agent_builder.py +91 -0
- opus_agent_base-0.1.0/src/opus_agent_base/agent/agent_manager.py +157 -0
- opus_agent_base-0.1.0/src/opus_agent_base/agent/agent_runner.py +52 -0
- opus_agent_base-0.1.0/src/opus_agent_base/cli/__init__.py +1 -0
- opus_agent_base-0.1.0/src/opus_agent_base/cli/cli.py +293 -0
- opus_agent_base-0.1.0/src/opus_agent_base/common/__init__.py +1 -0
- opus_agent_base-0.1.0/src/opus_agent_base/common/datetime_helper.py +157 -0
- opus_agent_base-0.1.0/src/opus_agent_base/common/logging_config.py +177 -0
- opus_agent_base-0.1.0/src/opus_agent_base/config/__init__.py +1 -0
- opus_agent_base-0.1.0/src/opus_agent_base/config/config_command_manager.py +191 -0
- opus_agent_base-0.1.0/src/opus_agent_base/config/config_manager.py +102 -0
- opus_agent_base-0.1.0/src/opus_agent_base/config/nested_config_manager.py +102 -0
- opus_agent_base-0.1.0/src/opus_agent_base/model/__init__.py +1 -0
- opus_agent_base-0.1.0/src/opus_agent_base/model/model_manager.py +72 -0
- opus_agent_base-0.1.0/src/opus_agent_base/prompt/__init__.py +1 -0
- opus_agent_base-0.1.0/src/opus_agent_base/prompt/instructions_manager.py +67 -0
- opus_agent_base-0.1.0/src/opus_agent_base/tools/__init__.py +1 -0
- opus_agent_base-0.1.0/src/opus_agent_base/tools/custom_tool.py +28 -0
- opus_agent_base-0.1.0/src/opus_agent_base/tools/custom_tools_manager.py +40 -0
- opus_agent_base-0.1.0/src/opus_agent_base/tools/fastmcp_client_helper.py +63 -0
- opus_agent_base-0.1.0/src/opus_agent_base/tools/fastmcp_server_config.py +10 -0
- opus_agent_base-0.1.0/src/opus_agent_base/tools/higher_order_tool.py +29 -0
- opus_agent_base-0.1.0/src/opus_agent_base/tools/higher_order_tools_manager.py +44 -0
- opus_agent_base-0.1.0/src/opus_agent_base/tools/mcp_manager.py +109 -0
- opus_agent_base-0.1.0/src/opus_agent_base/tools/mcp_server_registry.py +49 -0
- opus_agent_base-0.1.0/src/opus_agent_base/tools/meta_tool.py +102 -0
- opus_agent_base-0.1.0/src/opus_agent_base/tools/meta_tools_manager.py +58 -0
- opus_agent_base-0.1.0/src/opus_agent_base/tools/openapi_meta_tool.py +311 -0
- opus_agent_base-0.1.0/src/opus_agent_base/ui/__init__.py +2 -0
- opus_agent_base-0.1.0/src/opus_agent_base/ui/logo.py +28 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: opus-agent-base
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Base framework for Opus AI Agents
|
|
5
|
+
Author-email: Sathish <sathish316@gmail.com>
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Requires-Dist: boto3>=1.40.21
|
|
8
|
+
Requires-Dist: chromadb>=1.1.1
|
|
9
|
+
Requires-Dist: fastmcp>=2.12.4
|
|
10
|
+
Requires-Dist: httpx>=0.24.0
|
|
11
|
+
Requires-Dist: prompt-toolkit>=3.0.0
|
|
12
|
+
Requires-Dist: pydantic-ai-slim[anthropic,openai,retries]>=0.8.1
|
|
13
|
+
Requires-Dist: pydantic-ai>=0.8.1
|
|
14
|
+
Requires-Dist: pyyaml>=6.0.0
|
|
15
|
+
Requires-Dist: rapidfuzz>=3.14.1
|
|
16
|
+
Requires-Dist: rich>=13.0.0
|
|
17
|
+
Requires-Dist: singleton-decorator>=1.0.0
|
|
18
|
+
Requires-Dist: tenacity>=8.2.0
|
|
19
|
+
Requires-Dist: tiktoken>=0.12.0
|
|
20
|
+
Requires-Dist: typer>=0.19.2
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "opus-agent-base"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Base framework for Opus AI Agents"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "Sathish", email = "sathish316@gmail.com" }
|
|
7
|
+
]
|
|
8
|
+
requires-python = ">=3.12"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"boto3>=1.40.21",
|
|
11
|
+
"pydantic-ai>=0.8.1",
|
|
12
|
+
"pydantic-ai-slim[anthropic,openai,retries]>=0.8.1",
|
|
13
|
+
"tenacity>=8.2.0",
|
|
14
|
+
"httpx>=0.24.0",
|
|
15
|
+
"singleton-decorator>=1.0.0",
|
|
16
|
+
"fastmcp>=2.12.4",
|
|
17
|
+
"typer>=0.19.2",
|
|
18
|
+
"tiktoken>=0.12.0",
|
|
19
|
+
"chromadb>=1.1.1",
|
|
20
|
+
"rapidfuzz>=3.14.1",
|
|
21
|
+
"prompt-toolkit>=3.0.0",
|
|
22
|
+
"rich>=13.0.0",
|
|
23
|
+
"pyyaml>=6.0.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
opus-agent-base = "opus_agent_base:main"
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["hatchling"]
|
|
31
|
+
build-backend = "hatchling.build"
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["src/opus_agent_base"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Opus Agent Base - Core framework for building AI agents.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.1.0"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
"""Entry point for opus-agent-base CLI."""
|
|
10
|
+
# Lazy import to avoid circular dependencies
|
|
11
|
+
from opus_agent_base.cli.cli import create_cli_app
|
|
12
|
+
|
|
13
|
+
app = create_cli_app(
|
|
14
|
+
agent_name="Opus Agent Base",
|
|
15
|
+
agent_description="Base framework for Opus AI Agents",
|
|
16
|
+
agent_version=__version__,
|
|
17
|
+
)
|
|
18
|
+
app()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = ["main", "__version__"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Agent management and execution modules."""
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from opus_agent_base.config.config_manager import ConfigManager
|
|
2
|
+
from opus_agent_base.prompt.instructions_manager import InstructionsManager
|
|
3
|
+
from opus_agent_base.model.model_manager import ModelManager
|
|
4
|
+
from opus_agent_base.tools.custom_tool import CustomTool
|
|
5
|
+
from opus_agent_base.tools.higher_order_tool import HigherOrderTool
|
|
6
|
+
from opus_agent_base.tools.meta_tool import MetaTool
|
|
7
|
+
from opus_agent_base.tools.fastmcp_server_config import FastMCPServerConfig
|
|
8
|
+
from opus_agent_base.tools.mcp_manager import MCPManager
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
from pydantic_ai import Agent
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AgentBuilder:
|
|
15
|
+
"""Builder for the agent"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, config_manager: ConfigManager):
|
|
18
|
+
self.config_manager = config_manager
|
|
19
|
+
self.custom_tools: list[CustomTool] = []
|
|
20
|
+
self.higher_order_tools: list[HigherOrderTool] = []
|
|
21
|
+
self.meta_tools: list[MetaTool] = []
|
|
22
|
+
self.mcp_servers_config: list[FastMCPServerConfig] = []
|
|
23
|
+
|
|
24
|
+
def name(self, name: str):
|
|
25
|
+
self.name = name
|
|
26
|
+
return self
|
|
27
|
+
|
|
28
|
+
def set_system_prompt_keys(self, system_prompt_keys: list[str]):
|
|
29
|
+
self.system_prompt_keys = system_prompt_keys
|
|
30
|
+
return self
|
|
31
|
+
|
|
32
|
+
def add_instructions_manager(
|
|
33
|
+
self, instructions_manager: InstructionsManager = None
|
|
34
|
+
):
|
|
35
|
+
if instructions_manager is not None:
|
|
36
|
+
self.instructions_manager = instructions_manager
|
|
37
|
+
else:
|
|
38
|
+
self.instructions_manager = InstructionsManager()
|
|
39
|
+
return self
|
|
40
|
+
|
|
41
|
+
def instruction(self, key: str, file: str):
|
|
42
|
+
self.instructions_manager.put_from_file(key, file)
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
def add_model_manager(self):
|
|
46
|
+
self.model_manager = ModelManager(self.config_manager)
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
def custom_tool(self, custom_tool: CustomTool):
|
|
50
|
+
self.custom_tools.append(custom_tool)
|
|
51
|
+
return self
|
|
52
|
+
|
|
53
|
+
def higher_order_tool(self, higher_order_tool: HigherOrderTool):
|
|
54
|
+
self.higher_order_tools.append(higher_order_tool)
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
def meta_tool(self, meta_tool: MetaTool):
|
|
58
|
+
self.meta_tools.append(meta_tool)
|
|
59
|
+
return self
|
|
60
|
+
|
|
61
|
+
def add_mcp_server_config(self, mcp_server_config: FastMCPServerConfig):
|
|
62
|
+
self.mcp_servers_config.append(mcp_server_config)
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def add_mcp_servers_config(self, mcp_servers_config: list[FastMCPServerConfig]):
|
|
66
|
+
self.mcp_servers_config.extend(mcp_servers_config)
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
def set_deps_type(self, deps_type: Any):
|
|
70
|
+
self.deps_type = deps_type
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
def set_output_type(self, output_type: Any):
|
|
74
|
+
self.output_type = output_type
|
|
75
|
+
return self
|
|
76
|
+
|
|
77
|
+
def build_simple_agent(self) -> Agent:
|
|
78
|
+
system_prompt = "\n".join(
|
|
79
|
+
self.instructions_manager.get(key) for key in self.system_prompt_keys
|
|
80
|
+
)
|
|
81
|
+
agent_kwargs = {
|
|
82
|
+
"system_prompt": system_prompt,
|
|
83
|
+
"model": self.model_manager.get_model(),
|
|
84
|
+
"tools": [],
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Add output_type if set
|
|
88
|
+
if hasattr(self, "output_type") and self.output_type is not None:
|
|
89
|
+
agent_kwargs["output_type"] = self.output_type
|
|
90
|
+
|
|
91
|
+
return Agent(**agent_kwargs)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from fastmcp.client.client import ClientSession
|
|
5
|
+
from mcp.types import Tool as MCPTool
|
|
6
|
+
from pydantic_ai import Agent
|
|
7
|
+
from pydantic_ai.tools import Tool
|
|
8
|
+
from singleton_decorator import singleton
|
|
9
|
+
|
|
10
|
+
from opus_agent_base.agent.agent_builder import AgentBuilder
|
|
11
|
+
from opus_agent_base.common.logging_config import console_log
|
|
12
|
+
from opus_agent_base.tools.custom_tools_manager import CustomToolsManager
|
|
13
|
+
from opus_agent_base.tools.mcp_manager import MCPManager
|
|
14
|
+
from opus_agent_base.tools.higher_order_tools_manager import HigherOrderToolsManager
|
|
15
|
+
from opus_agent_base.tools.meta_tools_manager import MetaToolsManager
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@singleton
|
|
21
|
+
class AgentManager:
|
|
22
|
+
"""
|
|
23
|
+
Manager for the agent
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, name: str, builder: AgentBuilder):
|
|
27
|
+
self.name = name
|
|
28
|
+
self.config_manager = builder.config_manager
|
|
29
|
+
self.system_prompt_keys = builder.system_prompt_keys
|
|
30
|
+
self.instructions_manager = builder.instructions_manager
|
|
31
|
+
self.model_manager = builder.model_manager
|
|
32
|
+
self.custom_tools = builder.custom_tools
|
|
33
|
+
self.higher_order_tools = builder.higher_order_tools
|
|
34
|
+
self.meta_tools = builder.meta_tools
|
|
35
|
+
self.mcp_servers_config = builder.mcp_servers_config
|
|
36
|
+
|
|
37
|
+
async def initialize_agent(self):
|
|
38
|
+
# System prompt
|
|
39
|
+
logger.info("Initializing Agent")
|
|
40
|
+
agent_system_prompt = "\n".join(
|
|
41
|
+
self.instructions_manager.get(key)
|
|
42
|
+
for key in self.system_prompt_keys
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Initialize MCP servers
|
|
46
|
+
await self.initialize_mcp_servers()
|
|
47
|
+
|
|
48
|
+
# Initialize Agent tools
|
|
49
|
+
await self.initialize_agent_tools()
|
|
50
|
+
|
|
51
|
+
# Initialize Agent
|
|
52
|
+
self.agent = Agent(
|
|
53
|
+
system_prompt=agent_system_prompt,
|
|
54
|
+
model=self.model_manager.get_model(),
|
|
55
|
+
tools=self.agent_tools,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Add custom tools to Agent
|
|
59
|
+
self.custom_tools_manager = CustomToolsManager(
|
|
60
|
+
self.config_manager,
|
|
61
|
+
self.instructions_manager,
|
|
62
|
+
self.model_manager,
|
|
63
|
+
self.agent,
|
|
64
|
+
)
|
|
65
|
+
self.custom_tools_manager.initialize_tools(self.custom_tools)
|
|
66
|
+
|
|
67
|
+
# Add higher order tools to Agent
|
|
68
|
+
self.higher_order_tools_manager = HigherOrderToolsManager(
|
|
69
|
+
self.config_manager, self.agent, self.fastmcp_client_context
|
|
70
|
+
)
|
|
71
|
+
await self.higher_order_tools_manager.initialize_tools(self.higher_order_tools)
|
|
72
|
+
|
|
73
|
+
# Add meta tools to Agent
|
|
74
|
+
self.meta_tools_manager = MetaToolsManager(self.config_manager, self.agent)
|
|
75
|
+
meta_tools = await self.meta_tools_manager.initialize_tools(self.meta_tools)
|
|
76
|
+
|
|
77
|
+
logger.info("Agent initialized")
|
|
78
|
+
|
|
79
|
+
async def initialize_mcp_servers(self):
|
|
80
|
+
# Initialize MCP manager and MCP servers
|
|
81
|
+
logger.info("Initializing MCP servers")
|
|
82
|
+
self.mcp_manager = MCPManager(self.config_manager)
|
|
83
|
+
self.mcp_manager.add_mcp_servers(self.mcp_servers_config)
|
|
84
|
+
logger.info("MCP servers initialized")
|
|
85
|
+
|
|
86
|
+
async def initialize_agent_tools(self):
|
|
87
|
+
# Initialize Agent tools
|
|
88
|
+
self.agent_tools = []
|
|
89
|
+
self.fastmcp_client_context = await self.mcp_manager.initialize_fastmcp_client_context()
|
|
90
|
+
await self.mcp_manager.inspect_fastmcp_client_tools()
|
|
91
|
+
|
|
92
|
+
async def tools_initializer(session: ClientSession):
|
|
93
|
+
enabled_tools = {}
|
|
94
|
+
logger.info("Initializing agent tools")
|
|
95
|
+
client_tools = await session.list_tools()
|
|
96
|
+
for tool in client_tools:
|
|
97
|
+
tool_prefix = tool.name.split("_")[0]
|
|
98
|
+
if tool_prefix in self.config_manager.get_setting(
|
|
99
|
+
"mcp_config.allowed_tool_prefixes", []
|
|
100
|
+
):
|
|
101
|
+
if tool.name in self.config_manager.get_setting(
|
|
102
|
+
"mcp_config.allowed_tools"
|
|
103
|
+
).get(tool_prefix, []):
|
|
104
|
+
logger.debug(f"Wrapping FastMCP tool: {tool.name}")
|
|
105
|
+
self.agent_tools.append(self._wrap_tool(tool, self.fastmcp_client_context))
|
|
106
|
+
self._log_enabled_tools(enabled_tools, tool_prefix, tool.name)
|
|
107
|
+
else:
|
|
108
|
+
self._log_enabled_tools(enabled_tools, tool_prefix, tool.name)
|
|
109
|
+
self.agent_tools.append(self._wrap_tool(tool, self.fastmcp_client_context))
|
|
110
|
+
return enabled_tools
|
|
111
|
+
|
|
112
|
+
if self.fastmcp_client_context is not None:
|
|
113
|
+
result = await self.fastmcp_client_context(tools_initializer)
|
|
114
|
+
console_log(f"Enabled tools: {result}")
|
|
115
|
+
|
|
116
|
+
# Initialize Meta tools
|
|
117
|
+
result = await self.initialize_meta_tools()
|
|
118
|
+
logger.info(f"Enabled Meta tools: {result}")
|
|
119
|
+
|
|
120
|
+
async def initialize_meta_tools(self):
|
|
121
|
+
# Initialize Meta tools
|
|
122
|
+
result = []
|
|
123
|
+
logger.info("Initializing Meta tools")
|
|
124
|
+
for meta_tool in self.meta_tools:
|
|
125
|
+
await meta_tool.setup_tool()
|
|
126
|
+
agent_tool = await meta_tool.build_agent_tool()
|
|
127
|
+
self.agent_tools.append(agent_tool)
|
|
128
|
+
result.append(meta_tool.name)
|
|
129
|
+
|
|
130
|
+
logger.info("Meta tools initialized")
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
def get_agent(self):
|
|
134
|
+
return self.agent
|
|
135
|
+
|
|
136
|
+
def _wrap_tool(self, tool: MCPTool, fastmcp_client_context) -> Tool:
|
|
137
|
+
async def mcp_tool_function(**kwargs):
|
|
138
|
+
"""Dynamically created tool function for MCP tool"""
|
|
139
|
+
|
|
140
|
+
async def execute_tool(client: ClientSession):
|
|
141
|
+
result = await client.call_tool(tool.name, kwargs)
|
|
142
|
+
return result
|
|
143
|
+
|
|
144
|
+
result = await fastmcp_client_context(execute_tool)
|
|
145
|
+
return result
|
|
146
|
+
|
|
147
|
+
return Tool.from_schema(
|
|
148
|
+
name=tool.name,
|
|
149
|
+
description=tool.description,
|
|
150
|
+
json_schema=tool.inputSchema,
|
|
151
|
+
function=mcp_tool_function,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def _log_enabled_tools(self, enabled_tools, tool_prefix, tool_name):
|
|
155
|
+
if tool_prefix not in enabled_tools:
|
|
156
|
+
enabled_tools[tool_prefix] = set()
|
|
157
|
+
enabled_tools[tool_prefix].add(tool_name)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from opus_agent_base.agent.agent_builder import AgentBuilder
|
|
4
|
+
from opus_agent_base.agent.agent_manager import AgentManager
|
|
5
|
+
from opus_agent_base.common.logging_config import console_log, quick_setup
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AgentRunner:
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
agent_builder: AgentBuilder,
|
|
14
|
+
):
|
|
15
|
+
self.agent_builder = agent_builder
|
|
16
|
+
self.agent_manager = None
|
|
17
|
+
self.agent = None
|
|
18
|
+
self._setup_logging()
|
|
19
|
+
|
|
20
|
+
def _setup_logging(self):
|
|
21
|
+
log_level = self.agent_builder.config_manager.get_setting("debug.log_level", "ERROR")
|
|
22
|
+
quick_setup(log_level=log_level)
|
|
23
|
+
|
|
24
|
+
async def run_agent(self):
|
|
25
|
+
"""Run Agent CLI using PydanticAI CLI"""
|
|
26
|
+
try:
|
|
27
|
+
console_log("🚀 Starting Agent...")
|
|
28
|
+
# Initialize Agent
|
|
29
|
+
if self.agent is None:
|
|
30
|
+
self.agent_manager = AgentManager(
|
|
31
|
+
self.agent_builder.name,
|
|
32
|
+
self.agent_builder
|
|
33
|
+
)
|
|
34
|
+
await self.agent_manager.initialize_agent()
|
|
35
|
+
self.agent = self.agent_manager.get_agent()
|
|
36
|
+
|
|
37
|
+
console_log("✅ Agent started")
|
|
38
|
+
await self.agent.to_cli()
|
|
39
|
+
|
|
40
|
+
except ExceptionGroup as eg:
|
|
41
|
+
console_log("❌ Exception running agent")
|
|
42
|
+
logger.error("Exception running agent:")
|
|
43
|
+
for e in eg.exceptions:
|
|
44
|
+
import traceback
|
|
45
|
+
traceback.print_exception(e)
|
|
46
|
+
raise
|
|
47
|
+
except KeyboardInterrupt:
|
|
48
|
+
console_log("\n🛑 Agent interrupted")
|
|
49
|
+
logger.debug("Agent interrupted by user")
|
|
50
|
+
finally:
|
|
51
|
+
console_log("🏁 Agent session ended")
|
|
52
|
+
logger.debug("Agent session ended")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI interface for Opus agents."""
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from prompt_toolkit import PromptSession
|
|
9
|
+
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
|
10
|
+
from prompt_toolkit.completion import WordCompleter
|
|
11
|
+
from prompt_toolkit.history import FileHistory
|
|
12
|
+
from prompt_toolkit.styles import Style
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
from opus_agent_base.config.config_command_manager import ConfigCommandManager
|
|
17
|
+
from opus_agent_base.config.config_manager import ConfigManager
|
|
18
|
+
from opus_agent_base.ui.logo import display_logo
|
|
19
|
+
|
|
20
|
+
# Setup rich console for pretty output
|
|
21
|
+
console = Console()
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
config_manager = ConfigManager()
|
|
25
|
+
config_command_manager = ConfigCommandManager(config_manager, console)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_cli_app(
|
|
29
|
+
agent_name: str, agent_description: str, agent_version: str, agent_runner: callable = None
|
|
30
|
+
) -> typer.Typer:
|
|
31
|
+
"""Creates a Typer app for the agent CLI."""
|
|
32
|
+
app = typer.Typer(
|
|
33
|
+
name=agent_name,
|
|
34
|
+
help=agent_description,
|
|
35
|
+
rich_markup_mode="rich",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def run_cli_mode(run_agent_on_startup: bool = False):
|
|
39
|
+
"""Run the admin mode with slash commands."""
|
|
40
|
+
display_logo(console)
|
|
41
|
+
# Set up command history file
|
|
42
|
+
history_dir = Path.home() / ".opusai"
|
|
43
|
+
history_dir.mkdir(exist_ok=True)
|
|
44
|
+
history_file = history_dir / ".admin_history"
|
|
45
|
+
|
|
46
|
+
# Set up command completion for all available commands and subcommands
|
|
47
|
+
command_completer = WordCompleter(
|
|
48
|
+
[
|
|
49
|
+
"/help",
|
|
50
|
+
"/agent",
|
|
51
|
+
"/todo-agent",
|
|
52
|
+
"/sde-agent",
|
|
53
|
+
"/config",
|
|
54
|
+
"/config init",
|
|
55
|
+
"/config list",
|
|
56
|
+
"/config get",
|
|
57
|
+
"/config set",
|
|
58
|
+
"/config delete",
|
|
59
|
+
"/status",
|
|
60
|
+
"/clear",
|
|
61
|
+
"/exit",
|
|
62
|
+
"/quit",
|
|
63
|
+
],
|
|
64
|
+
ignore_case=True,
|
|
65
|
+
sentence=True,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Create custom style for prompt
|
|
69
|
+
prompt_style = Style.from_dict(
|
|
70
|
+
{
|
|
71
|
+
"prompt": "bold ansiblue",
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Create prompt session with history, auto-suggestions, and completion
|
|
76
|
+
session = PromptSession(
|
|
77
|
+
history=FileHistory(str(history_file)),
|
|
78
|
+
auto_suggest=AutoSuggestFromHistory(),
|
|
79
|
+
completer=command_completer,
|
|
80
|
+
complete_while_typing=True,
|
|
81
|
+
style=prompt_style,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Run agent on startup if requested
|
|
85
|
+
if run_agent_on_startup:
|
|
86
|
+
try:
|
|
87
|
+
if agent_runner:
|
|
88
|
+
asyncio.run(agent_runner())
|
|
89
|
+
else:
|
|
90
|
+
console.print("[yellow]No agent runner provided[/yellow]")
|
|
91
|
+
except KeyboardInterrupt:
|
|
92
|
+
console.print("\n[dim]Agent interrupted. Returning to CLI...[/dim]")
|
|
93
|
+
except Exception as e:
|
|
94
|
+
import traceback
|
|
95
|
+
|
|
96
|
+
console.print(f"[red]Agent error: {e}[/red]")
|
|
97
|
+
console.print("[dim]Stacktrace:[/dim]")
|
|
98
|
+
console.print(traceback.format_exc())
|
|
99
|
+
|
|
100
|
+
while True:
|
|
101
|
+
try:
|
|
102
|
+
# Use prompt_toolkit for input with command history support
|
|
103
|
+
command = session.prompt([("class:prompt", "\nopus> ")]).strip()
|
|
104
|
+
|
|
105
|
+
if not command:
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
if not command.startswith("/"):
|
|
109
|
+
console.print(
|
|
110
|
+
"[yellow]Commands must start with '/'. Type /help for available commands.[/yellow]"
|
|
111
|
+
)
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
# Parse command
|
|
115
|
+
parts = command[1:].split()
|
|
116
|
+
if not parts:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
cmd = parts[0].lower()
|
|
120
|
+
args = parts[1:] if len(parts) > 1 else []
|
|
121
|
+
|
|
122
|
+
# Handle commands
|
|
123
|
+
if cmd == "exit" or cmd == "quit":
|
|
124
|
+
console.print("[green]Goodbye![/green]")
|
|
125
|
+
break
|
|
126
|
+
elif cmd == "help":
|
|
127
|
+
show_admin_help()
|
|
128
|
+
elif cmd == "agent":
|
|
129
|
+
if agent_runner:
|
|
130
|
+
asyncio.run(agent_runner())
|
|
131
|
+
else:
|
|
132
|
+
console.print("[yellow]No agent runner configured[/yellow]")
|
|
133
|
+
elif cmd == "todo-agent":
|
|
134
|
+
from opus_todo_agent.todo_agent_runner import run_todo_agent
|
|
135
|
+
|
|
136
|
+
asyncio.run(run_todo_agent())
|
|
137
|
+
elif cmd == "sde-agent":
|
|
138
|
+
from opus_sde_agent.sde_agent_runner import run_sde_agent
|
|
139
|
+
|
|
140
|
+
asyncio.run(run_sde_agent())
|
|
141
|
+
elif cmd == "config":
|
|
142
|
+
config_command_manager.handle_config_command(args)
|
|
143
|
+
elif cmd == "status":
|
|
144
|
+
show_status()
|
|
145
|
+
elif cmd == "clear":
|
|
146
|
+
os.system("clear" if os.name == "posix" else "cls")
|
|
147
|
+
else:
|
|
148
|
+
console.print(f"[red]Unknown command: /{cmd}[/red]")
|
|
149
|
+
console.print("Type [bold]/help[/bold] for available commands.")
|
|
150
|
+
|
|
151
|
+
except KeyboardInterrupt:
|
|
152
|
+
console.print("\n[green]Goodbye![/green]")
|
|
153
|
+
break
|
|
154
|
+
except EOFError:
|
|
155
|
+
console.print("\n[green]Goodbye![/green]")
|
|
156
|
+
break
|
|
157
|
+
except Exception as e:
|
|
158
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
159
|
+
sys.exit(1)
|
|
160
|
+
|
|
161
|
+
def show_admin_help():
|
|
162
|
+
"""Show help for admin commands."""
|
|
163
|
+
table = Table(title="Admin Mode Commands")
|
|
164
|
+
table.add_column("Command", style="cyan", no_wrap=True)
|
|
165
|
+
table.add_column("Description", style="white")
|
|
166
|
+
|
|
167
|
+
commands = [
|
|
168
|
+
("/help", "Show this help message"),
|
|
169
|
+
("/agent", "Run Agent mode"),
|
|
170
|
+
("/config init", "Initialize config file from sample template"),
|
|
171
|
+
("/config list", "List all configuration settings"),
|
|
172
|
+
("/config get <key>", "Get a specific configuration setting"),
|
|
173
|
+
(
|
|
174
|
+
"/config set <key> <value>",
|
|
175
|
+
"Set a configuration setting",
|
|
176
|
+
),
|
|
177
|
+
("/config delete <key>", "Delete a configuration setting"),
|
|
178
|
+
("/status", "Show agent status and configuration"),
|
|
179
|
+
("/clear", "Clear the screen"),
|
|
180
|
+
("/exit, /quit", "Exit admin mode"),
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
for cmd, desc in commands:
|
|
184
|
+
table.add_row(cmd, desc)
|
|
185
|
+
|
|
186
|
+
console.print(table)
|
|
187
|
+
|
|
188
|
+
# Add helpful tips
|
|
189
|
+
console.print("\n[bold cyan]💡 Navigation & Editing Tips:[/bold cyan]")
|
|
190
|
+
tips_table = Table(show_header=False, box=None, padding=(0, 2))
|
|
191
|
+
tips_table.add_column("Key", style="yellow")
|
|
192
|
+
tips_table.add_column("Action", style="dim white")
|
|
193
|
+
|
|
194
|
+
tips = [
|
|
195
|
+
("↑/↓", "Navigate command history"),
|
|
196
|
+
("Tab", "Auto-complete commands"),
|
|
197
|
+
("→", "Accept auto-suggestion"),
|
|
198
|
+
("Ctrl+C", "Cancel / Exit"),
|
|
199
|
+
("Ctrl+L", "Clear screen"),
|
|
200
|
+
("Ctrl+A/E", "Jump to start/end of line"),
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
for key, action in tips:
|
|
204
|
+
tips_table.add_row(key, action)
|
|
205
|
+
|
|
206
|
+
console.print(tips_table)
|
|
207
|
+
console.print(
|
|
208
|
+
"\n[dim]Config keys support dot notation (e.g., todoist.api_key)[/dim]"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def show_status():
|
|
212
|
+
"""Show agent status and configuration."""
|
|
213
|
+
table = Table(title="Agent Status")
|
|
214
|
+
table.add_column("Component", style="cyan")
|
|
215
|
+
table.add_column("Status", style="white")
|
|
216
|
+
|
|
217
|
+
# Check environment variables
|
|
218
|
+
required_env_vars = [
|
|
219
|
+
"OPENAI_API_KEY",
|
|
220
|
+
"ANTHROPIC_API_KEY",
|
|
221
|
+
"GOOGLE_OAUTH_CLIENT_ID",
|
|
222
|
+
"GOOGLE_OAUTH_CLIENT_SECRET",
|
|
223
|
+
"SLACK_MCP_XOXC_TOKEN",
|
|
224
|
+
"SLACK_MCP_XOXD_TOKEN",
|
|
225
|
+
"SLACK_MCP_XOXP_TOKEN",
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
for var in required_env_vars:
|
|
229
|
+
value = os.getenv(var)
|
|
230
|
+
status = "[green]Set[/green]" if value else "[red]Not Set[/red]"
|
|
231
|
+
table.add_row(var, status)
|
|
232
|
+
|
|
233
|
+
# Configuration file status
|
|
234
|
+
config_exists = config_manager.config_file.exists()
|
|
235
|
+
config_status = (
|
|
236
|
+
"[green]Exists[/green]" if config_exists else "[yellow]Not Found[/yellow]"
|
|
237
|
+
)
|
|
238
|
+
table.add_row("Config File", config_status)
|
|
239
|
+
table.add_row("Config Path", str(config_manager.config_file))
|
|
240
|
+
|
|
241
|
+
if config_exists:
|
|
242
|
+
config = config_manager.load_config()
|
|
243
|
+
table.add_row("Config Settings", str(len(config)))
|
|
244
|
+
|
|
245
|
+
console.print(table)
|
|
246
|
+
|
|
247
|
+
@app.command()
|
|
248
|
+
def main(
|
|
249
|
+
agent: bool = typer.Option(
|
|
250
|
+
False,
|
|
251
|
+
"--agent",
|
|
252
|
+
"-ai",
|
|
253
|
+
help="Start in Agent mode with slash commands for configuration",
|
|
254
|
+
),
|
|
255
|
+
todo: bool = typer.Option(
|
|
256
|
+
False,
|
|
257
|
+
"--todo-agent",
|
|
258
|
+
"-todo",
|
|
259
|
+
help="Start in TODO Agent mode",
|
|
260
|
+
),
|
|
261
|
+
sde: bool = typer.Option(
|
|
262
|
+
False,
|
|
263
|
+
"--sde-agent",
|
|
264
|
+
"-sde",
|
|
265
|
+
help="Start in SDE Agent mode",
|
|
266
|
+
),
|
|
267
|
+
admin: bool = typer.Option(
|
|
268
|
+
False,
|
|
269
|
+
"--admin",
|
|
270
|
+
"-a",
|
|
271
|
+
help="Start in admin mode with slash commands for configuration",
|
|
272
|
+
),
|
|
273
|
+
version: bool = typer.Option(
|
|
274
|
+
False, "--version", "-v", help="Show version information"
|
|
275
|
+
),
|
|
276
|
+
):
|
|
277
|
+
f"""
|
|
278
|
+
{agent_name} - {agent_description}.
|
|
279
|
+
|
|
280
|
+
Use --admin to enter configuration mode with slash commands.
|
|
281
|
+
"""
|
|
282
|
+
if version:
|
|
283
|
+
console.print(f"[bold blue]{agent_name}[/bold blue] v{agent_version}")
|
|
284
|
+
console.print(agent_description)
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
if admin:
|
|
288
|
+
run_cli_mode(run_agent_on_startup=False)
|
|
289
|
+
else:
|
|
290
|
+
# Run the configured agent (if provided) or enter CLI mode
|
|
291
|
+
run_cli_mode(run_agent_on_startup=True)
|
|
292
|
+
|
|
293
|
+
return app
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Common utilities and helpers."""
|