oagi-core 0.11.0__py3-none-any.whl → 0.12.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
oagi/__init__.py CHANGED
@@ -8,6 +8,7 @@
8
8
  import importlib
9
9
  from typing import TYPE_CHECKING
10
10
 
11
+ from oagi.actor import Actor, AsyncActor, AsyncShortTask, AsyncTask, ShortTask, Task
11
12
  from oagi.client import AsyncClient, SyncClient
12
13
  from oagi.exceptions import (
13
14
  APIError,
@@ -22,13 +23,11 @@ from oagi.exceptions import (
22
23
  ValidationError,
23
24
  check_optional_dependency,
24
25
  )
25
- from oagi.task import Actor, AsyncActor, AsyncShortTask, AsyncTask, ShortTask, Task
26
26
  from oagi.types import ImageConfig
27
27
  from oagi.types.models import (
28
28
  ErrorDetail,
29
29
  ErrorResponse,
30
30
  GenerateResponse,
31
- LLMResponse,
32
31
  UploadFileResponse,
33
32
  )
34
33
 
@@ -116,7 +115,6 @@ __all__ = [
116
115
  # Configuration
117
116
  "ImageConfig",
118
117
  # Response models
119
- "LLMResponse",
120
118
  "GenerateResponse",
121
119
  "UploadFileResponse",
122
120
  "ErrorResponse",
oagi/actor/__init__.py ADDED
@@ -0,0 +1,21 @@
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 AsyncActor, AsyncTask
10
+ from .async_short import AsyncShortTask
11
+ from .short import ShortTask
12
+ from .sync import Actor, Task
13
+
14
+ __all__ = [
15
+ "Actor",
16
+ "AsyncActor",
17
+ "Task", # Deprecated: Use Actor instead
18
+ "AsyncTask", # Deprecated: Use AsyncActor instead
19
+ "ShortTask", # Deprecated
20
+ "AsyncShortTask", # Deprecated
21
+ ]
@@ -10,9 +10,12 @@ import warnings
10
10
 
11
11
  from ..client import AsyncClient
12
12
  from ..constants import DEFAULT_MAX_STEPS, MODEL_ACTOR
13
+ from ..logging import get_logger
13
14
  from ..types import URL, Image, Step
14
15
  from .base import BaseActor
15
16
 
17
+ logger = get_logger("actor.async")
18
+
16
19
 
17
20
  class AsyncActor(BaseActor):
18
21
  """Async base class for task automation with the OAGI API."""
@@ -51,20 +54,33 @@ class AsyncActor(BaseActor):
51
54
  """Send screenshot to the server and get the next actions.
52
55
 
53
56
  Args:
54
- screenshot: Screenshot as Image object or raw bytes
55
- instruction: Optional additional instruction for this step
57
+ screenshot: Screenshot as Image object, URL string, or raw bytes
58
+ instruction: Optional additional instruction for this step (currently unused)
56
59
  temperature: Sampling temperature for this step (overrides task default if provided)
57
60
 
58
61
  Returns:
59
62
  Step: The actions and reasoning for this step
60
63
  """
61
- kwargs = self._prepare_step(
62
- screenshot, instruction, temperature, prefix="async "
63
- )
64
+ self._validate_and_increment_step()
65
+ self._log_step_execution(prefix="async ")
64
66
 
65
67
  try:
66
- response = await self.client.create_message(**kwargs)
67
- return self._build_step_response(response, prefix="Async ")
68
+ screenshot_url = await self._ensure_screenshot_url_async(
69
+ screenshot, self.client
70
+ )
71
+ self._add_user_message_to_history(screenshot_url, self._build_step_prompt())
72
+
73
+ step, raw_output, usage = await self.client.chat_completion(
74
+ model=self.model,
75
+ messages=self.message_history,
76
+ temperature=self._get_temperature(temperature),
77
+ task_id=self.task_id,
78
+ )
79
+
80
+ self._add_assistant_message_to_history(raw_output)
81
+ self._log_step_completion(step, prefix="Async ")
82
+ return step
83
+
68
84
  except Exception as e:
69
85
  self._handle_step_error(e, prefix="async ")
70
86
 
@@ -14,7 +14,7 @@ from ..types import AsyncActionHandler, AsyncImageProvider
14
14
  from .async_ import AsyncActor
15
15
  from .base import BaseAutoMode
16
16
 
17
- logger = get_logger("async_short_task")
17
+ logger = get_logger("async_short_actor")
18
18
 
19
19
 
20
20
  class AsyncShortTask(AsyncActor, BaseAutoMode):
oagi/actor/base.py ADDED
@@ -0,0 +1,222 @@
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 uuid import uuid4
10
+
11
+ from ..constants import (
12
+ DEFAULT_MAX_STEPS,
13
+ MAX_STEPS_ACTOR,
14
+ MAX_STEPS_THINKER,
15
+ MODEL_THINKER,
16
+ )
17
+ from ..logging import get_logger
18
+ from ..types import URL, Image, Step
19
+ from ..utils.prompt_builder import build_prompt
20
+
21
+ logger = get_logger("actor.base")
22
+
23
+
24
+ class BaseActor:
25
+ """Base class with shared task management logic for sync/async actors."""
26
+
27
+ def __init__(
28
+ self,
29
+ api_key: str | None,
30
+ base_url: str | None,
31
+ model: str,
32
+ temperature: float | None,
33
+ ):
34
+ self.task_id: str = uuid4().hex # Client-side generated UUID
35
+ self.task_description: str | None = None
36
+ self.model = model
37
+ self.temperature = temperature
38
+ self.message_history: list = [] # OpenAI-compatible message history
39
+ self.max_steps: int = DEFAULT_MAX_STEPS
40
+ self.current_step: int = 0 # Current step counter
41
+ # Client will be set by subclasses
42
+ self.api_key: str | None = None
43
+ self.base_url: str | None = None
44
+
45
+ def _validate_max_steps(self, max_steps: int) -> int:
46
+ """Validate and cap max_steps based on model type.
47
+
48
+ Args:
49
+ max_steps: Requested maximum number of steps
50
+
51
+ Returns:
52
+ Validated max_steps (capped to model limit if exceeded)
53
+ """
54
+ limit = MAX_STEPS_THINKER if self.model == MODEL_THINKER else MAX_STEPS_ACTOR
55
+ if max_steps > limit:
56
+ logger.warning(
57
+ f"max_steps ({max_steps}) exceeds limit for model '{self.model}'. "
58
+ f"Capping to {limit}."
59
+ )
60
+ return limit
61
+ return max_steps
62
+
63
+ def _prepare_init_task(
64
+ self,
65
+ task_desc: str,
66
+ max_steps: int,
67
+ ):
68
+ """Prepare task initialization.
69
+
70
+ Args:
71
+ task_desc: Task description
72
+ max_steps: Maximum number of steps
73
+ """
74
+ self.task_id = uuid4().hex
75
+ self.task_description = task_desc
76
+ self.message_history = []
77
+ self.max_steps = self._validate_max_steps(max_steps)
78
+ self.current_step = 0
79
+ logger.info(f"Task initialized: '{task_desc}' (max_steps: {self.max_steps})")
80
+
81
+ def _validate_and_increment_step(self):
82
+ if not self.task_description:
83
+ raise ValueError("Task description must be set. Call init_task() first.")
84
+ if self.current_step >= self.max_steps:
85
+ raise ValueError(
86
+ f"Max steps limit ({self.max_steps}) reached. "
87
+ "Call init_task() to start a new task."
88
+ )
89
+ self.current_step += 1
90
+
91
+ def _get_temperature(self, temperature: float | None) -> float | None:
92
+ return temperature if temperature is not None else self.temperature
93
+
94
+ def _prepare_screenshot(self, screenshot: Image | bytes) -> bytes:
95
+ if isinstance(screenshot, Image):
96
+ return screenshot.read()
97
+ return screenshot
98
+
99
+ def _get_screenshot_url(self, screenshot: Image | URL | bytes) -> str | None:
100
+ """Get screenshot URL if it's a string, otherwise return None."""
101
+ if isinstance(screenshot, str):
102
+ return screenshot
103
+ return None
104
+
105
+ def _ensure_screenshot_url_sync(
106
+ self, screenshot: Image | URL | bytes, client
107
+ ) -> str:
108
+ """Get screenshot URL, uploading to S3 if needed (sync version).
109
+
110
+ Args:
111
+ screenshot: Screenshot as Image object, URL string, or raw bytes
112
+ client: SyncClient instance for S3 upload
113
+
114
+ Returns:
115
+ Screenshot URL (either direct or from S3 upload)
116
+ """
117
+ screenshot_url = self._get_screenshot_url(screenshot)
118
+ if screenshot_url is None:
119
+ screenshot_bytes = self._prepare_screenshot(screenshot)
120
+ upload_response = client.put_s3_presigned_url(screenshot_bytes)
121
+ screenshot_url = upload_response.download_url
122
+ return screenshot_url
123
+
124
+ async def _ensure_screenshot_url_async(
125
+ self, screenshot: Image | URL | bytes, client
126
+ ) -> str:
127
+ """Get screenshot URL, uploading to S3 if needed (async version).
128
+
129
+ Args:
130
+ screenshot: Screenshot as Image object, URL string, or raw bytes
131
+ client: AsyncClient instance for S3 upload
132
+
133
+ Returns:
134
+ Screenshot URL (either direct or from S3 upload)
135
+ """
136
+ screenshot_url = self._get_screenshot_url(screenshot)
137
+ if screenshot_url is None:
138
+ screenshot_bytes = self._prepare_screenshot(screenshot)
139
+ upload_response = await client.put_s3_presigned_url(screenshot_bytes)
140
+ screenshot_url = upload_response.download_url
141
+ return screenshot_url
142
+
143
+ def _add_user_message_to_history(
144
+ self, screenshot_url: str, prompt: str | None = None
145
+ ):
146
+ """Add user message with screenshot to message history.
147
+
148
+ Args:
149
+ screenshot_url: URL of the screenshot
150
+ prompt: Optional prompt text (for first message only)
151
+ """
152
+ content = []
153
+ if prompt:
154
+ content.append({"type": "text", "text": prompt})
155
+ content.append({"type": "image_url", "image_url": {"url": screenshot_url}})
156
+
157
+ self.message_history.append(
158
+ {
159
+ "role": "user",
160
+ "content": content,
161
+ }
162
+ )
163
+
164
+ def _add_assistant_message_to_history(self, raw_output: str):
165
+ """Add assistant response to message history.
166
+
167
+ Args:
168
+ raw_output: Raw model output string
169
+ """
170
+ if raw_output:
171
+ self.message_history.append(
172
+ {
173
+ "role": "assistant",
174
+ "content": raw_output,
175
+ }
176
+ )
177
+
178
+ def _build_step_prompt(self) -> str | None:
179
+ """Build prompt for first message only."""
180
+ if len(self.message_history) == 0:
181
+ return build_prompt(self.task_description)
182
+ return None
183
+
184
+ def _log_step_completion(self, step: Step, prefix: str = "") -> None:
185
+ """Log step completion status."""
186
+ if step.stop:
187
+ logger.info(f"{prefix}Task completed.")
188
+ else:
189
+ logger.debug(f"{prefix}Step completed with {len(step.actions)} actions")
190
+
191
+ def _log_step_execution(self, prefix: str = ""):
192
+ logger.debug(f"Executing {prefix}step for task: '{self.task_description}'")
193
+
194
+ def _handle_step_error(self, error: Exception, prefix: str = ""):
195
+ logger.error(f"Error during {prefix}step execution: {error}")
196
+ raise
197
+
198
+
199
+ class BaseAutoMode:
200
+ """Base class with shared auto_mode logic for ShortTask implementations."""
201
+
202
+ def _log_auto_mode_start(self, task_desc: str, max_steps: int, prefix: str = ""):
203
+ logger.info(
204
+ f"Starting {prefix}auto mode for task: '{task_desc}' (max_steps: {max_steps})"
205
+ )
206
+
207
+ def _log_auto_mode_step(self, step_num: int, max_steps: int, prefix: str = ""):
208
+ logger.debug(f"{prefix.capitalize()}auto mode step {step_num}/{max_steps}")
209
+
210
+ def _log_auto_mode_actions(self, action_count: int, prefix: str = ""):
211
+ verb = "asynchronously" if "async" in prefix else ""
212
+ logger.debug(f"Executing {action_count} actions {verb}".strip())
213
+
214
+ def _log_auto_mode_completion(self, steps: int, prefix: str = ""):
215
+ logger.info(
216
+ f"{prefix.capitalize()}auto mode completed successfully after {steps} steps"
217
+ )
218
+
219
+ def _log_auto_mode_max_steps(self, max_steps: int, prefix: str = ""):
220
+ logger.warning(
221
+ f"{prefix.capitalize()}auto mode reached max steps ({max_steps}) without completion"
222
+ )
@@ -14,7 +14,7 @@ from ..types import ActionHandler, ImageProvider
14
14
  from .base import BaseAutoMode
15
15
  from .sync import Actor
16
16
 
17
- logger = get_logger("short_task")
17
+ logger = get_logger("short_actor")
18
18
 
19
19
 
20
20
  class ShortTask(Actor, BaseAutoMode):
@@ -10,9 +10,12 @@ import warnings
10
10
 
11
11
  from ..client import SyncClient
12
12
  from ..constants import DEFAULT_MAX_STEPS, MODEL_ACTOR
13
+ from ..logging import get_logger
13
14
  from ..types import URL, Image, Step
14
15
  from .base import BaseActor
15
16
 
17
+ logger = get_logger("actor.sync")
18
+
16
19
 
17
20
  class Actor(BaseActor):
18
21
  """Base class for task automation with the OAGI API."""
@@ -51,18 +54,31 @@ class Actor(BaseActor):
51
54
  """Send screenshot to the server and get the next actions.
52
55
 
53
56
  Args:
54
- screenshot: Screenshot as Image object or raw bytes
55
- instruction: Optional additional instruction for this step
57
+ screenshot: Screenshot as Image object, URL string, or raw bytes
58
+ instruction: Optional additional instruction for this step (currently unused)
56
59
  temperature: Sampling temperature for this step (overrides task default if provided)
57
60
 
58
61
  Returns:
59
62
  Step: The actions and reasoning for this step
60
63
  """
61
- kwargs = self._prepare_step(screenshot, instruction, temperature)
64
+ self._validate_and_increment_step()
65
+ self._log_step_execution()
62
66
 
63
67
  try:
64
- response = self.client.create_message(**kwargs)
65
- return self._build_step_response(response)
68
+ screenshot_url = self._ensure_screenshot_url_sync(screenshot, self.client)
69
+ self._add_user_message_to_history(screenshot_url, self._build_step_prompt())
70
+
71
+ step, raw_output, usage = self.client.chat_completion(
72
+ model=self.model,
73
+ messages=self.message_history,
74
+ temperature=self._get_temperature(temperature),
75
+ task_id=self.task_id,
76
+ )
77
+
78
+ self._add_assistant_message_to_history(raw_output)
79
+ self._log_step_completion(step)
80
+ return step
81
+
66
82
  except Exception as e:
67
83
  self._handle_step_error(e)
68
84
 
oagi/agent/default.py CHANGED
@@ -16,7 +16,7 @@ from ..constants import (
16
16
  DEFAULT_TEMPERATURE,
17
17
  MODEL_ACTOR,
18
18
  )
19
- from ..handler import reset_handler
19
+ from ..handler.utils import reset_handler
20
20
  from ..types import (
21
21
  ActionEvent,
22
22
  AsyncActionHandler,
@@ -93,6 +93,7 @@ class AsyncDefaultAgent:
93
93
  step_num=step_num,
94
94
  image=_serialize_image(image),
95
95
  step=step,
96
+ task_id=self.actor.task_id,
96
97
  )
97
98
  )
98
99
 
@@ -98,6 +98,8 @@ def export_to_markdown(
98
98
  case StepEvent():
99
99
  lines.append(f"\n## Step {event.step_num}\n")
100
100
  lines.append(f"**Time:** {timestamp}\n")
101
+ if event.task_id:
102
+ lines.append(f"**Task ID:** `{event.task_id}`\n")
101
103
 
102
104
  if isinstance(event.image, bytes):
103
105
  if images_dir:
@@ -159,6 +161,8 @@ def export_to_markdown(
159
161
  }
160
162
  phase_title = phase_titles.get(event.phase, event.phase.capitalize())
161
163
  lines.append(f"\n### {phase_title} ({timestamp})\n")
164
+ if event.request_id:
165
+ lines.append(f"**Request ID:** `{event.request_id}`\n")
162
166
 
163
167
  if event.image:
164
168
  if isinstance(event.image, bytes):
@@ -227,6 +231,7 @@ def _convert_events_for_html(events: list[ObserverEvent]) -> list[dict]:
227
231
  "reason": event.step.reason,
228
232
  "actions": actions_list,
229
233
  "stop": event.step.stop,
234
+ "task_id": event.task_id,
230
235
  }
231
236
  )
