oagi-core 0.9.1__py3-none-any.whl → 0.10.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.
Files changed (42) hide show
  1. oagi/__init__.py +76 -33
  2. oagi/agent/__init__.py +2 -0
  3. oagi/agent/default.py +45 -12
  4. oagi/agent/factories.py +22 -3
  5. oagi/agent/observer/__init__.py +38 -0
  6. oagi/agent/observer/agent_observer.py +99 -0
  7. oagi/agent/observer/events.py +28 -0
  8. oagi/agent/observer/exporters.py +445 -0
  9. oagi/agent/observer/protocol.py +12 -0
  10. oagi/agent/registry.py +2 -2
  11. oagi/agent/tasker/models.py +1 -0
  12. oagi/agent/tasker/planner.py +41 -9
  13. oagi/agent/tasker/taskee_agent.py +178 -86
  14. oagi/agent/tasker/tasker_agent.py +25 -14
  15. oagi/cli/agent.py +50 -9
  16. oagi/cli/tracking.py +27 -17
  17. oagi/cli/utils.py +11 -4
  18. oagi/client/base.py +3 -7
  19. oagi/handler/_macos.py +55 -0
  20. oagi/handler/pyautogui_action_handler.py +19 -2
  21. oagi/server/agent_wrappers.py +5 -5
  22. oagi/server/config.py +3 -3
  23. oagi/server/models.py +2 -2
  24. oagi/server/session_store.py +2 -2
  25. oagi/server/socketio_server.py +1 -1
  26. oagi/task/async_.py +13 -34
  27. oagi/task/async_short.py +2 -2
  28. oagi/task/base.py +41 -7
  29. oagi/task/short.py +2 -2
  30. oagi/task/sync.py +11 -34
  31. oagi/types/__init__.py +24 -4
  32. oagi/types/async_image_provider.py +3 -2
  33. oagi/types/image_provider.py +3 -2
  34. oagi/types/step_observer.py +75 -16
  35. oagi/types/url.py +3 -0
  36. {oagi_core-0.9.1.dist-info → oagi_core-0.10.0.dist-info}/METADATA +38 -25
  37. oagi_core-0.10.0.dist-info/RECORD +68 -0
  38. oagi/types/url_image.py +0 -47
  39. oagi_core-0.9.1.dist-info/RECORD +0 -62
  40. {oagi_core-0.9.1.dist-info → oagi_core-0.10.0.dist-info}/WHEEL +0 -0
  41. {oagi_core-0.9.1.dist-info → oagi_core-0.10.0.dist-info}/entry_points.txt +0 -0
  42. {oagi_core-0.9.1.dist-info → oagi_core-0.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -11,7 +11,16 @@ from datetime import datetime
11
11
  from typing import Any
12
12
 
13
13
  from oagi import AsyncActor
14
- from oagi.types import AsyncActionHandler, AsyncImageProvider, AsyncStepObserver
14
+ from oagi.types import (
15
+ URL,
16
+ ActionEvent,
17
+ AsyncActionHandler,
18
+ AsyncImageProvider,
19
+ AsyncObserver,
20
+ Image,
21
+ PlanEvent,
22
+ StepEvent,
23
+ )
15
24
 
16
25
  from ..protocol import AsyncAgent
17
26
  from .memory import PlannerMemory
@@ -21,6 +30,13 @@ from .planner import Planner
21
30
  logger = logging.getLogger(__name__)
22
31
 
23
32
 
33
+ def _serialize_image(image: Image | str) -> bytes | str:
34
+ """Convert an image to bytes or keep URL as string."""
35
+ if isinstance(image, str):
36
+ return image
37
+ return image.read()
38
+
39
+
24
40
  class TaskeeAgent(AsyncAgent):
25
41
  """Executes a single todo with planning and reflection capabilities.
26
42
 
@@ -35,14 +51,14 @@ class TaskeeAgent(AsyncAgent):
35
51
  self,
36
52
  api_key: str | None = None,
37
53
  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,
54
+ model: str = "lux-actor-1",
55
+ max_steps: int = 20,
56
+ reflection_interval: int = 4,
57
+ temperature: float = 0.5,
42
58
  planner: Planner | None = None,
43
59
  external_memory: PlannerMemory | None = None,
44
60
  todo_index: int | None = None,
45
- step_observer: AsyncStepObserver | None = None,
61
+ step_observer: AsyncObserver | None = None,
46
62
  ):
47
63
  """Initialize the taskee agent.
48
64
 
@@ -50,7 +66,7 @@ class TaskeeAgent(AsyncAgent):
50
66
  api_key: OAGI API key
51
67
  base_url: OAGI API base URL
52
68
  model: Model to use for vision tasks
53
- max_steps_per_subtask: Maximum steps before reinitializing task
69
+ max_steps: Maximum steps before reinitializing task
54
70
  reflection_interval: Number of actions before triggering reflection
55
71
  temperature: Sampling temperature
56
72
  planner: Planner for planning and reflection
@@ -61,10 +77,10 @@ class TaskeeAgent(AsyncAgent):
61
77
  self.api_key = api_key
62
78
  self.base_url = base_url
63
79
  self.model = model
64
- self.max_steps_per_subtask = max_steps_per_subtask
80
+ self.max_steps = max_steps
65
81
  self.reflection_interval = reflection_interval
66
82
  self.temperature = temperature
67
- self.planner = planner or Planner()
83
+ self.planner = planner or Planner(api_key=api_key, base_url=base_url)
68
84
  self.external_memory = external_memory
69
85
  self.todo_index = todo_index
70
86
  self.step_observer = step_observer
@@ -101,17 +117,27 @@ class TaskeeAgent(AsyncAgent):
101
117
  self.success = False
102
118
 
103
119
  try:
120
+ self.actor = AsyncActor(
121
+ api_key=self.api_key,
122
+ base_url=self.base_url,
123
+ model=self.model,
124
+ temperature=self.temperature,
125
+ )
104
126
  # Initial planning
105
127
  await self._initial_plan(image_provider)
106
128
 
129
+ # Initialize the actor with the task
130
+ await self.actor.init_task(
131
+ self.current_instruction, max_steps=self.max_steps
132
+ )
133
+
107
134
  # Main execution loop with reinitializations
108
- max_total_steps = self.max_steps_per_subtask * 3 # Allow up to 3 reinits
109
- remaining_steps = max_total_steps
135
+ remaining_steps = self.max_steps
110
136
 
111
137
  while remaining_steps > 0 and not self.success:
112
138
  # Execute subtask
113
139
  steps_taken = await self._execute_subtask(
114
- min(self.max_steps_per_subtask, remaining_steps),
140
+ min(self.max_steps, remaining_steps),
115
141
  action_handler,
116
142
  image_provider,
117
143
  )
@@ -174,6 +200,17 @@ class TaskeeAgent(AsyncAgent):
174
200
  result=plan_output.instruction,
175
201
  )
