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/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
- actions_display.append(f"{action.type.value}({arg})")
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: 0.0.0.0, or OAGI_SERVER_HOST env var)",
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", "https://api.agiopen.org"),
59
- "OAGI_DEFAULT_MODEL": os.getenv("OAGI_DEFAULT_MODEL", "lux-actor-1"),
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", "0.0.0.0"),
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=60) # client for uploading image
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
- "/v2/message", json=payload, headers=headers, timeout=self.timeout
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("/health")
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 /v1/file/upload")
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
- "/v1/file/upload", headers=headers, timeout=self.timeout
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
- "/v1/generate", json=payload, headers=headers, timeout=self.timeout
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 = 60
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 ValueError:
282
- logger.error(f"Non-JSON API response: {response.status_code}")
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: missing field {e}",
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=60) # client for uploading image
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
- "/v2/message", json=payload, headers=headers, timeout=self.timeout
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("/health")
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 /v1/file/upload")
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
- "/v1/file/upload", headers=headers, timeout=self.timeout
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
- "/v1/generate", json=payload, headers=headers, timeout=self.timeout
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=30, description="Amount to scroll (positive for up, negative for down)"
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
- match = re.match(r"(\d+),\s*(\d+)", args_str)
140
- if not match:
139
+ coords = parse_coords(args_str)
140
+ if not coords:
141
141
  raise ValueError(f"Invalid coordinates format: {args_str}")
142
- x, y = int(match.group(1)), int(match.group(2))
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
- match = re.match(r"(\d+),\s*(\d+),\s*(\d+),\s*(\d+)", args_str)
148
- if not match:
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, x2, y2 = (
151
- int(match.group(1)),
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
- match = re.match(r"(\d+),\s*(\d+),\s*(\w+)", args_str)
163
- if not match:
155
+ result = parse_scroll(args_str)
156
+ if not result:
164
157
  raise ValueError(f"Invalid scroll format: {args_str}")
165
- x, y = int(match.group(1)), int(match.group(2))
166
- x, y = self._denormalize_coords(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="0.0.0.0", alias="OAGI_SERVER_HOST")
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="lux-actor-1", alias="OAGI_DEFAULT_MODEL")
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(default=20, alias="OAGI_MAX_STEPS", ge=1, le=100)
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="actor")
18
- model: str | None = Field(default="lux-actor-1")
19
- temperature: float | None = Field(default=0.1, ge=0.0, le=2.0)
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
@@ -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 = "actor",
21
- model: str = "lux-actor-1",
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 = "actor",
57
- model: str = "lux-actor-1",
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:
@@ -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 Action, ActionType
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="actor", # Default 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.split(",")
279
- if len(coords) >= 2:
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=x, y=y).model_dump(),
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.split(",")
294
- if len(coords) >= 4:
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(**common, x1=x1, y1=y1, x2=x2, y2=y2).model_dump(),
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
- 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
+ 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=x,
344
- y=y,
345
- direction=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 = "lux-actor-1",
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 = 20,
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 = "lux-actor-1",
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 = "lux-actor-1",
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 = 20,
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 = 20 # Maximum steps allowed
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 = "lux-actor-1",
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 = 20,
47
+ max_steps: int = DEFAULT_MAX_STEPS,
47
48
  executor: ActionHandler = None,
48
49
  image_provider: ImageProvider = None,
49
50
  temperature: float | None = None,