cua-agent 0.4.12__py3-none-any.whl → 0.4.14__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.

Potentially problematic release.


This version of cua-agent might be problematic. Click here for more details.

@@ -0,0 +1,234 @@
1
+ import asyncio
2
+ import uuid
3
+ from datetime import datetime
4
+ from typing import Dict, List, Any, Optional
5
+ from dataclasses import dataclass, asdict
6
+ from enum import Enum
7
+
8
+ from fastapi import FastAPI, HTTPException
9
+ from pydantic import BaseModel
10
+
11
+
12
+ class CompletionStatus(str, Enum):
13
+ PENDING = "pending"
14
+ COMPLETED = "completed"
15
+ FAILED = "failed"
16
+
17
+
18
+ @dataclass
19
+ class CompletionCall:
20
+ id: str
21
+ messages: List[Dict[str, Any]]
22
+ model: str
23
+ status: CompletionStatus
24
+ created_at: datetime
25
+ completed_at: Optional[datetime] = None
26
+ response: Optional[str] = None
27
+ tool_calls: Optional[List[Dict[str, Any]]] = None
28
+ error: Optional[str] = None
29
+
30
+
31
+ class ToolCall(BaseModel):
32
+ id: str
33
+ type: str = "function"
34
+ function: Dict[str, Any]
35
+
36
+
37
+ class CompletionRequest(BaseModel):
38
+ messages: List[Dict[str, Any]]
39
+ model: str
40
+
41
+
42
+ class CompletionResponse(BaseModel):
43
+ response: Optional[str] = None
44
+ tool_calls: Optional[List[Dict[str, Any]]] = None
45
+
46
+
47
+ class CompletionQueue:
48
+ def __init__(self):
49
+ self._queue: Dict[str, CompletionCall] = {}
50
+ self._pending_order: List[str] = []
51
+ self._lock = asyncio.Lock()
52
+
53
+ async def add_completion(self, messages: List[Dict[str, Any]], model: str) -> str:
54
+ """Add a completion call to the queue."""
55
+ async with self._lock:
56
+ call_id = str(uuid.uuid4())
57
+ completion_call = CompletionCall(
58
+ id=call_id,
59
+ messages=messages,
60
+ model=model,
61
+ status=CompletionStatus.PENDING,
62
+ created_at=datetime.now()
63
+ )
64
+ self._queue[call_id] = completion_call
65
+ self._pending_order.append(call_id)
66
+ return call_id
67
+
68
+ async def get_pending_calls(self) -> List[Dict[str, Any]]:
69
+ """Get all pending completion calls."""
70
+ async with self._lock:
71
+ pending_calls = []
72
+ for call_id in self._pending_order:
73
+ if call_id in self._queue and self._queue[call_id].status == CompletionStatus.PENDING:
74
+ call = self._queue[call_id]
75
+ pending_calls.append({
76
+ "id": call.id,
77
+ "model": call.model,
78
+ "created_at": call.created_at.isoformat(),
79
+ "messages": call.messages
80
+ })
81
+ return pending_calls
82
+
83
+ async def get_call_status(self, call_id: str) -> Optional[Dict[str, Any]]:
84
+ """Get the status of a specific completion call."""
85
+ async with self._lock:
86
+ if call_id not in self._queue:
87
+ return None
88
+
89
+ call = self._queue[call_id]
90
+ result = {
91
+ "id": call.id,
92
+ "status": call.status.value,
93
+ "created_at": call.created_at.isoformat(),
94
+ "model": call.model,
95
+ "messages": call.messages
96
+ }
97
+
98
+ if call.completed_at:
99
+ result["completed_at"] = call.completed_at.isoformat()
100
+ if call.response:
101
+ result["response"] = call.response
102
+ if call.tool_calls:
103
+ result["tool_calls"] = call.tool_calls
104
+ if call.error:
105
+ result["error"] = call.error
106
+
107
+ return result
108
+
109
+ async def complete_call(self, call_id: str, response: Optional[str] = None, tool_calls: Optional[List[Dict[str, Any]]] = None) -> bool:
110
+ """Mark a completion call as completed with a response or tool calls."""
111
+ async with self._lock:
112
+ if call_id not in self._queue:
113
+ return False
114
+
115
+ call = self._queue[call_id]
116
+ if call.status != CompletionStatus.PENDING:
117
+ return False
118
+
119
+ call.status = CompletionStatus.COMPLETED
120
+ call.completed_at = datetime.now()
121
+ call.response = response
122
+ call.tool_calls = tool_calls
123
+
124
+ # Remove from pending order
125
+ if call_id in self._pending_order:
126
+ self._pending_order.remove(call_id)
127
+
128
+ return True
129
+
130
+ async def fail_call(self, call_id: str, error: str) -> bool:
131
+ """Mark a completion call as failed with an error."""
132
+ async with self._lock:
133
+ if call_id not in self._queue:
134
+ return False
135
+
136
+ call = self._queue[call_id]
137
+ if call.status != CompletionStatus.PENDING:
138
+ return False
139
+
140
+ call.status = CompletionStatus.FAILED
141
+ call.completed_at = datetime.now()
142
+ call.error = error
143
+
144
+ # Remove from pending order
145
+ if call_id in self._pending_order:
146
+ self._pending_order.remove(call_id)
147
+
148
+ return True
149
+
150
+ async def wait_for_completion(self, call_id: str, timeout: float = 300.0) -> Optional[str]:
151
+ """Wait for a completion call to be completed and return the response."""
152
+ start_time = asyncio.get_event_loop().time()
153
+
154
+ while True:
155
+ status = await self.get_call_status(call_id)
156
+ if not status:
157
+ return None
158
+
159
+ if status["status"] == CompletionStatus.COMPLETED.value:
160
+ return status.get("response")
161
+ elif status["status"] == CompletionStatus.FAILED.value:
162
+ raise Exception(f"Completion failed: {status.get('error', 'Unknown error')}")
163
+
164
+ # Check timeout
165
+ if asyncio.get_event_loop().time() - start_time > timeout:
166
+ await self.fail_call(call_id, "Timeout waiting for human response")
167
+ raise TimeoutError("Timeout waiting for human response")
168
+
169
+ # Wait a bit before checking again
170
+ await asyncio.sleep(0.5)
171
+
172
+
173
+ # Global queue instance
174
+ completion_queue = CompletionQueue()
175
+
176
+ # FastAPI app
177
+ app = FastAPI(title="Human Completion Server", version="1.0.0")
178
+
179
+
180
+ @app.post("/queue", response_model=Dict[str, str])
181
+ async def queue_completion(request: CompletionRequest):
182
+ """Add a completion request to the queue."""
183
+ call_id = await completion_queue.add_completion(request.messages, request.model)
184
+ return {"id": call_id, "status": "queued"}
185
+
186
+
187
+ @app.get("/pending")
188
+ async def list_pending():
189
+ """List all pending completion calls."""
190
+ pending_calls = await completion_queue.get_pending_calls()
191
+ return {"pending_calls": pending_calls}
192
+
193
+
194
+ @app.get("/status/{call_id}")
195
+ async def get_status(call_id: str):
196
+ """Get the status of a specific completion call."""
197
+ status = await completion_queue.get_call_status(call_id)
198
+ if not status:
199
+ raise HTTPException(status_code=404, detail="Completion call not found")
200
+ return status
201
+
202
+
203
+ @app.post("/complete/{call_id}")
204
+ async def complete_call(call_id: str, response: CompletionResponse):
205
+ """Complete a call with a human response."""
206
+ success = await completion_queue.complete_call(
207
+ call_id,
208
+ response=response.response,
209
+ tool_calls=response.tool_calls
210
+ )
211
+ if success:
212
+ return {"status": "success", "message": "Call completed"}
213
+ else:
214
+ raise HTTPException(status_code=404, detail="Call not found or already completed")
215
+
216
+
217
+ @app.post("/fail/{call_id}")
218
+ async def fail_call(call_id: str, error: Dict[str, str]):
219
+ """Mark a call as failed."""
220
+ success = await completion_queue.fail_call(call_id, error.get("error", "Unknown error"))
221
+ if not success:
222
+ raise HTTPException(status_code=404, detail="Completion call not found or already completed")
223
+ return {"status": "failed"}
224
+
225
+
226
+ @app.get("/")
227
+ async def root():
228
+ """Root endpoint."""
229
+ return {"message": "Human Completion Server is running"}
230
+
231
+
232
+ if __name__ == "__main__":
233
+ import uvicorn
234
+ uvicorn.run(app, host="0.0.0.0", port=8002)