176
202
 
203
+ # Emit plan event
204
+ if self.step_observer:
205
+ await self.step_observer.on_event(
206
+ PlanEvent(
207
+ phase="initial",
208
+ image=_serialize_image(screenshot),
209
+ reasoning=plan_output.reasoning,
210
+ result=plan_output.instruction,
211
+ )
212
+ )
213
+
177
214
  # Set current instruction
178
215
  self.current_instruction = plan_output.instruction
179
216
  logger.info(f"Initial instruction: {self.current_instruction}")
@@ -201,89 +238,108 @@ class TaskeeAgent(AsyncAgent):
201
238
  """
202
239
  logger.info(f"Executing subtask with max {max_steps} steps")
203
240
 
204
- # Use async with for automatic resource management
205
- async with AsyncActor(
206
- api_key=self.api_key,
207
- base_url=self.base_url,
208
- model=self.model,
209
- temperature=self.temperature,
210
- ) as actor:
211
- # Store reference for potential cleanup in execute's finally block
212
- self.actor = actor
213
-
214
- # Initialize actor with current instruction
215
- await actor.init_task(self.current_instruction)
216
-
217
- steps_taken = 0
218
- for step_num in range(max_steps):
219
- # Capture screenshot
220
- screenshot = await image_provider()
221
-
222
- # Get next step from OAGI
223
- try:
224
- step = await actor.step(screenshot, instruction=None)
225
- except Exception as e:
226
- logger.error(f"Error getting step from OAGI: {e}")
227
- self._record_action(
228
- action_type="error",
229
- target="oagi_step",
230
- reasoning=str(e),
241
+ steps_taken = 0
242
+ client = self.planner._ensure_client()
243
+
244
+ for step_num in range(max_steps):
245
+ # Capture screenshot
246
+ screenshot = await image_provider()
247
+
248
+ # Upload screenshot first to get UUID (avoids re-upload in actor.step)
249
+ try:
250
+ upload_response = await client.put_s3_presigned_url(screenshot)
251
+ screenshot_uuid = upload_response.uuid
252
+ screenshot_url = upload_response.download_url
253
+ except Exception as e:
254
+ logger.error(f"Error uploading screenshot: {e}")
255
+ self._record_action(
256
+ action_type="error",
257
+ target="screenshot_upload",
258
+ reasoning=str(e),
259
+ )
260
+ break
261
+
262
+ # Get next step from OAGI using URL (avoids re-upload)
263
+ try:
264
+ step = await self.actor.step(URL(screenshot_url), instruction=None)
265
+ except Exception as e:
266
+ logger.error(f"Error getting step from OAGI: {e}")
267
+ self._record_action(
268
+ action_type="error",
269
+ target="oagi_step",
270
+ reasoning=str(e),
271
+ screenshot_uuid=screenshot_uuid,
272
+ )
273
+ break
274
+
275
+ # Log reasoning
276
+ if step.reason:
277
+ logger.info(f"Step {self.total_actions + 1}: {step.reason}")
278
+
279
+ # Emit step event
280
+ if self.step_observer:
281
+ await self.step_observer.on_event(
282
+ StepEvent(
283
+ step_num=self.total_actions + 1,
284
+ image=_serialize_image(screenshot),
285
+ step=step,
231
286
  )
232
- break
233
-
234
- # Log reasoning
235
- if step.reason:
236
- logger.info(f"Step {self.total_actions + 1}: {step.reason}")
237
-
238
- # Record OAGI actions
239
- if step.actions:
240
- # Log actions with details
241
- logger.info(f"Actions ({len(step.actions)}):")
242
- for action in step.actions:
243
- count_suffix = (
244
- f" x{action.count}"
245
- if action.count and action.count > 1
246
- else ""
247
- )
248
- logger.info(
249
- f" [{action.type.value}] {action.argument}{count_suffix}"
250
- )
287
+ )
251
288
 
252
- for action in step.actions:
253
- self._record_action(
254
- action_type=action.type.lower(),
255
- target=action.argument,
256
- reasoning=step.reason,
257
- )
289
+ # Record OAGI actions
290
+ if step.actions:
291
+ # Log actions with details
292
+ logger.info(f"Actions ({len(step.actions)}):")
293
+ for action in step.actions:
294
+ count_suffix = (
295
+ f" x{action.count}" if action.count and action.count > 1 else ""
296
+ )
297
+ logger.info(
298
+ f" [{action.type.value}] {action.argument}{count_suffix}"
299
+ )
258
300
 
259
- # Notify observer if present
260
- if self.step_observer:
261
- await self.step_observer.on_step(
262
- self.total_actions + 1, step.reason, step.actions
263
- )
301
+ for action in step.actions:
302
+ self._record_action(
303
+ action_type=action.type.lower(),
304
+ target=action.argument,
305
+ reasoning=step.reason,
306
+ screenshot_uuid=screenshot_uuid,
307
+ )
264
308
 
265
- # Execute actions
309
+ # Execute actions
310
+ error = None
311
+ try:
266
312
  await action_handler(step.actions)
267
- self.total_actions += len(step.actions)
268
- self.since_reflection += len(step.actions)
313
+ except Exception as e:
314
+ error = str(e)
315
+ raise
316
+
317
+ # Emit action event
318
+ if self.step_observer:
319
+ await self.step_observer.on_event(
320
+ ActionEvent(
321
+ step_num=self.total_actions + 1,
322
+ actions=step.actions,
323
+ error=error,
324
+ )
325
+ )
269
326
 
270
- steps_taken += 1
327
+ self.total_actions += len(step.actions)
328
+ self.since_reflection += len(step.actions)
271
329
 
272
- # Check if task is complete
273
- if step.stop:
274
- logger.info("OAGI signaled task completion")
275
- self.success = True
276
- break
330
+ steps_taken += 1
277
331
 
278
- # Check if reflection is needed
279
- if self.since_reflection >= self.reflection_interval:
280
- logger.info("Reflection interval reached")
281
- break
332
+ # Check if task is complete
333
+ if step.stop:
334
+ logger.info("OAGI signaled task completion")
335
+ break
282
336
 
283
- # Actor will be automatically closed by async with context manager
284
- # Clear reference after context manager closes it
285
- self.actor = None
286
- return steps_taken
337
+ # Check if reflection is needed
338
+ if self.since_reflection >= self.reflection_interval:
339
+ logger.info("Reflection interval reached")
340
+ break
341
+
342
+ return steps_taken
287
343
 
288
344
  async def _reflect_and_decide(self, image_provider: AsyncImageProvider) -> bool:
289
345
  """Reflect on progress and decide whether to continue.
