oagi-core 0.10.1__py3-none-any.whl → 0.10.3__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/agent/default.py +16 -3
- oagi/agent/factories.py +26 -10
- oagi/agent/observer/exporters.py +142 -251
- oagi/agent/observer/report_template.html +455 -0
- oagi/agent/tasker/planner.py +2 -1
- oagi/agent/tasker/taskee_agent.py +19 -4
- oagi/agent/tasker/tasker_agent.py +15 -4
- oagi/cli/agent.py +35 -12
- oagi/cli/display.py +2 -1
- oagi/cli/server.py +1 -1
- oagi/cli/utils.py +4 -3
- oagi/client/async_.py +19 -6
- oagi/client/base.py +14 -16
- oagi/client/sync.py +19 -6
- oagi/constants.py +43 -0
- oagi/handler/pyautogui_action_handler.py +14 -23
- oagi/server/config.py +6 -3
- oagi/server/models.py +5 -3
- oagi/server/session_store.py +6 -4
- oagi/server/socketio_server.py +22 -20
- oagi/task/async_.py +4 -3
- oagi/task/async_short.py +3 -2
- oagi/task/base.py +2 -1
- oagi/task/short.py +3 -2
- oagi/task/sync.py +4 -3
- oagi/types/__init__.py +12 -1
- oagi/types/models/__init__.py +10 -1
- oagi/types/models/action.py +51 -0
- {oagi_core-0.10.1.dist-info → oagi_core-0.10.3.dist-info}/METADATA +1 -1
- {oagi_core-0.10.1.dist-info → oagi_core-0.10.3.dist-info}/RECORD +33 -31
- {oagi_core-0.10.1.dist-info → oagi_core-0.10.3.dist-info}/WHEEL +0 -0
- {oagi_core-0.10.1.dist-info → oagi_core-0.10.3.dist-info}/entry_points.txt +0 -0
- {oagi_core-0.10.1.dist-info → oagi_core-0.10.3.dist-info}/licenses/LICENSE +0 -0
oagi/cli/display.py
CHANGED
|
@@ -29,7 +29,8 @@ def display_step_table(
|
|
|
29
29
|
actions_display = []
|
|
30
30
|
for action in step.actions[:3]:
|
|
31
31
|
arg = action.argument[:20] if action.argument else ""
|
|
32
|
-
|
|
32
|
+
count_str = f" x{action.count}" if action.count and action.count > 1 else ""
|
|
33
|
+
actions_display.append(f"{action.type.value}({arg}){count_str}")
|
|
33
34
|
|
|
34
35
|
actions_str = ", ".join(actions_display)
|
|
35
36
|
if len(step.actions) > 3:
|
oagi/cli/server.py
CHANGED
|
@@ -25,7 +25,7 @@ def add_server_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
25
25
|
start_parser.add_argument(
|
|
26
26
|
"--host",
|
|
27
27
|
type=str,
|
|
28
|
-
help="Server host (default:
|
|
28
|
+
help="Server host (default: 127.0.0.1, or OAGI_SERVER_HOST env var)",
|
|
29
29
|
)
|
|
30
30
|
start_parser.add_argument(
|
|
31
31
|
"--port",
|
oagi/cli/utils.py
CHANGED
|
@@ -11,6 +11,7 @@ import os
|
|
|
11
11
|
import sys
|
|
12
12
|
from importlib.metadata import version as get_version
|
|
13
13
|
|
|
14
|
+
from oagi.constants import DEFAULT_BASE_URL, MODEL_ACTOR
|
|
14
15
|
from oagi.exceptions import check_optional_dependency
|
|
15
16
|
|
|
16
17
|
|
|
@@ -55,10 +56,10 @@ def display_version() -> None:
|
|
|
55
56
|
def display_config() -> None:
|
|
56
57
|
config_vars = {
|
|
57
58
|
"OAGI_API_KEY": os.getenv("OAGI_API_KEY", ""),
|
|
58
|
-
"OAGI_BASE_URL": os.getenv("OAGI_BASE_URL",
|
|
59
|
-
"OAGI_DEFAULT_MODEL": os.getenv("OAGI_DEFAULT_MODEL",
|
|
59
|
+
"OAGI_BASE_URL": os.getenv("OAGI_BASE_URL", DEFAULT_BASE_URL),
|
|
60
|
+
"OAGI_DEFAULT_MODEL": os.getenv("OAGI_DEFAULT_MODEL", MODEL_ACTOR),
|
|
60
61
|
"OAGI_LOG_LEVEL": os.getenv("OAGI_LOG_LEVEL", "INFO"),
|
|
61
|
-
"OAGI_SERVER_HOST": os.getenv("OAGI_SERVER_HOST", "
|
|
62
|
+
"OAGI_SERVER_HOST": os.getenv("OAGI_SERVER_HOST", "127.0.0.1"),
|
|
62
63
|
"OAGI_SERVER_PORT": os.getenv("OAGI_SERVER_PORT", "8000"),
|
|
63
64
|
"OAGI_MAX_STEPS": os.getenv("OAGI_MAX_STEPS", "30"),
|
|
64
65
|
}
|
oagi/client/async_.py
CHANGED
|
@@ -10,6 +10,13 @@ from functools import wraps
|
|
|
10
10
|
|
|
11
11
|
import httpx
|
|
12
12
|
|
|
13
|
+
from ..constants import (
|
|
14
|
+
API_HEALTH_ENDPOINT,
|
|
15
|
+
API_V1_FILE_UPLOAD_ENDPOINT,
|
|
16
|
+
API_V1_GENERATE_ENDPOINT,
|
|
17
|
+
API_V2_MESSAGE_ENDPOINT,
|
|
18
|
+
HTTP_CLIENT_TIMEOUT,
|
|
19
|
+
)
|
|
13
20
|
from ..logging import get_logger
|
|
14
21
|
from ..types import Image
|
|
15
22
|
from ..types.models import GenerateResponse, LLMResponse, UploadFileResponse
|
|
@@ -41,7 +48,7 @@ class AsyncClient(BaseClient[httpx.AsyncClient]):
|
|
|
41
48
|
def __init__(self, base_url: str | None = None, api_key: str | None = None):
|
|
42
49
|
super().__init__(base_url, api_key)
|
|
43
50
|
self.client = httpx.AsyncClient(base_url=self.base_url)
|
|
44
|
-
self.upload_client = httpx.AsyncClient(timeout=
|
|
51
|
+
self.upload_client = httpx.AsyncClient(timeout=HTTP_CLIENT_TIMEOUT)
|
|
45
52
|
logger.info(f"AsyncClient initialized with base_url: {self.base_url}")
|
|
46
53
|
|
|
47
54
|
async def __aenter__(self):
|
|
@@ -121,7 +128,10 @@ class AsyncClient(BaseClient[httpx.AsyncClient]):
|
|
|
121
128
|
# Make request
|
|
122
129
|
try:
|
|
123
130
|
response = await self.client.post(
|
|
124
|
-
|
|
131
|
+
API_V2_MESSAGE_ENDPOINT,
|
|
132
|
+
json=payload,
|
|
133
|
+
headers=headers,
|
|
134
|
+
timeout=self.timeout,
|
|
125
135
|
)
|
|
126
136
|
return self._process_response(response)
|
|
127
137
|
except (httpx.TimeoutException, httpx.NetworkError) as e:
|
|
@@ -136,7 +146,7 @@ class AsyncClient(BaseClient[httpx.AsyncClient]):
|
|
|
136
146
|
"""
|
|
137
147
|
logger.debug("Making async health check request")
|
|
138
148
|
try:
|
|
139
|
-
response = await self.client.get(
|
|
149
|
+
response = await self.client.get(API_HEALTH_ENDPOINT)
|
|
140
150
|
response.raise_for_status()
|
|
141
151
|
result = response.json()
|
|
142
152
|
logger.debug("Async health check successful")
|
|
@@ -158,12 +168,12 @@ class AsyncClient(BaseClient[httpx.AsyncClient]):
|
|
|
158
168
|
Returns:
|
|
159
169
|
UploadFileResponse: The response from /v1/file/upload with uuid and presigned S3 URL
|
|
160
170
|
"""
|
|
161
|
-
logger.debug("Making async API request to
|
|
171
|
+
logger.debug(f"Making async API request to {API_V1_FILE_UPLOAD_ENDPOINT}")
|
|
162
172
|
|
|
163
173
|
try:
|
|
164
174
|
headers = self._build_headers(api_version)
|
|
165
175
|
response = await self.client.get(
|
|
166
|
-
|
|
176
|
+
API_V1_FILE_UPLOAD_ENDPOINT, headers=headers, timeout=self.timeout
|
|
167
177
|
)
|
|
168
178
|
return self._process_upload_response(response)
|
|
169
179
|
except (httpx.TimeoutException, httpx.NetworkError, httpx.HTTPStatusError) as e:
|
|
@@ -283,7 +293,10 @@ class AsyncClient(BaseClient[httpx.AsyncClient]):
|
|
|
283
293
|
# Make request
|
|
284
294
|
try:
|
|
285
295
|
response = await self.client.post(
|
|
286
|
-
|
|
296
|
+
API_V1_GENERATE_ENDPOINT,
|
|
297
|
+
json=payload,
|
|
298
|
+
headers=headers,
|
|
299
|
+
timeout=self.timeout,
|
|
287
300
|
)
|
|
288
301
|
return self._process_generate_response(response)
|
|
289
302
|
except (httpx.TimeoutException, httpx.NetworkError) as e:
|
oagi/client/base.py
CHANGED
|
@@ -11,6 +11,7 @@ from typing import Any, Generic, TypeVar
|
|
|
11
11
|
|
|
12
12
|
import httpx
|
|
13
13
|
|
|
14
|
+
from ..constants import API_KEY_HELP_URL, DEFAULT_BASE_URL, HTTP_CLIENT_TIMEOUT
|
|
14
15
|
from ..exceptions import (
|
|
15
16
|
APIError,
|
|
16
17
|
AuthenticationError,
|
|
@@ -41,20 +42,19 @@ class BaseClient(Generic[HttpClientT]):
|
|
|
41
42
|
|
|
42
43
|
def __init__(self, base_url: str | None = None, api_key: str | None = None):
|
|
43
44
|
# Get from environment if not provided
|
|
44
|
-
self.base_url = (
|
|
45
|
-
base_url or os.getenv("OAGI_BASE_URL") or "https://api.agiopen.org"
|
|
46
|
-
)
|
|
45
|
+
self.base_url = base_url or os.getenv("OAGI_BASE_URL") or DEFAULT_BASE_URL
|
|
47
46
|
self.api_key = api_key or os.getenv("OAGI_API_KEY")
|
|
48
47
|
|
|
49
48
|
# Validate required configuration
|
|
50
49
|
if not self.api_key:
|
|
51
50
|
raise ConfigurationError(
|
|
52
51
|
"OAGI API key must be provided either as 'api_key' parameter or "
|
|
53
|
-
"OAGI_API_KEY environment variable"
|
|
52
|
+
"OAGI_API_KEY environment variable. "
|
|
53
|
+
f"Get your API key at {API_KEY_HELP_URL}"
|
|
54
54
|
)
|
|
55
55
|
|
|
56
56
|
self.base_url = self.base_url.rstrip("/")
|
|
57
|
-
self.timeout =
|
|
57
|
+
self.timeout = HTTP_CLIENT_TIMEOUT
|
|
58
58
|
self.client: HttpClientT # Will be set by subclasses
|
|
59
59
|
|
|
60
60
|
logger.info(f"Client initialized with base_url: {self.base_url}")
|
|
@@ -273,22 +273,20 @@ class BaseClient(Generic[HttpClientT]):
|
|
|
273
273
|
NetworkError: If network error occurs
|
|
274
274
|
APIError: If API returns error or invalid response
|
|
275
275
|
"""
|
|
276
|
+
response_data = self._parse_response_json(response)
|
|
277
|
+
|
|
278
|
+
# Check for error status codes first (follows _process_response pattern)
|
|
279
|
+
if response.status_code != 200:
|
|
280
|
+
self._handle_response_error(response, response_data)
|
|
281
|
+
|
|
276
282
|
try:
|
|
277
|
-
response_data = response.json()
|
|
278
283
|
upload_file_response = UploadFileResponse(**response_data)
|
|
279
284
|
logger.debug("Calling /v1/file/upload successful")
|
|
280
285
|
return upload_file_response
|
|
281
|
-
except
|
|
282
|
-
logger.error(f"
|
|
283
|
-
raise APIError(
|
|
284
|
-
f"Invalid response format (status {response.status_code})",
|
|
285
|
-
status_code=response.status_code,
|
|
286
|
-
response=response,
|
|
287
|
-
)
|
|
288
|
-
except KeyError as e:
|
|
289
|
-
logger.error(f"Invalid response: {response.status_code}")
|
|
286
|
+
except Exception as e:
|
|
287
|
+
logger.error(f"Invalid upload response: {response.status_code}")
|
|
290
288
|
raise APIError(
|
|
291
|
-
f"Invalid presigned S3 URL response:
|
|
289
|
+
f"Invalid presigned S3 URL response: {e}",
|
|
292
290
|
status_code=response.status_code,
|
|
293
291
|
response=response,
|
|
294
292
|
)
|
oagi/client/sync.py
CHANGED
|
@@ -11,6 +11,13 @@ from functools import wraps
|
|
|
11
11
|
import httpx
|
|
12
12
|
from httpx import Response
|
|
13
13
|
|
|
14
|
+
from ..constants import (
|
|
15
|
+
API_HEALTH_ENDPOINT,
|
|
16
|
+
API_V1_FILE_UPLOAD_ENDPOINT,
|
|
17
|
+
API_V1_GENERATE_ENDPOINT,
|
|
18
|
+
API_V2_MESSAGE_ENDPOINT,
|
|
19
|
+
HTTP_CLIENT_TIMEOUT,
|
|
20
|
+
)
|
|
14
21
|
from ..logging import get_logger
|
|
15
22
|
from ..types import Image
|
|
16
23
|
from ..types.models import GenerateResponse, LLMResponse, UploadFileResponse
|
|
@@ -46,7 +53,7 @@ class SyncClient(BaseClient[httpx.Client]):
|
|
|
46
53
|
def __init__(self, base_url: str | None = None, api_key: str | None = None):
|
|
47
54
|
super().__init__(base_url, api_key)
|
|
48
55
|
self.client = httpx.Client(base_url=self.base_url)
|
|
49
|
-
self.upload_client = httpx.Client(timeout=
|
|
56
|
+
self.upload_client = httpx.Client(timeout=HTTP_CLIENT_TIMEOUT)
|
|
50
57
|
logger.info(f"SyncClient initialized with base_url: {self.base_url}")
|
|
51
58
|
|
|
52
59
|
def __enter__(self):
|
|
@@ -124,7 +131,10 @@ class SyncClient(BaseClient[httpx.Client]):
|
|
|
124
131
|
# Make request
|
|
125
132
|
try:
|
|
126
133
|
response = self.client.post(
|
|
127
|
-
|
|
134
|
+
API_V2_MESSAGE_ENDPOINT,
|
|
135
|
+
json=payload,
|
|
136
|
+
headers=headers,
|
|
137
|
+
timeout=self.timeout,
|
|
128
138
|
)
|
|
129
139
|
return self._process_response(response)
|
|
130
140
|
except (httpx.TimeoutException, httpx.NetworkError) as e:
|
|
@@ -139,7 +149,7 @@ class SyncClient(BaseClient[httpx.Client]):
|
|
|
139
149
|
"""
|
|
140
150
|
logger.debug("Making health check request")
|
|
141
151
|
try:
|
|
142
|
-
response = self.client.get(
|
|
152
|
+
response = self.client.get(API_HEALTH_ENDPOINT)
|
|
143
153
|
response.raise_for_status()
|
|
144
154
|
result = response.json()
|
|
145
155
|
logger.debug("Health check successful")
|
|
@@ -161,12 +171,12 @@ class SyncClient(BaseClient[httpx.Client]):
|
|
|
161
171
|
Returns:
|
|
162
172
|
UploadFileResponse: The response from /v1/file/upload with uuid and presigned S3 URL
|
|
163
173
|
"""
|
|
164
|
-
logger.debug("Making API request to
|
|
174
|
+
logger.debug(f"Making API request to {API_V1_FILE_UPLOAD_ENDPOINT}")
|
|
165
175
|
|
|
166
176
|
try:
|
|
167
177
|
headers = self._build_headers(api_version)
|
|
168
178
|
response = self.client.get(
|
|
169
|
-
|
|
179
|
+
API_V1_FILE_UPLOAD_ENDPOINT, headers=headers, timeout=self.timeout
|
|
170
180
|
)
|
|
171
181
|
return self._process_upload_response(response)
|
|
172
182
|
except (httpx.TimeoutException, httpx.NetworkError, httpx.HTTPStatusError) as e:
|
|
@@ -286,7 +296,10 @@ class SyncClient(BaseClient[httpx.Client]):
|
|
|
286
296
|
# Make request
|
|
287
297
|
try:
|
|
288
298
|
response = self.client.post(
|
|
289
|
-
|
|
299
|
+
API_V1_GENERATE_ENDPOINT,
|
|
300
|
+
json=payload,
|
|
301
|
+
headers=headers,
|
|
302
|
+
timeout=self.timeout,
|
|
290
303
|
)
|
|
291
304
|
return self._process_generate_response(response)
|
|
292
305
|
except (httpx.TimeoutException, httpx.NetworkError) as e:
|
oagi/constants.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
# URLs & API Endpoints
|
|
10
|
+
DEFAULT_BASE_URL = "https://api.agiopen.org"
|
|
11
|
+
API_KEY_HELP_URL = "https://developer.agiopen.org/api-keys"
|
|
12
|
+
API_V2_MESSAGE_ENDPOINT = "/v2/message"
|
|
13
|
+
API_V1_FILE_UPLOAD_ENDPOINT = "/v1/file/upload"
|
|
14
|
+
API_V1_GENERATE_ENDPOINT = "/v1/generate"
|
|
15
|
+
API_HEALTH_ENDPOINT = "/health"
|
|
16
|
+
|
|
17
|
+
# Model identifiers
|
|
18
|
+
MODEL_ACTOR = "lux-actor-1"
|
|
19
|
+
MODEL_THINKER = "lux-thinker-1"
|
|
20
|
+
|
|
21
|
+
# Agent modes
|
|
22
|
+
MODE_ACTOR = "actor"
|
|
23
|
+
MODE_THINKER = "thinker"
|
|
24
|
+
MODE_TASKER = "tasker"
|
|
25
|
+
|
|
26
|
+
# Default max steps per model
|
|
27
|
+
DEFAULT_MAX_STEPS = 20
|
|
28
|
+
DEFAULT_MAX_STEPS_THINKER = 100
|
|
29
|
+
DEFAULT_MAX_STEPS_TASKER = 60
|
|
30
|
+
|
|
31
|
+
# Reflection intervals
|
|
32
|
+
DEFAULT_REFLECTION_INTERVAL = 4
|
|
33
|
+
DEFAULT_REFLECTION_INTERVAL_TASKER = 20
|
|
34
|
+
|
|
35
|
+
# Timing & Delays
|
|
36
|
+
DEFAULT_STEP_DELAY = 0.3
|
|
37
|
+
|
|
38
|
+
# Temperature Defaults
|
|
39
|
+
DEFAULT_TEMPERATURE = 0.5
|
|
40
|
+
DEFAULT_TEMPERATURE_LOW = 0.1
|
|
41
|
+
|
|
42
|
+
# Timeout Values
|
|
43
|
+
HTTP_CLIENT_TIMEOUT = 60
|
|
@@ -6,14 +6,13 @@
|
|
|
6
6
|
# Licensed under the MIT License.
|
|
7
7
|
# -----------------------------------------------------------------------------
|
|
8
8
|
|
|
9
|
-
import re
|
|
10
9
|
import sys
|
|
11
10
|
import time
|
|
12
11
|
|
|
13
12
|
from pydantic import BaseModel, Field
|
|
14
13
|
|
|
15
14
|
from ..exceptions import check_optional_dependency
|
|
16
|
-
from ..types import Action, ActionType
|
|
15
|
+
from ..types import Action, ActionType, parse_coords, parse_drag_coords, parse_scroll
|
|
17
16
|
|
|
18
17
|
check_optional_dependency("pyautogui", "PyautoguiActionHandler", "desktop")
|
|
19
18
|
import pyautogui # noqa: E402
|
|
@@ -65,7 +64,8 @@ class PyautoguiConfig(BaseModel):
|
|
|
65
64
|
default=0.5, description="Duration for drag operations in seconds"
|
|
66
65
|
)
|
|
67
66
|
scroll_amount: int = Field(
|
|
68
|
-
default=
|
|
67
|
+
default=2 if sys.platform == "darwin" else 100,
|
|
68
|
+
description="Amount to scroll (positive for up, negative for down)",
|
|
69
69
|
)
|
|
70
70
|
wait_duration: float = Field(
|
|
71
71
|
default=1.0, description="Duration for wait actions in seconds"
|
|
@@ -136,36 +136,27 @@ class PyautoguiActionHandler:
|
|
|
136
136
|
|
|
137
137
|
def _parse_coords(self, args_str: str) -> tuple[int, int]:
|
|
138
138
|
"""Extract x, y coordinates from argument string."""
|
|
139
|
-
|
|
140
|
-
if not
|
|
139
|
+
coords = parse_coords(args_str)
|
|
140
|
+
if not coords:
|
|
141
141
|
raise ValueError(f"Invalid coordinates format: {args_str}")
|
|
142
|
-
|
|
143
|
-
return self._denormalize_coords(x, y)
|
|
142
|
+
return self._denormalize_coords(coords[0], coords[1])
|
|
144
143
|
|
|
145
144
|
def _parse_drag_coords(self, args_str: str) -> tuple[int, int, int, int]:
|
|
146
145
|
"""Extract x1, y1, x2, y2 coordinates from drag argument string."""
|
|
147
|
-
|
|
148
|
-
if not
|
|
146
|
+
coords = parse_drag_coords(args_str)
|
|
147
|
+
if not coords:
|
|
149
148
|
raise ValueError(f"Invalid drag coordinates format: {args_str}")
|
|
150
|
-
x1, y1
|
|
151
|
-
|
|
152
|
-
int(match.group(2)),
|
|
153
|
-
int(match.group(3)),
|
|
154
|
-
int(match.group(4)),
|
|
155
|
-
)
|
|
156
|
-
x1, y1 = self._denormalize_coords(x1, y1)
|
|
157
|
-
x2, y2 = self._denormalize_coords(x2, y2)
|
|
149
|
+
x1, y1 = self._denormalize_coords(coords[0], coords[1])
|
|
150
|
+
x2, y2 = self._denormalize_coords(coords[2], coords[3])
|
|
158
151
|
return x1, y1, x2, y2
|
|
159
152
|
|
|
160
153
|
def _parse_scroll(self, args_str: str) -> tuple[int, int, str]:
|
|
161
154
|
"""Extract x, y, direction from scroll argument string."""
|
|
162
|
-
|
|
163
|
-
if not
|
|
155
|
+
result = parse_scroll(args_str)
|
|
156
|
+
if not result:
|
|
164
157
|
raise ValueError(f"Invalid scroll format: {args_str}")
|
|
165
|
-
x, y =
|
|
166
|
-
x, y
|
|
167
|
-
direction = match.group(3).lower()
|
|
168
|
-
return x, y, direction
|
|
158
|
+
x, y = self._denormalize_coords(result[0], result[1])
|
|
159
|
+
return x, y, result[2]
|
|
169
160
|
|
|
170
161
|
def _normalize_key(self, key: str) -> str:
|
|
171
162
|
"""Normalize key names for consistency."""
|
oagi/server/config.py
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
from pydantic import Field
|
|
10
10
|
|
|
11
|
+
from ..constants import DEFAULT_MAX_STEPS, MODEL_ACTOR
|
|
11
12
|
from ..exceptions import check_optional_dependency
|
|
12
13
|
|
|
13
14
|
check_optional_dependency("pydantic_settings", "Server features", "server")
|
|
@@ -20,7 +21,7 @@ class ServerConfig(BaseSettings):
|
|
|
20
21
|
oagi_base_url: str = Field(default="https://api.agiopen.org", alias="OAGI_BASE_URL")
|
|
21
22
|
|
|
22
23
|
# Server settings
|
|
23
|
-
server_host: str = Field(default="
|
|
24
|
+
server_host: str = Field(default="127.0.0.1", alias="OAGI_SERVER_HOST")
|
|
24
25
|
server_port: int = Field(default=8000, alias="OAGI_SERVER_PORT")
|
|
25
26
|
cors_allowed_origins: str = Field(default="*", alias="OAGI_CORS_ORIGINS")
|
|
26
27
|
|
|
@@ -28,11 +29,13 @@ class ServerConfig(BaseSettings):
|
|
|
28
29
|
session_timeout_seconds: float = Field(default=10.0)
|
|
29
30
|
|
|
30
31
|
# Model settings
|
|
31
|
-
default_model: str = Field(default=
|
|
32
|
+
default_model: str = Field(default=MODEL_ACTOR, alias="OAGI_DEFAULT_MODEL")
|
|
32
33
|
default_temperature: float = Field(default=0.5, ge=0.0, le=2.0)
|
|
33
34
|
|
|
34
35
|
# Agent settings
|
|
35
|
-
max_steps: int = Field(
|
|
36
|
+
max_steps: int = Field(
|
|
37
|
+
default=DEFAULT_MAX_STEPS, alias="OAGI_MAX_STEPS", ge=1, le=200
|
|
38
|
+
)
|
|
36
39
|
|
|
37
40
|
# Socket.IO settings
|
|
38
41
|
socketio_path: str = Field(default="/socket.io")
|
oagi/server/models.py
CHANGED
|
@@ -10,13 +10,15 @@ from typing import Literal
|
|
|
10
10
|
|
|
11
11
|
from pydantic import BaseModel, Field
|
|
12
12
|
|
|
13
|
+
from ..constants import DEFAULT_TEMPERATURE_LOW, MODE_ACTOR, MODEL_ACTOR
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
# Client-to-server events
|
|
15
17
|
class InitEventData(BaseModel):
|
|
16
18
|
instruction: str = Field(...)
|
|
17
|
-
mode: str | None = Field(default=
|
|
18
|
-
model: str | None = Field(default=
|
|
19
|
-
temperature: float | None = Field(default=
|
|
19
|
+
mode: str | None = Field(default=MODE_ACTOR)
|
|
20
|
+
model: str | None = Field(default=MODEL_ACTOR)
|
|
21
|
+
temperature: float | None = Field(default=DEFAULT_TEMPERATURE_LOW, ge=0.0, le=2.0)
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
# Server-to-client events
|
oagi/server/session_store.py
CHANGED
|
@@ -11,14 +11,16 @@ from datetime import datetime
|
|
|
11
11
|
from typing import Any
|
|
12
12
|
from uuid import uuid4
|
|
13
13
|
|
|
14
|
+
from ..constants import MODE_ACTOR, MODEL_ACTOR
|
|
15
|
+
|
|
14
16
|
|
|
15
17
|
class Session:
|
|
16
18
|
def __init__(
|
|
17
19
|
self,
|
|
18
20
|
session_id: str,
|
|
19
21
|
instruction: str,
|
|
20
|
-
mode: str =
|
|
21
|
-
model: str =
|
|
22
|
+
mode: str = MODE_ACTOR,
|
|
23
|
+
model: str = MODEL_ACTOR,
|
|
22
24
|
temperature: float = 0.0,
|
|
23
25
|
):
|
|
24
26
|
self.session_id: str = session_id
|
|
@@ -53,8 +55,8 @@ class SessionStore:
|
|
|
53
55
|
def create_session(
|
|
54
56
|
self,
|
|
55
57
|
instruction: str,
|
|
56
|
-
mode: str =
|
|
57
|
-
model: str =
|
|
58
|
+
mode: str = MODE_ACTOR,
|
|
59
|
+
model: str = MODEL_ACTOR,
|
|
58
60
|
temperature: float = 0.0,
|
|
59
61
|
session_id: str | None = None,
|
|
60
62
|
) -> str:
|
oagi/server/socketio_server.py
CHANGED
|
@@ -15,8 +15,15 @@ from pydantic import ValidationError
|
|
|
15
15
|
|
|
16
16
|
from ..agent import AsyncDefaultAgent, create_agent
|
|
17
17
|
from ..client import AsyncClient
|
|
18
|
+
from ..constants import MODE_ACTOR
|
|
18
19
|
from ..exceptions import check_optional_dependency
|
|
19
|
-
from ..types.models.action import
|
|
20
|
+
from ..types.models.action import (
|
|
21
|
+
Action,
|
|
22
|
+
ActionType,
|
|
23
|
+
parse_coords,
|
|
24
|
+
parse_drag_coords,
|
|
25
|
+
parse_scroll,
|
|
26
|
+
)
|
|
20
27
|
from .agent_wrappers import SocketIOActionHandler, SocketIOImageProvider
|
|
21
28
|
from .config import ServerConfig
|
|
22
29
|
from .models import (
|
|
@@ -74,7 +81,7 @@ class SessionNamespace(socketio.AsyncNamespace):
|
|
|
74
81
|
session = Session(
|
|
75
82
|
session_id=session_id,
|
|
76
83
|
instruction="",
|
|
77
|
-
mode=
|
|
84
|
+
mode=MODE_ACTOR,
|
|
78
85
|
model=self.config.default_model,
|
|
79
86
|
temperature=self.config.default_temperature,
|
|
80
87
|
)
|
|
@@ -275,31 +282,29 @@ class SessionNamespace(socketio.AsyncNamespace):
|
|
|
275
282
|
| ActionType.LEFT_TRIPLE
|
|
276
283
|
| ActionType.RIGHT_SINGLE
|
|
277
284
|
):
|
|
278
|
-
coords = arg
|
|
279
|
-
if
|
|
280
|
-
x, y = int(coords[0]), int(coords[1])
|
|
281
|
-
else:
|
|
285
|
+
coords = parse_coords(arg)
|
|
286
|
+
if not coords:
|
|
282
287
|
logger.warning(f"Invalid action coordinates: {arg}")
|
|
283
288
|
return None
|
|
284
289
|
|
|
285
290
|
return await self.call(
|
|
286
291
|
action.type.value,
|
|
287
|
-
ClickEventData(**common, x=
|
|
292
|
+
ClickEventData(**common, x=coords[0], y=coords[1]).model_dump(),
|
|
288
293
|
to=session.socket_id,
|
|
289
294
|
timeout=self.config.socketio_timeout,
|
|
290
295
|
)
|
|
291
296
|
|
|
292
297
|
case ActionType.DRAG:
|
|
293
|
-
coords = arg
|
|
294
|
-
if
|
|
295
|
-
x1, y1, x2, y2 = (int(coords[i]) for i in range(4))
|
|
296
|
-
else:
|
|
298
|
+
coords = parse_drag_coords(arg)
|
|
299
|
+
if not coords:
|
|
297
300
|
logger.warning(f"Invalid drag coordinates: {arg}")
|
|
298
301
|
return None
|
|
299
302
|
|
|
300
303
|
return await self.call(
|
|
301
304
|
"drag",
|
|
302
|
-
DragEventData(
|
|
305
|
+
DragEventData(
|
|
306
|
+
**common, x1=coords[0], y1=coords[1], x2=coords[2], y2=coords[3]
|
|
307
|
+
).model_dump(),
|
|
303
308
|
to=session.socket_id,
|
|
304
309
|
timeout=self.config.socketio_timeout,
|
|
305
310
|
)
|
|
@@ -326,11 +331,8 @@ class SessionNamespace(socketio.AsyncNamespace):
|
|
|
326
331
|
)
|
|
327
332
|
|
|
328
333
|
case ActionType.SCROLL:
|
|
329
|
-
|
|
330
|
-
if
|
|
331
|
-
x, y = int(parts[0]), int(parts[1])
|
|
332
|
-
direction = parts[2].strip().lower()
|
|
333
|
-
else:
|
|
334
|
+
result = parse_scroll(arg)
|
|
335
|
+
if not result:
|
|
334
336
|
logger.warning(f"Invalid scroll coordinates: {arg}")
|
|
335
337
|
return None
|
|
336
338
|
|
|
@@ -340,9 +342,9 @@ class SessionNamespace(socketio.AsyncNamespace):
|
|
|
340
342
|
"scroll",
|
|
341
343
|
ScrollEventData(
|
|
342
344
|
**common,
|
|
343
|
-
x=
|
|
344
|
-
y=
|
|
345
|
-
direction=
|
|
345
|
+
x=result[0],
|
|
346
|
+
y=result[1],
|
|
347
|
+
direction=result[2],
|
|
346
348
|
count=count, # type: ignore
|
|
347
349
|
).model_dump(),
|
|
348
350
|
to=session.socket_id,
|
oagi/task/async_.py
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import warnings
|
|
10
10
|
|
|
11
11
|
from ..client import AsyncClient
|
|
12
|
+
from ..constants import DEFAULT_MAX_STEPS, MODEL_ACTOR
|
|
12
13
|
from ..types import URL, Image, Step
|
|
13
14
|
from .base import BaseActor
|
|
14
15
|
|
|
@@ -20,7 +21,7 @@ class AsyncActor(BaseActor):
|
|
|
20
21
|
self,
|
|
21
22
|
api_key: str | None = None,
|
|
22
23
|
base_url: str | None = None,
|
|
23
|
-
model: str =
|
|
24
|
+
model: str = MODEL_ACTOR,
|
|
24
25
|
temperature: float | None = None,
|
|
25
26
|
):
|
|
26
27
|
super().__init__(api_key, base_url, model, temperature)
|
|
@@ -31,7 +32,7 @@ class AsyncActor(BaseActor):
|
|
|
31
32
|
async def init_task(
|
|
32
33
|
self,
|
|
33
34
|
task_desc: str,
|
|
34
|
-
max_steps: int =
|
|
35
|
+
max_steps: int = DEFAULT_MAX_STEPS,
|
|
35
36
|
):
|
|
36
37
|
"""Initialize a new task with the given description.
|
|
37
38
|
|
|
@@ -89,7 +90,7 @@ class AsyncTask(AsyncActor):
|
|
|
89
90
|
self,
|
|
90
91
|
api_key: str | None = None,
|
|
91
92
|
base_url: str | None = None,
|
|
92
|
-
model: str =
|
|
93
|
+
model: str = MODEL_ACTOR,
|
|
93
94
|
temperature: float | None = None,
|
|
94
95
|
):
|
|
95
96
|
warnings.warn(
|
oagi/task/async_short.py
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import warnings
|
|
10
10
|
|
|
11
|
+
from ..constants import DEFAULT_MAX_STEPS, MODEL_ACTOR
|
|
11
12
|
from ..logging import get_logger
|
|
12
13
|
from ..types import AsyncActionHandler, AsyncImageProvider
|
|
13
14
|
from .async_ import AsyncActor
|
|
@@ -27,7 +28,7 @@ class AsyncShortTask(AsyncActor, BaseAutoMode):
|
|
|
27
28
|
self,
|
|
28
29
|
api_key: str | None = None,
|
|
29
30
|
base_url: str | None = None,
|
|
30
|
-
model: str =
|
|
31
|
+
model: str = MODEL_ACTOR,
|
|
31
32
|
temperature: float | None = None,
|
|
32
33
|
):
|
|
33
34
|
warnings.warn(
|
|
@@ -43,7 +44,7 @@ class AsyncShortTask(AsyncActor, BaseAutoMode):
|
|
|
43
44
|
async def auto_mode(
|
|
44
45
|
self,
|
|
45
46
|
task_desc: str,
|
|
46
|
-
max_steps: int =
|
|
47
|
+
max_steps: int = DEFAULT_MAX_STEPS,
|
|
47
48
|
executor: AsyncActionHandler = None,
|
|
48
49
|
image_provider: AsyncImageProvider = None,
|
|
49
50
|
temperature: float | None = None,
|
oagi/task/base.py
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
from uuid import uuid4
|
|
10
10
|
|
|
11
|
+
from ..constants import DEFAULT_MAX_STEPS
|
|
11
12
|
from ..logging import get_logger
|
|
12
13
|
from ..types import URL, Image, Step
|
|
13
14
|
from ..types.models import LLMResponse
|
|
@@ -30,7 +31,7 @@ class BaseActor:
|
|
|
30
31
|
self.model = model
|
|
31
32
|
self.temperature = temperature
|
|
32
33
|
self.message_history: list = [] # OpenAI-compatible message history
|
|
33
|
-
self.max_steps: int =
|
|
34
|
+
self.max_steps: int = DEFAULT_MAX_STEPS
|
|
34
35
|
self.current_step: int = 0 # Current step counter
|
|
35
36
|
# Client will be set by subclasses
|
|
36
37
|
self.api_key: str | None = None
|
oagi/task/short.py
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import warnings
|
|
10
10
|
|
|
11
|
+
from ..constants import DEFAULT_MAX_STEPS, MODEL_ACTOR
|
|
11
12
|
from ..logging import get_logger
|
|
12
13
|
from ..types import ActionHandler, ImageProvider
|
|
13
14
|
from .base import BaseAutoMode
|
|
@@ -27,7 +28,7 @@ class ShortTask(Actor, BaseAutoMode):
|
|
|
27
28
|
self,
|
|
28
29
|
api_key: str | None = None,
|
|
29
30
|
base_url: str | None = None,
|
|
30
|
-
model: str =
|
|
31
|
+
model: str = MODEL_ACTOR,
|
|
31
32
|
temperature: float | None = None,
|
|
32
33
|
):
|
|
33
34
|
warnings.warn(
|
|
@@ -43,7 +44,7 @@ class ShortTask(Actor, BaseAutoMode):
|
|
|
43
44
|
def auto_mode(
|
|
44
45
|
self,
|
|
45
46
|
task_desc: str,
|
|
46
|
-
max_steps: int =
|
|
47
|
+
max_steps: int = DEFAULT_MAX_STEPS,
|
|
47
48
|
executor: ActionHandler = None,
|
|
48
49
|
image_provider: ImageProvider = None,
|
|
49
50
|
temperature: float | None = None,
|