oagi-core 0.10.1__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.
- oagi/__init__.py +148 -0
- oagi/agent/__init__.py +33 -0
- oagi/agent/default.py +124 -0
- oagi/agent/factories.py +74 -0
- oagi/agent/observer/__init__.py +38 -0
- oagi/agent/observer/agent_observer.py +99 -0
- oagi/agent/observer/events.py +28 -0
- oagi/agent/observer/exporters.py +445 -0
- oagi/agent/observer/protocol.py +12 -0
- oagi/agent/protocol.py +55 -0
- oagi/agent/registry.py +155 -0
- oagi/agent/tasker/__init__.py +33 -0
- oagi/agent/tasker/memory.py +160 -0
- oagi/agent/tasker/models.py +77 -0
- oagi/agent/tasker/planner.py +408 -0
- oagi/agent/tasker/taskee_agent.py +512 -0
- oagi/agent/tasker/tasker_agent.py +324 -0
- oagi/cli/__init__.py +11 -0
- oagi/cli/agent.py +281 -0
- oagi/cli/display.py +56 -0
- oagi/cli/main.py +77 -0
- oagi/cli/server.py +94 -0
- oagi/cli/tracking.py +55 -0
- oagi/cli/utils.py +89 -0
- oagi/client/__init__.py +12 -0
- oagi/client/async_.py +290 -0
- oagi/client/base.py +457 -0
- oagi/client/sync.py +293 -0
- oagi/exceptions.py +118 -0
- oagi/handler/__init__.py +24 -0
- oagi/handler/_macos.py +55 -0
- oagi/handler/async_pyautogui_action_handler.py +44 -0
- oagi/handler/async_screenshot_maker.py +47 -0
- oagi/handler/pil_image.py +102 -0
- oagi/handler/pyautogui_action_handler.py +291 -0
- oagi/handler/screenshot_maker.py +41 -0
- oagi/logging.py +55 -0
- oagi/server/__init__.py +13 -0
- oagi/server/agent_wrappers.py +98 -0
- oagi/server/config.py +46 -0
- oagi/server/main.py +157 -0
- oagi/server/models.py +98 -0
- oagi/server/session_store.py +116 -0
- oagi/server/socketio_server.py +405 -0
- oagi/task/__init__.py +21 -0
- oagi/task/async_.py +101 -0
- oagi/task/async_short.py +76 -0
- oagi/task/base.py +157 -0
- oagi/task/short.py +76 -0
- oagi/task/sync.py +99 -0
- oagi/types/__init__.py +50 -0
- oagi/types/action_handler.py +30 -0
- oagi/types/async_action_handler.py +30 -0
- oagi/types/async_image_provider.py +38 -0
- oagi/types/image.py +17 -0
- oagi/types/image_provider.py +35 -0
- oagi/types/models/__init__.py +32 -0
- oagi/types/models/action.py +33 -0
- oagi/types/models/client.py +68 -0
- oagi/types/models/image_config.py +47 -0
- oagi/types/models/step.py +17 -0
- oagi/types/step_observer.py +93 -0
- oagi/types/url.py +3 -0
- oagi_core-0.10.1.dist-info/METADATA +245 -0
- oagi_core-0.10.1.dist-info/RECORD +68 -0
- oagi_core-0.10.1.dist-info/WHEEL +4 -0
- oagi_core-0.10.1.dist-info/entry_points.txt +2 -0
- oagi_core-0.10.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# -----------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) OpenAGI Foundation
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# This file is part of the official API project.
|
|
6
|
+
# Licensed under the MIT License.
|
|
7
|
+
# -----------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from pydantic import ValidationError
|
|
15
|
+
|
|
16
|
+
from ..agent import AsyncDefaultAgent, create_agent
|
|
17
|
+
from ..client import AsyncClient
|
|
18
|
+
from ..exceptions import check_optional_dependency
|
|
19
|
+
from ..types.models.action import Action, ActionType
|
|
20
|
+
from .agent_wrappers import SocketIOActionHandler, SocketIOImageProvider
|
|
21
|
+
from .config import ServerConfig
|
|
22
|
+
from .models import (
|
|
23
|
+
BaseActionEventData,
|
|
24
|
+
ClickEventData,
|
|
25
|
+
DragEventData,
|
|
26
|
+
ErrorEventData,
|
|
27
|
+
FinishEventData,
|
|
28
|
+
HotkeyEventData,
|
|
29
|
+
InitEventData,
|
|
30
|
+
ScrollEventData,
|
|
31
|
+
TypeEventData,
|
|
32
|
+
WaitEventData,
|
|
33
|
+
)
|
|
34
|
+
from .session_store import Session, session_store
|
|
35
|
+
|
|
36
|
+
check_optional_dependency("socketio", "Server features", "server")
|
|
37
|
+
import socketio # noqa: E402
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
sio = socketio.AsyncServer(
|
|
42
|
+
async_mode="asgi",
|
|
43
|
+
cors_allowed_origins="*",
|
|
44
|
+
logger=False,
|
|
45
|
+
engineio_logger=False,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SessionNamespace(socketio.AsyncNamespace):
|
|
50
|
+
def __init__(self, namespace: str, config: ServerConfig):
|
|
51
|
+
super().__init__(namespace)
|
|
52
|
+
self.config = config
|
|
53
|
+
self.background_tasks: dict[str, asyncio.Task] = {}
|
|
54
|
+
|
|
55
|
+
async def on_connect(self, sid: str, environ: dict, auth: dict | None) -> bool:
|
|
56
|
+
session_id = self.namespace.split("/")[-1]
|
|
57
|
+
logger.info(f"Client {sid} connected to session {session_id}")
|
|
58
|
+
|
|
59
|
+
session = session_store.get_session(session_id)
|
|
60
|
+
if session:
|
|
61
|
+
session.socket_id = sid
|
|
62
|
+
session.namespace = self.namespace
|
|
63
|
+
session_store.update_activity(session_id)
|
|
64
|
+
|
|
65
|
+
# Create OAGI client if not exists
|
|
66
|
+
if not session.oagi_client:
|
|
67
|
+
session.oagi_client = AsyncClient(
|
|
68
|
+
base_url=self.config.oagi_base_url,
|
|
69
|
+
api_key=self.config.oagi_api_key,
|
|
70
|
+
)
|
|
71
|
+
else:
|
|
72
|
+
logger.warning(f"Connection to non-existent session {session_id}")
|
|
73
|
+
# Create session on connect if it doesn't exist
|
|
74
|
+
session = Session(
|
|
75
|
+
session_id=session_id,
|
|
76
|
+
instruction="",
|
|
77
|
+
mode="actor", # Default mode
|
|
78
|
+
model=self.config.default_model,
|
|
79
|
+
temperature=self.config.default_temperature,
|
|
80
|
+
)
|
|
81
|
+
session.socket_id = sid
|
|
82
|
+
session.namespace = self.namespace
|
|
83
|
+
session.oagi_client = AsyncClient(
|
|
84
|
+
base_url=self.config.oagi_base_url,
|
|
85
|
+
api_key=self.config.oagi_api_key,
|
|
86
|
+
)
|
|
87
|
+
session_store.sessions[session_id] = session
|
|
88
|
+
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
async def on_disconnect(self, sid: str) -> None:
|
|
92
|
+
session_id = self.namespace.split("/")[-1]
|
|
93
|
+
logger.info(f"Client {sid} disconnected from session {session_id}")
|
|
94
|
+
|
|
95
|
+
# Cancel any background tasks
|
|
96
|
+
if sid in self.background_tasks:
|
|
97
|
+
self.background_tasks[sid].cancel()
|
|
98
|
+
del self.background_tasks[sid]
|
|
99
|
+
|
|
100
|
+
# Start cleanup task
|
|
101
|
+
asyncio.create_task(self._cleanup_after_timeout(session_id))
|
|
102
|
+
|
|
103
|
+
async def _cleanup_after_timeout(self, session_id: str) -> None:
|
|
104
|
+
await asyncio.sleep(self.config.session_timeout_seconds)
|
|
105
|
+
|
|
106
|
+
session = session_store.get_session(session_id)
|
|
107
|
+
if session:
|
|
108
|
+
current_time = datetime.now().timestamp()
|
|
109
|
+
if (
|
|
110
|
+
current_time - session.last_activity
|
|
111
|
+
>= self.config.session_timeout_seconds
|
|
112
|
+
):
|
|
113
|
+
logger.info(f"Session {session_id} timed out, cleaning up")
|
|
114
|
+
|
|
115
|
+
# Close OAGI client
|
|
116
|
+
if session.oagi_client:
|
|
117
|
+
await session.oagi_client.close()
|
|
118
|
+
|
|
119
|
+
session_store.delete_session(session_id)
|
|
120
|
+
|
|
121
|
+
async def on_init(self, sid: str, data: dict) -> None:
|
|
122
|
+
try:
|
|
123
|
+
session_id = self.namespace.split("/")[-1]
|
|
124
|
+
logger.info(f"Initializing session {session_id}")
|
|
125
|
+
|
|
126
|
+
# Validate input
|
|
127
|
+
event_data = InitEventData(**data)
|
|
128
|
+
|
|
129
|
+
# Get or create session
|
|
130
|
+
session = session_store.get_session(session_id)
|
|
131
|
+
if not session:
|
|
132
|
+
logger.error(f"Session {session_id} not found")
|
|
133
|
+
await self.emit(
|
|
134
|
+
"error",
|
|
135
|
+
ErrorEventData(
|
|
136
|
+
message=f"Session {session_id} not found"
|
|
137
|
+
).model_dump(),
|
|
138
|
+
room=sid,
|
|
139
|
+
)
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
# Update session with init data
|
|
143
|
+
session.instruction = event_data.instruction
|
|
144
|
+
if event_data.mode:
|
|
145
|
+
session.mode = event_data.mode
|
|
146
|
+
if event_data.model:
|
|
147
|
+
session.model = event_data.model
|
|
148
|
+
if event_data.temperature is not None:
|
|
149
|
+
session.temperature = event_data.temperature
|
|
150
|
+
session.status = "running"
|
|
151
|
+
session_store.update_activity(session_id)
|
|
152
|
+
|
|
153
|
+
logger.info(
|
|
154
|
+
f"Session {session_id} initialized with: {event_data.instruction} "
|
|
155
|
+
f"(mode={event_data.mode}, model={event_data.model})"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Create agent and wrappers
|
|
159
|
+
agent = create_agent(
|
|
160
|
+
mode=session.mode,
|
|
161
|
+
api_key=self.config.oagi_api_key,
|
|
162
|
+
base_url=self.config.oagi_base_url,
|
|
163
|
+
max_steps=self.config.max_steps,
|
|
164
|
+
model=event_data.model,
|
|
165
|
+
temperature=event_data.temperature,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
action_handler = SocketIOActionHandler(self, session)
|
|
169
|
+
image_provider = SocketIOImageProvider(self, session, session.oagi_client)
|
|
170
|
+
|
|
171
|
+
# Start execution in background using agent
|
|
172
|
+
task = asyncio.create_task(
|
|
173
|
+
self._run_agent_task(
|
|
174
|
+
agent,
|
|
175
|
+
session,
|
|
176
|
+
action_handler,
|
|
177
|
+
image_provider,
|
|
178
|
+
event_data.instruction,
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
self.background_tasks[sid] = task
|
|
182
|
+
|
|
183
|
+
except ValidationError as e:
|
|
184
|
+
logger.error(f"Invalid init data: {e}")
|
|
185
|
+
await self.emit(
|
|
186
|
+
"error",
|
|
187
|
+
ErrorEventData(
|
|
188
|
+
message="Invalid init data",
|
|
189
|
+
details={"validation_errors": e.errors()},
|
|
190
|
+
).model_dump(),
|
|
191
|
+
room=sid,
|
|
192
|
+
)
|
|
193
|
+
except Exception as e:
|
|
194
|
+
logger.error(f"Error in init: {e}", exc_info=True)
|
|
195
|
+
await self.emit(
|
|
196
|
+
"error",
|
|
197
|
+
ErrorEventData(message=str(e)).model_dump(),
|
|
198
|
+
room=sid,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
async def _run_agent_task(
|
|
202
|
+
self,
|
|
203
|
+
agent: AsyncDefaultAgent,
|
|
204
|
+
session: Session,
|
|
205
|
+
action_handler: SocketIOActionHandler,
|
|
206
|
+
image_provider: SocketIOImageProvider,
|
|
207
|
+
instruction: str,
|
|
208
|
+
) -> None:
|
|
209
|
+
try:
|
|
210
|
+
# Execute task using agent
|
|
211
|
+
success = await agent.execute(
|
|
212
|
+
instruction=instruction,
|
|
213
|
+
action_handler=action_handler,
|
|
214
|
+
image_provider=image_provider,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Update session status
|
|
218
|
+
if success:
|
|
219
|
+
session.status = "completed"
|
|
220
|
+
logger.info(
|
|
221
|
+
f"Task completed successfully for session {session.session_id}"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Emit finish event
|
|
225
|
+
await self.call(
|
|
226
|
+
"finish",
|
|
227
|
+
FinishEventData(index=0, total=1).model_dump(),
|
|
228
|
+
to=session.socket_id,
|
|
229
|
+
timeout=self.config.socketio_timeout,
|
|
230
|
+
)
|
|
231
|
+
else:
|
|
232
|
+
session.status = "failed"
|
|
233
|
+
logger.warning(f"Task failed for session {session.session_id}")
|
|
234
|
+
|
|
235
|
+
session_store.update_activity(session.session_id)
|
|
236
|
+
|
|
237
|
+
except asyncio.CancelledError:
|
|
238
|
+
logger.info(f"Agent task cancelled for session {session.session_id}")
|
|
239
|
+
session.status = "cancelled"
|
|
240
|
+
except Exception as e:
|
|
241
|
+
logger.error(f"Error in agent task: {e}", exc_info=True)
|
|
242
|
+
session.status = "failed"
|
|
243
|
+
if session.socket_id:
|
|
244
|
+
await self.emit(
|
|
245
|
+
"error",
|
|
246
|
+
ErrorEventData(message=f"Execution failed: {str(e)}").model_dump(),
|
|
247
|
+
room=session.socket_id,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
async def _emit_actions(self, session: Session, actions: list[Action]) -> None:
|
|
251
|
+
total = len(actions)
|
|
252
|
+
|
|
253
|
+
for i, action in enumerate(actions):
|
|
254
|
+
try:
|
|
255
|
+
ack = await self._emit_single_action(session, action, i, total)
|
|
256
|
+
session.actions_executed += 1
|
|
257
|
+
|
|
258
|
+
if ack and not ack.get("success"):
|
|
259
|
+
logger.warning(f"Action {i} failed: {ack.get('error')}")
|
|
260
|
+
|
|
261
|
+
except Exception as e:
|
|
262
|
+
logger.error(f"Error emitting action {i}: {e}", exc_info=True)
|
|
263
|
+
|
|
264
|
+
async def _emit_single_action(
|
|
265
|
+
self, session: Session, action: Action, index: int, total: int
|
|
266
|
+
) -> dict | None:
|
|
267
|
+
arg = action.argument.strip("()")
|
|
268
|
+
common = BaseActionEventData(index=index, total=total).model_dump()
|
|
269
|
+
|
|
270
|
+
logger.info(f"Emitting action {index + 1}/{total}: {action.type.value} {arg}")
|
|
271
|
+
match action.type:
|
|
272
|
+
case (
|
|
273
|
+
ActionType.CLICK
|
|
274
|
+
| ActionType.LEFT_DOUBLE
|
|
275
|
+
| ActionType.LEFT_TRIPLE
|
|
276
|
+
| ActionType.RIGHT_SINGLE
|
|
277
|
+
):
|
|
278
|
+
coords = arg.split(",")
|
|
279
|
+
if len(coords) >= 2:
|
|
280
|
+
x, y = int(coords[0]), int(coords[1])
|
|
281
|
+
else:
|
|
282
|
+
logger.warning(f"Invalid action coordinates: {arg}")
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
return await self.call(
|
|
286
|
+
action.type.value,
|
|
287
|
+
ClickEventData(**common, x=x, y=y).model_dump(),
|
|
288
|
+
to=session.socket_id,
|
|
289
|
+
timeout=self.config.socketio_timeout,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
case ActionType.DRAG:
|
|
293
|
+
coords = arg.split(",")
|
|
294
|
+
if len(coords) >= 4:
|
|
295
|
+
x1, y1, x2, y2 = (int(coords[i]) for i in range(4))
|
|
296
|
+
else:
|
|
297
|
+
logger.warning(f"Invalid drag coordinates: {arg}")
|
|
298
|
+
return None
|
|
299
|
+
|
|
300
|
+
return await self.call(
|
|
301
|
+
"drag",
|
|
302
|
+
DragEventData(**common, x1=x1, y1=y1, x2=x2, y2=y2).model_dump(),
|
|
303
|
+
to=session.socket_id,
|
|
304
|
+
timeout=self.config.socketio_timeout,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
case ActionType.HOTKEY:
|
|
308
|
+
combo = arg.strip()
|
|
309
|
+
count = action.count or 1
|
|
310
|
+
|
|
311
|
+
return await self.call(
|
|
312
|
+
"hotkey",
|
|
313
|
+
HotkeyEventData(**common, combo=combo, count=count).model_dump(),
|
|
314
|
+
to=session.socket_id,
|
|
315
|
+
timeout=self.config.socketio_timeout,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
case ActionType.TYPE:
|
|
319
|
+
text = arg.strip()
|
|
320
|
+
|
|
321
|
+
return await self.call(
|
|
322
|
+
"type",
|
|
323
|
+
TypeEventData(**common, text=text).model_dump(),
|
|
324
|
+
to=session.socket_id,
|
|
325
|
+
timeout=self.config.socketio_timeout,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
case ActionType.SCROLL:
|
|
329
|
+
parts = arg.split(",")
|
|
330
|
+
if len(parts) >= 3:
|
|
331
|
+
x, y = int(parts[0]), int(parts[1])
|
|
332
|
+
direction = parts[2].strip().lower()
|
|
333
|
+
else:
|
|
334
|
+
logger.warning(f"Invalid scroll coordinates: {arg}")
|
|
335
|
+
return None
|
|
336
|
+
|
|
337
|
+
count = action.count or 1
|
|
338
|
+
|
|
339
|
+
return await self.call(
|
|
340
|
+
"scroll",
|
|
341
|
+
ScrollEventData(
|
|
342
|
+
**common,
|
|
343
|
+
x=x,
|
|
344
|
+
y=y,
|
|
345
|
+
direction=direction,
|
|
346
|
+
count=count, # type: ignore
|
|
347
|
+
).model_dump(),
|
|
348
|
+
to=session.socket_id,
|
|
349
|
+
timeout=self.config.socketio_timeout,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
case ActionType.WAIT:
|
|
353
|
+
try:
|
|
354
|
+
duration_ms = int(arg) if arg else 1000
|
|
355
|
+
except (ValueError, TypeError):
|
|
356
|
+
duration_ms = 1000
|
|
357
|
+
|
|
358
|
+
return await self.call(
|
|
359
|
+
"wait",
|
|
360
|
+
WaitEventData(**common, duration_ms=duration_ms).model_dump(),
|
|
361
|
+
to=session.socket_id,
|
|
362
|
+
timeout=self.config.socketio_timeout,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
case ActionType.FINISH:
|
|
366
|
+
return await self.call(
|
|
367
|
+
"finish",
|
|
368
|
+
FinishEventData(**common).model_dump(),
|
|
369
|
+
to=session.socket_id,
|
|
370
|
+
timeout=self.config.socketio_timeout,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
case _:
|
|
374
|
+
logger.warning(f"Unknown action type: {action.type}")
|
|
375
|
+
return None
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
# Dynamic namespace registration
|
|
379
|
+
_registered_namespaces: dict[str, SessionNamespace] = {}
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def get_or_create_namespace(namespace: str, config: ServerConfig) -> SessionNamespace:
|
|
383
|
+
if namespace not in _registered_namespaces:
|
|
384
|
+
ns = SessionNamespace(namespace, config)
|
|
385
|
+
sio.register_namespace(ns)
|
|
386
|
+
_registered_namespaces[namespace] = ns
|
|
387
|
+
logger.info(f"Registered namespace: {namespace}")
|
|
388
|
+
return _registered_namespaces[namespace]
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
# Patch connect handler for dynamic registration
|
|
392
|
+
original_connect = sio._handle_connect
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
async def _patched_handle_connect(eio_sid: str, namespace: str, data: Any) -> Any:
|
|
396
|
+
if namespace and namespace.startswith("/session/"):
|
|
397
|
+
config = ServerConfig()
|
|
398
|
+
get_or_create_namespace(namespace, config)
|
|
399
|
+
return await original_connect(eio_sid, namespace, data)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
sio._handle_connect = _patched_handle_connect
|
|
403
|
+
|
|
404
|
+
# Create ASGI app
|
|
405
|
+
socket_app = socketio.ASGIApp(sio, socketio_path="socket.io")
|
oagi/task/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# -----------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) OpenAGI Foundation
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# This file is part of the official API project.
|
|
6
|
+
# Licensed under the MIT License.
|
|
7
|
+
# -----------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
from .async_ import AsyncActor, AsyncTask
|
|
10
|
+
from .async_short import AsyncShortTask
|
|
11
|
+
from .short import ShortTask
|
|
12
|
+
from .sync import Actor, Task
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"Actor",
|
|
16
|
+
"AsyncActor",
|
|
17
|
+
"Task", # Deprecated: Use Actor instead
|
|
18
|
+
"AsyncTask", # Deprecated: Use AsyncActor instead
|
|
19
|
+
"ShortTask", # Deprecated
|
|
20
|
+
"AsyncShortTask", # Deprecated
|
|
21
|
+
]
|
oagi/task/async_.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# -----------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) OpenAGI Foundation
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# This file is part of the official API project.
|
|
6
|
+
# Licensed under the MIT License.
|
|
7
|
+
# -----------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
import warnings
|
|
10
|
+
|
|
11
|
+
from ..client import AsyncClient
|
|
12
|
+
from ..types import URL, Image, Step
|
|
13
|
+
from .base import BaseActor
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AsyncActor(BaseActor):
|
|
17
|
+
"""Async base class for task automation with the OAGI API."""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
api_key: str | None = None,
|
|
22
|
+
base_url: str | None = None,
|
|
23
|
+
model: str = "lux-actor-1",
|
|
24
|
+
temperature: float | None = None,
|
|
25
|
+
):
|
|
26
|
+
super().__init__(api_key, base_url, model, temperature)
|
|
27
|
+
self.client = AsyncClient(base_url=base_url, api_key=api_key)
|
|
28
|
+
self.api_key = self.client.api_key
|
|
29
|
+
self.base_url = self.client.base_url
|
|
30
|
+
|
|
31
|
+
async def init_task(
|
|
32
|
+
self,
|
|
33
|
+
task_desc: str,
|
|
34
|
+
max_steps: int = 20,
|
|
35
|
+
):
|
|
36
|
+
"""Initialize a new task with the given description.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
task_desc: Task description
|
|
40
|
+
max_steps: Maximum number of steps allowed
|
|
41
|
+
"""
|
|
42
|
+
self._prepare_init_task(task_desc, max_steps)
|
|
43
|
+
|
|
44
|
+
async def step(
|
|
45
|
+
self,
|
|
46
|
+
screenshot: Image | URL | bytes,
|
|
47
|
+
instruction: str | None = None,
|
|
48
|
+
temperature: float | None = None,
|
|
49
|
+
) -> Step:
|
|
50
|
+
"""Send screenshot to the server and get the next actions.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
screenshot: Screenshot as Image object or raw bytes
|
|
54
|
+
instruction: Optional additional instruction for this step
|
|
55
|
+
temperature: Sampling temperature for this step (overrides task default if provided)
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Step: The actions and reasoning for this step
|
|
59
|
+
"""
|
|
60
|
+
kwargs = self._prepare_step(
|
|
61
|
+
screenshot, instruction, temperature, prefix="async "
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
response = await self.client.create_message(**kwargs)
|
|
66
|
+
return self._build_step_response(response, prefix="Async ")
|
|
67
|
+
except Exception as e:
|
|
68
|
+
self._handle_step_error(e, prefix="async ")
|
|
69
|
+
|
|
70
|
+
async def close(self):
|
|
71
|
+
"""Close the underlying HTTP client to free resources."""
|
|
72
|
+
await self.client.close()
|
|
73
|
+
|
|
74
|
+
async def __aenter__(self):
|
|
75
|
+
return self
|
|
76
|
+
|
|
77
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
78
|
+
await self.close()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class AsyncTask(AsyncActor):
|
|
82
|
+
"""Deprecated: Use AsyncActor instead.
|
|
83
|
+
|
|
84
|
+
This class is deprecated and will be removed in a future version.
|
|
85
|
+
Please use AsyncActor instead.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
api_key: str | None = None,
|
|
91
|
+
base_url: str | None = None,
|
|
92
|
+
model: str = "lux-actor-1",
|
|
93
|
+
temperature: float | None = None,
|
|
94
|
+
):
|
|
95
|
+
warnings.warn(
|
|
96
|
+
"AsyncTask is deprecated and will be removed in a future version. "
|
|
97
|
+
"Please use AsyncActor instead.",
|
|
98
|
+
DeprecationWarning,
|
|
99
|
+
stacklevel=2,
|
|
100
|
+
)
|
|
101
|
+
super().__init__(api_key, base_url, model, temperature)
|
oagi/task/async_short.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# -----------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) OpenAGI Foundation
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# This file is part of the official API project.
|
|
6
|
+
# Licensed under the MIT License.
|
|
7
|
+
# -----------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
import warnings
|
|
10
|
+
|
|
11
|
+
from ..logging import get_logger
|
|
12
|
+
from ..types import AsyncActionHandler, AsyncImageProvider
|
|
13
|
+
from .async_ import AsyncActor
|
|
14
|
+
from .base import BaseAutoMode
|
|
15
|
+
|
|
16
|
+
logger = get_logger("async_short_task")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AsyncShortTask(AsyncActor, BaseAutoMode):
|
|
20
|
+
"""Deprecated: This class is deprecated and will be removed in a future version.
|
|
21
|
+
|
|
22
|
+
Async task implementation with automatic mode for short-duration tasks.
|
|
23
|
+
Please use AsyncActor directly with custom automation logic instead.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
api_key: str | None = None,
|
|
29
|
+
base_url: str | None = None,
|
|
30
|
+
model: str = "lux-actor-1",
|
|
31
|
+
temperature: float | None = None,
|
|
32
|
+
):
|
|
33
|
+
warnings.warn(
|
|
34
|
+
"AsyncShortTask is deprecated and will be removed in a future version. "
|
|
35
|
+
"Please use AsyncActor with custom automation logic instead.",
|
|
36
|
+
DeprecationWarning,
|
|
37
|
+
stacklevel=2,
|
|
38
|
+
)
|
|
39
|
+
super().__init__(
|
|
40
|
+
api_key=api_key, base_url=base_url, model=model, temperature=temperature
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
async def auto_mode(
|
|
44
|
+
self,
|
|
45
|
+
task_desc: str,
|
|
46
|
+
max_steps: int = 20,
|
|
47
|
+
executor: AsyncActionHandler = None,
|
|
48
|
+
image_provider: AsyncImageProvider = None,
|
|
49
|
+
temperature: float | None = None,
|
|
50
|
+
) -> bool:
|
|
51
|
+
"""Run the task in automatic mode with the provided executor and image provider.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
task_desc: Task description
|
|
55
|
+
max_steps: Maximum number of steps
|
|
56
|
+
executor: Async handler to execute actions
|
|
57
|
+
image_provider: Async provider for screenshots
|
|
58
|
+
temperature: Sampling temperature for all steps (overrides task default if provided)
|
|
59
|
+
"""
|
|
60
|
+
self._log_auto_mode_start(task_desc, max_steps, prefix="async ")
|
|
61
|
+
|
|
62
|
+
await self.init_task(task_desc, max_steps=max_steps)
|
|
63
|
+
|
|
64
|
+
for i in range(max_steps):
|
|
65
|
+
self._log_auto_mode_step(i + 1, max_steps, prefix="async ")
|
|
66
|
+
image = await image_provider()
|
|
67
|
+
step = await self.step(image, temperature=temperature)
|
|
68
|
+
if executor:
|
|
69
|
+
self._log_auto_mode_actions(len(step.actions), prefix="async ")
|
|
70
|
+
await executor(step.actions)
|
|
71
|
+
if step.stop:
|
|
72
|
+
self._log_auto_mode_completion(i + 1, prefix="async ")
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
self._log_auto_mode_max_steps(max_steps, prefix="async ")
|
|
76
|
+
return False
|