@@ -314,6 +370,7 @@ class TaskeeAgent(AsyncAgent):
314
370
  memory=self.external_memory,
315
371
  todo_index=self.todo_index,
316
372
  current_instruction=self.current_instruction,
373
+ reflection_interval=self.reflection_interval,
317
374
  )
318
375
 
319
376
  # Record reflection
@@ -324,6 +381,22 @@ class TaskeeAgent(AsyncAgent):
324
381
  result=("continue" if reflection.continue_current else "pivot"),
325
382
  )
326
383
 
384
+ # Emit plan event for reflection
385
+ if self.step_observer:
386
+ decision = (
387
+ "success"
388
+ if reflection.success_assessment
389
+ else ("continue" if reflection.continue_current else "pivot")
390
+ )
391
+ await self.step_observer.on_event(
392
+ PlanEvent(
393
+ phase="reflection",
394
+ image=_serialize_image(screenshot),
395
+ reasoning=reflection.reasoning,
396
+ result=decision,
397
+ )
398
+ )
399
+
327
400
  # Update success assessment
328
401
  if reflection.success_assessment:
329
402
  self.success = True
@@ -337,6 +410,11 @@ class TaskeeAgent(AsyncAgent):
337
410
  if not reflection.continue_current and reflection.new_instruction:
