cuti 0.1.0__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.
- cuti/__init__.py +21 -0
- cuti/__main__.py +110 -0
- cuti/agents/__init__.py +37 -0
- cuti/agents/base.py +269 -0
- cuti/agents/claude_agent.py +268 -0
- cuti/agents/context.py +180 -0
- cuti/agents/gemini_agent.py +380 -0
- cuti/agents/pool.py +228 -0
- cuti/agents/router.py +469 -0
- cuti/builtin_agents/__init__.py +16 -0
- cuti/builtin_agents/code-reviewer.md +90 -0
- cuti/builtin_agents/docs-generator.md +125 -0
- cuti/builtin_agents/gemini-codebase-analysis.md +139 -0
- cuti/builtin_agents/refactor-assistant.md +116 -0
- cuti/builtin_agents/test-writer.md +99 -0
- cuti/builtin_agents/ui-design-expert.md +96 -0
- cuti/claude_usage_monitor.py +454 -0
- cuti/cli/__init__.py +7 -0
- cuti/cli/app.py +173 -0
- cuti/cli/commands/__init__.py +3 -0
- cuti/cli/commands/agent.py +121 -0
- cuti/cli/commands/alias.py +150 -0
- cuti/cli/commands/devcontainer.py +271 -0
- cuti/cli/commands/queue.py +244 -0
- cuti/cli/utils.py +62 -0
- cuti/core/__init__.py +17 -0
- cuti/core/claude_interface.py +317 -0
- cuti/core/config.py +86 -0
- cuti/core/models.py +210 -0
- cuti/core/queue.py +219 -0
- cuti/core/storage.py +316 -0
- cuti/services/__init__.py +17 -0
- cuti/services/agent_manager.py +272 -0
- cuti/services/aliases.py +549 -0
- cuti/services/claude_agent_manager.py +450 -0
- cuti/services/claude_logs_reader.py +275 -0
- cuti/services/claude_monitor_integration.py +623 -0
- cuti/services/claude_orchestration.py +349 -0
- cuti/services/claude_settings_manager.py +175 -0
- cuti/services/claude_usage_monitor.py +459 -0
- cuti/services/devcontainer.py +593 -0
- cuti/services/history.py +474 -0
- cuti/services/log_sync.py +320 -0
- cuti/services/monitoring.py +656 -0
- cuti/services/queue_service.py +136 -0
- cuti/services/task_expansion.py +820 -0
- cuti/services/task_history.py +353 -0
- cuti/services/workspace_manager.py +358 -0
- cuti/utils/__init__.py +16 -0
- cuti/utils/constants.py +41 -0
- cuti/utils/helpers.py +138 -0
- cuti/utils/logger.py +69 -0
- cuti/web/__init__.py +3 -0
- cuti/web/api/__init__.py +3 -0
- cuti/web/api/agents.py +350 -0
- cuti/web/api/claude_agents.py +138 -0
- cuti/web/api/claude_code_agents.py +138 -0
- cuti/web/api/claude_logs.py +131 -0
- cuti/web/api/claude_settings.py +77 -0
- cuti/web/api/enhanced_chat.py +368 -0
- cuti/web/api/monitoring.py +441 -0
- cuti/web/api/queue.py +143 -0
- cuti/web/api/websocket.py +218 -0
- cuti/web/api/workspace.py +70 -0
- cuti/web/app.py +291 -0
- cuti/web/main.py +92 -0
- cuti/web/routes.py +106 -0
- cuti/web/static/css/agent-cards.css +181 -0
- cuti/web/static/css/agents.css +1129 -0
- cuti/web/static/css/agents_orchestration.css +602 -0
- cuti/web/static/css/main.css +2649 -0
- cuti/web/static/css/markdown.css +359 -0
- cuti/web/static/css/symphony-toggle-enhanced.css +463 -0
- cuti/web/static/css/symphony-toggle.css +407 -0
- cuti/web/static/js/app.js +1251 -0
- cuti/web/static/js/markdown-renderer.js +249 -0
- cuti/web/static/js/symphony-toggle.js +321 -0
- cuti/web/templates/agents.html +711 -0
- cuti/web/templates/agents_orchestration.html +773 -0
- cuti/web/templates/base.html +36 -0
- cuti/web/templates/chat.html +607 -0
- cuti/web/templates/components/card.html +24 -0
- cuti/web/templates/components/header.html +44 -0
- cuti/web/templates/components/sidebar.html +57 -0
- cuti/web/templates/components/status_bar.html +27 -0
- cuti/web/templates/enhanced_chat.html +663 -0
- cuti/web/templates/statistics.html +1738 -0
- cuti/web/templates/symphony_toggle_demo.html +289 -0
- cuti/web/utils.py +152 -0
- cuti-0.1.0.dist-info/METADATA +725 -0
- cuti-0.1.0.dist-info/RECORD +94 -0
- cuti-0.1.0.dist-info/WHEEL +4 -0
- cuti-0.1.0.dist-info/entry_points.txt +3 -0
- cuti-0.1.0.dist-info/licenses/LICENSE +21 -0
cuti/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cuti - Production-ready claude code utils with command queuing, web interface, and monitoring.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.1.0"
|
|
6
|
+
__author__ = "claude-code, nociza"
|
|
7
|
+
__description__ = "Production-ready claude code utils with command queuing, prompt aliases, web interface, and monitoring."
|
|
8
|
+
|
|
9
|
+
# Import main components for convenience
|
|
10
|
+
from .services.queue_service import QueueManager
|
|
11
|
+
from .core.models import QueuedPrompt, PromptStatus
|
|
12
|
+
from .services.aliases import PromptAliasManager
|
|
13
|
+
from .services.history import PromptHistoryManager
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"QueueManager",
|
|
17
|
+
"QueuedPrompt",
|
|
18
|
+
"PromptStatus",
|
|
19
|
+
"PromptAliasManager",
|
|
20
|
+
"PromptHistoryManager",
|
|
21
|
+
]
|
cuti/__main__.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Main entry point for cuti when run with uvx or python -m cuti.
|
|
4
|
+
Starts the web server by default.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import argparse
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .web.app import main as web_main
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main():
|
|
16
|
+
"""Main entry point for uvx cuti command."""
|
|
17
|
+
parser = argparse.ArgumentParser(
|
|
18
|
+
prog="cuti",
|
|
19
|
+
description="Production-ready cuti system with web interface",
|
|
20
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
21
|
+
epilog="""
|
|
22
|
+
Examples:
|
|
23
|
+
uvx cuti # Start web interface for current directory
|
|
24
|
+
uvx cuti --port 3000 # Start web interface on port 3000
|
|
25
|
+
uvx cuti --host 0.0.0.0 # Bind to all interfaces
|
|
26
|
+
uvx cuti /path/to/project # Start web interface for specific directory
|
|
27
|
+
|
|
28
|
+
The web interface will automatically start the queue processor in the background.
|
|
29
|
+
Claude Code will be launched in the working directory you specify (or current directory).
|
|
30
|
+
Access the dashboard at http://localhost:8000
|
|
31
|
+
"""
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"working_directory",
|
|
36
|
+
nargs="?",
|
|
37
|
+
default=None,
|
|
38
|
+
help="Working directory for Claude Code (default: current directory)"
|
|
39
|
+
)
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"--host",
|
|
42
|
+
default="127.0.0.1",
|
|
43
|
+
help="Host to bind to (default: 127.0.0.1)"
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--port",
|
|
47
|
+
type=int,
|
|
48
|
+
default=8000,
|
|
49
|
+
help="Port to bind to (default: 8000)"
|
|
50
|
+
)
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
"--storage-dir",
|
|
53
|
+
default="~/.cuti",
|
|
54
|
+
help="Storage directory (default: ~/.cuti)"
|
|
55
|
+
)
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"--version",
|
|
58
|
+
action="version",
|
|
59
|
+
version="cuti 0.1.0"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
args = parser.parse_args()
|
|
63
|
+
|
|
64
|
+
# Determine working directory
|
|
65
|
+
if args.working_directory:
|
|
66
|
+
working_dir = Path(args.working_directory).resolve()
|
|
67
|
+
if not working_dir.exists():
|
|
68
|
+
print(f"❌ Error: Directory '{working_dir}' does not exist")
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
else:
|
|
71
|
+
working_dir = Path.cwd()
|
|
72
|
+
|
|
73
|
+
# Allow environment variables to override CLI
|
|
74
|
+
host = os.getenv("CLAUDE_QUEUE_WEB_HOST", args.host)
|
|
75
|
+
port_str = os.getenv("CLAUDE_QUEUE_WEB_PORT")
|
|
76
|
+
port = int(port_str) if port_str else args.port
|
|
77
|
+
storage_dir = os.getenv("CLAUDE_QUEUE_STORAGE_DIR", args.storage_dir)
|
|
78
|
+
|
|
79
|
+
print(f"🚀 Starting cuti web interface...")
|
|
80
|
+
print(f"📍 Host: {host}")
|
|
81
|
+
print(f"🔌 Port: {port}")
|
|
82
|
+
print(f"📁 Working Directory: {working_dir}")
|
|
83
|
+
print(f"💾 Storage: {Path(storage_dir).expanduser()}")
|
|
84
|
+
print(f"🌐 Dashboard: http://{host}:{port}")
|
|
85
|
+
print(f"📚 API Docs: http://{host}:{port}/docs")
|
|
86
|
+
print()
|
|
87
|
+
|
|
88
|
+
# Set environment variable for working directory
|
|
89
|
+
os.environ["CUTI_WORKING_DIR"] = str(working_dir)
|
|
90
|
+
|
|
91
|
+
# Override sys.argv for the web main function
|
|
92
|
+
sys.argv = [
|
|
93
|
+
"cuti-web",
|
|
94
|
+
"--host", host,
|
|
95
|
+
"--port", str(port),
|
|
96
|
+
"--storage-dir", storage_dir
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
web_main()
|
|
101
|
+
except KeyboardInterrupt:
|
|
102
|
+
print("\n👋 Shutting down cuti...")
|
|
103
|
+
sys.exit(0)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
print(f"❌ Error starting cuti: {e}")
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
main()
|
cuti/agents/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Multi-agent orchestration system for cuti.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .base import (
|
|
6
|
+
BaseAgent,
|
|
7
|
+
AgentCapability,
|
|
8
|
+
AgentStatus,
|
|
9
|
+
AgentMetadata,
|
|
10
|
+
AgentExecutionContext,
|
|
11
|
+
AgentConfig
|
|
12
|
+
)
|
|
13
|
+
from .pool import AgentPool
|
|
14
|
+
from .router import TaskRouter, TaskRoutingStrategy, RoutingDecision, CoordinationEngine
|
|
15
|
+
from .context import SharedMemoryManager
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
# Base classes
|
|
19
|
+
'BaseAgent',
|
|
20
|
+
'AgentCapability',
|
|
21
|
+
'AgentStatus',
|
|
22
|
+
'AgentMetadata',
|
|
23
|
+
'AgentExecutionContext',
|
|
24
|
+
'AgentConfig',
|
|
25
|
+
|
|
26
|
+
# Management
|
|
27
|
+
'AgentPool',
|
|
28
|
+
|
|
29
|
+
# Routing
|
|
30
|
+
'TaskRouter',
|
|
31
|
+
'TaskRoutingStrategy',
|
|
32
|
+
'RoutingDecision',
|
|
33
|
+
'CoordinationEngine',
|
|
34
|
+
|
|
35
|
+
# Context
|
|
36
|
+
'SharedMemoryManager'
|
|
37
|
+
]
|
cuti/agents/base.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes for agent abstraction layer.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Dict, List, Optional, Any, AsyncGenerator, Union
|
|
9
|
+
import asyncio
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
from ..core.models import QueuedPrompt, ExecutionResult
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AgentCapability(Enum):
|
|
16
|
+
"""Capabilities that agents can provide."""
|
|
17
|
+
CODE_UNDERSTANDING = "code_understanding"
|
|
18
|
+
CODE_GENERATION = "code_generation"
|
|
19
|
+
CODE_REFACTORING = "code_refactoring"
|
|
20
|
+
DEBUGGING = "debugging"
|
|
21
|
+
TESTING = "testing"
|
|
22
|
+
DOCUMENTATION = "documentation"
|
|
23
|
+
ARCHITECTURE_DESIGN = "architecture_design"
|
|
24
|
+
SECURITY_ANALYSIS = "security_analysis"
|
|
25
|
+
PERFORMANCE_OPTIMIZATION = "performance_optimization"
|
|
26
|
+
MULTIMODAL_PROCESSING = "multimodal_processing"
|
|
27
|
+
LARGE_CONTEXT_PROCESSING = "large_context_processing"
|
|
28
|
+
REAL_TIME_COLLABORATION = "real_time_collaboration"
|
|
29
|
+
FILE_SYSTEM_OPERATIONS = "file_system_operations"
|
|
30
|
+
DATABASE_OPERATIONS = "database_operations"
|
|
31
|
+
API_INTEGRATION = "api_integration"
|
|
32
|
+
WEB_BROWSING = "web_browsing"
|
|
33
|
+
DATA_ANALYSIS = "data_analysis"
|
|
34
|
+
MACHINE_LEARNING = "machine_learning"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AgentStatus(Enum):
|
|
38
|
+
"""Agent operational status."""
|
|
39
|
+
AVAILABLE = "available"
|
|
40
|
+
BUSY = "busy"
|
|
41
|
+
OFFLINE = "offline"
|
|
42
|
+
ERROR = "error"
|
|
43
|
+
RATE_LIMITED = "rate_limited"
|
|
44
|
+
INITIALIZING = "initializing"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class AgentMetadata:
|
|
49
|
+
"""Metadata about an agent."""
|
|
50
|
+
name: str
|
|
51
|
+
version: str
|
|
52
|
+
capabilities: List[AgentCapability]
|
|
53
|
+
max_context_tokens: int
|
|
54
|
+
supports_streaming: bool
|
|
55
|
+
supports_multimodal: bool
|
|
56
|
+
rate_limit_info: Optional[Dict[str, Any]] = None
|
|
57
|
+
cost_per_input_token: Optional[float] = None
|
|
58
|
+
cost_per_output_token: Optional[float] = None
|
|
59
|
+
installation_command: Optional[str] = None
|
|
60
|
+
authentication_required: bool = True
|
|
61
|
+
supported_languages: List[str] = field(default_factory=list)
|
|
62
|
+
special_features: List[str] = field(default_factory=list)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class AgentConfig:
|
|
67
|
+
"""Configuration for an agent."""
|
|
68
|
+
type: str
|
|
69
|
+
name: str
|
|
70
|
+
command: Optional[str] = None
|
|
71
|
+
api_key_env: Optional[str] = None
|
|
72
|
+
api_key: Optional[str] = None
|
|
73
|
+
timeout: int = 3600
|
|
74
|
+
max_concurrent: int = 1
|
|
75
|
+
working_directory: str = "."
|
|
76
|
+
environment_vars: Dict[str, str] = field(default_factory=dict)
|
|
77
|
+
system_prompt: Optional[str] = None
|
|
78
|
+
custom_settings: Dict[str, Any] = field(default_factory=dict)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class AgentExecutionContext:
|
|
83
|
+
"""Context for agent execution."""
|
|
84
|
+
session_id: str
|
|
85
|
+
shared_memory: Dict[str, Any]
|
|
86
|
+
available_tools: List[str]
|
|
87
|
+
coordination_data: Dict[str, Any]
|
|
88
|
+
parent_task_id: Optional[str] = None
|
|
89
|
+
collaboration_mode: bool = False
|
|
90
|
+
previous_outputs: List[Dict[str, Any]] = field(default_factory=list)
|
|
91
|
+
execution_history: List[str] = field(default_factory=list)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class BaseAgent(ABC):
|
|
95
|
+
"""Abstract base class for all agents."""
|
|
96
|
+
|
|
97
|
+
def __init__(self, config: AgentConfig):
|
|
98
|
+
self.config = config
|
|
99
|
+
self.name = config.name
|
|
100
|
+
self.status = AgentStatus.OFFLINE
|
|
101
|
+
self.metadata = self._initialize_metadata()
|
|
102
|
+
self.current_executions: Dict[str, QueuedPrompt] = {}
|
|
103
|
+
self._initialized = False
|
|
104
|
+
self._health_check_failures = 0
|
|
105
|
+
self._last_health_check = None
|
|
106
|
+
|
|
107
|
+
@abstractmethod
|
|
108
|
+
def _initialize_metadata(self) -> AgentMetadata:
|
|
109
|
+
"""Initialize agent metadata."""
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
@abstractmethod
|
|
113
|
+
async def initialize(self) -> bool:
|
|
114
|
+
"""Initialize the agent and test connectivity."""
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
@abstractmethod
|
|
118
|
+
async def execute_prompt(
|
|
119
|
+
self,
|
|
120
|
+
prompt: QueuedPrompt,
|
|
121
|
+
context: AgentExecutionContext
|
|
122
|
+
) -> ExecutionResult:
|
|
123
|
+
"""Execute a prompt with the agent."""
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
@abstractmethod
|
|
127
|
+
async def stream_prompt(
|
|
128
|
+
self,
|
|
129
|
+
prompt: QueuedPrompt,
|
|
130
|
+
context: AgentExecutionContext
|
|
131
|
+
) -> AsyncGenerator[str, None]:
|
|
132
|
+
"""Stream execution results."""
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
@abstractmethod
|
|
136
|
+
async def health_check(self) -> bool:
|
|
137
|
+
"""Check if agent is healthy and available."""
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
@abstractmethod
|
|
141
|
+
async def is_installed(self) -> bool:
|
|
142
|
+
"""Check if the agent CLI is installed."""
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
@abstractmethod
|
|
146
|
+
async def install(self) -> bool:
|
|
147
|
+
"""Install the agent CLI."""
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
async def can_handle_task(self, prompt: QueuedPrompt) -> float:
|
|
151
|
+
"""
|
|
152
|
+
Return confidence score (0-1) for handling this task.
|
|
153
|
+
Default implementation uses keyword matching.
|
|
154
|
+
"""
|
|
155
|
+
content_lower = prompt.content.lower()
|
|
156
|
+
confidence = 0.0
|
|
157
|
+
|
|
158
|
+
# Check for capability-related keywords
|
|
159
|
+
capability_keywords = {
|
|
160
|
+
AgentCapability.CODE_GENERATION: ['generate', 'create', 'write code', 'implement'],
|
|
161
|
+
AgentCapability.CODE_REFACTORING: ['refactor', 'restructure', 'clean up', 'improve code'],
|
|
162
|
+
AgentCapability.DEBUGGING: ['debug', 'fix', 'error', 'bug', 'issue'],
|
|
163
|
+
AgentCapability.TESTING: ['test', 'unit test', 'integration test', 'testing'],
|
|
164
|
+
AgentCapability.DOCUMENTATION: ['document', 'readme', 'comment', 'explain'],
|
|
165
|
+
AgentCapability.ARCHITECTURE_DESIGN: ['architecture', 'design', 'system', 'structure'],
|
|
166
|
+
AgentCapability.SECURITY_ANALYSIS: ['security', 'vulnerability', 'audit', 'secure'],
|
|
167
|
+
AgentCapability.PERFORMANCE_OPTIMIZATION: ['optimize', 'performance', 'speed up', 'efficient'],
|
|
168
|
+
AgentCapability.DATA_ANALYSIS: ['analyze', 'data', 'statistics', 'insights'],
|
|
169
|
+
AgentCapability.MACHINE_LEARNING: ['ml', 'machine learning', 'model', 'train']
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for capability in self.metadata.capabilities:
|
|
173
|
+
if capability in capability_keywords:
|
|
174
|
+
keywords = capability_keywords[capability]
|
|
175
|
+
if any(keyword in content_lower for keyword in keywords):
|
|
176
|
+
confidence = max(confidence, 0.7)
|
|
177
|
+
|
|
178
|
+
# Base confidence for general capabilities
|
|
179
|
+
if confidence == 0.0 and self.metadata.capabilities:
|
|
180
|
+
confidence = 0.3
|
|
181
|
+
|
|
182
|
+
return confidence
|
|
183
|
+
|
|
184
|
+
def get_current_load(self) -> float:
|
|
185
|
+
"""Return current load percentage (0-1)."""
|
|
186
|
+
max_concurrent = self.config.max_concurrent
|
|
187
|
+
current_count = len(self.current_executions)
|
|
188
|
+
return min(1.0, current_count / max_concurrent)
|
|
189
|
+
|
|
190
|
+
async def estimate_execution_time(self, prompt: QueuedPrompt) -> Optional[int]:
|
|
191
|
+
"""
|
|
192
|
+
Estimate execution time in seconds.
|
|
193
|
+
Default implementation based on content length and complexity.
|
|
194
|
+
"""
|
|
195
|
+
content_length = len(prompt.content)
|
|
196
|
+
base_time = 10 # Base 10 seconds
|
|
197
|
+
|
|
198
|
+
# Add time based on content length (1 second per 100 characters)
|
|
199
|
+
length_time = content_length / 100
|
|
200
|
+
|
|
201
|
+
# Add time for files (5 seconds per file)
|
|
202
|
+
file_time = len(prompt.context_files) * 5
|
|
203
|
+
|
|
204
|
+
# Estimate total time
|
|
205
|
+
estimated_time = int(base_time + length_time + file_time)
|
|
206
|
+
|
|
207
|
+
# Cap at timeout
|
|
208
|
+
return min(estimated_time, self.config.timeout)
|
|
209
|
+
|
|
210
|
+
async def estimate_cost(self, prompt: QueuedPrompt) -> Optional[float]:
|
|
211
|
+
"""
|
|
212
|
+
Estimate cost in USD.
|
|
213
|
+
Default implementation based on token counts.
|
|
214
|
+
"""
|
|
215
|
+
if not self.metadata.cost_per_input_token or not self.metadata.cost_per_output_token:
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
# Rough estimation: 1 token ≈ 4 characters
|
|
219
|
+
estimated_input_tokens = len(prompt.content) / 4
|
|
220
|
+
|
|
221
|
+
# Add tokens for context files (rough estimate)
|
|
222
|
+
for file_path in prompt.context_files:
|
|
223
|
+
estimated_input_tokens += 500 # Assume 500 tokens per file
|
|
224
|
+
|
|
225
|
+
# Estimate output tokens (usually 2-3x input for code tasks)
|
|
226
|
+
estimated_output_tokens = estimated_input_tokens * 2.5
|
|
227
|
+
|
|
228
|
+
input_cost = estimated_input_tokens * self.metadata.cost_per_input_token
|
|
229
|
+
output_cost = estimated_output_tokens * self.metadata.cost_per_output_token
|
|
230
|
+
|
|
231
|
+
return input_cost + output_cost
|
|
232
|
+
|
|
233
|
+
def add_execution(self, prompt_id: str, prompt: QueuedPrompt):
|
|
234
|
+
"""Track an active execution."""
|
|
235
|
+
self.current_executions[prompt_id] = prompt
|
|
236
|
+
self.status = AgentStatus.BUSY if self.get_current_load() >= 1.0 else AgentStatus.AVAILABLE
|
|
237
|
+
|
|
238
|
+
def remove_execution(self, prompt_id: str):
|
|
239
|
+
"""Remove a completed execution."""
|
|
240
|
+
if prompt_id in self.current_executions:
|
|
241
|
+
del self.current_executions[prompt_id]
|
|
242
|
+
self.status = AgentStatus.AVAILABLE if self._initialized else AgentStatus.OFFLINE
|
|
243
|
+
|
|
244
|
+
async def prepare_system_prompt(self, context: AgentExecutionContext) -> str:
|
|
245
|
+
"""
|
|
246
|
+
Prepare system prompt with context and collaboration instructions.
|
|
247
|
+
"""
|
|
248
|
+
system_prompt = self.config.system_prompt or f"You are {self.name}, an AI assistant."
|
|
249
|
+
|
|
250
|
+
if context.collaboration_mode:
|
|
251
|
+
system_prompt += f"""
|
|
252
|
+
|
|
253
|
+
You are collaborating with other AI agents on this task.
|
|
254
|
+
Session ID: {context.session_id}
|
|
255
|
+
Your role: {context.coordination_data.get('role', 'collaborator')}
|
|
256
|
+
|
|
257
|
+
Previous outputs from other agents:
|
|
258
|
+
{context.previous_outputs}
|
|
259
|
+
|
|
260
|
+
Shared context:
|
|
261
|
+
{context.shared_memory}
|
|
262
|
+
|
|
263
|
+
Please coordinate your response with the overall task objectives.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
return system_prompt
|
|
267
|
+
|
|
268
|
+
def __repr__(self) -> str:
|
|
269
|
+
return f"<{self.__class__.__name__} name='{self.name}' status={self.status.value}>"
|