oagi-core 0.9.0__py3-none-any.whl → 0.9.2__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.
Files changed (38) hide show
  1. oagi/__init__.py +13 -16
  2. oagi/agent/default.py +26 -10
  3. oagi/agent/factories.py +5 -0
  4. oagi/agent/tasker/planner.py +11 -2
  5. oagi/agent/tasker/taskee_agent.py +45 -20
  6. oagi/agent/tasker/tasker_agent.py +11 -7
  7. oagi/cli/agent.py +39 -21
  8. oagi/cli/display.py +56 -0
  9. oagi/cli/tracking.py +45 -0
  10. oagi/cli/utils.py +11 -4
  11. oagi/client/base.py +3 -7
  12. oagi/handler/__init__.py +24 -0
  13. oagi/handler/_macos.py +55 -0
  14. oagi/{async_pyautogui_action_handler.py → handler/async_pyautogui_action_handler.py} +1 -1
  15. oagi/{async_screenshot_maker.py → handler/async_screenshot_maker.py} +1 -1
  16. oagi/{pil_image.py → handler/pil_image.py} +2 -2
  17. oagi/{pyautogui_action_handler.py → handler/pyautogui_action_handler.py} +14 -4
  18. oagi/{screenshot_maker.py → handler/screenshot_maker.py} +2 -2
  19. oagi/logging.py +8 -0
  20. oagi/server/config.py +3 -3
  21. oagi/server/models.py +1 -1
  22. oagi/server/socketio_server.py +1 -1
  23. oagi/task/__init__.py +10 -3
  24. oagi/task/async_.py +27 -2
  25. oagi/task/async_short.py +16 -4
  26. oagi/task/base.py +2 -0
  27. oagi/task/short.py +16 -4
  28. oagi/task/sync.py +27 -2
  29. oagi/types/__init__.py +2 -0
  30. oagi/types/step_observer.py +34 -0
  31. {oagi_core-0.9.0.dist-info → oagi_core-0.9.2.dist-info}/METADATA +4 -29
  32. oagi_core-0.9.2.dist-info/RECORD +63 -0
  33. oagi/async_single_step.py +0 -85
  34. oagi/single_step.py +0 -87
  35. oagi_core-0.9.0.dist-info/RECORD +0 -60
  36. {oagi_core-0.9.0.dist-info → oagi_core-0.9.2.dist-info}/WHEEL +0 -0
  37. {oagi_core-0.9.0.dist-info → oagi_core-0.9.2.dist-info}/entry_points.txt +0 -0
  38. {oagi_core-0.9.0.dist-info → oagi_core-0.9.2.dist-info}/licenses/LICENSE +0 -0
oagi/__init__.py CHANGED
@@ -7,7 +7,6 @@
7
7
  # -----------------------------------------------------------------------------
8
8
  import importlib
9
9
 
10
- from oagi.async_single_step import async_single_step
11
10
  from oagi.client import AsyncClient, SyncClient