338
411
  logger.info(f"Pivoting to new instruction: {reflection.new_instruction}")
339
412
  self.current_instruction = reflection.new_instruction
413
+
414
+ # the following line create a new actor
415
+ await self.actor.init_task(
416
+ self.current_instruction, max_steps=self.max_steps
417
+ )
340
418
  return True
341
419
 
342
420
  return reflection.continue_current
@@ -362,6 +440,17 @@ class TaskeeAgent(AsyncAgent):
362
440
  reasoning=summary,
363
441
  )
364
442
 
443
+ # Emit plan event for summary
444
+ if self.step_observer:
445
+ await self.step_observer.on_event(
446
+ PlanEvent(
447
+ phase="summary",
448
+ image=None,
449
+ reasoning=summary,
450
+ result=None,
451
+ )
452
+ )
453
+
365
454
  logger.info(f"Execution summary: {summary}")
366
455
 
367
456
  def _record_action(
@@ -370,6 +459,7 @@ class TaskeeAgent(AsyncAgent):
370
459
  target: str | None,
371
460
  reasoning: str | None = None,
372
461
  result: str | None = None,
462
+ screenshot_uuid: str | None = None,
373
463
  ) -> None:
374
464
  """Record an action to the history.
375
465
 
@@ -378,6 +468,7 @@ class TaskeeAgent(AsyncAgent):
378
468
  target: Target of the action
379
469
  reasoning: Reasoning for the action
380
470
  result: Result of the action
471
+ screenshot_uuid: UUID of uploaded screenshot for this action
381
472
  """
382
473
  action = Action(
383
474
  timestamp=datetime.now().isoformat(),
@@ -386,6 +477,7 @@ class TaskeeAgent(AsyncAgent):
386
477
  reasoning=reasoning,
387
478
  result=result,
388
479
  details={},
480
+ screenshot_uuid=screenshot_uuid,
389
481
  )
390
482
  self.actions.append(action)
391
483
 
@@ -9,7 +9,7 @@
9
9
  import logging
10
10
  from typing import Any
11
11
 
12
- from oagi.types import AsyncActionHandler, AsyncImageProvider, AsyncStepObserver
12
+ from oagi.types import AsyncActionHandler, AsyncImageProvider, AsyncObserver, SplitEvent
13
13
 
14
14
  from ..protocol import AsyncAgent
15
15
  from .memory import PlannerMemory
@@ -34,12 +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
+ step_observer: AsyncObserver | None = None,
43
43
  ):
