oagi 0.1.0__py3-none-any.whl → 0.2.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.

Potentially problematic release.


This version of oagi might be problematic. Click here for more details.

oagi/__init__.py CHANGED
@@ -6,8 +6,48 @@
6
6
  # Licensed under the MIT License.
7
7
  # -----------------------------------------------------------------------------
8
8
 
9
+ from oagi.exceptions import (
10
+ APIError,
11
+ AuthenticationError,
12
+ ConfigurationError,
13
+ NetworkError,
14
+ NotFoundError,
15
+ OAGIError,
16
+ RateLimitError,
17
+ RequestTimeoutError,
18
+ ServerError,
19
+ ValidationError,
20
+ )
9
21
  from oagi.pyautogui_action_handler import PyautoguiActionHandler
10
22
  from oagi.screenshot_maker import ScreenshotMaker
11
23
  from oagi.short_task import ShortTask
24
+ from oagi.single_step import single_step
25
+ from oagi.sync_client import ErrorDetail, ErrorResponse, LLMResponse, SyncClient
26
+ from oagi.task import Task
12
27
 
13
- __all__ = ["ShortTask", "PyautoguiActionHandler", "ScreenshotMaker"]
28
+ __all__ = [
29
+ # Core classes
30
+ "Task",
31
+ "ShortTask",
32
+ "SyncClient",
33
+ # Functions
34
+ "single_step",
35
+ # Handler classes
36
+ "PyautoguiActionHandler",
37
+ "ScreenshotMaker",
38
+ # Response models
39
+ "LLMResponse",
40
+ "ErrorResponse",
41
+ "ErrorDetail",
42
+ # Exceptions
43
+ "OAGIError",
44
+ "APIError",
45
+ "AuthenticationError",
46
+ "ConfigurationError",
47
+ "NetworkError",
48
+ "NotFoundError",
49
+ "RateLimitError",
50
+ "ServerError",
51
+ "RequestTimeoutError",
52
+ "ValidationError",
53
+ ]
oagi/exceptions.py ADDED
@@ -0,0 +1,75 @@
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 httpx
10
+
11
+
12
+ class OAGIError(Exception):
13
+ pass
14
+
15
+
16
+ class APIError(OAGIError):
17
+ def __init__(
18
+ self,
19
+ message: str,
20
+ code: str | None = None,
21
+ status_code: int | None = None,
22
+ response: httpx.Response | None = None,
23
+ ):
24
+ """Initialize APIError.
25
+
26
+ Args:
27
+ message: Human-readable error message
28
+ code: API error code for programmatic handling
29
+ status_code: HTTP status code
30
+ response: Original HTTP response object
31
+ """
32
+ super().__init__(message)
33
+ self.message = message
34
+ self.code = code
35
+ self.status_code = status_code
36
+ self.response = response
37
+
38
+ def __str__(self) -> str:
39
+ if self.code:
40
+ return f"API Error [{self.code}]: {self.message}"
41
+ return f"API Error: {self.message}"
42
+
43
+
44
+ class AuthenticationError(APIError):
45
+ pass
46
+
47
+
48
+ class RateLimitError(APIError):
49
+ pass
50
+
51
+
52
+ class ValidationError(APIError):
53
+ pass
54
+
55
+
56
+ class NotFoundError(APIError):
57
+ pass
58
+
59
+
60
+ class ServerError(APIError):
61
+ pass
62
+
63
+
64
+ class NetworkError(OAGIError):
65
+ def __init__(self, message: str, original_error: Exception | None = None):
66
+ super().__init__(message)
67
+ self.original_error = original_error
68
+
69
+
70
+ class RequestTimeoutError(NetworkError):
71
+ pass
72
+
73
+
74
+ class ConfigurationError(OAGIError):
75
+ pass
oagi/short_task.py CHANGED
@@ -7,92 +7,14 @@
7
7
  # -----------------------------------------------------------------------------
8
8
 
9
9
  from .logging import get_logger
10
- from .sync_client import SyncClient, encode_screenshot_from_bytes
11
- from .types import ActionHandler, Image, ImageProvider, Step
10
+ from .task import Task
11
+ from .types import ActionHandler, ImageProvider
12
12
 