232
237
 
@@ -275,6 +280,7 @@ def _convert_events_for_html(events: list[ObserverEvent]) -> list[dict]:
275
280
  "image": image_data,
276
281
  "reasoning": event.reasoning,
277
282
  "result": event.result,
283
+ "request_id": event.request_id,
278
284
  }
279
285
  )
280
286
 
@@ -46,6 +46,19 @@
46
46
  font-size: 0.9em;
47
47
  }
48
48
 
49
+ .task-id, .request-id {
50
+ color: #666;
51
+ font-size: 0.9em;
52
+ margin-left: 10px;
53
+ }
54
+
55
+ .task-id code, .request-id code {
56
+ background: #e9ecef;
57
+ padding: 2px 6px;
58
+ border-radius: 3px;
59
+ font-family: monospace;
60
+ }
61
+
49
62
  .screenshot-container {
50
63
  position: relative;
51
64
  display: inline-block;
@@ -339,6 +352,9 @@
339
352
  html += '<div class="step">';
340
353
  html += `<h2>Step ${event.step_num}</h2>`;
341
354
  html += `<span class="timestamp">${timestamp}</span>`;
355
+ if (event.task_id) {
356
+ html += ` <span class="task-id">Task ID: <code>${event.task_id}</code></span>`;
357
+ }
342
358
 
343
359
  if (event.image) {
344
360
  const actionsJson = JSON.stringify(event.action_coords || []).replace(/"/g, '&quot;');
@@ -409,6 +425,9 @@
409
425
  html += '<div class="plan">';
410
426
  html += `<h3>${phaseTitle}</h3>`;
411
427
  html += `<span class="timestamp">${timestamp}</span>`;
428
+ if (event.request_id) {
429
+ html += ` <span class="request-id">Request ID: <code>${event.request_id}</code></span>`;
430
+ }
412
431
 
413
432
  if (event.image) {
414
433
  html += '<div class="screenshot-container">';
@@ -122,7 +122,7 @@ class Planner:
122
122
  screenshot: Image | URL | None = None,
123
123
  memory: PlannerMemory | None = None,
124
124
  todo_index: int | None = None,
125
- ) -> PlannerOutput:
125
+ ) -> tuple[PlannerOutput, str | None]:
126
126
  """Generate initial plan for a todo.
127
127
 
128
128
  Args:
@@ -133,7 +133,7 @@ class Planner:
133
133
  todo_index: Optional todo index for formatting internal context
134
134
 
135
135
  Returns:
136
- PlannerOutput with instruction, reasoning, and optional subtodos
136
+ Tuple of (PlannerOutput, request_id) where request_id is from API response
137
137
  """
138
138
  # Ensure we have a client
139
139
  client = self._ensure_client()
@@ -170,8 +170,8 @@ class Planner:
170
170
  current_screenshot=screenshot_uuid,
171
171
  )
172
172
 
173
- # Parse response
174
- return self._parse_planner_output(response.response)
173
+ # Parse response and return with request_id
174
+ return self._parse_planner_output(response.response), response.request_id
175
175
 
176
176
  async def reflect(
177
177
  self,
@@ -182,7 +182,7 @@ class Planner:
182
182
  todo_index: int | None = None,
183
183
  current_instruction: str | None = None,
184
184
  reflection_interval: int = DEFAULT_REFLECTION_INTERVAL,
185
- ) -> ReflectionOutput:
185
+ ) -> tuple[ReflectionOutput, str | None]:
186
186
  """Reflect on recent actions and progress.
187
187
 
188
188
  Args:
@@ -195,7 +195,7 @@ class Planner:
195
195
  reflection_interval: Window size for recent actions/screenshots
196
196
 
197
197
  Returns:
198
- ReflectionOutput with continuation decision and reasoning
198
+ Tuple of (ReflectionOutput, request_id) where request_id is from API response
199
199
  """