44
44
  """Initialize the tasker agent.
45
45
 
@@ -59,7 +59,7 @@ class TaskerAgent(AsyncAgent):
59
59
  self.max_steps = max_steps
60
60
  self.temperature = temperature
61
61
  self.reflection_interval = reflection_interval
62
- self.planner = planner or Planner()
62
+ self.planner = planner or Planner(api_key=api_key, base_url=base_url)
63
63
  self.step_observer = step_observer
64
64
 
65
65
  # Memory for tracking workflow
@@ -100,18 +100,13 @@ class TaskerAgent(AsyncAgent):
100
100
  or a failure occurs.
101
101
 
102
102
  Args:
103
- instruction: Overall instruction (can be same as task description)
103
+ instruction: Not used in TaskerAgent
104
104
  action_handler: Handler for executing actions
105
105
  image_provider: Provider for capturing screenshots
106
106
 
107
107
  Returns:
108
108
  True if all todos completed successfully, False otherwise
109
109
  """
110
- # If task not set, use instruction as task description
111
- if not self.memory.task_description:
112
- logger.warning("Task not set, using instruction as task description")
113
- self.memory.task_description = instruction
114
-
115
110
  overall_success = True
116
111
 
117
112
  # Execute todos until none remain
@@ -127,6 +122,14 @@ class TaskerAgent(AsyncAgent):
127
122
  todo, todo_index = todo_info
128
123
  logger.info(f"Executing todo {todo_index}: {todo.description}")
129
124
 
125
+ # Emit split event at the start of todo
126
+ if self.step_observer:
127
+ await self.step_observer.on_event(
128
+ SplitEvent(
129
+ label=f"Start of todo {todo_index + 1}: {todo.description}"
130
+ )
131
+ )
132
+
130
133
  # Execute the todo
131
134
  success = await self._execute_todo(
132
135
  todo_index,
@@ -134,6 +137,14 @@ class TaskerAgent(AsyncAgent):
134
137
  image_provider,
135
138
  )
136
139
 
140
+ # Emit split event after each todo
141
+ if self.step_observer:
142
+ await self.step_observer.on_event(
143
+ SplitEvent(
144
+ label=f"End of todo {todo_index + 1}: {todo.description}"
145
+ )
146
+ )
147
+
137
148
  if not success:
138
149
  logger.warning(f"Todo {todo_index} failed")
139
150
  overall_success = False
@@ -171,7 +182,7 @@ class TaskerAgent(AsyncAgent):
171
182
  api_key=self.api_key,
172
183
  base_url=self.base_url,
173
184
  model=self.model,
174
- max_steps_per_subtask=10, # Smaller steps per subtask
185
+ max_steps=self.max_steps, # Smaller steps per subtask
175
186
  reflection_interval=self.reflection_interval,
176
187
  temperature=self.temperature,
177
188
  planner=self.planner,
oagi/cli/agent.py CHANGED
@@ -13,6 +13,7 @@ import sys
13
13
  import time
14
14
  import traceback
15
15
 
16
+ from oagi.agent.observer import AsyncAgentObserver
16
17
  from oagi.exceptions import check_optional_dependency
17
18
 
18
19
  from .display import display_step_table
@@ -30,12 +31,14 @@ def add_agent_parser(subparsers: argparse._SubParsersAction) -> None:
30
31
  run_parser.add_argument(
31
32
  "instruction", type=str, help="Task instruction for the agent to execute"
32
33
  )
33
- run_parser.add_argument("--model", type=str, help="Model to use (default: lux-v1)")
34
34
  run_parser.add_argument(
35
- "--max-steps", type=int, help="Maximum number of steps (default: 30)"
35
+ "--model", type=str, help="Model to use (default: lux-actor-1)"
36
36
  )
37
37
  run_parser.add_argument(
38
- "--temperature", type=float, help="Sampling temperature (default: 0.0)"
38
+ "--max-steps", type=int, help="Maximum number of steps (default: 20)"
39
+ )
40
+ run_parser.add_argument(
41
+ "--temperature", type=float, help="Sampling temperature (default: 0.5)"
39
42
  )
40
43
  run_parser.add_argument(
41
44
  "--mode",
@@ -51,6 +54,17 @@ def add_agent_parser(subparsers: argparse._SubParsersAction) -> None:
51
54
  type=str,
52
55
  help="OAGI base URL (default: https://api.agiopen.org, or OAGI_BASE_URL env var)",
53
56
  )
