quantalogic 0.2.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.
Files changed (68) hide show
  1. quantalogic/__init__.py +20 -0
  2. quantalogic/agent.py +638 -0
  3. quantalogic/agent_config.py +138 -0
  4. quantalogic/coding_agent.py +83 -0
  5. quantalogic/event_emitter.py +223 -0
  6. quantalogic/generative_model.py +226 -0
  7. quantalogic/interactive_text_editor.py +190 -0
  8. quantalogic/main.py +185 -0
  9. quantalogic/memory.py +217 -0
  10. quantalogic/model_names.py +19 -0
  11. quantalogic/print_event.py +66 -0
  12. quantalogic/prompts.py +99 -0
  13. quantalogic/server/__init__.py +3 -0
  14. quantalogic/server/agent_server.py +633 -0
  15. quantalogic/server/models.py +60 -0
  16. quantalogic/server/routes.py +117 -0
  17. quantalogic/server/state.py +199 -0
  18. quantalogic/server/static/js/event_visualizer.js +430 -0
  19. quantalogic/server/static/js/quantalogic.js +571 -0
  20. quantalogic/server/templates/index.html +134 -0
  21. quantalogic/tool_manager.py +68 -0
  22. quantalogic/tools/__init__.py +46 -0
  23. quantalogic/tools/agent_tool.py +88 -0
  24. quantalogic/tools/download_http_file_tool.py +64 -0
  25. quantalogic/tools/edit_whole_content_tool.py +70 -0
  26. quantalogic/tools/elixir_tool.py +240 -0
  27. quantalogic/tools/execute_bash_command_tool.py +116 -0
  28. quantalogic/tools/input_question_tool.py +57 -0
  29. quantalogic/tools/language_handlers/__init__.py +21 -0
  30. quantalogic/tools/language_handlers/c_handler.py +33 -0
  31. quantalogic/tools/language_handlers/cpp_handler.py +33 -0
  32. quantalogic/tools/language_handlers/go_handler.py +33 -0
  33. quantalogic/tools/language_handlers/java_handler.py +37 -0
  34. quantalogic/tools/language_handlers/javascript_handler.py +42 -0
  35. quantalogic/tools/language_handlers/python_handler.py +29 -0
  36. quantalogic/tools/language_handlers/rust_handler.py +33 -0
  37. quantalogic/tools/language_handlers/scala_handler.py +33 -0
  38. quantalogic/tools/language_handlers/typescript_handler.py +42 -0
  39. quantalogic/tools/list_directory_tool.py +123 -0
  40. quantalogic/tools/llm_tool.py +119 -0
  41. quantalogic/tools/markitdown_tool.py +105 -0
  42. quantalogic/tools/nodejs_tool.py +515 -0
  43. quantalogic/tools/python_tool.py +469 -0
  44. quantalogic/tools/read_file_block_tool.py +140 -0
  45. quantalogic/tools/read_file_tool.py +79 -0
  46. quantalogic/tools/replace_in_file_tool.py +300 -0
  47. quantalogic/tools/ripgrep_tool.py +353 -0
  48. quantalogic/tools/search_definition_names.py +419 -0
  49. quantalogic/tools/task_complete_tool.py +35 -0
  50. quantalogic/tools/tool.py +146 -0
  51. quantalogic/tools/unified_diff_tool.py +387 -0
  52. quantalogic/tools/write_file_tool.py +97 -0
  53. quantalogic/utils/__init__.py +17 -0
  54. quantalogic/utils/ask_user_validation.py +12 -0
  55. quantalogic/utils/download_http_file.py +77 -0
  56. quantalogic/utils/get_coding_environment.py +15 -0
  57. quantalogic/utils/get_environment.py +26 -0
  58. quantalogic/utils/get_quantalogic_rules_content.py +19 -0
  59. quantalogic/utils/git_ls.py +121 -0
  60. quantalogic/utils/read_file.py +54 -0
  61. quantalogic/utils/read_http_text_content.py +101 -0
  62. quantalogic/xml_parser.py +242 -0
  63. quantalogic/xml_tool_parser.py +99 -0
  64. quantalogic-0.2.0.dist-info/LICENSE +201 -0
  65. quantalogic-0.2.0.dist-info/METADATA +1034 -0
  66. quantalogic-0.2.0.dist-info/RECORD +68 -0
  67. quantalogic-0.2.0.dist-info/WHEEL +4 -0
  68. quantalogic-0.2.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,117 @@
