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
oagi/server/main.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
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 logging
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from ..exceptions import check_optional_dependency
|
|
14
|
+
from .config import ServerConfig
|
|
15
|
+
from .models import SessionStatusData
|
|
16
|
+
from .session_store import session_store
|
|
17
|
+
from .socketio_server import socket_app
|
|
18
|
+
|
|
19
|
+
check_optional_dependency("fastapi", "Server features", "server")
|
|
20
|
+
check_optional_dependency("uvicorn", "Server features", "server")
|
|
21
|
+
|
|
22
|
+
import uvicorn # noqa: E402
|
|
23
|
+
from fastapi import FastAPI, HTTPException # noqa: E402
|
|
24
|
+
from fastapi.middleware.cors import CORSMiddleware # noqa: E402
|
|
25
|
+
|
|
26
|
+
logging.basicConfig(
|
|
27
|
+
level=logging.INFO,
|
|
28
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def create_app(config: ServerConfig | None = None) -> FastAPI:
|
|
35
|
+
if config is None:
|
|
36
|
+
config = ServerConfig()
|
|
37
|
+
|
|
38
|
+
app = FastAPI(
|
|
39
|
+
title="OAGI Socket.IO Server",
|
|
40
|
+
description="Real-time task automation server for OAGI SDK",
|
|
41
|
+
version="0.1.0",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
cors_origins = (
|
|
45
|
+
config.cors_allowed_origins.split(",")
|
|
46
|
+
if config.cors_allowed_origins != "*"
|
|
47
|
+
else ["*"]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
app.add_middleware(
|
|
51
|
+
CORSMiddleware,
|
|
52
|
+
allow_origins=cors_origins,
|
|
53
|
+
allow_credentials=True,
|
|
54
|
+
allow_methods=["*"],
|
|
55
|
+
allow_headers=["*"],
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@app.get("/")
|
|
59
|
+
async def root() -> dict[str, str]:
|
|
60
|
+
return {
|
|
61
|
+
"name": "OAGI Socket.IO Server",
|
|
62
|
+
"version": "0.1.0",
|
|
63
|
+
"status": "running",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@app.get("/health")
|
|
67
|
+
async def health_check() -> dict[str, Any]:
|
|
68
|
+
return {
|
|
69
|
+
"status": "healthy",
|
|
70
|
+
"server": {
|
|
71
|
+
"name": "OAGI Socket.IO Server",
|
|
72
|
+
"version": "0.1.0",
|
|
73
|
+
},
|
|
74
|
+
"config": {
|
|
75
|
+
"base_url": config.oagi_base_url,
|
|
76
|
+
"default_model": config.default_model,
|
|
77
|
+
},
|
|
78
|
+
"sessions": {
|
|
79
|
+
"active": len(session_store.sessions),
|
|
80
|
+
"connected": sum(
|
|
81
|
+
1 for s in session_store.sessions.values() if s.socket_id
|
|
82
|
+
),
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@app.get("/sessions")
|
|
87
|
+
async def list_sessions() -> dict[str, Any]:
|
|
88
|
+
return {
|
|
89
|
+
"sessions": session_store.list_sessions(),
|
|
90
|
+
"total": len(session_store.sessions),
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@app.get("/sessions/{session_id}")
|
|
94
|
+
async def get_session(session_id: str) -> SessionStatusData:
|
|
95
|
+
session = session_store.get_session(session_id)
|
|
96
|
+
if not session:
|
|
97
|
+
raise HTTPException(
|
|
98
|
+
status_code=404, detail=f"Session {session_id} not found"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return SessionStatusData(
|
|
102
|
+
session_id=session.session_id,
|
|
103
|
+
status=session.status, # type: ignore
|
|
104
|
+
instruction=session.instruction,
|
|
105
|
+
created_at=session.created_at,
|
|
106
|
+
actions_executed=session.actions_executed,
|
|
107
|
+
last_activity=datetime.fromtimestamp(session.last_activity).isoformat(),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@app.delete("/sessions/{session_id}")
|
|
111
|
+
async def delete_session(session_id: str) -> dict[str, str]:
|
|
112
|
+
session = session_store.get_session(session_id)
|
|
113
|
+
if not session:
|
|
114
|
+
raise HTTPException(
|
|
115
|
+
status_code=404, detail=f"Session {session_id} not found"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if session.oagi_client:
|
|
119
|
+
try:
|
|
120
|
+
await session.oagi_client.close()
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.warning(f"Error closing OAGI client: {e}")
|
|
123
|
+
|
|
124
|
+
deleted = session_store.delete_session(session_id)
|
|
125
|
+
if deleted:
|
|
126
|
+
return {"message": f"Session {session_id} deleted"}
|
|
127
|
+
else:
|
|
128
|
+
raise HTTPException(status_code=500, detail="Failed to delete session")
|
|
129
|
+
|
|
130
|
+
@app.post("/sessions/cleanup")
|
|
131
|
+
async def cleanup_sessions(timeout_hours: float = 1.0) -> dict[str, Any]:
|
|
132
|
+
timeout_seconds = timeout_hours * 3600
|
|
133
|
+
cleaned = session_store.cleanup_inactive_sessions(timeout_seconds)
|
|
134
|
+
return {
|
|
135
|
+
"cleaned": cleaned,
|
|
136
|
+
"remaining": len(session_store.sessions),
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Mount Socket.IO application
|
|
140
|
+
app.mount("/", socket_app)
|
|
141
|
+
|
|
142
|
+
logger.info(
|
|
143
|
+
f"Server created - will listen on {config.server_host}:{config.server_port}"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return app
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
config = ServerConfig()
|
|
151
|
+
app = create_app(config)
|
|
152
|
+
uvicorn.run(
|
|
153
|
+
app,
|
|
154
|
+
host=config.server_host,
|
|
155
|
+
port=config.server_port,
|
|
156
|
+
log_level="info",
|
|
157
|
+
)
|
oagi/server/models.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
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 typing import Literal
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Client-to-server events
|
|
15
|
+
class InitEventData(BaseModel):
|
|
16
|
+
instruction: str = Field(...)
|
|
17
|
+
mode: str | None = Field(default="actor")
|
|
18
|
+
model: str | None = Field(default="lux-actor-1")
|
|
19
|
+
temperature: float | None = Field(default=0.1, ge=0.0, le=2.0)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Server-to-client events
|
|
23
|
+
class BaseActionEventData(BaseModel):
|
|
24
|
+
index: int = Field(..., ge=0)
|
|
25
|
+
total: int = Field(..., ge=1)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ClickEventData(BaseActionEventData):
|
|
29
|
+
x: int = Field(..., ge=0, le=1000)
|
|
30
|
+
y: int = Field(..., ge=0, le=1000)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DragEventData(BaseActionEventData):
|
|
34
|
+
x1: int = Field(..., ge=0, le=1000)
|
|
35
|
+
y1: int = Field(..., ge=0, le=1000)
|
|
36
|
+
x2: int = Field(..., ge=0, le=1000)
|
|
37
|
+
y2: int = Field(..., ge=0, le=1000)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class HotkeyEventData(BaseActionEventData):
|
|
41
|
+
combo: str = Field(...)
|
|
42
|
+
count: int = Field(default=1, ge=1)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TypeEventData(BaseActionEventData):
|
|
46
|
+
text: str = Field(...)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ScrollEventData(BaseActionEventData):
|
|
50
|
+
x: int = Field(..., ge=0, le=1000)
|
|
51
|
+
y: int = Field(..., ge=0, le=1000)
|
|
52
|
+
direction: Literal["up", "down"] = Field(...)
|
|
53
|
+
count: int = Field(default=1, ge=1)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class WaitEventData(BaseActionEventData):
|
|
57
|
+
duration_ms: int = Field(default=1000, ge=0)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class FinishEventData(BaseActionEventData):
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Screenshot request/response
|
|
65
|
+
class ScreenshotRequestData(BaseModel):
|
|
66
|
+
presigned_url: str = Field(...)
|
|
67
|
+
uuid: str = Field(...)
|
|
68
|
+
expires_at: str = Field(...)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ScreenshotResponseData(BaseModel):
|
|
72
|
+
success: bool = Field(...)
|
|
73
|
+
error: str | None = Field(None)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Action acknowledgement
|
|
77
|
+
class ActionAckData(BaseModel):
|
|
78
|
+
index: int = Field(...)
|
|
79
|
+
success: bool = Field(...)
|
|
80
|
+
error: str | None = Field(None)
|
|
81
|
+
execution_time_ms: int | None = Field(None)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Session status
|
|
85
|
+
class SessionStatusData(BaseModel):
|
|
86
|
+
session_id: str = Field(...)
|
|
87
|
+
status: Literal["initialized", "running", "completed", "failed"] = Field(...)
|
|
88
|
+
instruction: str = Field(...)
|
|
89
|
+
created_at: str = Field(...)
|
|
90
|
+
actions_executed: int = Field(default=0)
|
|
91
|
+
last_activity: str = Field(...)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# Error event
|
|
95
|
+
class ErrorEventData(BaseModel):
|
|
96
|
+
message: str = Field(...)
|
|
97
|
+
code: str | None = Field(None)
|
|
98
|
+
details: dict | None = Field(None)
|
|
@@ -0,0 +1,116 @@
|
|
|
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 secrets
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Any
|
|
12
|
+
from uuid import uuid4
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Session:
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
session_id: str,
|
|
19
|
+
instruction: str,
|
|
20
|
+
mode: str = "actor",
|
|
21
|
+
model: str = "lux-actor-1",
|
|
22
|
+
temperature: float = 0.0,
|
|
23
|
+
):
|
|
24
|
+
self.session_id: str = session_id
|
|
25
|
+
self.instruction: str = instruction
|
|
26
|
+
self.mode: str = mode
|
|
27
|
+
self.model: str = model
|
|
28
|
+
self.temperature: float = temperature
|
|
29
|
+
|
|
30
|
+
# OAGI task state
|
|
31
|
+
self.task_id: str = uuid4().hex
|
|
32
|
+
self.message_history: list[dict[str, Any]] = []
|
|
33
|
+
self.current_screenshot_url: str | None = None
|
|
34
|
+
|
|
35
|
+
# Socket state
|
|
36
|
+
self.socket_id: str | None = None
|
|
37
|
+
self.namespace: str | None = None
|
|
38
|
+
self.last_activity: float = datetime.now().timestamp()
|
|
39
|
+
|
|
40
|
+
# Status tracking
|
|
41
|
+
self.status: str = "initialized"
|
|
42
|
+
self.created_at: str = datetime.now().isoformat()
|
|
43
|
+
self.actions_executed: int = 0
|
|
44
|
+
|
|
45
|
+
# OAGI client reference
|
|
46
|
+
self.oagi_client: Any | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SessionStore:
|
|
50
|
+
def __init__(self):
|
|
51
|
+
self.sessions: dict[str, Session] = {}
|
|
52
|
+
|
|
53
|
+
def create_session(
|
|
54
|
+
self,
|
|
55
|
+
instruction: str,
|
|
56
|
+
mode: str = "actor",
|
|
57
|
+
model: str = "lux-actor-1",
|
|
58
|
+
temperature: float = 0.0,
|
|
59
|
+
session_id: str | None = None,
|
|
60
|
+
) -> str:
|
|
61
|
+
if session_id is None:
|
|
62
|
+
session_id = f"ses_{secrets.token_urlsafe(16)}"
|
|
63
|
+
|
|
64
|
+
session = Session(session_id, instruction, mode, model, temperature)
|
|
65
|
+
self.sessions[session_id] = session
|
|
66
|
+
return session_id
|
|
67
|
+
|
|
68
|
+
def get_session(self, session_id: str) -> Session | None:
|
|
69
|
+
return self.sessions.get(session_id)
|
|
70
|
+
|
|
71
|
+
def get_session_by_socket_id(self, socket_id: str) -> Session | None:
|
|
72
|
+
for session in self.sessions.values():
|
|
73
|
+
if session.socket_id == socket_id:
|
|
74
|
+
return session
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def delete_session(self, session_id: str) -> bool:
|
|
78
|
+
if session_id in self.sessions:
|
|
79
|
+
self.sessions.pop(session_id)
|
|
80
|
+
return True
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
def update_activity(self, session_id: str) -> None:
|
|
84
|
+
session = self.sessions.get(session_id)
|
|
85
|
+
if session:
|
|
86
|
+
session.last_activity = datetime.now().timestamp()
|
|
87
|
+
|
|
88
|
+
def list_sessions(self) -> list[dict[str, Any]]:
|
|
89
|
+
return [
|
|
90
|
+
{
|
|
91
|
+
"session_id": session.session_id,
|
|
92
|
+
"status": session.status,
|
|
93
|
+
"instruction": session.instruction,
|
|
94
|
+
"created_at": session.created_at,
|
|
95
|
+
"actions_executed": session.actions_executed,
|
|
96
|
+
"connected": session.socket_id is not None,
|
|
97
|
+
}
|
|
98
|
+
for session in self.sessions.values()
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
def cleanup_inactive_sessions(self, timeout_seconds: float) -> int:
|
|
102
|
+
current_time = datetime.now().timestamp()
|
|
103
|
+
sessions_to_delete = []
|
|
104
|
+
|
|
105
|
+
for session_id, session in self.sessions.items():
|
|
106
|
+
if current_time - session.last_activity > timeout_seconds:
|
|
107
|
+
sessions_to_delete.append(session_id)
|
|
108
|
+
|
|
109
|
+
for session_id in sessions_to_delete:
|
|
110
|
+
self.delete_session(session_id)
|
|
111
|
+
|
|
112
|
+
return len(sessions_to_delete)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# Global instance
|
|
116
|
+
session_store = SessionStore()
|