13
13
  logger = get_logger("short_task")
14
14
 
15
15
 
16
- class ShortTask:
17
- def __init__(self, api_key: str | None = None, base_url: str | None = None):
18
- self.client = SyncClient(base_url=base_url, api_key=api_key)
19
- self.api_key = self.client.api_key
20
- self.base_url = self.client.base_url
21
- self.task_id: str | None = None
22
- self.task_description: str | None = None
23
- self.model = "vision-model-v1" # default model
24
-
25
- def init_task(self, task_desc: str, max_steps: int = 5):
26
- """Initialize a new task with the given description."""
27
- self.task_description = task_desc
28
- response = self.client.create_message(
29
- model=self.model,
30
- screenshot="",
31
- task_description=self.task_description,
32
- task_id=None,
33
- )
34
- self.task_id = response.task_id # Reset task_id for new task
35
- logger.info(f"Task initialized: '{task_desc}' (max_steps: {max_steps})")
36
-
37
- def step(self, screenshot: Image) -> Step:
38
- """Send screenshot to the server and get the next actions."""
39
- if not self.task_description:
40
- raise ValueError("Task description must be set. Call init_task() first.")
41
-
42
- logger.debug(f"Executing step for task: '{self.task_description}'")
43
-
44
- try:
45
- # Convert Image to bytes using the protocol
46
- screenshot_bytes = screenshot.read()
47
- screenshot_b64 = encode_screenshot_from_bytes(screenshot_bytes)
48
-
49
- # Call API
50
- response = self.client.create_message(
51
- model=self.model,
52
- screenshot=screenshot_b64,
53
- task_description=self.task_description,
54
- task_id=self.task_id,
55
- )
56
-
57
- # Update task_id from response
58
- if self.task_id != response.task_id:
59
- if self.task_id is None:
60
- logger.debug(f"Task ID assigned: {response.task_id}")
61
- else:
62
- logger.debug(
63
- f"Task ID changed: {self.task_id} -> {response.task_id}"
64
- )
65
- self.task_id = response.task_id
66
-
67
- # Convert API response to Step
68
- result = Step(
69
- reason=response.reason,
70
- actions=response.actions,
71
- stop=response.is_complete,
72
- )
73
-
74
- if response.is_complete:
75
- logger.info(f"Task completed after {response.current_step} steps")
76
- else:
77
- logger.debug(
78
- f"Step {response.current_step} completed with {len(response.actions)} actions"
79
- )
80
-
81
- return result
82
-
83
- except Exception as e:
84
- logger.error(f"Error during step execution: {e}")
85
- raise
86
-
87
- def close(self):
88
- """Close the underlying HTTP client to free resources."""
89
- self.client.close()
90
-
91
- def __enter__(self):
92
- return self
93
-
94
- def __exit__(self, exc_type, exc_val, exc_tb):
95
- self.close()
16
+ class ShortTask(Task):
17
+ """Task implementation with automatic mode for short-duration tasks."""
96
18
 