57
+ run_parser.add_argument(
58
+ "--export",
59
+ type=str,
60
+ choices=["markdown", "html", "json"],
61
+ help="Export execution history to file (markdown, html, or json)",
62
+ )
63
+ run_parser.add_argument(
64
+ "--export-file",
65
+ type=str,
66
+ help="Output file path for export (default: execution_report.[md|html|json])",
67
+ )
54
68
 
55
69
 
56
70
  def handle_agent_command(args: argparse.Namespace) -> None:
@@ -79,15 +93,27 @@ def run_agent(args: argparse.Namespace) -> None:
79
93
  base_url = args.oagi_base_url or os.getenv(
80
94
  "OAGI_BASE_URL", "https://api.agiopen.org"
81
95
  )
82
- model = args.model or "lux-v1"
83
- max_steps = args.max_steps or 30
84
- temperature = args.temperature if args.temperature is not None else 0.0
96
+ model = args.model or "lux-actor-1"
97
+ max_steps = args.max_steps or 20
98
+ temperature = args.temperature if args.temperature is not None else 0.5
85
99
  mode = args.mode or "actor"
100
+ export_format = args.export
101
+ export_file = args.export_file
86
102
 
87
- # Create step tracker
103
+ # Create observers
88
104
  step_tracker = StepTracker()
105
+ agent_observer = AsyncAgentObserver() if export_format else None
106
+
107
+ # Use a combined observer that forwards to both
108
+ class CombinedObserver:
109
+ async def on_event(self, event):
110
+ await step_tracker.on_event(event)
111
+ if agent_observer:
112
+ await agent_observer.on_event(event)
89
113
 
90
- # Create agent with step tracker
114
+ observer = CombinedObserver()
115
+
116
+ # Create agent with observer
91
117
  agent = create_agent(
92
118
  mode=mode,
93
119
  api_key=api_key,
@@ -95,7 +121,7 @@ def run_agent(args: argparse.Namespace) -> None:
95
121
  model=model,
96
122
  max_steps=max_steps,
97
123
  temperature=temperature,
98
- step_observer=step_tracker,
124
+ step_observer=observer,
99
125
  )
100
126
 
101
127
  # Create handlers
@@ -135,6 +161,21 @@ def run_agent(args: argparse.Namespace) -> None:
135
161
  else:
136
162
  print("\nNo steps were executed.")
137
163
 
164
+ # Export if requested
165
+ if export_format and agent_observer:
166
+ # Determine output file path
167
+ if export_file:
168
+ output_path = export_file
169
+ else:
170
+ ext_map = {"markdown": "md", "html": "html", "json": "json"}
171
+ output_path = f"execution_report.{ext_map[export_format]}"
172
+
173
+ try:
174
+ agent_observer.export(export_format, output_path)
175
+ print(f"\nExecution history exported to: {output_path}")
176
+ except Exception as e:
177
+ print(f"\nError exporting execution history: {e}", file=sys.stderr)
178
+
138
179
  if interrupted:
139
180
  sys.exit(130)
140
181
  elif not success:
oagi/cli/tracking.py CHANGED
@@ -9,7 +9,7 @@
9
9
  from dataclasses import dataclass
10
10
  from datetime import datetime
11
11
 
12
- from oagi.types import Action
12
+ from oagi.types import Action, ActionEvent, ObserverEvent, StepEvent
13
13
 
14
14
 
15
15
  @dataclass
@@ -23,23 +23,33 @@ class StepData:
23
23
 
24
24
 
25
25
  class StepTracker:
26
- """Tracks agent step execution by implementing AsyncStepObserver protocol."""
26
+ """Tracks agent step execution by implementing AsyncObserver protocol."""
27
27
 
28
28
  def __init__(self):
29
29
  self.steps: list[StepData] = []
30
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)
31
+ async def on_event(self, event: ObserverEvent) -> None:
32
+ """Handle observer events.
33
+
34
+ Args:
35
+ event: The observer event to handle.
36
+ """
37
+ match event:
38
+ case StepEvent():
39
+ step_data = StepData(
40
+ step_num=event.step_num,
41
+ timestamp=event.timestamp,
42
+ reasoning=event.step.reason,
43
+ actions=event.step.actions,
44
+ action_count=len(event.step.actions),
45
+ status="running",
46
+ )
47
+ self.steps.append(step_data)
48
+ case ActionEvent():
49
+ # Update status of corresponding step
50
+ for step in self.steps:
51
+ if step.step_num == event.step_num:
52
+ step.status = "error" if event.error else "completed"
53
+ break
54
+ case _:
55
+ pass
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"),