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.
- agent/adapters/__init__.py +2 -0
- agent/adapters/huggingfacelocal_adapter.py +15 -3
- agent/adapters/human_adapter.py +348 -0
- agent/agent.py +29 -21
- agent/callbacks/trajectory_saver.py +35 -26
- agent/cli.py +1 -1
- agent/computers/__init__.py +41 -0
- agent/computers/base.py +70 -0
- agent/{computer_handler.py → computers/cua.py} +26 -23
- agent/computers/custom.py +209 -0
- agent/human_tool/__init__.py +29 -0
- agent/human_tool/__main__.py +38 -0
- agent/human_tool/server.py +234 -0
- agent/human_tool/ui.py +630 -0
- agent/integrations/hud/__init__.py +77 -0
- agent/integrations/hud/adapter.py +121 -0
- agent/integrations/hud/agent.py +373 -0
- agent/integrations/hud/computer_handler.py +187 -0
- agent/loops/uitars.py +9 -1
- agent/types.py +1 -53
- agent/ui/gradio/app.py +1 -0
- agent/ui/gradio/ui_components.py +20 -9
- {cua_agent-0.4.12.dist-info → cua_agent-0.4.14.dist-info}/METADATA +9 -6
- cua_agent-0.4.14.dist-info/RECORD +50 -0
- cua_agent-0.4.12.dist-info/RECORD +0 -38
- {cua_agent-0.4.12.dist-info → cua_agent-0.4.14.dist-info}/WHEEL +0 -0
- {cua_agent-0.4.12.dist-info → cua_agent-0.4.14.dist-info}/entry_points.txt +0 -0
|
@@ -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)
|