oagi-core 0.10.3__py3-none-any.whl → 0.11.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/agent/default.py CHANGED
@@ -16,6 +16,7 @@ from ..constants import (
16
16
  DEFAULT_TEMPERATURE,
17
17
  MODEL_ACTOR,
18
18
  )
19
+ from ..handler import reset_handler
19
20
  from ..types import (
20
21
  ActionEvent,
21
22
  AsyncActionHandler,
@@ -68,6 +69,9 @@ class AsyncDefaultAgent:
68
69
  logger.info(f"Starting async task execution: {instruction}")
69
70
  await self.actor.init_task(instruction, max_steps=self.max_steps)
70
71
 
72
+ # Reset handler state at automation start
73
+ reset_handler(action_handler)
74
+
71
75
  for i in range(self.max_steps):
72
76
  step_num = i + 1
73
77
  logger.debug(f"Executing step {step_num}/{self.max_steps}")
oagi/agent/factories.py CHANGED
@@ -65,8 +65,8 @@ def create_thinker_agent(
65
65
  )
66
66
 
67
67
 
68
- @async_agent_register(mode="tasker")
69
- def create_planner_agent(
68
+ @async_agent_register(mode="tasker:cvs_appointment")
69
+ def create_cvs_appointment_agent(
70
70
  api_key: str | None = None,
71
71
  base_url: str | None = None,
72
72
  model: str = MODEL_ACTOR,
@@ -75,6 +75,12 @@ def create_planner_agent(
75
75
  reflection_interval: int = DEFAULT_REFLECTION_INTERVAL_TASKER,
76
76
  step_observer: AsyncStepObserver | None = None,
77
77
  step_delay: float = DEFAULT_STEP_DELAY,
78
+ # CVS-specific parameters
79
+ first_name: str = "First",
80
+ last_name: str = "Last",
81
+ email: str = "user@example.com",
82
+ birthday: str = "01-01-1990", # MM-DD-YYYY
83
+ zip_code: str = "00000",
78
84
  ) -> AsyncAgent:
79
85
  tasker = TaskerAgent(
80
86
  api_key=api_key,
@@ -86,5 +92,71 @@ def create_planner_agent(
86
92
  step_observer=step_observer,
87
93
  step_delay=step_delay,
88
94
  )
89
- # tasker.set_task()
95
+
96
+ month, day, year = birthday.split("-")
97
+ instruction = (
98
+ f"Schedule an appointment at CVS for {first_name} {last_name} "
99
+ f"with email {email} and birthday {birthday}"
100
+ )
101
+ todos = [
102
+ "Open a new tab, go to www.cvs.com, type 'flu shot' in the search bar and press enter, "
103
+ "wait for the page to load, then click on the button of Schedule vaccinations on the "
104
+ "top of the page",
105
+ f"Enter the first name '{first_name}', last name '{last_name}', and email '{email}' "
106
+ "in the form. Do not use any suggested autofills. Make sure the mobile phone number "
107
+ "is empty.",
108
+ f"Slightly scroll down to see the date of birth, enter Month '{month}', Day '{day}', "
109
+ f"and Year '{year}' in the form",
110
+ "Click on 'Continue as guest' button, wait for the page to load with wait, "
111
+ "click on 'Add vaccines' button, select 'Flu' and click on 'Add vaccines'",
112
+ f"Click on 'next' to enter the page with recommendation vaccines, then click on "
113
+ f"'next' again, until on the page of entering zip code, enter '{zip_code}', select "
114
+ "the first option from the dropdown menu, and click on 'Search'",
115
+ ]
116
+
117
+ tasker.set_task(instruction, todos)
118
+ return tasker
119
+
120
+
121
+ @async_agent_register(mode="tasker:software_qa")
122
+ def create_software_qa_agent(
123
+ api_key: str | None = None,
124
+ base_url: str | None = None,
125
+ model: str = MODEL_ACTOR,
126
+ max_steps: int = DEFAULT_MAX_STEPS_TASKER,
127
+ temperature: float = DEFAULT_TEMPERATURE_LOW,
128
+ reflection_interval: int = DEFAULT_REFLECTION_INTERVAL_TASKER,
129
+ step_observer: AsyncStepObserver | None = None,
130
+ step_delay: float = DEFAULT_STEP_DELAY,
131
+ ) -> AsyncAgent:
132
+ tasker = TaskerAgent(
133
+ api_key=api_key,
134
+ base_url=base_url,
135
+ model=model,
136
+ max_steps=max_steps,
137
+ temperature=temperature,
138
+ reflection_interval=reflection_interval,
139
+ step_observer=step_observer,
140
+ step_delay=step_delay,
141
+ )
142
+
143
+ instruction = "QA: click through every sidebar button in the Nuclear Player UI"
144
+ todos = [
145
+ "Click on 'Dashboard' in the left sidebar",
146
+ "Click on 'Downloads' in the left sidebar",
147
+ "Click on 'Lyrics' in the left sidebar",
148
+ "Click on 'Plugins' in the left sidebar",
149
+ "Click on 'Search Results' in the left sidebar",
150
+ "Click on 'Settings' in the left sidebar",
151
+ "Click on 'Equalizer' in the left sidebar",
152
+ "Click on 'Visualizer' in the left sidebar",
153
+ "Click on 'Listening History' in the left sidebar",
154
+ "Click on 'Favorite Albums' in the left sidebar",
155
+ "Click on 'Favorite Tracks' in the left sidebar",
156
+ "Click on 'Favorite Artists' in the left sidebar",
157
+ "Click on 'Local Library' in the left sidebar",
158
+ "Click on 'Playlists' in the left sidebar",
159
+ ]
160
+
161
+ tasker.set_task(instruction, todos)
90
162
  return tasker
@@ -11,7 +11,7 @@ from typing import Any
11
11
 
12
12
  from ...client import AsyncClient
13
13
  from ...constants import DEFAULT_REFLECTION_INTERVAL
14
- from ...types import URL, Image
14
+ from ...types import URL, Image, extract_uuid_from_url
15
15
  from .memory import PlannerMemory
16
16
  from .models import Action, PlannerOutput, ReflectionOutput
17
17
 
@@ -138,11 +138,16 @@ class Planner:
138
138
  # Ensure we have a client
139
139
  client = self._ensure_client()
140
140
 
141
- # Upload screenshot if provided
141
+ # Get screenshot UUID - either extract from URL or upload
142
142
  screenshot_uuid = None
143
143
  if screenshot:
144
- upload_response = await client.put_s3_presigned_url(screenshot)
145
- screenshot_uuid = upload_response.uuid
144
+ # Check if screenshot is already a URL (already uploaded to S3)
145
+ if isinstance(screenshot, str):
146
+ screenshot_uuid = extract_uuid_from_url(screenshot)
147
+ # If not a URL or UUID extraction failed, upload the image
148
+ if not screenshot_uuid:
149
+ upload_response = await client.put_s3_presigned_url(screenshot)
150
+ screenshot_uuid = upload_response.uuid
146
151
 
147
152
  # Extract memory data if provided
148
153
  (
@@ -195,11 +200,16 @@ class Planner:
195
200
  # Ensure we have a client
196
201
  client = self._ensure_client()
197
202
 
198
- # Upload screenshot if provided
203
+ # Get screenshot UUID - either extract from URL or upload
199
204
  result_screenshot_uuid = None
200
205
  if screenshot:
201
- upload_response = await client.put_s3_presigned_url(screenshot)
202
- result_screenshot_uuid = upload_response.uuid
206
+ # Check if screenshot is already a URL (already uploaded to S3)
207
+ if isinstance(screenshot, str):
208
+ result_screenshot_uuid = extract_uuid_from_url(screenshot)
209
+ # If not a URL or UUID extraction failed, upload the image
210
+ if not result_screenshot_uuid:
211
+ upload_response = await client.put_s3_presigned_url(screenshot)
212
+ result_screenshot_uuid = upload_response.uuid
203
213
 
204
214
  # Extract memory data if provided
205
215
  (
@@ -19,6 +19,7 @@ from oagi.constants import (
19
19
  DEFAULT_TEMPERATURE,
20
20
  MODEL_ACTOR,
21
21
  )
22
+ from oagi.handler import reset_handler
22
23
  from oagi.types import (
23
24
  URL,
24
25
  ActionEvent,
@@ -28,6 +29,7 @@ from oagi.types import (
28
29
  Image,
29
30
  PlanEvent,
30
31
  StepEvent,
32
+ extract_uuid_from_url,
31
33
  )
32
34
 
33
35
  from ..protocol import AsyncAgent
@@ -121,6 +123,9 @@ class TaskeeAgent(AsyncAgent):
121
123
  Returns:
122
124
  True if successful, False otherwise
123
125
  """
126
+ # Reset handler state at todo execution start
127
+ reset_handler(action_handler)
128
+
124
129
  self.current_todo = instruction
125
130
  self.actions = []
126
131
  self.total_actions = 0
@@ -256,11 +261,21 @@ class TaskeeAgent(AsyncAgent):
256
261
  # Capture screenshot
257
262
  screenshot = await image_provider()
258
263
 
259
- # Upload screenshot first to get UUID (avoids re-upload in actor.step)
264
+ # Get screenshot UUID - either extract from URL or upload
260
265
  try:
261
- upload_response = await client.put_s3_presigned_url(screenshot)
262
- screenshot_uuid = upload_response.uuid
263
- screenshot_url = upload_response.download_url
266
+ screenshot_uuid = None
267
+ screenshot_url = None
268
+
269
+ # Check if screenshot is already a URL (from SocketIOImageProvider)
270
+ if isinstance(screenshot, str):
271
+ screenshot_uuid = extract_uuid_from_url(screenshot)
272
+ screenshot_url = screenshot
273
+
274
+ # If not a URL or UUID extraction failed, upload the image
275
+ if not screenshot_uuid:
276
+ upload_response = await client.put_s3_presigned_url(screenshot)
277
+ screenshot_uuid = upload_response.uuid
278
+ screenshot_url = upload_response.download_url
264
279
  except Exception as e:
265
280
  logger.error(f"Error uploading screenshot: {e}")
266
281
  self._record_action(
@@ -16,6 +16,7 @@ from oagi.constants import (
16
16
  DEFAULT_TEMPERATURE,
17
17
  MODEL_ACTOR,
18
18
  )
19
+ from oagi.handler import reset_handler
19
20
  from oagi.types import AsyncActionHandler, AsyncImageProvider, AsyncObserver, SplitEvent
20
21
 
21
22
  from ..protocol import AsyncAgent
@@ -112,6 +113,9 @@ class TaskerAgent(AsyncAgent):
112
113
  Returns:
113
114
  True if all todos completed successfully, False otherwise
114
115
  """
116
+ # Reset handler state at automation start
117
+ reset_handler(action_handler)
118
+
115
119
  overall_success = True
116
120
 
117
121
  # Execute todos until none remain
oagi/cli/agent.py CHANGED
@@ -17,12 +17,9 @@ from oagi.agent.observer import AsyncAgentObserver
17
17
  from oagi.constants import (
18
18
  API_KEY_HELP_URL,
19
19
  DEFAULT_BASE_URL,
20
- DEFAULT_MAX_STEPS,
21
20
  DEFAULT_MAX_STEPS_THINKER,
22
21
  DEFAULT_STEP_DELAY,
23
- DEFAULT_TEMPERATURE,
24
22
  MODE_ACTOR,
25
- MODEL_ACTOR,
26
23
  MODEL_THINKER,
27
24
  )
28
25
  from oagi.exceptions import check_optional_dependency
@@ -40,22 +37,30 @@ def add_agent_parser(subparsers: argparse._SubParsersAction) -> None:
40
37
  "run", help="Run an agent with the given instruction"
41
38
  )
42
39
  run_parser.add_argument(
43
- "instruction", type=str, help="Task instruction for the agent to execute"
40
+ "instruction",
41
+ type=str,
42
+ nargs="?",
43
+ default="",
44
+ help="Task instruction for the agent to execute (optional for pre-configured modes)",
44
45
  )
45
46
  run_parser.add_argument(
46
- "--model", type=str, help=f"Model to use (default: {MODEL_ACTOR})"
47
+ "--model", type=str, help="Model to use (default: determined by mode)"
47
48
  )
48
49
  run_parser.add_argument(
49
- "--max-steps", type=int, help="Maximum number of steps (default: 20)"
50
+ "--max-steps",
51
+ type=int,
52
+ help="Maximum number of steps (default: determined by mode)",
50
53
  )
51
54
  run_parser.add_argument(
52
- "--temperature", type=float, help="Sampling temperature (default: 0.5)"
55
+ "--temperature",
56
+ type=float,
57
+ help="Sampling temperature (default: determined by mode)",
53
58
  )
54
59
  run_parser.add_argument(
55
60
  "--mode",
56
61
  type=str,
57
62
  default=MODE_ACTOR,
58
- help=f"Agent mode to use (default: {MODE_ACTOR}). Available modes: actor, planner",
63
+ help=f"Agent mode to use (default: {MODE_ACTOR}). Use 'oagi agent modes' to list available modes",
59
64
  )
60
65
  run_parser.add_argument(
61
66
  "--oagi-api-key", type=str, help="OAGI API key (default: OAGI_API_KEY env var)"
@@ -82,6 +87,9 @@ def add_agent_parser(subparsers: argparse._SubParsersAction) -> None:
82
87
  help=f"Delay in seconds after each step before next screenshot (default: {DEFAULT_STEP_DELAY})",
83
88
  )
84
89
 
90
+ # agent modes command
91
+ agent_subparsers.add_parser("modes", help="List available agent modes")
92
+
85
93
  # agent permission command
86
94
  agent_subparsers.add_parser(
87
95
  "permission",
@@ -92,10 +100,22 @@ def add_agent_parser(subparsers: argparse._SubParsersAction) -> None:
92
100
  def handle_agent_command(args: argparse.Namespace) -> None:
93
101
  if args.agent_command == "run":
94
102
  run_agent(args)
103
+ elif args.agent_command == "modes":
104
+ list_modes()
95
105
  elif args.agent_command == "permission":
96
106
  check_permissions()
97
107
 
98
108
 
109
+ def list_modes() -> None:
110
+ """List all available agent modes."""
111
+ from oagi.agent import list_agent_modes # noqa: PLC0415
112
+
113
+ modes = list_agent_modes()
114
+ print("Available agent modes:")
115
+ for mode in modes:
116
+ print(f" - {mode}")
117
+
118
+
99
119
  def check_permissions() -> None:
100
120
  """Check and request macOS permissions for screen recording and accessibility.
101
121
 
@@ -207,14 +227,6 @@ def run_agent(args: argparse.Namespace) -> None:
207
227
  sys.exit(1)
208
228
 
209
229
  base_url = args.oagi_base_url or os.getenv("OAGI_BASE_URL", DEFAULT_BASE_URL)
210
- model = args.model or MODEL_ACTOR
211
- default_max_steps = (
212
- DEFAULT_MAX_STEPS_THINKER if model == MODEL_THINKER else DEFAULT_MAX_STEPS
213
- )
214
- max_steps = args.max_steps or default_max_steps
215
- temperature = (
216
- args.temperature if args.temperature is not None else DEFAULT_TEMPERATURE
217
- )
218
230
  mode = args.mode or MODE_ACTOR
219
231
  step_delay = args.step_delay if args.step_delay is not None else DEFAULT_STEP_DELAY
220
232
  export_format = args.export
@@ -233,26 +245,38 @@ def run_agent(args: argparse.Namespace) -> None:
233
245
 
234
246
  observer = CombinedObserver()
235
247
 
236
- # Create agent with observer
237
- agent = create_agent(
238
- mode=mode,
239
- api_key=api_key,
240
- base_url=base_url,
241
- model=model,
242
- max_steps=max_steps,
243
- temperature=temperature,
244
- step_observer=observer,
245
- step_delay=step_delay,
246
- )
248
+ # Build agent kwargs - only pass explicitly provided values, let factory use defaults
249
+ agent_kwargs = {
250
+ "mode": mode,
251
+ "api_key": api_key,
252
+ "base_url": base_url,
253
+ "step_observer": observer,
254
+ "step_delay": step_delay,
255
+ }
256
+ if args.model:
257
+ agent_kwargs["model"] = args.model
258
+ # If thinker model specified without max_steps, use thinker's default
259
+ if args.model == MODEL_THINKER and not args.max_steps:
260
+ agent_kwargs["max_steps"] = DEFAULT_MAX_STEPS_THINKER
261
+ if args.max_steps:
262
+ agent_kwargs["max_steps"] = args.max_steps
263
+ if args.temperature is not None:
264
+ agent_kwargs["temperature"] = args.temperature
265
+
266
+ # Create agent
267
+ agent = create_agent(**agent_kwargs)
247
268
 
248
269
  # Create handlers
249
270
  action_handler = AsyncPyautoguiActionHandler()
250
271
  image_provider = AsyncScreenshotMaker()
251
272
 
252
- print(f"Starting agent with instruction: {args.instruction}")
273
+ if args.instruction:
274
+ print(f"Starting agent with instruction: {args.instruction}")
275
+ else:
276
+ print(f"Starting agent with mode: {mode} (using pre-configured instruction)")
253
277
  print(
254
- f"Mode: {mode}, Model: {model}, Max steps: {max_steps}, "
255
- f"Temperature: {temperature}, Step delay: {step_delay}s"
278
+ f"Mode: {mode}, Model: {agent.model}, Max steps: {agent.max_steps}, "
279
+ f"Temperature: {agent.temperature}, Step delay: {step_delay}s"
256
280
  )
257
281
  print("-" * 60)
258
282
 
oagi/handler/__init__.py CHANGED
@@ -14,6 +14,21 @@ from oagi.handler.pyautogui_action_handler import (
14
14
  )
15
15
  from oagi.handler.screenshot_maker import ScreenshotMaker
16
16
 
17
+
18
+ def reset_handler(handler) -> None:
19
+ """Reset handler state if supported.
20
+
21
+ Uses duck-typing to check if the handler has a reset() method.
22
+ This allows handlers to reset their internal state (e.g., capslock state)
23
+ at the start of a new automation task.
24
+
25
+ Args:
26
+ handler: The action handler to reset
27
+ """
28
+ if hasattr(handler, "reset"):
29
+ handler.reset()
30
+
31
+
17
32
  __all__ = [
18
33
  "PILImage",
19
34
  "PyautoguiActionHandler",
@@ -21,4 +36,5 @@ __all__ = [
21
36
  "AsyncPyautoguiActionHandler",
22
37
  "ScreenshotMaker",
23
38
  "AsyncScreenshotMaker",
39
+ "reset_handler",
24
40
  ]
oagi/handler/_macos.py CHANGED
@@ -6,6 +6,15 @@
6
6
  # Licensed under the MIT License.
7
7
  # -----------------------------------------------------------------------------
8
8
 
9
+ """macOS-specific keyboard and mouse input handling.
10
+
11
+ This module provides:
12
+ - macos_click(): Fix for PyAutoGUI multi-click bug on macOS
13
+ - typewrite_exact(): Type text exactly, ignoring system capslock state
14
+ """
15
+
16
+ import time
17
+
9
18
  import pyautogui
10
19
 
11
20
  from ..exceptions import check_optional_dependency
@@ -13,6 +22,134 @@ from ..exceptions import check_optional_dependency
13
22
  check_optional_dependency("Quartz", "macOS multiple clicks", "desktop")
14
23
  import Quartz # noqa: E402
15
24
 
25
+ # macOS virtual key codes for typeable characters
26
+ KEYCODE_MAP = {
27
+ "a": 0x00,
28
+ "b": 0x0B,
29
+ "c": 0x08,
30
+ "d": 0x02,
31
+ "e": 0x0E,
32
+ "f": 0x03,
33
+ "g": 0x05,
34
+ "h": 0x04,
35
+ "i": 0x22,
36
+ "j": 0x26,
37
+ "k": 0x28,
38
+ "l": 0x25,
39
+ "m": 0x2E,
40
+ "n": 0x2D,
41
+ "o": 0x1F,
42
+ "p": 0x23,
43
+ "q": 0x0C,
44
+ "r": 0x0F,
45
+ "s": 0x01,
46
+ "t": 0x11,
47
+ "u": 0x20,
48
+ "v": 0x09,
49
+ "w": 0x0D,
50
+ "x": 0x07,
51
+ "y": 0x10,
52
+ "z": 0x06,
53
+ "1": 0x12,
54
+ "2": 0x13,
55
+ "3": 0x14,
56
+ "4": 0x15,
57
+ "5": 0x17,
58
+ "6": 0x16,
59
+ "7": 0x1A,
60
+ "8": 0x1C,
61
+ "9": 0x19,
62
+ "0": 0x1D,
63
+ " ": 0x31, # space
64
+ "-": 0x1B,
65
+ "=": 0x18,
66
+ "[": 0x21,
67
+ "]": 0x1E,
68
+ "\\": 0x2A,
69
+ ";": 0x29,
70
+ "'": 0x27,
71
+ "`": 0x32,
72
+ ",": 0x2B,
73
+ ".": 0x2F,
74
+ "/": 0x2C,
75
+ "\t": 0x30, # tab
76
+ "\n": 0x24, # return
77
+ }
78
+
79
+ # Characters that require shift key (on US keyboard layout)
80
+ SHIFT_CHARS = set('~!@#$%^&*()_+{}|:"<>?ABCDEFGHIJKLMNOPQRSTUVWXYZ')
81
+
82
+ # Mapping of shifted characters to their base key
83
+ SHIFT_KEY_MAP = {
84
+ "~": "`",
85
+ "!": "1",
86
+ "@": "2",
87
+ "#": "3",
88
+ "$": "4",
89
+ "%": "5",
90
+ "^": "6",
91
+ "&": "7",
92
+ "*": "8",
93
+ "(": "9",
94
+ ")": "0",
95
+ "_": "-",
96
+ "+": "=",
97
+ "{": "[",
98
+ "}": "]",
99
+ "|": "\\",
100
+ ":": ";",
101
+ '"': "'",
102
+ "<": ",",
103
+ ">": ".",
104
+ "?": "/",
105
+ }
106
+
107
+
108
+ def typewrite_exact(text: str, interval: float = 0.01) -> None:
109
+ """Type text exactly as specified, ignoring system capslock state.
110
+
111
+ This function uses Quartz CGEventCreateKeyboardEvent with explicit
112
+ flag control via CGEventSetFlags() to type each character with the
113
+ correct case, regardless of the system's capslock state.
114
+
115
+ Args:
116
+ text: The text to type exactly as specified
117
+ interval: Time in seconds between each character (default: 0.01)
118
+ """
119
+ for char in text:
120
+ # Determine if this character needs shift
121
+ needs_shift = char in SHIFT_CHARS
122
+
123
+ # Get the base key (for shifted chars, look up the unshifted version)
124
+ if char.isupper():
125
+ base_char = char.lower()
126
+ elif char in SHIFT_KEY_MAP:
127
+ base_char = SHIFT_KEY_MAP[char]
128
+ else:
129
+ base_char = char
130
+
131
+ # Get keycode for the base character
132
+ keycode = KEYCODE_MAP.get(base_char)
133
+ if keycode is None:
134
+ # Character not in our keycode map, skip it
135
+ continue
136
+
137
+ # Set flags: shift if needed, otherwise clear all flags
138
+ flags = Quartz.kCGEventFlagMaskShift if needs_shift else 0
139
+
140
+ # Key down
141
+ event_down = Quartz.CGEventCreateKeyboardEvent(None, keycode, True)
142
+ Quartz.CGEventSetFlags(event_down, flags)
143
+ Quartz.CGEventPost(Quartz.kCGHIDEventTap, event_down)
144
+
145
+ # Key up
146
+ event_up = Quartz.CGEventCreateKeyboardEvent(None, keycode, False)
147
+ Quartz.CGEventSetFlags(event_up, flags)
148
+ Quartz.CGEventPost(Quartz.kCGHIDEventTap, event_up)
149
+
150
+ if interval > 0:
151
+ time.sleep(interval)
152
+
16
153
 
17
154
  def macos_click(x: int, y: int, clicks: int = 1) -> None:
18
155
  """
@@ -0,0 +1,101 @@
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
+ """Windows-specific keyboard input handling.
10
+
11
+ This module provides typewrite_exact() which types text exactly as specified,
12
+ ignoring the system's capslock state by using SendInput with KEYEVENTF_UNICODE.
13
+ """
14
+
15
+ import ctypes
16
+ import time
17
+ from ctypes import wintypes
18
+
19
+ INPUT_KEYBOARD = 1
20
+ KEYEVENTF_UNICODE = 0x0004
21
+ KEYEVENTF_KEYUP = 0x0002
22
+
23
+
24
+ class KEYBDINPUT(ctypes.Structure):
25
+ _fields_ = [
26
+ ("wVk", wintypes.WORD),
27
+ ("wScan", wintypes.WORD),
28
+ ("dwFlags", wintypes.DWORD),
29
+ ("time", wintypes.DWORD),
30
+ ("dwExtraInfo", ctypes.POINTER(ctypes.c_ulong)),
31
+ ]
32
+
33
+
34
+ class MOUSEINPUT(ctypes.Structure):
35
+ _fields_ = [
36
+ ("dx", ctypes.c_long),
37
+ ("dy", ctypes.c_long),
38
+ ("mouseData", wintypes.DWORD),
39
+ ("dwFlags", wintypes.DWORD),
40
+ ("time", wintypes.DWORD),
41
+ ("dwExtraInfo", ctypes.POINTER(ctypes.c_ulong)),
42
+ ]
43
+
44
+
45
+ class HARDWAREINPUT(ctypes.Structure):
46
+ _fields_ = [
47
+ ("uMsg", wintypes.DWORD),
48
+ ("wParamL", wintypes.WORD),
49
+ ("wParamH", wintypes.WORD),
50
+ ]
51
+
52
+
53
+ class INPUT(ctypes.Structure):
54
+ class _I(ctypes.Union):
55
+ _fields_ = [
56
+ ("ki", KEYBDINPUT),
57
+ ("mi", MOUSEINPUT),
58
+ ("hi", HARDWAREINPUT),
59
+ ]
60
+
61
+ _anonymous_ = ("i",)
62
+ _fields_ = [
63
+ ("type", wintypes.DWORD),
64
+ ("i", _I),
65
+ ]
66
+
67
+
68
+ # Configure SendInput with proper argtypes for 64-bit compatibility
69
+ SendInput = ctypes.windll.user32.SendInput
70
+ SendInput.argtypes = [wintypes.UINT, ctypes.POINTER(INPUT), ctypes.c_int]
71
+ SendInput.restype = wintypes.UINT
72
+
73
+
74
+ def typewrite_exact(text: str, interval: float = 0.01) -> None:
75
+ """Type text exactly using Unicode input - ignores capslock, keyboard layout, etc.
76
+
77
+ This function uses SendInput with KEYEVENTF_UNICODE to send characters
78
+ directly by their Unicode codepoint, completely bypassing keyboard state
79
+ (capslock, layout, etc.).
80
+
81
+ Args:
82
+ text: The text to type exactly as specified
83
+ interval: Time in seconds between each character (default: 0.01)
84
+ """
85
+ for char in text:
86
+ inputs = (INPUT * 2)()
87
+
88
+ # Key down
89
+ inputs[0].type = INPUT_KEYBOARD
90
+ inputs[0].ki.wScan = ord(char)
91
+ inputs[0].ki.dwFlags = KEYEVENTF_UNICODE
92
+
93
+ # Key up
94
+ inputs[1].type = INPUT_KEYBOARD
95
+ inputs[1].ki.wScan = ord(char)
96
+ inputs[1].ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP
97
+
98
+ SendInput(2, inputs, ctypes.sizeof(INPUT))
99
+
100
+ if interval > 0:
101
+ time.sleep(interval)
@@ -29,6 +29,14 @@ class AsyncPyautoguiActionHandler:
29
29
  self.sync_handler = PyautoguiActionHandler(config=config)
30
30
  self.config = config or PyautoguiConfig()
31
31
 
32
+ def reset(self):
33
+ """Reset handler state.
34
+
35
+ Delegates to the underlying synchronous handler's reset method.
36
+ Called at automation start/end and when FINISH action is received.
37
+ """
38
+ self.sync_handler.reset()
39
+
32
40
  async def __call__(self, actions: list[Action]) -> None:
33
41
  """
34
42
  Execute actions asynchronously using a thread pool executor.
@@ -0,0 +1,55 @@
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
+
10
+ class CapsLockManager:
11
+ """Manages caps lock state for text transformation.
12
+
13
+ This class maintains an internal caps lock state that can be toggled
14
+ independently of the system's caps lock state. This allows for consistent
15
+ text case handling during automation regardless of the system state.
16
+ """
17
+
18
+ def __init__(self, mode: str = "session"):
19
+ """Initialize caps lock manager.
20
+
21
+ Args:
22
+ mode: Either "session" (internal state) or "system" (OS-level)
23
+ """
24
+ self.mode = mode
25
+ self.caps_enabled = False
26
+
27
+ def reset(self):
28
+ """Reset caps lock state to default (off).
29
+
30
+ Called at automation start/end and when FINISH action is received.
31
+ """
32
+ self.caps_enabled = False
33
+
34
+ def toggle(self):
35
+ """Toggle caps lock state in session mode."""
36
+ if self.mode == "session":
37
+ self.caps_enabled = not self.caps_enabled
38
+
39
+ def transform_text(self, text: str) -> str:
40
+ """Transform text based on caps lock state.
41
+
42
+ Args:
43
+ text: Input text to transform
44
+
45
+ Returns:
46
+ Transformed text (uppercase alphabets if caps enabled in session mode)
47
+ """
48
+ if self.mode == "session" and self.caps_enabled:
49
+ # Transform letters to uppercase, preserve special characters
50
+ return "".join(c.upper() if c.isalpha() else c for c in text)
51
+ return text
52
+
53
+ def should_use_system_capslock(self) -> bool:
54
+ """Check if system-level caps lock should be used."""
55
+ return self.mode == "system"
@@ -13,48 +13,15 @@ from pydantic import BaseModel, Field
13
13
 
14
14
  from ..exceptions import check_optional_dependency
15
15
  from ..types import Action, ActionType, parse_coords, parse_drag_coords, parse_scroll
16
+ from .capslock_manager import CapsLockManager
16
17
 
17
18
  check_optional_dependency("pyautogui", "PyautoguiActionHandler", "desktop")
18
19
  import pyautogui # noqa: E402
19
20
 
20
21
  if sys.platform == "darwin":
21
22
  from . import _macos
22
-
23
-
24
- class CapsLockManager:
25
- """Manages caps lock state for text transformation."""
26
-
27
- def __init__(self, mode: str = "session"):
28
- """Initialize caps lock manager.
29
-
30
- Args:
31
- mode: Either "session" (internal state) or "system" (OS-level)
32
- """
33
- self.mode = mode
34
- self.caps_enabled = False
35
-
36
- def toggle(self):
37
- """Toggle caps lock state in session mode."""
38
- if self.mode == "session":
39
- self.caps_enabled = not self.caps_enabled
40
-
41
- def transform_text(self, text: str) -> str:
42
- """Transform text based on caps lock state.
43
-
44
- Args:
45
- text: Input text to transform
46
-
47
- Returns:
48
- Transformed text (uppercase if caps enabled in session mode)
49
- """
50
- if self.mode == "session" and self.caps_enabled:
51
- # Transform letters to uppercase, preserve special characters
52
- return "".join(c.upper() if c.isalpha() else c for c in text)
53
- return text
54
-
55
- def should_use_system_capslock(self) -> bool:
56
- """Check if system-level caps lock should be used."""
57
- return self.mode == "system"
23
+ elif sys.platform == "win32":
24
+ from . import _windows
58
25
 
59
26
 
60
27
  class PyautoguiConfig(BaseModel):
@@ -111,6 +78,14 @@ class PyautoguiActionHandler:
111
78
  # Initialize caps lock manager
112
79
  self.caps_manager = CapsLockManager(mode=self.config.capslock_mode)
113
80
 
81
+ def reset(self):
82
+ """Reset handler state.
83
+
84
+ Called at automation start/end and when FINISH action is received.
85
+ Resets the internal capslock state.
86
+ """
87
+ self.caps_manager.reset()
88
+
114
89
  def _denormalize_coords(self, x: float, y: float) -> tuple[int, int]:
115
90
  """Convert coordinates from 0-1000 range to actual screen coordinates.
116
91
 
@@ -238,7 +213,14 @@ class PyautoguiActionHandler:
238
213
  text = arg.strip("\"'")
239
214
  # Apply caps lock transformation if needed
240
215
  text = self.caps_manager.transform_text(text)
241
- pyautogui.typewrite(text)
216
+ # Use platform-specific typing that ignores system capslock
217
+ if sys.platform == "darwin":
218
+ _macos.typewrite_exact(text)
219
+ elif sys.platform == "win32":
220
+ _windows.typewrite_exact(text)
221
+ else:
222
+ # Fallback for other platforms
223
+ pyautogui.typewrite(text)
242
224
 
243
225
  case ActionType.SCROLL:
244
226
  x, y, direction = self._parse_scroll(arg)
@@ -251,8 +233,8 @@ class PyautoguiActionHandler:
251
233
  pyautogui.scroll(scroll_amount)
252
234
 
253
235
  case ActionType.FINISH:
254
- # Task completion - no action needed
255
- pass
236
+ # Task completion - reset handler state
237
+ self.reset()
256
238
 
257
239
  case ActionType.WAIT:
258
240
  # Wait for a short period
@@ -11,7 +11,7 @@ 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
14
+ from ..constants import DEFAULT_TEMPERATURE_LOW, MODE_ACTOR, MODEL_ACTOR
15
15
 
16
16
 
17
17
  class Session:
@@ -21,7 +21,7 @@ class Session:
21
21
  instruction: str,
22
22
  mode: str = MODE_ACTOR,
23
23
  model: str = MODEL_ACTOR,
24
- temperature: float = 0.0,
24
+ temperature: float = DEFAULT_TEMPERATURE_LOW,
25
25
  ):
26
26
  self.session_id: str = session_id
27
27
  self.instruction: str = instruction
@@ -57,7 +57,7 @@ class SessionStore:
57
57
  instruction: str,
58
58
  mode: str = MODE_ACTOR,
59
59
  model: str = MODEL_ACTOR,
60
- temperature: float = 0.0,
60
+ temperature: float = DEFAULT_TEMPERATURE_LOW,
61
61
  session_id: str | None = None,
62
62
  ) -> str:
63
63
  if session_id is None:
@@ -158,8 +158,8 @@ class SessionNamespace(socketio.AsyncNamespace):
158
158
  session_store.update_activity(session_id)
159
159
 
160
160
  logger.info(
161
- f"Session {session_id} initialized with: {event_data.instruction} "
162
- f"(mode={event_data.mode}, model={event_data.model})"
161
+ f"Session {session_id} initialized with: {session.instruction} "
162
+ f"(mode={session.mode}, model={session.model})"
163
163
  )
164
164
 
165
165
  # Create agent and wrappers
@@ -168,8 +168,8 @@ class SessionNamespace(socketio.AsyncNamespace):
168
168
  api_key=self.config.oagi_api_key,
169
169
  base_url=self.config.oagi_base_url,
170
170
  max_steps=self.config.max_steps,
171
- model=event_data.model,
172
- temperature=event_data.temperature,
171
+ model=session.model,
172
+ temperature=session.temperature,
173
173
  )
174
174
 
175
175
  action_handler = SocketIOActionHandler(self, session)
oagi/types/__init__.py CHANGED
@@ -32,7 +32,7 @@ from .step_observer import (
32
32
  SplitEvent,
33
33
  StepEvent,
34
34
  )
35
- from .url import URL
35
+ from .url import URL, extract_uuid_from_url
36
36
 
37
37
  __all__ = [
38
38
  "Action",
@@ -55,6 +55,7 @@ __all__ = [
55
55
  "ImageProvider",
56
56
  "AsyncImageProvider",
57
57
  "URL",
58
+ "extract_uuid_from_url",
58
59
  "parse_coords",
59
60
  "parse_drag_coords",
60
61
  "parse_scroll",
oagi/types/url.py CHANGED
@@ -1,3 +1,28 @@
1
+ import re
1
2
  from typing import NewType
2
3
 
3
4
  URL = NewType("URL", str)
5
+
6
+ # Pattern to extract UUID from S3 URL
7
+ # Formats:
8
+ # - https://bucket.s3.amazonaws.com/{user_id}/{uuid}.{ext} (download URL)
9
+ # - https://bucket.s3.amazonaws.com/{user_id}/{uuid}?... (presigned URL)
10
+ _UUID_PATTERN = re.compile(
11
+ r"/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:\.[a-z]+)?(?:\?|$)",
12
+ re.IGNORECASE,
13
+ )
14
+
15
+
16
+ def extract_uuid_from_url(url: str) -> str | None:
17
+ """Extract UUID from S3 URL.
18
+
19
+ Args:
20
+ url: S3 URL in one of these formats:
21
+ - https://bucket.s3.amazonaws.com/{user_id}/{uuid}.jpg (download URL)
22
+ - https://bucket.s3.amazonaws.com/{user_id}/{uuid}?... (presigned URL)
23
+
24
+ Returns:
25
+ UUID string if found, None otherwise
26
+ """
27
+ match = _UUID_PATTERN.search(url)
28
+ return match.group(1) if match else None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: oagi-core
3
- Version: 0.10.3
3
+ Version: 0.11.0
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>
@@ -116,6 +116,39 @@ config = PyautoguiConfig(
116
116
  action_handler = AsyncPyautoguiActionHandler(config=config)
117
117
  ```
118
118
 
119
+ ### Command Line Interface
120
+
121
+ Run agents directly from the terminal:
122
+
123
+ ```bash
124
+ # Run with actor model
125
+ oagi agent run "Go to nasdaq.com, search for AAPL. Under More, go to Insider Activity" --model lux-actor-1
126
+
127
+ # Run with thinker mode (uses lux-thinker-1 model with more steps)
128
+ oagi agent run "Look up the store hours for the nearest Apple Store to zip code 23456 using the Apple Store Locator" --model lux-thinker-1
129
+
130
+ # Run pre-configured tasker workflows (no instruction needed)
131
+ oagi agent run --mode tasker:software_qa
132
+
133
+ # List all available modes
134
+ oagi agent modes
135
+
136
+ # Check macOS permissions (screen recording & accessibility)
137
+ oagi agent permission
138
+
139
+ # Export execution history
140
+ oagi agent run "Complete the form" --export html --export-file report.html
141
+ ```
142
+
143
+ CLI options:
144
+ - `--mode`: Agent mode (default: actor). Use `oagi agent modes` to list available modes
145
+ - `--model`: Override the model (default: determined by mode)
146
+ - `--max-steps`: Maximum steps (default: determined by mode)
147
+ - `--temperature`: Sampling temperature (default: determined by mode)
148
+ - `--step-delay`: Delay after each action before next screenshot (default: 0.3s)
149
+ - `--export`: Export format (markdown, html, json)
150
+ - `--export-file`: Output file path for export
151
+
119
152
  ### Image Processing
120
153
 
121
154
  Process and optimize images before sending to API:
@@ -3,8 +3,8 @@ oagi/constants.py,sha256=ZhlY_g3Z8w8njvoVykuGe3nty8A1LoGpRo5oJPh6qS0,1142
3
3
  oagi/exceptions.py,sha256=Rco37GQTPYUfc2vRO3hozxPF_s8mKFDpFvBg2UKWo3Y,3066
4
4
  oagi/logging.py,sha256=YT3KCMFj5fzO98R9xlDDgfSotUuz1xRD6OZeYM2rKoo,1760
5
5
  oagi/agent/__init__.py,sha256=KTVLUMhbjgpTJoOWMUZkkiqwhgumvbOZV2tJ9XCLfao,901
6
- oagi/agent/default.py,sha256=zHcTGyMv5QKE2tqwAsKsN6YyMWeSxCGCMzmv2PHdzMk,4520
7
- oagi/agent/factories.py,sha256=i-GIOEYmwBiiTq4W8UzKfZPkuZcMuOrlc_8dkxM1zLs,2727
6
+ oagi/agent/default.py,sha256=Ers1Dk6BX-s24ZZXOfiXt9wsilx_LHuTNgi3Yj7Hin4,4653
7
+ oagi/agent/factories.py,sha256=syi_EOlU4SUjo-0CKaML8eIPu3ToUEKua2VHp9lvNF0,5839
8
8
  oagi/agent/protocol.py,sha256=IQJGiMN4yZIacrh5e9JQsoM9TyHb8wJRQR4LAk8dSA0,1615
9
9
  oagi/agent/registry.py,sha256=7bMA2-pH3xQ9ZavrHB_mnc2fOGSMeICPbOGtHoM7It0,4851
10
10
  oagi/agent/observer/__init__.py,sha256=YZ4qvR22pFB0mSDMX6iKKLbBA1dB-nqC7HZVvdMIVGw,909
@@ -16,11 +16,11 @@ oagi/agent/observer/report_template.html,sha256=NOp280_-P2g_4TYbtqPhYxv-y_x3Pwov
16
16
  oagi/agent/tasker/__init__.py,sha256=1iTEFe7lzcqh96TL9R0QADPpLJLrUP0shtZ4DlZSv_8,764
17
17
  oagi/agent/tasker/memory.py,sha256=NR13l5yxRA8GUE-oupAP4W1n80ZNG0SxpUfxsNltkUY,5033
18
18
  oagi/agent/tasker/models.py,sha256=sMQgwIMKhT1tvVF2yoc1hh8GwEiJ6i6qPMy9WoiA8JM,2137
19
- oagi/agent/tasker/planner.py,sha256=RxDilAGJAaC8gu9EkTwBATDLv7lQlzc3LMyxs0KMR74,14516
20
- oagi/agent/tasker/taskee_agent.py,sha256=MHl_ZH0p8fJ9iSG-VZe_4WUzdyjg4cLZbvEnccQcV2w,17263
21
- oagi/agent/tasker/tasker_agent.py,sha256=P0O4Ned8U0JfMFLWT2JCYXyfuHbU8XziPSotrRuNzMM,11157
19
+ oagi/agent/tasker/planner.py,sha256=nSOOcB4NzrOONFhfNvYzxxrelU5NxpbLMpC7B5Q-L-k,15211
20
+ oagi/agent/tasker/taskee_agent.py,sha256=jYG-c36zbhDM3EEt6-YzLlk-K6n7mjFNquEd6BPaRvw,17867
21
+ oagi/agent/tasker/tasker_agent.py,sha256=kbWvvRVS12S8eFd0PRszFrcSSWRzz_f6Jd7LuaRcwJQ,11285
22
22
  oagi/cli/__init__.py,sha256=aDnJViTseShpo5fdGPTj-ELysZhmdvB6Z8mEj2D-_N4,359
23
- oagi/cli/agent.py,sha256=eFwdVZdZcG9ZTMupwoH_JqKSRKDAto3j3z4Xqz5XH7g,10225
23
+ oagi/cli/agent.py,sha256=fd7WtR5zoRJmyHDr67zfBvEX-_BLIjPbBqp-dhy4AAk,11061
24
24
  oagi/cli/display.py,sha256=Y8_Dn5RIEfRqZUHVGF6URItW0C3XC7bPLWoAmmhvBS0,1829
25
25
  oagi/cli/main.py,sha256=faHns0HaQCGyylDn2YZLpjQESuEiMYjoQVoMkt8FsH4,2292
26
26
  oagi/cli/server.py,sha256=JFpzCOeaftITxesz8Ya-_Efs03bgotBg7aYwmMZhPwU,3033
@@ -30,41 +30,43 @@ oagi/client/__init__.py,sha256=F9DShPUdb6vZYmN1fpM1VYzp4MWqUao_e_R1KYmM4Q4,410
30
30
  oagi/client/async_.py,sha256=Z8DyQHEl_XAyw1Fs0o3qQzEX9lGlZhqCTSEtQh5XHSw,11204
31
31
  oagi/client/base.py,sha256=tKYmhJufg7sqEOOp7nn0S9jv4hDYfM-pBuluh0pqew0,16756
32
32
  oagi/client/sync.py,sha256=CcesgCk_b26v_DF9OKpjfSHKSMjK0N2ywAxwaGnJ4fo,11040
33
- oagi/handler/__init__.py,sha256=Ha11L42K33K3L9S4lQ10UC0DnD5g6egtQUsJpS_tKgg,835
34
- oagi/handler/_macos.py,sha256=aHkp-xGzvWL_SBjuS690i9jf93OITFJfGHzHeYCK65I,1957
35
- oagi/handler/async_pyautogui_action_handler.py,sha256=hQzseR1yBD0QMpgsEVNsUmuApGVAIIyGYD06BXd82Dc,1615
33
+ oagi/handler/__init__.py,sha256=vEUvyiUvTRf1GE4vTiz_bQjLv3psdmHyt0mfKJHq24M,1247
34
+ oagi/handler/_macos.py,sha256=Gs8GrhA_WAyv9Yw0D41duliP32Xk6vouyMeWjWJJT90,5187
35
+ oagi/handler/_windows.py,sha256=MSgPDYEOetSjbn9eJDSrdzBVlUGgGsTlegaTDc4C4Ss,2828
36
+ oagi/handler/async_pyautogui_action_handler.py,sha256=wfNRBBURZnwQkNTcs9OPMmFJIAPtnXmcqxWbjda_q7I,1863
36
37
  oagi/handler/async_screenshot_maker.py,sha256=8QCtUV59ozpOpvkqhUMb8QDI2qje2gsoFT1qB60tfJM,1689
38
+ oagi/handler/capslock_manager.py,sha256=40LzWt1_1wbncF5koUTdbd9V3eo5Ex_mEWwjtEmHAf4,1878
37
39
  oagi/handler/pil_image.py,sha256=yUcAoGBL-aZ0PCjSaAmQsDwtyzjldXHqXQp_OYRk6e4,4080
38
- oagi/handler/pyautogui_action_handler.py,sha256=vxSkD_Xsshxb6cfoFFd_zpAwQm21cYTk4w1BGenKZes,10604
40
+ oagi/handler/pyautogui_action_handler.py,sha256=BVmpKuYAMINJ5Ue_PK_WxFScAqLeyXC64g4NWQUtG_M,10146
39
41
  oagi/handler/screenshot_maker.py,sha256=j1jTW-awx3vAnb1N5_FIMBC0Z-rNVQbiBP-S6Gh5dlE,1284
40
42
  oagi/server/__init__.py,sha256=uZx8u3vJUb87kkNzwmmVrgAgbqRu0WxyMIQCLSx56kk,452
41
43
  oagi/server/agent_wrappers.py,sha256=j8va0A7u80bzOM82nndAplK1uaO_T3kufHWScK6kfWM,3263
42
44
  oagi/server/config.py,sha256=AJ1PLKuxrc6pRuur1hm5DwG2g2otxPwOCfKgzIACkSk,1691
43
45
  oagi/server/main.py,sha256=jnTxk7Prc5CzlsUnkBNJp4MOoYN-7HN_Be_m1d3COa8,4829
44
46
  oagi/server/models.py,sha256=DXjuf5icpCOgCUGMzzoLfRCoreM541KBWKBZnCk5_S0,2688
45
- oagi/server/session_store.py,sha256=319CDqGT9bnqaHK5JiLCG2mKxJmIbq1eH3BpjhWLxdI,3685
46
- oagi/server/socketio_server.py,sha256=0JUf8Y6r9Y7gmltznnsGsEeTp1FhT7AxohSHeBuH3R8,14233
47
+ oagi/server/session_store.py,sha256=l7t31rNWuZkIPLnaqrllVusHkJkE8j50PMfyb1di9mI,3750
48
+ oagi/server/socketio_server.py,sha256=8RRf8mAmsArOX2nWylT0g5T5On0gzMod0TWRmk0vrgA,14218
47
49
  oagi/task/__init__.py,sha256=g_8_7ZLDLKuCGzyrB42OzY3gSOjd_SxzkJW3_pf-PXs,662
48
50
  oagi/task/async_.py,sha256=12BrdE-51bEz2-PZv5X2VW__I_nwq2K1YxGfD3wFxos,3190
49
51
  oagi/task/async_short.py,sha256=wVMYpsKGbvqYIe2Ws7cMf8-t7SZKmtrgjW1x_RENMgg,2820
50
52
  oagi/task/base.py,sha256=6F_nmJsb2Zw6nUibJyAEW0uQBY_jHiQINUbd_jT5wkQ,5696
51
53
  oagi/task/short.py,sha256=D5VX8QGy0o8W7njy74jx95PxU0Rv2Nvoa-2T17aBaZQ,2629
52
54
  oagi/task/sync.py,sha256=pKRpIFcetm1n2BgmYGWQeWgV3kKLAQRG9xPlBVQ_pho,3024
53
- oagi/types/__init__.py,sha256=nhxQBaDssygtscetliQa3AaryvuNZhkVmBYsVcuR-qg,1332
55
+ oagi/types/__init__.py,sha256=_UyzzRnoKvp00BUBjxW9Tv3_xBNf8Lxb2PUC2DkjOkg,1384
54
56
  oagi/types/action_handler.py,sha256=NH8E-m5qpGqWcXzTSWfF7W0Xdp8SkzJsbhCmQ0B96cg,1075
55
57
  oagi/types/async_action_handler.py,sha256=k1AaqSkFcXlxwW8sn-w0WFHGsIqHFLbcOPrkknmSVug,1116
56
58
  oagi/types/async_image_provider.py,sha256=UwDl7VOCA3tiSP5k1fnxK86iEa84Yr57MVaoBSa3hOE,1203
57
59
  oagi/types/image.py,sha256=KgPCCTJ6D5vHIaGZdbTE7eQEa1WlT6G9tf59ZuUCV2U,537
58
60
  oagi/types/image_provider.py,sha256=IhKEnwCGZ5l_rO3AvJ6xv5RZMTmTDmqsFRynI9h0R_M,1145
59
61
  oagi/types/step_observer.py,sha256=wXuChzsof7Rh4azvDTIQ22gAwZAYjMAOVIuL8ZGtw-M,2315
60
- oagi/types/url.py,sha256=Q-1jf5L_4rad4dxyLTg4MXadGgpkH3w4dcoVrVupW-A,54
62
+ oagi/types/url.py,sha256=145jLl3yecFBVKhJDbrR63C48D3l9_w0kpA_8C_gM78,868
61
63
  oagi/types/models/__init__.py,sha256=gnFh4TddritHjT0Chy-4fv3KZIC6bYCUyGmWm_2IuZw,879
62
64
  oagi/types/models/action.py,sha256=Q14xfYJrj9IsrqxDpEIzd6iWS-gLmNHfIX6Ef8k0O9E,2497
63
65
  oagi/types/models/client.py,sha256=1xIKBgLSheHfqYbcyRKMDOLQJaKijaKQ5l-COc6e7_k,1471
64
66
  oagi/types/models/image_config.py,sha256=tl6abVg_-IAPLwpaWprgknXu7wRWriMg-AEVyUX73v0,1567
65
67
  oagi/types/models/step.py,sha256=RSI4H_2rrUBq_xyCoWKaq7JHdJWNobtQppaKC1l0aWU,471
66
- oagi_core-0.10.3.dist-info/METADATA,sha256=XvsZ9dqWB1fDxdazYcT5u8-k9aeKqZBmtmmm_x0JEMo,8269
67
- oagi_core-0.10.3.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
68
- oagi_core-0.10.3.dist-info/entry_points.txt,sha256=zzgsOSWX6aN3KUB0Z1it8DMxFFBJBqmZVqMVAJRjYuw,44
69
- oagi_core-0.10.3.dist-info/licenses/LICENSE,sha256=sy5DLA2M29jFT4UfWsuBF9BAr3FnRkYtnAu6oDZiIf8,1075
70
- oagi_core-0.10.3.dist-info/RECORD,,
68
+ oagi_core-0.11.0.dist-info/METADATA,sha256=y251pGqY3CfcN4umrr21EEMTUTRzvYUQXkruKv6d-e4,9497
69
+ oagi_core-0.11.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
70
+ oagi_core-0.11.0.dist-info/entry_points.txt,sha256=zzgsOSWX6aN3KUB0Z1it8DMxFFBJBqmZVqMVAJRjYuw,44
71
+ oagi_core-0.11.0.dist-info/licenses/LICENSE,sha256=sy5DLA2M29jFT4UfWsuBF9BAr3FnRkYtnAu6oDZiIf8,1075
72
+ oagi_core-0.11.0.dist-info/RECORD,,