agyqueue 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.
- agyqueue/__init__.py +1 -0
- agyqueue/client.py +129 -0
- agyqueue/config.py +72 -0
- agyqueue/dashboard.html +1155 -0
- agyqueue/mcp_server.py +438 -0
- agyqueue/models.py +38 -0
- agyqueue/notifications.py +187 -0
- agyqueue/storage.py +423 -0
- agyqueue/task_queue.py +111 -0
- agyqueue/worker.py +671 -0
- agyqueue-0.1.0.dist-info/METADATA +287 -0
- agyqueue-0.1.0.dist-info/RECORD +15 -0
- agyqueue-0.1.0.dist-info/WHEEL +5 -0
- agyqueue-0.1.0.dist-info/entry_points.txt +2 -0
- agyqueue-0.1.0.dist-info/top_level.txt +1 -0
agyqueue/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# AgyQueue package initialization
|
agyqueue/client.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import urllib.request
|
|
3
|
+
import urllib.error
|
|
4
|
+
import urllib.parse
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from typing import Optional, Any, List, Dict
|
|
8
|
+
|
|
9
|
+
class AgyQueueClient:
|
|
10
|
+
"""Client SDK for interacting with the AgyQueue microservice REST API."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, base_url: Optional[str] = None):
|
|
13
|
+
self.base_url = (base_url or os.environ.get("AGYQUEUE_SERVER_URL", "http://127.0.0.1:8000")).rstrip("/")
|
|
14
|
+
|
|
15
|
+
def _request(self, path: str, method: str = "GET", data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
16
|
+
url = f"{self.base_url}{path}"
|
|
17
|
+
req_data = None
|
|
18
|
+
headers = {"Content-Type": "application/json"}
|
|
19
|
+
|
|
20
|
+
if data is not None:
|
|
21
|
+
req_data = json.dumps(data).encode("utf-8")
|
|
22
|
+
|
|
23
|
+
req = urllib.request.Request(url, data=req_data, headers=headers, method=method)
|
|
24
|
+
try:
|
|
25
|
+
with urllib.request.urlopen(req, timeout=10.0) as resp:
|
|
26
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
27
|
+
except urllib.error.HTTPError as e:
|
|
28
|
+
try:
|
|
29
|
+
err_body = e.read().decode("utf-8")
|
|
30
|
+
return json.loads(err_body)
|
|
31
|
+
except Exception:
|
|
32
|
+
return {"error": f"HTTP Error {e.code}: {e.reason}"}
|
|
33
|
+
except Exception as e:
|
|
34
|
+
return {"error": f"Connection failed: {str(e)}"}
|
|
35
|
+
|
|
36
|
+
def submit_task(self, prompt: str, task_type: str = "generic") -> Dict[str, Any]:
|
|
37
|
+
"""Submit a new task to the AgyQueue service."""
|
|
38
|
+
return self._request("/api/tasks", method="POST", data={"prompt": prompt, "task_type": task_type})
|
|
39
|
+
|
|
40
|
+
def get_task_status(self, task_id: str) -> Dict[str, Any]:
|
|
41
|
+
"""Fetch the execution status and progress of a task."""
|
|
42
|
+
return self._request(f"/api/tasks/{task_id}", method="GET")
|
|
43
|
+
|
|
44
|
+
def get_task_result(self, task_id: str) -> Dict[str, Any]:
|
|
45
|
+
"""Retrieve the result or error of a completed task."""
|
|
46
|
+
return self._request(f"/api/tasks/{task_id}/result", method="GET")
|
|
47
|
+
|
|
48
|
+
def list_tasks(self) -> List[Dict[str, Any]]:
|
|
49
|
+
"""List all tasks in the queue."""
|
|
50
|
+
res = self._request("/api/tasks", method="GET")
|
|
51
|
+
if isinstance(res, list):
|
|
52
|
+
return res
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
def cancel_task(self, task_id: str) -> Dict[str, Any]:
|
|
56
|
+
"""Cancel a running or queued task."""
|
|
57
|
+
return self._request(f"/api/tasks/{task_id}/cancel", method="POST")
|
|
58
|
+
|
|
59
|
+
def wait_for_task(self, task_id: str, poll_interval: float = 2.0, timeout: float = 300.0) -> Dict[str, Any]:
|
|
60
|
+
"""Wait for a task to reach a terminal state (COMPLETED, FAILED, CANCELLED)."""
|
|
61
|
+
start_time = time.time()
|
|
62
|
+
while time.time() - start_time < timeout:
|
|
63
|
+
status_res = self.get_task_status(task_id)
|
|
64
|
+
if "error" in status_res:
|
|
65
|
+
return status_res
|
|
66
|
+
|
|
67
|
+
status = status_res.get("status")
|
|
68
|
+
if status in ("COMPLETED", "FAILED", "CANCELLED"):
|
|
69
|
+
return status_res
|
|
70
|
+
|
|
71
|
+
time.sleep(poll_interval)
|
|
72
|
+
|
|
73
|
+
return {"error": "Timeout waiting for task execution completion"}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# AI Agent Tool Wrappers
|
|
77
|
+
# These helper functions match the standard function signature/docstring pattern expected by AI Agent frameworks (like Google ADK, LangChain, etc.).
|
|
78
|
+
|
|
79
|
+
def get_agyqueue_client() -> AgyQueueClient:
|
|
80
|
+
return AgyQueueClient()
|
|
81
|
+
|
|
82
|
+
def submit_async_task(prompt: str, task_type: str = "generic") -> str:
|
|
83
|
+
"""Submit a long-running asynchronous task to the background queue.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
prompt: Detailed instructions or code validation prompt.
|
|
87
|
+
task_type: Type of task executor (e.g. 'sre_k8s_analysis', 'fastapi_gen', 'generic').
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
A JSON string containing the submitted task's ID and initial status.
|
|
91
|
+
"""
|
|
92
|
+
client = get_agyqueue_client()
|
|
93
|
+
return json.dumps(client.submit_task(prompt, task_type))
|
|
94
|
+
|
|
95
|
+
def check_task_progress(task_id: str) -> str:
|
|
96
|
+
"""Check the current status, progress percentage, and step of an active task.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
task_id: The unique task ID returned when the task was submitted.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
A JSON string detailing current state, progress percentage, and active step.
|
|
103
|
+
"""
|
|
104
|
+
client = get_agyqueue_client()
|
|
105
|
+
return json.dumps(client.get_task_status(task_id))
|
|
106
|
+
|
|
107
|
+
def get_task_output(task_id: str) -> str:
|
|
108
|
+
"""Retrieve the final completed markdown report or error stack for a task.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
task_id: The unique task ID returned when the task was submitted.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
A JSON string containing either the completed markdown result or the error payload.
|
|
115
|
+
"""
|
|
116
|
+
client = get_agyqueue_client()
|
|
117
|
+
return json.dumps(client.get_task_result(task_id))
|
|
118
|
+
|
|
119
|
+
def cancel_running_task(task_id: str) -> str:
|
|
120
|
+
"""Request immediate cancellation and cleanup of a running background task.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
task_id: The unique task ID to cancel.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
A JSON string confirming cancellation status.
|
|
127
|
+
"""
|
|
128
|
+
client = get_agyqueue_client()
|
|
129
|
+
return json.dumps(client.cancel_task(task_id))
|
agyqueue/config.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
class Settings:
|
|
4
|
+
"""Consolidated configuration settings for the AgyQueue application."""
|
|
5
|
+
|
|
6
|
+
@property
|
|
7
|
+
def store_type(self) -> str:
|
|
8
|
+
return os.environ.get("AGYQUEUE_STORE_TYPE", "sqlite").lower()
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def db_path(self) -> str:
|
|
12
|
+
return os.environ.get("AGYQUEUE_DB_PATH", "agyqueue.db")
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def db_host(self) -> Optional[str]:
|
|
16
|
+
return os.environ.get("DB_HOST")
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def db_port(self) -> str:
|
|
20
|
+
return os.environ.get("DB_PORT", "5432")
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def db_name(self) -> str:
|
|
24
|
+
return os.environ.get("DB_NAME", "agyqueue")
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def db_user(self) -> str:
|
|
28
|
+
return os.environ.get("DB_USER", "postgres")
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def db_password(self) -> Optional[str]:
|
|
32
|
+
return os.environ.get("DB_PASSWORD")
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def database_url(self) -> Optional[str]:
|
|
36
|
+
# Direct URL connection if provided
|
|
37
|
+
url = os.environ.get("DATABASE_URL")
|
|
38
|
+
if url:
|
|
39
|
+
return url
|
|
40
|
+
|
|
41
|
+
# Build URL from components if postgres host is configured
|
|
42
|
+
host = self.db_host
|
|
43
|
+
if host:
|
|
44
|
+
pw = self.db_password
|
|
45
|
+
pw_str = f":{pw}" if pw else ""
|
|
46
|
+
return f"postgresql://{self.db_user}{pw_str}@{host}:{self.db_port}/{self.db_name}"
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def redis_url(self) -> Optional[str]:
|
|
51
|
+
return os.environ.get("REDIS_URL")
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def transport(self) -> str:
|
|
55
|
+
return os.environ.get("AGYQUEUE_TRANSPORT", "stdio").lower()
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def host(self) -> str:
|
|
59
|
+
return os.environ.get("AGYQUEUE_HOST", "127.0.0.1")
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def port(self) -> int:
|
|
63
|
+
return int(os.environ.get("AGYQUEUE_PORT", "8000"))
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def heartbeat_timeout(self) -> float:
|
|
67
|
+
return float(os.environ.get("HEARTBEAT_TIMEOUT_SECONDS", "15.0"))
|
|
68
|
+
|
|
69
|
+
from typing import Optional
|
|
70
|
+
|
|
71
|
+
# Global settings instance
|
|
72
|
+
settings = Settings()
|