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.
Files changed (68) hide show
  1. oagi/__init__.py +148 -0
  2. oagi/agent/__init__.py +33 -0
  3. oagi/agent/default.py +124 -0
  4. oagi/agent/factories.py +74 -0
  5. oagi/agent/observer/__init__.py +38 -0
  6. oagi/agent/observer/agent_observer.py +99 -0
  7. oagi/agent/observer/events.py +28 -0
  8. oagi/agent/observer/exporters.py +445 -0
  9. oagi/agent/observer/protocol.py +12 -0
  10. oagi/agent/protocol.py +55 -0
  11. oagi/agent/registry.py +155 -0
  12. oagi/agent/tasker/__init__.py +33 -0
  13. oagi/agent/tasker/memory.py +160 -0
  14. oagi/agent/tasker/models.py +77 -0
  15. oagi/agent/tasker/planner.py +408 -0
  16. oagi/agent/tasker/taskee_agent.py +512 -0
  17. oagi/agent/tasker/tasker_agent.py +324 -0
  18. oagi/cli/__init__.py +11 -0
  19. oagi/cli/agent.py +281 -0
  20. oagi/cli/display.py +56 -0
  21. oagi/cli/main.py +77 -0
  22. oagi/cli/server.py +94 -0
  23. oagi/cli/tracking.py +55 -0
  24. oagi/cli/utils.py +89 -0
  25. oagi/client/__init__.py +12 -0
  26. oagi/client/async_.py +290 -0
  27. oagi/client/base.py +457 -0
  28. oagi/client/sync.py +293 -0
  29. oagi/exceptions.py +118 -0
  30. oagi/handler/__init__.py +24 -0
  31. oagi/handler/_macos.py +55 -0
  32. oagi/handler/async_pyautogui_action_handler.py +44 -0
  33. oagi/handler/async_screenshot_maker.py +47 -0
  34. oagi/handler/pil_image.py +102 -0
  35. oagi/handler/pyautogui_action_handler.py +291 -0
  36. oagi/handler/screenshot_maker.py +41 -0
  37. oagi/logging.py +55 -0
  38. oagi/server/__init__.py +13 -0
  39. oagi/server/agent_wrappers.py +98 -0
  40. oagi/server/config.py +46 -0
  41. oagi/server/main.py +157 -0
  42. oagi/server/models.py +98 -0
  43. oagi/server/session_store.py +116 -0
  44. oagi/server/socketio_server.py +405 -0
  45. oagi/task/__init__.py +21 -0
  46. oagi/task/async_.py +101 -0
  47. oagi/task/async_short.py +76 -0
  48. oagi/task/base.py +157 -0
  49. oagi/task/short.py +76 -0
  50. oagi/task/sync.py +99 -0
  51. oagi/types/__init__.py +50 -0
  52. oagi/types/action_handler.py +30 -0
  53. oagi/types/async_action_handler.py +30 -0
  54. oagi/types/async_image_provider.py +38 -0
  55. oagi/types/image.py +17 -0
  56. oagi/types/image_provider.py +35 -0
  57. oagi/types/models/__init__.py +32 -0
  58. oagi/types/models/action.py +33 -0
  59. oagi/types/models/client.py +68 -0
  60. oagi/types/models/image_config.py +47 -0
  61. oagi/types/models/step.py +17 -0
  62. oagi/types/step_observer.py +93 -0
  63. oagi/types/url.py +3 -0
  64. oagi_core-0.10.1.dist-info/METADATA +245 -0
  65. oagi_core-0.10.1.dist-info/RECORD +68 -0
  66. oagi_core-0.10.1.dist-info/WHEEL +4 -0
  67. oagi_core-0.10.1.dist-info/entry_points.txt +2 -0
  68. 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()