97
19
  def auto_mode(
98
20
  self,
oagi/single_step.py ADDED
@@ -0,0 +1,82 @@
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 pathlib import Path
10
+
11
+ from .task import Task
12
+ from .types import Image, Step
13
+
14
+
15
+ def single_step(
16
+ task_description: str,
17
+ screenshot: str | bytes | Path | Image,
18
+ instruction: str | None = None,
19
+ api_key: str | None = None,
20
+ base_url: str | None = None,
21
+ ) -> Step:
22
+ """
23
+ Perform a single-step inference without maintaining task state.
24
+
25
+ This is useful for one-off analyses where you don't need to maintain
26
+ a conversation or task context across multiple steps.
27
+
28
+ Args:
29
+ task_description: Description of the task to perform
30
+ screenshot: Screenshot as Image, bytes, or file path
31
+ instruction: Optional additional instruction for the task
32
+ api_key: OAGI API key (uses environment variable if not provided)
33
+ base_url: OAGI base URL (uses environment variable if not provided)
34
+
35
+ Returns:
36
+ Step: Object containing reasoning, actions, and completion status
37
+
38
+ Example:
39
+ >>> # Using with bytes
40
+ >>> with open("screenshot.png", "rb") as f:
41
+ ... image_bytes = f.read()
42
+ >>> step = single_step(
43
+ ... task_description="Click the submit button",
44
+ ... screenshot=image_bytes
45
+ ... )
46
+
47
+ >>> # Using with file path
48
+ >>> step = single_step(
49
+ ... task_description="Fill in the form",
50
+ ... screenshot=Path("screenshot.png"),
51
+ ... instruction="Use test@example.com for email"
52
+ ... )
53
+
54
+ >>> # Using with Image object
55
+ >>> from oagi.types import Image
56
+ >>> image = Image(...)
57
+ >>> step = single_step(
58
+ ... task_description="Navigate to settings",
59
+ ... screenshot=image
60
+ ... )
61
+ """
62
+ # Convert file paths to bytes
63
+ if isinstance(screenshot, (str, Path)):
64
+ path = Path(screenshot) if isinstance(screenshot, str) else screenshot
65
+ if path.exists():
66
+ with open(path, "rb") as f:
67
+ screenshot_bytes = f.read()
68
+ else:
69
+ raise FileNotFoundError(f"Screenshot file not found: {path}")
70
+ elif isinstance(screenshot, bytes):
71
+ screenshot_bytes = screenshot
72
+ elif isinstance(screenshot, Image):
73
+ screenshot_bytes = screenshot.read()
74
+ else:
75
+ raise ValueError(
76
+ f"screenshot must be Image, bytes, str, or Path, got {type(screenshot)}"
77
+ )
78
+
79
+ # Use Task to perform single step
80
+ with Task(api_key=api_key, base_url=base_url) as task:
81
+ task.init_task(task_description)
82
+ return task.step(screenshot_bytes, instruction=instruction)
oagi/sync_client.py CHANGED
@@ -8,11 +8,23 @@
8
8
 
9
9
  import base64
10
10
  import os
11
- from typing import Optional
11
+ from functools import wraps
12
12
 
13
13
  import httpx
14
+ from httpx import Response
14
15
  from pydantic import BaseModel
15
16
 
17
+ from .exceptions import (
18
+ APIError,
19
+ AuthenticationError,
20
+ ConfigurationError,
21
+ NetworkError,
22
+ NotFoundError,
23
+ RateLimitError,
24
+ RequestTimeoutError,
25
+ ServerError,
26
+ ValidationError,
27
+ )
16
28
  from .logging import get_logger
17
29
  from .types import Action
18
30
 
@@ -25,6 +37,19 @@ class Usage(BaseModel):
25
37
  total_tokens: int
26
38
 
27
39
 
40
+ class ErrorDetail(BaseModel):
41
+ """Detailed error information."""
42
+
43
+ code: str
44
+ message: str
45
+
46
+
47
+ class ErrorResponse(BaseModel):
48
+ """Standard error response format."""
49
+
50
+ error: ErrorDetail | None
51
+
52
+
28
53
  class LLMResponse(BaseModel):
29
54
  id: str
30
55
  task_id: str
@@ -37,29 +62,45 @@ class LLMResponse(BaseModel):
37
62
  actions: list[Action]
38
63
  reason: str | None = None
39
64
  usage: Usage
65
+ error: ErrorDetail | None = None
40
66
 
41
67
 
42
- class ErrorResponse(BaseModel):
43
- error: str
44
- message: str
45
- code: int
68
+ def _log_trace_id(response: Response):
69
+ logger.error(f"Request Id: {response.headers.get('x-request-id', '')}")
70
+ logger.error(f"Trace Id: {response.headers.get('x-trace-id', '')}")
71
+
72
+
73
+ def log_trace_on_failure(func):
74
+ """Decorator that logs trace ID when a method fails."""
75
+
76
+ @wraps(func)
77
+ def wrapper(*args, **kwargs):
78
+ try:
79
+ return func(*args, **kwargs)
80
+ except Exception as e:
81
+ # Try to get response from the exception if it has one
82
+ if (response := getattr(e, "response", None)) is not None:
83
+ _log_trace_id(response)
84
+ raise
85
+
86
+ return wrapper
46
87
 
47
88
 
48
89
  class SyncClient:
49
- def __init__(self, base_url: Optional[str] = None, api_key: Optional[str] = None):
90
+ def __init__(self, base_url: str | None = None, api_key: str | None = None):
50
91
  # Get from environment if not provided
51
92
  self.base_url = base_url or os.getenv("OAGI_BASE_URL")
52
93
  self.api_key = api_key or os.getenv("OAGI_API_KEY")
53
94
 
54
95
  # Validate required configuration
55
96
  if not self.base_url:
56
- raise ValueError(
97
+ raise ConfigurationError(
57
98
  "OAGI base URL must be provided either as 'base_url' parameter or "
58
99
  "OAGI_BASE_URL environment variable"
59
100
  )
60
101
 
61
102
  if not self.api_key:
62
- raise ValueError(
103
+ raise ConfigurationError(
63
104
  "OAGI API key must be provided either as 'api_key' parameter or "
64
105
  "OAGI_API_KEY environment variable"
65
106
  )
@@ -80,14 +121,16 @@ class SyncClient:
80
121
  """Close the underlying httpx client"""
81
122
  self.client.close()
82
123
 
124
+ @log_trace_on_failure
83
125
  def create_message(
84
126
  self,
85
127
  model: str,
86
128
  screenshot: str, # base64 encoded
87
- task_description: Optional[str] = None,
88
- task_id: Optional[str] = None,
89
- max_actions: Optional[int] = 5,
90
- api_version: Optional[str] = None,
129
+ task_description: str | None = None,
130
+ task_id: str | None = None,
131
+ instruction: str | None = None,
132
+ max_actions: int | None = 5,
133
+ api_version: str | None = None,
91
134
  ) -> LLMResponse:
92
135
  """
93
136
  Call the /v1/message endpoint to analyze task and screenshot
@@ -97,6 +140,7 @@ class SyncClient:
97
140
  screenshot: Base64-encoded screenshot image
98
141
  task_description: Description of the task (required for new sessions)
99
142
  task_id: Task ID for continuing existing task
143
+ instruction: Additional instruction when continuing a session (only works with task_id)
100
144
  max_actions: Maximum number of actions to return (1-20)
101
145
  api_version: API version header
102
146
 
@@ -118,6 +162,8 @@ class SyncClient:
118
162
  payload["task_description"] = task_description
119
163
  if task_id is not None:
120
164
  payload["task_id"] = task_id
165
+ if instruction is not None:
166
+ payload["instruction"] = instruction
121
167
  if max_actions is not None:
122
168
  payload["max_actions"] = max_actions
123
169
 
@@ -126,32 +172,92 @@ class SyncClient:
126
172
  f"Request includes task_description: {task_description is not None}, task_id: {task_id is not None}"
127
173
  )
128
174
 
129
- response = self.client.post(
130
- "/v1/message", json=payload, headers=headers, timeout=self.timeout
131
- )
175
+ try:
176
+ response = self.client.post(
177
+ "/v1/message", json=payload, headers=headers, timeout=self.timeout
178
+ )
179
+ except httpx.TimeoutException as e:
180
+ logger.error(f"Request timed out after {self.timeout} seconds")
181
+ raise RequestTimeoutError(
182
+ f"Request timed out after {self.timeout} seconds", e
183
+ )
184
+ except httpx.NetworkError as e:
185
+ logger.error(f"Network error: {e}")
186
+ raise NetworkError(f"Network error: {e}", e)
132
187
 
133
- if response.status_code == 200:
134
- result = LLMResponse(**response.json())
135
- logger.info(
136
- f"API request successful - task_id: {result.task_id}, step: {result.current_step}, complete: {result.is_complete}"
188
+ try:
189
+ response_data = response.json()
190
+ except ValueError:
191
+ # If response is not JSON, raise API error
192
+ logger.error(f"Non-JSON API response: {response.status_code}")
193
+ raise APIError(
194
+ f"Invalid response format (status {response.status_code})",
195
+ status_code=response.status_code,
196
+ response=response,
137
197
  )
138
- logger.debug(f"Response included {len(result.actions)} actions")
139
- return result
140
- else:
141
- # Handle error responses
142
- try:
143
- error_data = response.json()
144
- error = ErrorResponse(**error_data)
145
- logger.error(f"API Error {error.code}: {error.error} - {error.message}")
146
- raise httpx.HTTPStatusError(
147
- f"API Error {error.code}: {error.error} - {error.message}",
148
- request=response.request,
198
+
199
+ # Check if it's an error response (non-200 status or has error field)
200
+ if response.status_code != 200:
201
+ error_resp = ErrorResponse(**response_data)
202
+ if error_resp.error:
203
+ error_code = error_resp.error.code
204
+ error_msg = error_resp.error.message
205
+ logger.error(f"API Error [{error_code}]: {error_msg}")
206
+
207
+ # Map to specific exception types based on status code
208
+ exception_class = self._get_exception_class(response.status_code)
209
+ raise exception_class(
210
+ error_msg,
211
+ code=error_code,
212
+ status_code=response.status_code,
213
+ response=response,
214
+ )
215
+ else:
216
+ # Error response without error details
217
+ logger.error(
218
+ f"API error response without details: {response.status_code}"
219
+ )
220
+ exception_class = self._get_exception_class(response.status_code)
221
+ raise exception_class(
222
+ f"API error (status {response.status_code})",
223
+ status_code=response.status_code,
149
224
  response=response,
150
225
  )
151
- except ValueError:
152
- # If response is not JSON, raise generic error
153
- logger.error(f"Non-JSON API error response: {response.status_code}")
154
- response.raise_for_status()
226
+
227
+ # Parse successful response
228
+ result = LLMResponse(**response_data)
229
+
230
+ # Check if the response contains an error (even with 200 status)
231
+ if result.error:
232
+ logger.error(
233
+ f"API Error in response: [{result.error.code}]: {result.error.message}"
234
+ )
235
+ raise APIError(
236
+ result.error.message,
237
+ code=result.error.code,
238
+ status_code=200,
239
+ response=response,
240
+ )
241
+
242
+ logger.info(
243
+ f"API request successful - task_id: {result.task_id}, step: {result.current_step}, complete: {result.is_complete}"
244
+ )
245
+ logger.debug(f"Response included {len(result.actions)} actions")
246
+ return result
247
+
248
+ def _get_exception_class(self, status_code: int) -> type[APIError]:
249
+ """Get the appropriate exception class based on status code."""
250
+ status_map = {
251
+ 401: AuthenticationError,
252
+ 404: NotFoundError,
253
+ 422: ValidationError,
254
+ 429: RateLimitError,
255
+ }
256
+
257
+ if status_code >= 500:
258
+ return ServerError
259
+
260
+ return status_map.get(status_code, APIError)
155
261
 
156
262
  def health_check(self) -> dict:
157
263
  """
oagi/task.py ADDED
@@ -0,0 +1,109 @@
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 .logging import get_logger
10
+ from .sync_client import SyncClient, encode_screenshot_from_bytes
11
+ from .types import Image, Step
12
+
13
+ logger = get_logger("task")
14
+
15
+
16
+ class Task:
17
+ """Base class for task automation with the OAGI API."""
18
+
19
+ def __init__(self, api_key: str | None = None, base_url: str | None = None):
20
+ self.client = SyncClient(base_url=base_url, api_key=api_key)
21
+ self.api_key = self.client.api_key
22
+ self.base_url = self.client.base_url
23
+ self.task_id: str | None = None
24
+ self.task_description: str | None = None
25
+ self.model = "vision-model-v1" # default model
26
+
27
+ def init_task(self, task_desc: str, max_steps: int = 5):
28
+ """Initialize a new task with the given description."""
29
+ self.task_description = task_desc
30
+ response = self.client.create_message(
31
+ model=self.model,
32
+ screenshot="",
33
+ task_description=self.task_description,
34
+ task_id=None,
35
+ )
36
+ self.task_id = response.task_id # Reset task_id for new task
37
+ logger.info(f"Task initialized: '{task_desc}' (max_steps: {max_steps})")
38
+
39
+ def step(self, screenshot: Image | bytes, instruction: str | None = None) -> Step:
40
+ """Send screenshot to the server and get the next actions.
41
+
42
+ Args:
43
+ screenshot: Screenshot as Image object or raw bytes
44
+ instruction: Optional additional instruction for this step (only works with existing task_id)
45
+
46
+ Returns:
47
+ Step: The actions and reasoning for this step
48
+ """
49
+ if not self.task_description:
50
+ raise ValueError("Task description must be set. Call init_task() first.")
51
+
52
+ logger.debug(f"Executing step for task: '{self.task_description}'")
53
+
54
+ try:
55
+ # Convert Image to bytes using the protocol
56
+ if isinstance(screenshot, Image):
57
+ screenshot_bytes = screenshot.read()
58
+ else:
59
+ screenshot_bytes = screenshot
60
+ screenshot_b64 = encode_screenshot_from_bytes(screenshot_bytes)
61
+
62
+ # Call API
63
+ response = self.client.create_message(
64
+ model=self.model,
65
+ screenshot=screenshot_b64,
66
+ task_description=self.task_description,
67
+ task_id=self.task_id,
68
+ instruction=instruction,
69
+ )
70
+
71
+ # Update task_id from response
72
+ if self.task_id != response.task_id:
73
+ if self.task_id is None:
74
+ logger.debug(f"Task ID assigned: {response.task_id}")
75
+ else:
76
+ logger.debug(
77
+ f"Task ID changed: {self.task_id} -> {response.task_id}"
78
+ )
79
+ self.task_id = response.task_id
80
+
81
+ # Convert API response to Step
82
+ result = Step(
83
+ reason=response.reason,
84
+ actions=response.actions,
85
+ stop=response.is_complete,
86
+ )
87
+
88
+ if response.is_complete:
89
+ logger.info(f"Task completed after {response.current_step} steps")
90
+ else:
91
+ logger.debug(
92
+ f"Step {response.current_step} completed with {len(response.actions)} actions"
93
+ )
94
+
95
+ return result
96
+
97
+ except Exception as e:
98
+ logger.error(f"Error during step execution: {e}")
99
+ raise
100
+
101
+ def close(self):
102
+ """Close the underlying HTTP client to free resources."""
103
+ self.client.close()
104
+
105
+ def __enter__(self):
106
+ return self
107
+
108
+ def __exit__(self, exc_type, exc_val, exc_tb):
109
+ self.close()
oagi/types/image.py CHANGED
@@ -6,9 +6,10 @@
6
6
  # Licensed under the MIT License.
7
7
  # -----------------------------------------------------------------------------
8
8
 
9
- from typing import Protocol
9
+ from typing import Protocol, runtime_checkable
10
10
 
11
11
 
12
+ @runtime_checkable
12
13
  class Image(Protocol):
13
14
  """Protocol for image objects that can be read as bytes."""
14
15
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: oagi
3
- Version: 0.1.0
3
+ Version: 0.2.1
4
4
  Summary: Official API of OpenAGI Foundation
5
5
  Project-URL: Homepage, https://github.com/agiopen-org/oagi
6
6
  Author-email: OpenAGI Foundation <contact@agiopen.org>
@@ -33,3 +33,23 @@ Requires-Dist: pydantic>=2.0.0
33
33
  Description-Content-Type: text/markdown
34
34
 
35
35
  # OAGI Python SDK
36
+
37
+ ## Basic Usage
38
+ ```bash
39
+ pip install oagi # python >= 3.10
40
+ ```
41
+ ```bash
42
+ export OAGI_BASE_URL=""
43
+ export OAGI_API_KEY="sk-xxxx"
44
+ ```
45
+
46
+ ```python
47
+ from oagi import PyautoguiActionHandler, ScreenshotMaker, ShortTask
48
+ short_task = ShortTask()
49
+ is_completed = short_task.auto_mode(
50
+ "Search weather with Google",
51
+ max_steps=5,
52
+ executor=PyautoguiActionHandler(),
53
+ image_provider=(sm := ScreenshotMaker()),
54
+ )
55
+ ```
@@ -1,17 +1,20 @@
1
- oagi/__init__.py,sha256=Qk4_6TmIBIGQhFSDPbt06h-WCpW_nqZ9PYBeXu-1XDA,531
1
+ oagi/__init__.py,sha256=ms9ahLdHNrMWtiqX93q8Iv55ag__tO4Id0DQ3hA2TVM,1347
2
+ oagi/exceptions.py,sha256=VMwVS8ouE9nHhBpN3AZMYt5_U2kGcihWaTnBhoQLquo,1662
2
3
  oagi/logging.py,sha256=CWe89mA5MKTipIvfrqSYkv2CAFNBSwHMDQMDkG_g64g,1350
3
4
  oagi/pyautogui_action_handler.py,sha256=LBWmtqkXzZSJo07s3uOw-NWUE9rZZtbNAx0YI83pCbk,5482
4
5
  oagi/screenshot_maker.py,sha256=lyJSMFagHeaqg59CQGMTqLvSzQN_pBbhbV2oIFG46vA,2077
5
- oagi/short_task.py,sha256=cDmMjQ-rv04FH6AVHLAasgCeVKWfeteaeuJp6LZNq2s,4479
6
- oagi/sync_client.py,sha256=nH68my12ujxMusV4PkSl0gDiW-vFMvUKf_jSeC4Seks,5907
6
+ oagi/short_task.py,sha256=ofcMi7vbu9W1MCSGOk_FNEHJcB02pfgNcx1-Y8UkpJY,1552
7
+ oagi/single_step.py,sha256=JEsF7ABa4wwW5Pi5AfjeKzyuKhC4kC4fcotnmnNye5o,2874
8
+ oagi/sync_client.py,sha256=E6EgFIe-H91rdsPhF1puwrBTpOnKaL6JA1WHR4R-CLY,9395
9
+ oagi/task.py,sha256=NmpNMu8CJll50zGsGtVie1kdpKeWnAAWudEa-aasBbU,3959
7
10
  oagi/types/__init__.py,sha256=eh-1IEqMTY2hUrvQJeTg6vsvlE6F4Iz5C0_K86AnWn8,549
8
11
  oagi/types/action_handler.py,sha256=NH8E-m5qpGqWcXzTSWfF7W0Xdp8SkzJsbhCmQ0B96cg,1075
9
- oagi/types/image.py,sha256=1vmQaOgNJuNso3WtiPN-sAP2DGjMDHwogrB0Pgi9uQ8,499
12
+ oagi/types/image.py,sha256=KgPCCTJ6D5vHIaGZdbTE7eQEa1WlT6G9tf59ZuUCV2U,537
10
13
  oagi/types/image_provider.py,sha256=oYFdOYznrK_VOR9egzOjw5wFM5w8EY2sY01pH0ANAgU,1112
11
14
  oagi/types/models/__init__.py,sha256=4qhKxWXsXEVzD6U_RM6PXR45os765qigtZs1BsS4WHg,414
12
15
  oagi/types/models/action.py,sha256=8Xd3IcH32ENq7uXczo-mbQ736yUOGxO_TaZTfHVRY7w,935
13
16
  oagi/types/models/step.py,sha256=RSI4H_2rrUBq_xyCoWKaq7JHdJWNobtQppaKC1l0aWU,471
14
- oagi-0.1.0.dist-info/METADATA,sha256=vVl2-HqzJKJsScHkslj4zZ7ZAjKaPw3LtB9eakqssQ8,1656
15
- oagi-0.1.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
16
- oagi-0.1.0.dist-info/licenses/LICENSE,sha256=sy5DLA2M29jFT4UfWsuBF9BAr3FnRkYtnAu6oDZiIf8,1075
17
- oagi-0.1.0.dist-info/RECORD,,
17
+ oagi-0.2.1.dist-info/METADATA,sha256=5W_aB_J2LUEyKAvE6G_iOcySv7tf0PWiGikOQe_K7l4,2066
18
+ oagi-0.2.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
19
+ oagi-0.2.1.dist-info/licenses/LICENSE,sha256=sy5DLA2M29jFT4UfWsuBF9BAr3FnRkYtnAu6oDZiIf8,1075
20
+ oagi-0.2.1.dist-info/RECORD,,
File without changes