12
11
  from oagi.exceptions import (
13
12
  APIError,
@@ -21,8 +20,7 @@ from oagi.exceptions import (
21
20
  ServerError,
22
21
  ValidationError,
23
22
  )
24
- from oagi.single_step import single_step
25
- from oagi.task import AsyncShortTask, AsyncTask, ShortTask, Task
23
+ from oagi.task import Actor, AsyncActor, AsyncShortTask, AsyncTask, ShortTask, Task
26
24
  from oagi.types import (
27
25
  AsyncActionHandler,
28
26
  AsyncImageProvider,
@@ -33,12 +31,12 @@ from oagi.types.models import ErrorDetail, ErrorResponse, LLMResponse
33
31
  # Lazy imports for pyautogui-dependent modules
34
32
  # These will only be imported when actually accessed
35
33
  _LAZY_IMPORTS = {
36
- "AsyncPyautoguiActionHandler": "oagi.async_pyautogui_action_handler",
37
- "AsyncScreenshotMaker": "oagi.async_screenshot_maker",
38
- "PILImage": "oagi.pil_image",
39
- "PyautoguiActionHandler": "oagi.pyautogui_action_handler",
40
- "PyautoguiConfig": "oagi.pyautogui_action_handler",
41
- "ScreenshotMaker": "oagi.screenshot_maker",
34
+ "AsyncPyautoguiActionHandler": "oagi.handler.async_pyautogui_action_handler",
35
+ "AsyncScreenshotMaker": "oagi.handler.async_screenshot_maker",
36
+ "PILImage": "oagi.handler.pil_image",
37
+ "PyautoguiActionHandler": "oagi.handler.pyautogui_action_handler",
38
+ "PyautoguiConfig": "oagi.handler.pyautogui_action_handler",
39
+ "ScreenshotMaker": "oagi.handler.screenshot_maker",
42
40
  # Agent modules (to avoid circular imports)
43
41
  "TaskerAgent": "oagi.agent.tasker",
44
42
  # Server modules (optional - requires server dependencies)
@@ -59,18 +57,17 @@ def __getattr__(name: str):
59
57
 
60
58
  __all__ = [
61
59
  # Core sync classes
62
- "Task",
63
- "ShortTask",
60
+ "Actor",
61
+ "AsyncActor",
62
+ "Task", # Deprecated: Use Actor instead
63
+ "ShortTask", # Deprecated
64
64
  "SyncClient",
65
65
  # Core async classes
66
- "AsyncTask",
67
- "AsyncShortTask",
66
+ "AsyncTask", # Deprecated: Use AsyncActor instead
67
+ "AsyncShortTask", # Deprecated
68
68
  "AsyncClient",
69
69
  # Agent classes
70
70
  "TaskerAgent",
71
- # Functions
72
- "single_step",
73
- "async_single_step",
74
71
  # Async protocols
75
72
  "AsyncActionHandler",
76
73
  "AsyncImageProvider",
oagi/agent/default.py CHANGED
@@ -8,10 +8,11 @@
8
8
 
9
9
  import logging
10
10
 
11
- from .. import AsyncTask
11
+ from .. import AsyncActor
12
12
  from ..types import (
13
13
  AsyncActionHandler,
14
14
  AsyncImageProvider,
15
+ AsyncStepObserver,
15
16
  )
16
17
 
17
18
  logger = logging.getLogger(__name__)
@@ -24,15 +25,17 @@ class AsyncDefaultAgent:
24
25
  self,
25
26
  api_key: str | None = None,
26
27
  base_url: str | None = None,
27
- model: str = "lux-v1",
28
- max_steps: int = 30,
29
- temperature: float | None = None,
28
+ model: str = "lux-actor-1",
29
+ max_steps: int = 20,
30
+ temperature: float | None = 0.5,
31
+ step_observer: AsyncStepObserver | None = None,
30
32
  ):
31
33
  self.api_key = api_key
32
34
  self.base_url = base_url
33
35
  self.model = model
34
36
  self.max_steps = max_steps
35
37
  self.temperature = temperature
38
+ self.step_observer = step_observer
36
39
 
37
40
  async def execute(
38
41
  self,
@@ -40,11 +43,11 @@ class AsyncDefaultAgent:
40
43
  action_handler: AsyncActionHandler,
41
44
  image_provider: AsyncImageProvider,
42
45
  ) -> bool:
43
- async with AsyncTask(
46
+ async with AsyncActor(
44
47
  api_key=self.api_key, base_url=self.base_url, model=self.model
45
- ) as self.task:
48
+ ) as self.actor:
46
49
  logger.info(f"Starting async task execution: {instruction}")
47
- await self.task.init_task(instruction, max_steps=self.max_steps)
50
+ await self.actor.init_task(instruction, max_steps=self.max_steps)
48
51
 
49
52
  for i in range(self.max_steps):
50
53
  logger.debug(f"Executing step {i + 1}/{self.max_steps}")
@@ -53,15 +56,28 @@ class AsyncDefaultAgent:
53
56
  image = await image_provider()
54
57
 
55
58
  # Get next step from OAGI
56
- step = await self.task.step(image, temperature=self.temperature)
59
+ step = await self.actor.step(image, temperature=self.temperature)
57
60
 
58
61
  # Log reasoning
59
62
  if step.reason:
60
- logger.debug(f"Step {i + 1} reasoning: {step.reason}")
63
+ logger.info(f"Step {i + 1}: {step.reason}")
64
+
65
+ # Notify observer if present
66
+ if self.step_observer:
67
+ await self.step_observer.on_step(i + 1, step.reason, step.actions)
61
68
 
62
69
  # Execute actions if any
63
70
  if step.actions:
64
- logger.debug(f"Executing {len(step.actions)} actions")
71
+ logger.info(f"Actions ({len(step.actions)}):")
72
+ for action in step.actions:
73
+ count_suffix = (
74
+ f" x{action.count}"
75
+ if action.count and action.count > 1
76
+ else ""
77
+ )
78
+ logger.info(
79
+ f" [{action.type.value}] {action.argument}{count_suffix}"
80
+ )
65
81
  await action_handler(step.actions)
66
82
 
67
83
  # Check if task is complete
oagi/agent/factories.py CHANGED
@@ -6,6 +6,7 @@
6
6
  # Licensed under the MIT License.
7
7
  # -----------------------------------------------------------------------------
8
8
  from oagi.agent.tasker import TaskerAgent
9
+ from oagi.types import AsyncStepObserver
9
10
 
10
11
  from .default import AsyncDefaultAgent
11
12
  from .protocol import AsyncAgent
@@ -19,6 +20,7 @@ def create_default_agent(
19
20
  model: str = "lux-v1",
20
21
  max_steps: int = 20,
21
22
  temperature: float = 0.1,
23
+ step_observer: AsyncStepObserver | None = None,
22
24
  ) -> AsyncAgent:
23
25
  return AsyncDefaultAgent(
24
26
  api_key=api_key,
@@ -26,6 +28,7 @@ def create_default_agent(
26
28
  model=model,
27
29
  max_steps=max_steps,
28
30
  temperature=temperature,
31
+ step_observer=step_observer,
29
32
  )
30
33
 
31
34
 
@@ -37,6 +40,7 @@ def create_planner_agent(
37
40
  max_steps: int = 30,
38
41
  temperature: float = 0.0,
39
42
  reflection_interval: int = 20,
43
+ step_observer: AsyncStepObserver | None = None,
40
44
  ) -> AsyncAgent:
41
45
  tasker = TaskerAgent(
42
46
  api_key=api_key,
@@ -45,6 +49,7 @@ def create_planner_agent(
45
49
  max_steps=max_steps,
46
50
  temperature=temperature,
47
51
  reflection_interval=reflection_interval,
52
+ step_observer=step_observer,
48
53
  )
49
54
  # tasker.set_task()
50
55
  return tasker
@@ -20,19 +20,28 @@ class Planner:
20
20
  This class provides planning and reflection capabilities using OAGI workers.
21
21
  """
22
22
 
23
- def __init__(self, client: AsyncClient | None = None):
23
+ def __init__(
24
+ self,
25
+ client: AsyncClient | None = None,
26
+ api_key: str | None = None,
27
+ base_url: str | None = None,
28
+ ):
24
29
  """Initialize the planner.
25
30
 
26
31
  Args:
27
32
  client: AsyncClient for OAGI API calls. If None, one will be created when needed.
33
+ api_key: API key for creating internal client
34
+ base_url: Base URL for creating internal client
28
35
  """
29
36
  self.client = client
37
+ self.api_key = api_key
38
+ self.base_url = base_url
30
39
  self._owns_client = False # Track if we created the client
31
40
 
32
41
  def _ensure_client(self) -> AsyncClient:
33
42
  """Ensure we have a client, creating one if needed."""
34
43
  if not self.client:
35
- self.client = AsyncClient()
44
+ self.client = AsyncClient(api_key=self.api_key, base_url=self.base_url)
36
45
  self._owns_client = True
37
46
  return self.client
38
47
 
@@ -10,8 +10,8 @@ import logging
10
10
  from datetime import datetime
11
11
  from typing import Any
12
12
 
13
- from oagi import AsyncTask
14
- from oagi.types import AsyncActionHandler, AsyncImageProvider
13
+ from oagi import AsyncActor
14
+ from oagi.types import AsyncActionHandler, AsyncImageProvider, AsyncStepObserver
15
15
 
16
16
  from ..protocol import AsyncAgent
17
17
  from .memory import PlannerMemory
@@ -35,13 +35,14 @@ class TaskeeAgent(AsyncAgent):
35
35
  self,
36
36
  api_key: str | None = None,
37
37
  base_url: str | None = None,
38
- model: str = "lux-v1",
39
- max_steps_per_subtask: int = 10,
40
- reflection_interval: int = 20,
41
- temperature: float = 0.0,
38
+ model: str = "lux-actor-1",
39
+ max_steps_per_subtask: int = 20,
40
+ reflection_interval: int = 4,
41
+ temperature: float = 0.5,
42
42
  planner: Planner | None = None,
43
43
  external_memory: PlannerMemory | None = None,
44
44
  todo_index: int | None = None,
45
+ step_observer: AsyncStepObserver | None = None,
45
46
  ):
46
47
  """Initialize the taskee agent.
47
48
 
@@ -55,6 +56,7 @@ class TaskeeAgent(AsyncAgent):
55
56
  planner: Planner for planning and reflection
56
57
  external_memory: External memory from parent agent
57
58
  todo_index: Index of the todo being executed
59
+ step_observer: Optional observer for step tracking
58
60
  """
59
61
  self.api_key = api_key
60
62
  self.base_url = base_url
@@ -62,12 +64,13 @@ class TaskeeAgent(AsyncAgent):
62
64
  self.max_steps_per_subtask = max_steps_per_subtask
63
65
  self.reflection_interval = reflection_interval
64
66
  self.temperature = temperature
65
- self.planner = planner or Planner()
67
+ self.planner = planner or Planner(api_key=api_key, base_url=base_url)
66
68
  self.external_memory = external_memory
67
69
  self.todo_index = todo_index
70
+ self.step_observer = step_observer
68
71
 
69
72
  # Internal state
70
- self.task: AsyncTask | None = None
73
+ self.actor: AsyncActor | None = None
71
74
  self.current_todo: str = ""
72
75
  self.current_instruction: str = ""
73
76
  self.actions: list[Action] = []
@@ -135,10 +138,10 @@ class TaskeeAgent(AsyncAgent):
135
138
  )
136
139
  return False
137
140
  finally:
138
- # Clean up task
139
- if self.task:
140
- await self.task.close()
141
- self.task = None
141
+ # Clean up actor
142
+ if self.actor:
143
+ await self.actor.close()
144
+ self.actor = None
142
145
 
143
146
  async def _initial_plan(self, image_provider: AsyncImageProvider) -> None:
144
147
  """Generate initial plan for the todo.
@@ -199,17 +202,17 @@ class TaskeeAgent(AsyncAgent):
199
202
  logger.info(f"Executing subtask with max {max_steps} steps")
200
203
 
201
204
  # Use async with for automatic resource management
202
- async with AsyncTask(
205
+ async with AsyncActor(
203
206
  api_key=self.api_key,
204
207
  base_url=self.base_url,
205
208
  model=self.model,
206
209
  temperature=self.temperature,
207
- ) as task:
210
+ ) as actor:
208
211
  # Store reference for potential cleanup in execute's finally block
209
- self.task = task
212
+ self.actor = actor
210
213
 
211
- # Initialize task with current instruction
212
- await task.init_task(self.current_instruction)
214
+ # Initialize actor with current instruction
215
+ await actor.init_task(self.current_instruction)
213
216
 
214
217
  steps_taken = 0
215
218
  for step_num in range(max_steps):
@@ -218,7 +221,7 @@ class TaskeeAgent(AsyncAgent):
218
221
 
219
222
  # Get next step from OAGI
220
223
  try:
221
- step = await task.step(screenshot, instruction=None)
224
+ step = await actor.step(screenshot, instruction=None)
222
225
  except Exception as e:
223
226
  logger.error(f"Error getting step from OAGI: {e}")
224
227
  self._record_action(
@@ -228,8 +231,30 @@ class TaskeeAgent(AsyncAgent):
228
231
  )
229
232
  break
230
233
 
234
+ # Log reasoning
235
+ if step.reason:
236
+ logger.info(f"Step {self.total_actions + 1}: {step.reason}")
237
+
238
+ # Notify observer if present
239
+ if self.step_observer:
240
+ await self.step_observer.on_step(
241
+ self.total_actions + 1, step.reason, step.actions
242
+ )
243
+
231
244
  # Record OAGI actions
232
245
  if step.actions:
246
+ # Log actions with details
247
+ logger.info(f"Actions ({len(step.actions)}):")
248
+ for action in step.actions:
249
+ count_suffix = (
250
+ f" x{action.count}"
251
+ if action.count and action.count > 1
252
+ else ""
253
+ )
254
+ logger.info(
255
+ f" [{action.type.value}] {action.argument}{count_suffix}"
256
+ )
257
+
233
258
  for action in step.actions:
234
259
  self._record_action(
235
260
  action_type=action.type.lower(),
@@ -255,9 +280,9 @@ class TaskeeAgent(AsyncAgent):
255
280
  logger.info("Reflection interval reached")
256
281
  break
257
282
 
258
- # Task will be automatically closed by async with context manager
283
+ # Actor will be automatically closed by async with context manager
259
284
  # Clear reference after context manager closes it
260
- self.task = None
285
+ self.actor = None
261
286
  return steps_taken
262
287
 
263
288
  async def _reflect_and_decide(self, image_provider: AsyncImageProvider) -> bool:
@@ -9,7 +9,7 @@
9
9
  import logging
10
10
  from typing import Any
11
11
 
12
- from oagi.types import AsyncActionHandler, AsyncImageProvider
12
+ from oagi.types import AsyncActionHandler, AsyncImageProvider, AsyncStepObserver
13
13
 
14
14
  from ..protocol import AsyncAgent
15
15
  from .memory import PlannerMemory
@@ -34,11 +34,12 @@ class TaskerAgent(AsyncAgent):
34
34
  self,
35
35
  api_key: str | None = None,
36
36
  base_url: str | None = None,
37
- model: str = "lux-v1",
38
- max_steps: int = 30,
39
- temperature: float = 0.0,
40
- reflection_interval: int = 20,
37
+ model: str = "lux-actor-1",
38
+ max_steps: int = 60,
39
+ temperature: float = 0.5,
40
+ reflection_interval: int = 4,
41
41
  planner: Planner | None = None,
42
+ step_observer: AsyncStepObserver | None = None,
42
43
  ):
43
44
  """Initialize the tasker agent.
44
45
 
@@ -50,6 +51,7 @@ class TaskerAgent(AsyncAgent):
50
51
  temperature: Sampling temperature
51
52
  reflection_interval: Actions before reflection
52
53
  planner: Planner for planning and reflection
54
+ step_observer: Optional observer for step tracking
53
55
  """
54
56
  self.api_key = api_key
55
57
  self.base_url = base_url
@@ -57,7 +59,8 @@ class TaskerAgent(AsyncAgent):
57
59
  self.max_steps = max_steps
58
60
  self.temperature = temperature
59
61
  self.reflection_interval = reflection_interval
60
- self.planner = planner or Planner()
62
+ self.planner = planner or Planner(api_key=api_key, base_url=base_url)
63
+ self.step_observer = step_observer
61
64
 
62
65
  # Memory for tracking workflow
63
66
  self.memory = PlannerMemory()
@@ -168,12 +171,13 @@ class TaskerAgent(AsyncAgent):
168
171
  api_key=self.api_key,
169
172
  base_url=self.base_url,
170
173
  model=self.model,
171
- max_steps_per_subtask=10, # Smaller steps per subtask
174
+ max_steps_per_subtask=20, # Smaller steps per subtask
172
175
  reflection_interval=self.reflection_interval,
173
176
  temperature=self.temperature,
174
177
  planner=self.planner,
175
178
  external_memory=self.memory, # Share memory with child
176
179
  todo_index=todo_index, # Pass the todo index
180
+ step_observer=self.step_observer, # Pass step observer
177
181
  )
178
182
 
179
183
  self.current_todo_index = todo_index
oagi/cli/agent.py CHANGED
@@ -10,9 +10,14 @@ import argparse
10
10
  import asyncio
11
11
  import os
12
12
  import sys
13
+ import time
14
+ import traceback
13
15
 
14
16
  from oagi.exceptions import check_optional_dependency
15
17
 
18
+ from .display import display_step_table
19
+ from .tracking import StepTracker
20
+
16
21
 
17
22
  def add_agent_parser(subparsers: argparse._SubParsersAction) -> None:
18
23
  agent_parser = subparsers.add_parser("agent", help="Agent execution commands")
@@ -25,12 +30,14 @@ def add_agent_parser(subparsers: argparse._SubParsersAction) -> None:
25
30
  run_parser.add_argument(
26
31
  "instruction", type=str, help="Task instruction for the agent to execute"
27
32
  )
28
- run_parser.add_argument("--model", type=str, help="Model to use (default: lux-v1)")
29
33
  run_parser.add_argument(
30
- "--max-steps", type=int, help="Maximum number of steps (default: 30)"
34
+ "--model", type=str, help="Model to use (default: lux-actor-1)"
35
+ )
36
+ run_parser.add_argument(
37
+ "--max-steps", type=int, help="Maximum number of steps (default: 20)"
31
38
  )
32
39
  run_parser.add_argument(
33
- "--temperature", type=float, help="Sampling temperature (default: 0.0)"
40
+ "--temperature", type=float, help="Sampling temperature (default: 0.5)"
34
41
  )
35
42
  run_parser.add_argument(
36
43
  "--mode",
@@ -74,12 +81,15 @@ def run_agent(args: argparse.Namespace) -> None:
74
81
  base_url = args.oagi_base_url or os.getenv(
75
82
  "OAGI_BASE_URL", "https://api.agiopen.org"
76
83
  )
77
- model = args.model or "lux-v1"
78
- max_steps = args.max_steps or 30
79
- temperature = args.temperature if args.temperature is not None else 0.0
84
+ model = args.model or "lux-actor-1"
85
+ max_steps = args.max_steps or 20
86
+ temperature = args.temperature if args.temperature is not None else 0.5
80
87
  mode = args.mode or "actor"
81
88
 
82
- # Create agent
89
+ # Create step tracker
90
+ step_tracker = StepTracker()
91
+
92
+ # Create agent with step tracker
83
93
  agent = create_agent(
84
94
  mode=mode,
85
95
  api_key=api_key,
@@ -87,6 +97,7 @@ def run_agent(args: argparse.Namespace) -> None:
87
97
  model=model,
88
98
  max_steps=max_steps,
89
99
  temperature=temperature,
100
+ step_observer=step_tracker,
90
101
  )
91
102
 
92
103
  # Create handlers
@@ -99,7 +110,10 @@ def run_agent(args: argparse.Namespace) -> None:
99
110
  )
100
111
  print("-" * 60)
101
112
 
102
- # Run agent
113
+ start_time = time.time()
114
+ success = False
115
+ interrupted = False
116
+
103
117
  try:
104
118
  success = asyncio.run(
105
119
  agent.execute(
@@ -108,18 +122,22 @@ def run_agent(args: argparse.Namespace) -> None:
108
122
  image_provider=image_provider,
109
123
  )
110
124
  )
111
-
112
- print("-" * 60)
113
- if success:
114
- print("Task completed successfully!")
115
- sys.exit(0)
116
- else:
117
- print("Task failed or reached max steps without completion.")
118
- sys.exit(1)
119
-
120
125
  except KeyboardInterrupt:
121
- print("\nAgent execution interrupted.")
122
- sys.exit(130)
126
+ print("\nAgent execution interrupted by user (Ctrl+C)")
127
+ interrupted = True
123
128
  except Exception as e:
124
- print(f"Error during agent execution: {e}", file=sys.stderr)
125
- sys.exit(1)
129
+ print(f"\nError during agent execution: {e}", file=sys.stderr)
130
+ traceback.print_exc()
131
+ finally:
132
+ duration = time.time() - start_time
133
+
134
+ if step_tracker.steps:
135
+ print("\n" + "=" * 60)
136
+ display_step_table(step_tracker.steps, success, duration)
137
+ else:
138
+ print("\nNo steps were executed.")
139
+
140
+ if interrupted:
141
+ sys.exit(130)
142
+ elif not success:
143
+ sys.exit(1)
oagi/cli/display.py ADDED
@@ -0,0 +1,56 @@
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 rich.console import Console
10
+ from rich.table import Table
11
+
12
+ from .tracking import StepData
13
+
14
+
15
+ def display_step_table(
16
+ steps: list[StepData], success: bool, duration: float | None = None
17
+ ):
18
+ console = Console()
19
+
20
+ table = Table(title="Agent Execution Summary", show_lines=True)
21
+ table.add_column("Step", justify="center", style="cyan", width=6)
22
+ table.add_column("Reasoning", style="white")
23
+ table.add_column("Actions", style="yellow", width=35)
24
+ table.add_column("Status", justify="center", width=8)
25
+
26
+ for step in steps:
27
+ reason = step.reasoning or "N/A"
28
+
29
+ actions_display = []
30
+ for action in step.actions[:3]:
31
+ arg = action.argument[:20] if action.argument else ""
32
+ actions_display.append(f"{action.type.value}({arg})")
33
+
34
+ actions_str = ", ".join(actions_display)
35
+ if len(step.actions) > 3:
36
+ actions_str += f" (+{len(step.actions) - 3} more)"
37
+
38
+ status_display = "✓" if step.status == "complete" else "→"
39
+
40
+ table.add_row(
41
+ str(step.step_num),
42
+ reason,
43
+ actions_str,
44
+ status_display,
45
+ )
46
+
47
+ console.print(table)
48
+
49
+ status_text = "Success" if success else "Failed/Interrupted"
50
+ console.print(
51
+ f"\nTotal Steps: {len(steps)} | Status: {status_text}",
52
+ style="bold",
53
+ )
54
+
55
+ if duration:
56
+ console.print(f"Duration: {duration:.2f}s")
oagi/cli/tracking.py ADDED
@@ -0,0 +1,45 @@
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 dataclasses import dataclass
10
+ from datetime import datetime
11
+
12
+ from oagi.types import Action
13
+
14
+
15
+ @dataclass
16
+ class StepData:
17
+ step_num: int
18
+ timestamp: datetime
19
+ reasoning: str | None
20
+ actions: list[Action]
21
+ action_count: int
22
+ status: str
23
+
24
+
25
+ class StepTracker:
26
+ """Tracks agent step execution by implementing AsyncStepObserver protocol."""
27
+
28
+ def __init__(self):
29
+ self.steps: list[StepData] = []
30
+
31
+ async def on_step(
32
+ self,
33
+ step_num: int,
34
+ reasoning: str | None,
35
+ actions: list[Action],
36
+ ) -> None:
37
+ step_data = StepData(
38
+ step_num=step_num,
39
+ timestamp=datetime.now(),
40
+ reasoning=reasoning,
41
+ actions=actions,
42
+ action_count=len(actions),
43
+ status="running",
44
+ )
45
+ self.steps.append(step_data)
oagi/cli/utils.py CHANGED
@@ -15,10 +15,16 @@ from oagi.exceptions import check_optional_dependency
15
15
 
16
16
 
17
17
  def get_sdk_version() -> str:
18
- try:
19
- return get_version("oagi")
20
- except Exception:
21
- return "unknown"
18
+ # Try oagi-core first (development install), then oagi (metapackage install)
19
+ for package_name in ["oagi-core", "oagi"]:
20
+ try:
21
+ version = get_version(package_name)
22
+ # Skip if version is 0.0.0 (placeholder/invalid)
23
+ if version != "0.0.0":
24
+ return version
25
+ except Exception:
26
+ continue
27
+ return "unknown"
22
28
 
23
29
 
24
30
  def display_version() -> None:
@@ -50,6 +56,7 @@ def display_config() -> None:
50
56
  config_vars = {
51
57
  "OAGI_API_KEY": os.getenv("OAGI_API_KEY", ""),
52
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"),
53
60
  "OAGI_LOG_LEVEL": os.getenv("OAGI_LOG_LEVEL", "INFO"),
54
61
  "OAGI_SERVER_HOST": os.getenv("OAGI_SERVER_HOST", "0.0.0.0"),
55
62
  "OAGI_SERVER_PORT": os.getenv("OAGI_SERVER_PORT", "8000"),
oagi/client/base.py CHANGED
@@ -41,16 +41,12 @@ class BaseClient(Generic[HttpClientT]):
41
41
 
42
42
  def __init__(self, base_url: str | None = None, api_key: str | None = None):
43
43
  # Get from environment if not provided
44
- self.base_url = base_url or os.getenv("OAGI_BASE_URL")
44
+ self.base_url = (
45
+ base_url or os.getenv("OAGI_BASE_URL") or "https://api.agiopen.org"
46
+ )
45
47
  self.api_key = api_key or os.getenv("OAGI_API_KEY")
46
48
 
47
49
  # Validate required configuration
48
- if not self.base_url:
49
- raise ConfigurationError(
50
- "OAGI base URL must be provided either as 'base_url' parameter or "
51
- "OAGI_BASE_URL environment variable"
52
- )
53
-
54
50
  if not self.api_key:
55
51
  raise ConfigurationError(
56
52
  "OAGI API key must be provided either as 'api_key' parameter or "