1
+ """API routes for the QuantaLogic server."""
2
+
3
+ import asyncio
4
+ import json
5
+ import uuid
6
+ from asyncio import Queue
7
+ from datetime import datetime
8
+ from typing import Empty, Optional
9
+
10
+ from fastapi import HTTPException, Request
11
+ from fastapi.responses import StreamingResponse
12
+ from fastapi.templating import Jinja2Templates
13
+ from loguru import logger
14
+
15
+ from quantalogic.server.models import TaskStatus, TaskSubmission, UserValidationResponse
16
+ from quantalogic.server.state import agent_state
17
+
18
+ # Initialize templates
19
+ templates = Jinja2Templates(directory="quantalogic/server/templates")
20
+
21
+
22
+ async def submit_validation_response(validation_id: str, response: UserValidationResponse):
23
+ """Submit a validation response."""
24
+ if validation_id not in agent_state.validation_responses:
25
+ raise HTTPException(status_code=404, detail="Validation request not found")
26
+
27
+ response_queue = agent_state.validation_responses[validation_id]
28
+ await response_queue.put(response.response)
29
+ return {"status": "success"}
30
+
31
+
32
+ async def event_stream(request: Request, task_id: Optional[str] = None):
33
+ """SSE endpoint for streaming agent events."""
34
+ client_id = agent_state.add_client(task_id)
35
+
36
+ try:
37
+ # Determine the appropriate queue based on task_id
38
+ if task_id:
39
+ if task_id not in agent_state.task_event_queues:
40
+ agent_state.task_event_queues[task_id] = Queue()
41
+ queue = agent_state.task_event_queues[task_id]
42
+ else:
43
+ queue = agent_state.event_queues[client_id]
44
+
45
+ async def generate():
46
+ try:
47
+ while True:
48
+ # Check for client disconnection
49
+ if await request.is_disconnected():
50
+ break
51
+
52
+ try:
53
+ # Non-blocking get with a short timeout
54
+ event = queue.get_nowait()
55
+ yield f"data: {json.dumps(event)}\n\n"
56
+ except Empty:
57
+ # Prevent tight loop and allow for disconnection checks
58
+ await asyncio.sleep(0.1)
59
+ except Exception as e:
60
+ logger.error(f"Error in event stream for client {client_id}: {e}")
61
+ finally:
62
+ # Ensure cleanup of client and task-specific resources
63
+ agent_state.remove_client(client_id)
64
+ if task_id:
65
+ agent_state.remove_task_event_queue(task_id)
66
+
67
+ return StreamingResponse(
68
+ generate(),
69
+ media_type="text/event-stream",
70
+ headers={
71
+ "Cache-Control": "no-cache",
72
+ "Connection": "keep-alive",
73
+ "Content-Type": "text/event-stream",
74
+ },
75
+ )
76
+ except Exception as e:
77
+ logger.error(f"Event stream initialization error: {e}")
78
+ agent_state.remove_client(client_id)
79
+ raise
80
+
81
+
82
+ async def get_index(request: Request):
83
+ """Serve the main application page."""
84
+ return templates.TemplateResponse("index.html", {"request": request, "title": "QuantaLogic"})
85
+
86
+
87
+ async def submit_task(request: TaskSubmission):
88
+ """Submit a new task and return its ID."""
89
+ task_id = str(uuid.uuid4())
90
+ agent_state.tasks[task_id] = {
91
+ "status": "pending",
92
+ "created_at": datetime.now().isoformat(),
93
+ "task": request.task,
94
+ "model_name": request.model_name,
95
+ }
96
+ return {"task_id": task_id}
97
+
98
+
99
+ async def get_task_status(task_id: str):
100
+ """Get the status of a specific task."""
101
+ if task_id not in agent_state.tasks:
102
+ raise HTTPException(status_code=404, detail="Task not found")
103
+
104
+ task_data = agent_state.tasks[task_id]
105
+ return TaskStatus(**task_data)
106
+
107
+
108
+ async def list_tasks(status: Optional[str] = None, limit: int = 10, offset: int = 0):
109
+ """List all tasks with optional filtering."""
110
+ tasks = []
111
+ for task_id, task_data in agent_state.tasks.items():
112
+ if status is None or task_data["status"] == status:
113
+ tasks.append({"task_id": task_id, **task_data})
114
+
115
+ # Apply pagination
116
+ paginated_tasks = tasks[offset : offset + limit]
117
+ return {"tasks": paginated_tasks, "total": len(tasks), "limit": limit, "offset": offset}
@@ -0,0 +1,199 @@
1
+ """State management for the QuantaLogic server."""
2
+
3
+ import asyncio
4
+ import sys
5
+ import traceback
6
+ from datetime import datetime
7
+ from queue import Queue
8
+ from threading import Lock
9
+ from typing import Any, Dict, Optional
10
+
11
+ from loguru import logger
12
+ from rich.console import Console
13
+
14
+ # Configure logger
15
+ logger.remove()
16
+ logger.add(
17
+ sys.stderr,
18
+ format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
19
+ level="INFO",
20
+ )
21
+
22
+
23
+ class ServerState:
24
+ """Global server state management."""
25
+
26
+ def __init__(self):
27
+ """Initialize the ServerState with default values for server management."""
28
+ self.interrupt_count = 0
29
+ self.force_exit = False
30
+ self.is_shutting_down = False
31
+ self.shutdown_initiated = asyncio.Event()
32
+ self.shutdown_complete = asyncio.Event()
33
+ self.server = None
34
+
35
+ async def initiate_shutdown(self, force: bool = False):
36
+ """Initiate the shutdown process."""
37
+ if not self.is_shutting_down or force:
38
+ logger.info("Initiating server shutdown...")
39
+ self.is_shutting_down = True
40
+ self.force_exit = force
41
+ self.shutdown_initiated.set()
42
+ if force:
43
+ logger.warning("Forcing immediate shutdown...")
44
+ sys.exit(1)
45
+ await self.shutdown_complete.wait()
46
+
47
+ def handle_interrupt(self):
48
+ """Handle interrupt signal."""
49
+ self.interrupt_count += 1
50
+ if self.interrupt_count == 1:
51
+ logger.info("Graceful shutdown initiated (press Ctrl+C again to force)")
52
+ asyncio.create_task(self.initiate_shutdown(force=False))
53
+ else:
54
+ logger.warning("Forced shutdown initiated...")
55
+ asyncio.create_task(self.initiate_shutdown(force=True))
56
+
57
+
58
+ class AgentState:
59
+ """Manages agent state and event queues."""
60
+
61
+ def __init__(self):
62
+ """Initialize the agent state."""
63
+ self.agent = None
64
+ self.event_queues: Dict[str, Queue] = {}
65
+ self.task_event_queues: Dict[str, Queue] = {}
66
+ self.queue_lock = Lock()
67
+ self.client_counter = 0
68
+ self.console = Console()
69
+ self.validation_requests: Dict[str, Dict[str, Any]] = {}
70
+ self.validation_responses: Dict[str, asyncio.Queue] = {}
71
+ self.tasks: Dict[str, Dict[str, Any]] = {}
72
+ self.task_queues: Dict[str, asyncio.Queue] = {}
73
+ self.agents: Dict[str, Any] = {} # Dictionary to store agents by task ID
74
+
75
+ def add_client(self, task_id: Optional[str] = None) -> str:
76
+ """Add a new client and return its ID.
77
+
78
+ Args:
79
+ task_id (Optional[str]): Optional task ID to associate with the client.
80
+
81
+ Returns:
82
+ str: Unique client ID
83
+ """
84
+ with self.queue_lock:
85
+ self.client_counter += 1
86
+ client_id = f"client_{self.client_counter}"
87
+
88
+ # Create a client-specific event queue
89
+ self.event_queues[client_id] = Queue()
90
+
91
+ # If a task_id is provided, create or use an existing task-specific queue and agent
92
+ if task_id:
93
+ if task_id not in self.task_event_queues:
94
+ self.task_event_queues[task_id] = Queue()
95
+ if task_id not in self.agents:
96
+ self.agents[task_id] = self.create_agent_for_task(task_id)
97
+
98
+ logger.info(f"New client connected: {client_id} for task: {task_id}")
99
+ return client_id
100
+
101
+ def create_agent_for_task(self, task_id: str) -> Any:
102
+ """Create and return a new agent for the specified task.
103
+
104
+ Args:
105
+ task_id (str): The task ID for which to create the agent.
106
+
107
+ Returns:
108
+ Any: The created agent instance.
109
+ """
110
+ # Placeholder for agent creation logic
111
+ agent = ... # Replace with actual agent creation logic
112
+ logger.info(f"Agent created for task: {task_id}")
113
+ return agent
114
+
115
+ def get_agent_for_task(self, task_id: str) -> Optional[Any]:
116
+ """Retrieve the agent for the specified task.
117
+
118
+ Args:
119
+ task_id (str): The task ID for which to retrieve the agent.
120
+
121
+ Returns:
122
+ Optional[Any]: The agent instance if found, else None.
123
+ """
124
+ return self.agents.get(task_id)
125
+
126
+ def remove_client(self, client_id: str):
127
+ """Remove a client and its event queue."""
128
+ with self.queue_lock:
129
+ if client_id in self.event_queues:
130
+ del self.event_queues[client_id]
131
+ logger.info(f"Client disconnected: {client_id}")
132
+
133
+ def _format_data_for_client(self, data: Dict[str, Any]) -> Dict[str, Any]:
134
+ """Format data for client consumption."""
135
+ if isinstance(data, dict):
136
+ return {k: str(v) if isinstance(v, datetime | bytes) else v for k, v in data.items()}
137
+ return data
138
+
139
+ def broadcast_event(self, event_type: str, data: Dict[str, Any]):
140
+ """Broadcast an event to all connected clients or specific task queue.
141
+
142
+ Args:
143
+ event_type (str): Type of the event.
144
+ data (Dict[str, Any]): Event data.
145
+ """
146
+ from quantalogic.models import EventMessage # Import here to avoid circular dependency
147
+
148
+ try:
149
+ formatted_data = self._format_data_for_client(data)
150
+ event = EventMessage(
151
+ id=f"evt_{datetime.now().timestamp()}",
152
+ event=event_type,
153
+ task_id=data.get("task_id"),
154
+ data=formatted_data,
155
+ timestamp=datetime.now().isoformat(),
156
+ )
157
+
158
+ with self.queue_lock:
159
+ task_id = data.get("task_id")
160
+
161
+ # If task_id is provided, send to task-specific queue and use task-specific agent
162
+ if task_id and task_id in self.task_event_queues:
163
+ self.task_event_queues[task_id].put(event.model_dump())
164
+ agent = self.get_agent_for_task(task_id)
165
+ if agent:
166
+ # Use the agent for task-specific processing
167
+ # Placeholder for agent-specific logic
168
+ pass
169
+ logger.debug(f"Event sent to task-specific queue: {task_id}")
170
+
171
+ # Optionally broadcast to global event queues if needed
172
+ else:
173
+ for queue in self.event_queues.values():
174
+ queue.put(event.model_dump())
175
+ logger.debug("Event broadcast to all client queues")
176
+
177
+ except Exception as e:
178
+ logger.error(f"Error broadcasting event: {e}")
179
+ logger.error(traceback.format_exc())
180
+
181
+ def remove_task_event_queue(self, task_id: str):
182
+ """Remove a task-specific event queue safely.
183
+
184
+ Args:
185
+ task_id (str): The task ID to remove from event queues.
186
+ """
187
+ with self.queue_lock:
188
+ if task_id in self.task_event_queues:
189
+ del self.task_event_queues[task_id]
190
+
191
+ # Additional cleanup for related task resources
192
+ if task_id in self.tasks:
193
+ del self.tasks[task_id]
194
+
195
+ if task_id in self.task_queues:
196
+ del self.task_queues[task_id]
197
+
198
+ if task_id in self.agents:
199
+ del self.agents[task_id]