oagi-core 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oagi/__init__.py +108 -0
- oagi/agent/__init__.py +31 -0
- oagi/agent/default.py +75 -0
- oagi/agent/factories.py +50 -0
- oagi/agent/protocol.py +55 -0
- oagi/agent/registry.py +155 -0
- oagi/agent/tasker/__init__.py +35 -0
- oagi/agent/tasker/memory.py +184 -0
- oagi/agent/tasker/models.py +83 -0
- oagi/agent/tasker/planner.py +385 -0
- oagi/agent/tasker/taskee_agent.py +395 -0
- oagi/agent/tasker/tasker_agent.py +323 -0
- oagi/async_pyautogui_action_handler.py +44 -0
- oagi/async_screenshot_maker.py +47 -0
- oagi/async_single_step.py +85 -0
- oagi/cli/__init__.py +11 -0
- oagi/cli/agent.py +125 -0
- oagi/cli/main.py +77 -0
- oagi/cli/server.py +94 -0
- oagi/cli/utils.py +82 -0
- oagi/client/__init__.py +12 -0
- oagi/client/async_.py +293 -0
- oagi/client/base.py +465 -0
- oagi/client/sync.py +296 -0
- oagi/exceptions.py +118 -0
- oagi/logging.py +47 -0
- oagi/pil_image.py +102 -0
- oagi/pyautogui_action_handler.py +268 -0
- oagi/screenshot_maker.py +41 -0
- oagi/server/__init__.py +13 -0
- oagi/server/agent_wrappers.py +98 -0
- oagi/server/config.py +46 -0
- oagi/server/main.py +157 -0
- oagi/server/models.py +98 -0
- oagi/server/session_store.py +116 -0
- oagi/server/socketio_server.py +405 -0
- oagi/single_step.py +87 -0
- oagi/task/__init__.py +14 -0
- oagi/task/async_.py +97 -0
- oagi/task/async_short.py +64 -0
- oagi/task/base.py +121 -0
- oagi/task/short.py +64 -0
- oagi/task/sync.py +97 -0
- oagi/types/__init__.py +28 -0
- oagi/types/action_handler.py +30 -0
- oagi/types/async_action_handler.py +30 -0
- oagi/types/async_image_provider.py +37 -0
- oagi/types/image.py +17 -0
- oagi/types/image_provider.py +34 -0
- oagi/types/models/__init__.py +32 -0
- oagi/types/models/action.py +33 -0
- oagi/types/models/client.py +64 -0
- oagi/types/models/image_config.py +47 -0
- oagi/types/models/step.py +17 -0
- oagi/types/url_image.py +47 -0
- oagi_core-0.9.0.dist-info/METADATA +257 -0
- oagi_core-0.9.0.dist-info/RECORD +60 -0
- oagi_core-0.9.0.dist-info/WHEEL +4 -0
- oagi_core-0.9.0.dist-info/entry_points.txt +2 -0
- oagi_core-0.9.0.dist-info/licenses/LICENSE +21 -0
oagi/cli/main.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
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 argparse
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
from oagi.cli.agent import add_agent_parser, handle_agent_command
|
|
13
|
+
from oagi.cli.server import add_server_parser, handle_server_command
|
|
14
|
+
from oagi.cli.utils import display_config, display_version, setup_logging
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
18
|
+
parser = argparse.ArgumentParser(
|
|
19
|
+
prog="oagi", description="OAGI SDK Command Line Interface"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"-v", "--verbose", action="store_true", help="Enable verbose (debug) logging"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"--version",
|
|
28
|
+
action="version",
|
|
29
|
+
version="%(prog)s (use 'oagi version' for detailed info)",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Create subparsers for commands
|
|
33
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
34
|
+
|
|
35
|
+
add_server_parser(subparsers)
|
|
36
|
+
add_agent_parser(subparsers)
|
|
37
|
+
|
|
38
|
+
subparsers.add_parser("version", help="Show SDK version and environment info")
|
|
39
|
+
|
|
40
|
+
config_parser = subparsers.add_parser("config", help="Configuration management")
|
|
41
|
+
config_subparsers = config_parser.add_subparsers(
|
|
42
|
+
dest="config_command", required=True
|
|
43
|
+
)
|
|
44
|
+
config_subparsers.add_parser("show", help="Display current configuration")
|
|
45
|
+
|
|
46
|
+
return parser
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def main() -> None:
|
|
50
|
+
parser = create_parser()
|
|
51
|
+
args = parser.parse_args()
|
|
52
|
+
|
|
53
|
+
setup_logging(args.verbose)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
if args.command == "server":
|
|
57
|
+
handle_server_command(args)
|
|
58
|
+
elif args.command == "agent":
|
|
59
|
+
handle_agent_command(args)
|
|
60
|
+
elif args.command == "version":
|
|
61
|
+
display_version()
|
|
62
|
+
elif args.command == "config":
|
|
63
|
+
if args.config_command == "show":
|
|
64
|
+
display_config()
|
|
65
|
+
else:
|
|
66
|
+
parser.print_help()
|
|
67
|
+
sys.exit(1)
|
|
68
|
+
except KeyboardInterrupt:
|
|
69
|
+
print("\nInterrupted by user.")
|
|
70
|
+
sys.exit(130)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
print(f"Unexpected error: {e}", file=sys.stderr)
|
|
73
|
+
sys.exit(1)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
main()
|
oagi/cli/server.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
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 argparse
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
from oagi.exceptions import check_optional_dependency
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def add_server_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
16
|
+
server_parser = subparsers.add_parser("server", help="Server management commands")
|
|
17
|
+
server_subparsers = server_parser.add_subparsers(
|
|
18
|
+
dest="server_command", required=True
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# server start command
|
|
22
|
+
start_parser = server_subparsers.add_parser(
|
|
23
|
+
"start", help="Start the Socket.IO server"
|
|
24
|
+
)
|
|
25
|
+
start_parser.add_argument(
|
|
26
|
+
"--host",
|
|
27
|
+
type=str,
|
|
28
|
+
help="Server host (default: 0.0.0.0, or OAGI_SERVER_HOST env var)",
|
|
29
|
+
)
|
|
30
|
+
start_parser.add_argument(
|
|
31
|
+
"--port",
|
|
32
|
+
type=int,
|
|
33
|
+
help="Server port (default: 8000, or OAGI_SERVER_PORT env var)",
|
|
34
|
+
)
|
|
35
|
+
start_parser.add_argument(
|
|
36
|
+
"--oagi-api-key", type=str, help="OAGI API key (default: OAGI_API_KEY env var)"
|
|
37
|
+
)
|
|
38
|
+
start_parser.add_argument(
|
|
39
|
+
"--oagi-base-url",
|
|
40
|
+
type=str,
|
|
41
|
+
help="OAGI base URL (default: https://api.agiopen.org, or OAGI_BASE_URL env var)",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def handle_server_command(args: argparse.Namespace) -> None:
|
|
46
|
+
if args.server_command == "start":
|
|
47
|
+
start_server(args)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def start_server(args: argparse.Namespace) -> None:
|
|
51
|
+
# Check if server extras are installed
|
|
52
|
+
check_optional_dependency("fastapi", "Server", "server")
|
|
53
|
+
check_optional_dependency("uvicorn", "Server", "server")
|
|
54
|
+
|
|
55
|
+
import uvicorn # noqa: PLC0415
|
|
56
|
+
|
|
57
|
+
from oagi.server import create_app # noqa: PLC0415
|
|
58
|
+
from oagi.server.config import ServerConfig # noqa: PLC0415
|
|
59
|
+
|
|
60
|
+
# Create config with CLI overrides
|
|
61
|
+
config_kwargs = {}
|
|
62
|
+
if args.oagi_api_key:
|
|
63
|
+
config_kwargs["oagi_api_key"] = args.oagi_api_key
|
|
64
|
+
if args.oagi_base_url:
|
|
65
|
+
config_kwargs["oagi_base_url"] = args.oagi_base_url
|
|
66
|
+
if args.host:
|
|
67
|
+
config_kwargs["server_host"] = args.host
|
|
68
|
+
if args.port:
|
|
69
|
+
config_kwargs["server_port"] = args.port
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
config = ServerConfig(**config_kwargs)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
print(f"Error: Invalid configuration - {e}", file=sys.stderr)
|
|
75
|
+
sys.exit(1)
|
|
76
|
+
|
|
77
|
+
# Create and run app
|
|
78
|
+
print(
|
|
79
|
+
f"Starting OAGI Socket.IO server on {config.server_host}:{config.server_port}"
|
|
80
|
+
)
|
|
81
|
+
print(f"OAGI API: {config.oagi_base_url}")
|
|
82
|
+
print(f"Model: {config.default_model}")
|
|
83
|
+
print("\nPress Ctrl+C to stop the server")
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
app = create_app(config)
|
|
87
|
+
uvicorn.run(
|
|
88
|
+
app, host=config.server_host, port=config.server_port, log_level="info"
|
|
89
|
+
)
|
|
90
|
+
except KeyboardInterrupt:
|
|
91
|
+
print("\nServer stopped.")
|
|
92
|
+
except Exception as e:
|
|
93
|
+
print(f"Error starting server: {e}", file=sys.stderr)
|
|
94
|
+
sys.exit(1)
|
oagi/cli/utils.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
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
from importlib.metadata import version as get_version
|
|
13
|
+
|
|
14
|
+
from oagi.exceptions import check_optional_dependency
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_sdk_version() -> str:
|
|
18
|
+
try:
|
|
19
|
+
return get_version("oagi")
|
|
20
|
+
except Exception:
|
|
21
|
+
return "unknown"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def display_version() -> None:
|
|
25
|
+
sdk_version = get_sdk_version()
|
|
26
|
+
python_version = (
|
|
27
|
+
f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
28
|
+
)
|
|
29
|
+
platform = sys.platform
|
|
30
|
+
|
|
31
|
+
print(f"OAGI SDK version: {sdk_version}")
|
|
32
|
+
print(f"Python version: {python_version}")
|
|
33
|
+
print(f"Platform: {platform}")
|
|
34
|
+
|
|
35
|
+
# Check installed extras
|
|
36
|
+
extras = []
|
|
37
|
+
if check_optional_dependency("pyautogui", "Desktop", "desktop", raise_error=False):
|
|
38
|
+
extras.append("desktop")
|
|
39
|
+
|
|
40
|
+
if check_optional_dependency("fastapi", "Server", "server", raise_error=False):
|
|
41
|
+
extras.append("server")
|
|
42
|
+
|
|
43
|
+
if extras:
|
|
44
|
+
print(f"Installed extras: {', '.join(extras)}")
|
|
45
|
+
else:
|
|
46
|
+
print("Installed extras: none")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def display_config() -> None:
|
|
50
|
+
config_vars = {
|
|
51
|
+
"OAGI_API_KEY": os.getenv("OAGI_API_KEY", ""),
|
|
52
|
+
"OAGI_BASE_URL": os.getenv("OAGI_BASE_URL", "https://api.agiopen.org"),
|
|
53
|
+
"OAGI_LOG_LEVEL": os.getenv("OAGI_LOG_LEVEL", "INFO"),
|
|
54
|
+
"OAGI_SERVER_HOST": os.getenv("OAGI_SERVER_HOST", "0.0.0.0"),
|
|
55
|
+
"OAGI_SERVER_PORT": os.getenv("OAGI_SERVER_PORT", "8000"),
|
|
56
|
+
"OAGI_MAX_STEPS": os.getenv("OAGI_MAX_STEPS", "30"),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
print("Current Configuration:")
|
|
60
|
+
print("-" * 50)
|
|
61
|
+
for key, value in config_vars.items():
|
|
62
|
+
if key == "OAGI_API_KEY" and value:
|
|
63
|
+
# Mask API key
|
|
64
|
+
masked = value[:8] + "..." if len(value) > 8 else "***"
|
|
65
|
+
print(f"{key}: {masked}")
|
|
66
|
+
else:
|
|
67
|
+
display_value = value if value else "(not set)"
|
|
68
|
+
print(f"{key}: {display_value}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def setup_logging(verbose: bool) -> None:
|
|
72
|
+
if verbose:
|
|
73
|
+
os.environ["OAGI_LOG_LEVEL"] = "DEBUG"
|
|
74
|
+
logging.basicConfig(
|
|
75
|
+
level=logging.DEBUG,
|
|
76
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
77
|
+
)
|
|
78
|
+
else:
|
|
79
|
+
log_level = os.getenv("OAGI_LOG_LEVEL", "INFO")
|
|
80
|
+
logging.basicConfig(
|
|
81
|
+
level=getattr(logging, log_level), format="%(levelname)s: %(message)s"
|
|
82
|
+
)
|
oagi/client/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
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 .async_ import AsyncClient
|
|
10
|
+
from .sync import SyncClient
|
|
11
|
+
|
|
12
|
+
__all__ = ["SyncClient", "AsyncClient"]
|
oagi/client/async_.py
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
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 functools import wraps
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from ..logging import get_logger
|
|
14
|
+
from ..types import Image
|
|
15
|
+
from ..types.models import GenerateResponse, LLMResponse, UploadFileResponse
|
|
16
|
+
from .base import BaseClient
|
|
17
|
+
|
|
18
|
+
logger = get_logger("async_client")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def async_log_trace_on_failure(func):
|
|
22
|
+
"""Async decorator that logs trace ID when a method fails."""
|
|
23
|
+
|
|
24
|
+
@wraps(func)
|
|
25
|
+
async def wrapper(*args, **kwargs):
|
|
26
|
+
try:
|
|
27
|
+
return await func(*args, **kwargs)
|
|
28
|
+
except Exception as e:
|
|
29
|
+
# Try to get response from the exception if it has one
|
|
30
|
+
if (response := getattr(e, "response", None)) is not None:
|
|
31
|
+
logger.error(f"Request Id: {response.headers.get('x-request-id', '')}")
|
|
32
|
+
logger.error(f"Trace Id: {response.headers.get('x-trace-id', '')}")
|
|
33
|
+
raise
|
|
34
|
+
|
|
35
|
+
return wrapper
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AsyncClient(BaseClient[httpx.AsyncClient]):
|
|
39
|
+
"""Asynchronous HTTP client for the OAGI API."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, base_url: str | None = None, api_key: str | None = None):
|
|
42
|
+
super().__init__(base_url, api_key)
|
|
43
|
+
self.client = httpx.AsyncClient(base_url=self.base_url)
|
|
44
|
+
self.upload_client = httpx.AsyncClient(timeout=60) # client for uploading image
|
|
45
|
+
logger.info(f"AsyncClient initialized with base_url: {self.base_url}")
|
|
46
|
+
|
|
47
|
+
async def __aenter__(self):
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
51
|
+
await self.client.aclose()
|
|
52
|
+
await self.upload_client.aclose()
|
|
53
|
+
|
|
54
|
+
async def close(self):
|
|
55
|
+
"""Close the underlying httpx async clients."""
|
|
56
|
+
await self.client.aclose()
|
|
57
|
+
await self.upload_client.aclose()
|
|
58
|
+
|
|
59
|
+
@async_log_trace_on_failure
|
|
60
|
+
async def create_message(
|
|
61
|
+
self,
|
|
62
|
+
model: str,
|
|
63
|
+
screenshot: bytes | None = None,
|
|
64
|
+
screenshot_url: str | None = None,
|
|
65
|
+
task_description: str | None = None,
|
|
66
|
+
task_id: str | None = None,
|
|
67
|
+
instruction: str | None = None,
|
|
68
|
+
messages_history: list | None = None,
|
|
69
|
+
temperature: float | None = None,
|
|
70
|
+
api_version: str | None = None,
|
|
71
|
+
) -> "LLMResponse":
|
|
72
|
+
"""
|
|
73
|
+
Call the /v2/message endpoint to analyze task and screenshot
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
model: The model to use for task analysis
|
|
77
|
+
screenshot: Screenshot image bytes (mutually exclusive with screenshot_url)
|
|
78
|
+
screenshot_url: Direct URL to screenshot (mutually exclusive with screenshot)
|
|
79
|
+
task_description: Description of the task (required for new sessions)
|
|
80
|
+
task_id: Task ID for continuing existing task
|
|
81
|
+
instruction: Additional instruction when continuing a session
|
|
82
|
+
messages_history: OpenAI-compatible chat message history
|
|
83
|
+
temperature: Sampling temperature (0.0-2.0) for LLM inference
|
|
84
|
+
api_version: API version header
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
LLMResponse: The response from the API
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
ValueError: If both or neither screenshot and screenshot_url are provided
|
|
91
|
+
httpx.HTTPStatusError: For HTTP error responses
|
|
92
|
+
"""
|
|
93
|
+
# Validate that exactly one is provided
|
|
94
|
+
if (screenshot is None) == (screenshot_url is None):
|
|
95
|
+
raise ValueError(
|
|
96
|
+
"Exactly one of 'screenshot' or 'screenshot_url' must be provided"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
self._log_request_info(model, task_description, task_id)
|
|
100
|
+
|
|
101
|
+
# Upload screenshot to S3 if bytes provided, otherwise use URL directly
|
|
102
|
+
upload_file_response = None
|
|
103
|
+
if screenshot is not None:
|
|
104
|
+
upload_file_response = await self.put_s3_presigned_url(
|
|
105
|
+
screenshot, api_version
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Prepare message payload
|
|
109
|
+
headers, payload = self._prepare_message_payload(
|
|
110
|
+
model=model,
|
|
111
|
+
upload_file_response=upload_file_response,
|
|
112
|
+
task_description=task_description,
|
|
113
|
+
task_id=task_id,
|
|
114
|
+
instruction=instruction,
|
|
115
|
+
messages_history=messages_history,
|
|
116
|
+
temperature=temperature,
|
|
117
|
+
api_version=api_version,
|
|
118
|
+
screenshot_url=screenshot_url,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Make request
|
|
122
|
+
try:
|
|
123
|
+
response = await self.client.post(
|
|
124
|
+
"/v2/message", json=payload, headers=headers, timeout=self.timeout
|
|
125
|
+
)
|
|
126
|
+
return self._process_response(response)
|
|
127
|
+
except (httpx.TimeoutException, httpx.NetworkError) as e:
|
|
128
|
+
self._handle_upload_http_errors(e)
|
|
129
|
+
|
|
130
|
+
async def health_check(self) -> dict:
|
|
131
|
+
"""
|
|
132
|
+
Call the /health endpoint for health check
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
dict: Health check response
|
|
136
|
+
"""
|
|
137
|
+
logger.debug("Making async health check request")
|
|
138
|
+
try:
|
|
139
|
+
response = await self.client.get("/health")
|
|
140
|
+
response.raise_for_status()
|
|
141
|
+
result = response.json()
|
|
142
|
+
logger.debug("Async health check successful")
|
|
143
|
+
return result
|
|
144
|
+
except httpx.HTTPStatusError as e:
|
|
145
|
+
logger.warning(f"Async health check failed: {e}")
|
|
146
|
+
raise
|
|
147
|
+
|
|
148
|
+
async def get_s3_presigned_url(
|
|
149
|
+
self,
|
|
150
|
+
api_version: str | None = None,
|
|
151
|
+
) -> UploadFileResponse:
|
|
152
|
+
"""
|
|
153
|
+
Call the /v1/file/upload endpoint to get a S3 presigned URL
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
api_version: API version header
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
UploadFileResponse: The response from /v1/file/upload with uuid and presigned S3 URL
|
|
160
|
+
"""
|
|
161
|
+
logger.debug("Making async API request to /v1/file/upload")
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
headers = self._build_headers(api_version)
|
|
165
|
+
response = await self.client.get(
|
|
166
|
+
"/v1/file/upload", headers=headers, timeout=self.timeout
|
|
167
|
+
)
|
|
168
|
+
return self._process_upload_response(response)
|
|
169
|
+
except (httpx.TimeoutException, httpx.NetworkError, httpx.HTTPStatusError) as e:
|
|
170
|
+
self._handle_upload_http_errors(e, getattr(e, "response", None))
|
|
171
|
+
|
|
172
|
+
async def upload_to_s3(
|
|
173
|
+
self,
|
|
174
|
+
url: str,
|
|
175
|
+
content: bytes | Image,
|
|
176
|
+
) -> None:
|
|
177
|
+
"""
|
|
178
|
+
Upload image bytes to S3 using presigned URL
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
url: S3 presigned URL
|
|
182
|
+
content: Image bytes or Image object to upload
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
APIError: If upload fails
|
|
186
|
+
"""
|
|
187
|
+
logger.debug("Async uploading image to S3")
|
|
188
|
+
|
|
189
|
+
# Convert Image to bytes if needed
|
|
190
|
+
if isinstance(content, Image):
|
|
191
|
+
content = content.read()
|
|
192
|
+
|
|
193
|
+
response = None
|
|
194
|
+
try:
|
|
195
|
+
response = await self.upload_client.put(url=url, content=content)
|
|
196
|
+
response.raise_for_status()
|
|
197
|
+
except Exception as e:
|
|
198
|
+
self._handle_s3_upload_error(e, response)
|
|
199
|
+
|
|
200
|
+
async def put_s3_presigned_url(
|
|
201
|
+
self,
|
|
202
|
+
screenshot: bytes | Image,
|
|
203
|
+
api_version: str | None = None,
|
|
204
|
+
) -> UploadFileResponse:
|
|
205
|
+
"""
|
|
206
|
+
Get S3 presigned URL and upload image (convenience method)
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
screenshot: Screenshot image bytes or Image object
|
|
210
|
+
api_version: API version header
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
UploadFileResponse: The response from /v1/file/upload with uuid and presigned S3 URL
|
|
214
|
+
"""
|
|
215
|
+
upload_file_response = await self.get_s3_presigned_url(api_version)
|
|
216
|
+
await self.upload_to_s3(upload_file_response.url, screenshot)
|
|
217
|
+
return upload_file_response
|
|
218
|
+
|
|
219
|
+
@async_log_trace_on_failure
|
|
220
|
+
async def call_worker(
|
|
221
|
+
self,
|
|
222
|
+
worker_id: str,
|
|
223
|
+
overall_todo: str,
|
|
224
|
+
task_description: str,
|
|
225
|
+
todos: list[dict],
|
|
226
|
+
deliverables: list[dict],
|
|
227
|
+
history: list[dict] | None = None,
|
|
228
|
+
current_todo_index: int | None = None,
|
|
229
|
+
task_execution_summary: str | None = None,
|
|
230
|
+
current_screenshot: str | None = None,
|
|
231
|
+
current_subtask_instruction: str | None = None,
|
|
232
|
+
window_steps: list[dict] | None = None,
|
|
233
|
+
window_screenshots: list[str] | None = None,
|
|
234
|
+
result_screenshot: str | None = None,
|
|
235
|
+
prior_notes: str | None = None,
|
|
236
|
+
latest_todo_summary: str | None = None,
|
|
237
|
+
api_version: str | None = None,
|
|
238
|
+
) -> GenerateResponse:
|
|
239
|
+
"""Call the /v1/generate endpoint for OAGI worker processing.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
worker_id: One of "oagi_first", "oagi_follow", "oagi_task_summary"
|
|
243
|
+
overall_todo: Current todo description
|
|
244
|
+
task_description: Overall task description
|
|
245
|
+
todos: List of todo dicts with index, description, status, execution_summary
|
|
246
|
+
deliverables: List of deliverable dicts with description, achieved
|
|
247
|
+
history: List of history dicts with todo_index, todo_description, action_count, summary, completed
|
|
248
|
+
current_todo_index: Index of current todo being executed
|
|
249
|
+
task_execution_summary: Summary of overall task execution
|
|
250
|
+
current_screenshot: Uploaded file UUID for screenshot (oagi_first)
|
|
251
|
+
current_subtask_instruction: Subtask instruction (oagi_follow)
|
|
252
|
+
window_steps: Action steps list (oagi_follow)
|
|
253
|
+
window_screenshots: Uploaded file UUIDs list (oagi_follow)
|
|
254
|
+
result_screenshot: Uploaded file UUID for result screenshot (oagi_follow)
|
|
255
|
+
prior_notes: Execution notes (oagi_follow)
|
|
256
|
+
latest_todo_summary: Latest summary (oagi_task_summary)
|
|
257
|
+
api_version: API version header
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
GenerateResponse with LLM output and usage stats
|
|
261
|
+
|
|
262
|
+
Raises:
|
|
263
|
+
ValueError: If worker_id is invalid
|
|
264
|
+
APIError: If API returns error
|
|
265
|
+
"""
|
|
266
|
+
# Prepare request (validation, payload, headers)
|
|
267
|
+
payload, headers = self._prepare_worker_request(
|
|
268
|
+
worker_id=worker_id,
|
|
269
|
+
overall_todo=overall_todo,
|
|
270
|
+
task_description=task_description,
|
|
271
|
+
todos=todos,
|
|
272
|
+
deliverables=deliverables,
|
|
273
|
+
history=history,
|
|
274
|
+
current_todo_index=current_todo_index,
|
|
275
|
+
task_execution_summary=task_execution_summary,
|
|
276
|
+
current_screenshot=current_screenshot,
|
|
277
|
+
current_subtask_instruction=current_subtask_instruction,
|
|
278
|
+
window_steps=window_steps,
|
|
279
|
+
window_screenshots=window_screenshots,
|
|
280
|
+
result_screenshot=result_screenshot,
|
|
281
|
+
prior_notes=prior_notes,
|
|
282
|
+
latest_todo_summary=latest_todo_summary,
|
|
283
|
+
api_version=api_version,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Make request
|
|
287
|
+
try:
|
|
288
|
+
response = await self.client.post(
|
|
289
|
+
"/v1/generate", json=payload, headers=headers, timeout=self.timeout
|
|
290
|
+
)
|
|
291
|
+
return self._process_generate_response(response)
|
|
292
|
+
except (httpx.TimeoutException, httpx.NetworkError) as e:
|
|
293
|
+
self._handle_upload_http_errors(e)
|