200
200
  # Ensure we have a client
201
201
  client = self._ensure_client()
@@ -260,8 +260,8 @@ class Planner:
260
260
  prior_notes=prior_notes,
261
261
  )
262
262
 
263
- # Parse response
264
- return self._parse_reflection_output(response.response)
263
+ # Parse response and return with request_id
264
+ return self._parse_reflection_output(response.response), response.request_id
265
265
 
266
266
  async def summarize(
267
267
  self,
@@ -269,7 +269,7 @@ class Planner:
269
269
  context: dict[str, Any],
270
270
  memory: PlannerMemory | None = None,
271
271
  todo_index: int | None = None,
272
- ) -> str:
272
+ ) -> tuple[str, str | None]:
273
273
  """Generate execution summary.
274
274
 
275
275
  Args:
@@ -279,7 +279,7 @@ class Planner:
279
279
  todo_index: Optional todo index for formatting internal context
280
280
 
281
281
  Returns:
282
- String summary of the execution
282
+ Tuple of (summary string, request_id) where request_id is from API response
283
283
  """
284
284
  # Ensure we have a client
285
285
  client = self._ensure_client()
@@ -314,9 +314,11 @@ class Planner:
314
314
  # Parse response and extract summary
315
315
  try:
316
316
  result = json.loads(response.response)
317
- return result.get("task_summary", response.response)
317
+ summary = result.get("task_summary", response.response)
318
318
  except json.JSONDecodeError:
319
- return response.response
319
+ summary = response.response
320
+
321
+ return summary, response.request_id
320
322
 
321
323
  def _format_execution_notes(self, context: dict[str, Any]) -> str:
322
324
  """Format execution history notes.
@@ -19,7 +19,7 @@ from oagi.constants import (
19
19
  DEFAULT_TEMPERATURE,
20
20
  MODEL_ACTOR,
21
21
  )
22
- from oagi.handler import reset_handler
22
+ from oagi.handler.utils import reset_handler
23
23
  from oagi.types import (
24
24
  URL,
25
25
  ActionEvent,
@@ -200,7 +200,7 @@ class TaskeeAgent(AsyncAgent):
200
200
  context = self._get_context()
201
201
 
202
202
  # Generate plan using LLM planner
203
- plan_output = await self.planner.initial_plan(
203
+ plan_output, request_id = await self.planner.initial_plan(
204
204
  self.current_todo,
205
205
  context,
206
206
  screenshot,
@@ -224,6 +224,7 @@ class TaskeeAgent(AsyncAgent):
224
224
  image=_serialize_image(screenshot),
225
225
  reasoning=plan_output.reasoning,
226
226
  result=plan_output.instruction,
227
+ request_id=request_id,
227
228
  )
228
229
  )
229
230
 
@@ -309,6 +310,7 @@ class TaskeeAgent(AsyncAgent):
309
310
  step_num=self.total_actions + 1,
310
311
  image=_serialize_image(screenshot),
311
312
  step=step,
313
+ task_id=self.actor.task_id,
312
314
  )
313
315
  )
314
316
 
@@ -393,7 +395,7 @@ class TaskeeAgent(AsyncAgent):
393
395
  recent_actions = self.actions[-self.since_reflection :]
394
396
 
395
397
  # Reflect using planner
396
- reflection = await self.planner.reflect(
398
+ reflection, request_id = await self.planner.reflect(
397
399
  recent_actions,
398
400
  context,
399
401
  screenshot,
@@ -424,6 +426,7 @@ class TaskeeAgent(AsyncAgent):
424
426
  image=_serialize_image(screenshot),
425
427
  reasoning=reflection.reasoning,
426
428
  result=decision,
429
+ request_id=request_id,
427
430
  )
428
431
  )
429
432
 
@@ -456,7 +459,7 @@ class TaskeeAgent(AsyncAgent):
456
459
  context = self._get_context()
457
460
  context["current_todo"] = self.current_todo
458
461
 
459
- summary = await self.planner.summarize(
462
+ summary, request_id = await self.planner.summarize(
460
463
  self.actions,
461
464
  context,
462
465
  memory=self.external_memory,
@@ -478,6 +481,7 @@ class TaskeeAgent(AsyncAgent):
478
481
  image=None,
479
482
  reasoning=summary,
480
483
  result=None,
484
+ request_id=request_id,
481
485
  )
482
486
  )
483
487
 
@@ -16,7 +16,7 @@ from oagi.constants import (
16
16
  DEFAULT_TEMPERATURE,
17
17
  MODEL_ACTOR,
18
18
  )
19
- from oagi.handler import reset_handler
19
+ from oagi.handler.utils import reset_handler
20
20
  from oagi.types import AsyncActionHandler, AsyncImageProvider, AsyncObserver, SplitEvent
21
21
 
22
22
  from ..protocol import AsyncAgent