oagi-core 0.10.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.
Files changed (68) hide show
  1. oagi/__init__.py +148 -0
  2. oagi/agent/__init__.py +33 -0
  3. oagi/agent/default.py +124 -0
  4. oagi/agent/factories.py +74 -0
  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/protocol.py +55 -0
  11. oagi/agent/registry.py +155 -0
  12. oagi/agent/tasker/__init__.py +33 -0
  13. oagi/agent/tasker/memory.py +160 -0
  14. oagi/agent/tasker/models.py +77 -0
  15. oagi/agent/tasker/planner.py +408 -0
  16. oagi/agent/tasker/taskee_agent.py +512 -0
  17. oagi/agent/tasker/tasker_agent.py +324 -0
  18. oagi/cli/__init__.py +11 -0
  19. oagi/cli/agent.py +281 -0
  20. oagi/cli/display.py +56 -0
  21. oagi/cli/main.py +77 -0
  22. oagi/cli/server.py +94 -0
  23. oagi/cli/tracking.py +55 -0
  24. oagi/cli/utils.py +89 -0
  25. oagi/client/__init__.py +12 -0
  26. oagi/client/async_.py +290 -0
  27. oagi/client/base.py +457 -0
  28. oagi/client/sync.py +293 -0
  29. oagi/exceptions.py +118 -0
  30. oagi/handler/__init__.py +24 -0
  31. oagi/handler/_macos.py +55 -0
  32. oagi/handler/async_pyautogui_action_handler.py +44 -0
  33. oagi/handler/async_screenshot_maker.py +47 -0
  34. oagi/handler/pil_image.py +102 -0
  35. oagi/handler/pyautogui_action_handler.py +291 -0
  36. oagi/handler/screenshot_maker.py +41 -0
  37. oagi/logging.py +55 -0
  38. oagi/server/__init__.py +13 -0
  39. oagi/server/agent_wrappers.py +98 -0
  40. oagi/server/config.py +46 -0
  41. oagi/server/main.py +157 -0
  42. oagi/server/models.py +98 -0
  43. oagi/server/session_store.py +116 -0
  44. oagi/server/socketio_server.py +405 -0
  45. oagi/task/__init__.py +21 -0
  46. oagi/task/async_.py +101 -0
  47. oagi/task/async_short.py +76 -0
  48. oagi/task/base.py +157 -0
  49. oagi/task/short.py +76 -0
  50. oagi/task/sync.py +99 -0
  51. oagi/types/__init__.py +50 -0
  52. oagi/types/action_handler.py +30 -0
  53. oagi/types/async_action_handler.py +30 -0
  54. oagi/types/async_image_provider.py +38 -0
  55. oagi/types/image.py +17 -0
  56. oagi/types/image_provider.py +35 -0
  57. oagi/types/models/__init__.py +32 -0
  58. oagi/types/models/action.py +33 -0
  59. oagi/types/models/client.py +68 -0
  60. oagi/types/models/image_config.py +47 -0
  61. oagi/types/models/step.py +17 -0
  62. oagi/types/step_observer.py +93 -0
  63. oagi/types/url.py +3 -0
  64. oagi_core-0.10.1.dist-info/METADATA +245 -0
  65. oagi_core-0.10.1.dist-info/RECORD +68 -0
  66. oagi_core-0.10.1.dist-info/WHEEL +4 -0
  67. oagi_core-0.10.1.dist-info/entry_points.txt +2 -0
  68. oagi_core-0.10.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,445 @@
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) OpenAGI Foundation
3
+ # All rights reserved.
4
+ #
5
+ # This file is part of the official API project.
6
+ # Licensed under the MIT License.
7
+ # -----------------------------------------------------------------------------
8
+
9
+ import base64
10
+ import json
11
+ from pathlib import Path
12
+
13
+ from ...types import (
14
+ ActionEvent,
15
+ ImageEvent,
16
+ LogEvent,
17
+ ObserverEvent,
18
+ PlanEvent,
19
+ SplitEvent,
20
+ StepEvent,
21
+ )
22
+
23
+
24
+ def export_to_markdown(
25
+ events: list[ObserverEvent],
26
+ path: str,
27
+ images_dir: str | None = None,
28
+ ) -> None:
29
+ """Export events to a Markdown file.
30
+
31
+ Args:
32
+ events: List of events to export.
33
+ path: Path to the output Markdown file.
34
+ images_dir: Directory to save images. If None, images are not saved.
35
+ """
36
+ output_path = Path(path)
37
+ output_path.parent.mkdir(parents=True, exist_ok=True)
38
+
39
+ if images_dir:
40
+ images_path = Path(images_dir)
41
+ images_path.mkdir(parents=True, exist_ok=True)
42
+
43
+ lines: list[str] = ["# Agent Execution Report\n"]
44
+ image_counter = 0
45
+
46
+ for event in events:
47
+ timestamp = event.timestamp.strftime("%H:%M:%S")
48
+
49
+ match event:
50
+ case StepEvent():
51
+ lines.append(f"\n## Step {event.step_num}\n")
52
+ lines.append(f"**Time:** {timestamp}\n")
53
+
54
+ if isinstance(event.image, bytes):
55
+ if images_dir:
56
+ image_counter += 1
57
+ image_filename = f"step_{event.step_num}.png"
58
+ image_path = Path(images_dir) / image_filename
59
+ image_path.write_bytes(event.image)
60
+ rel_path = Path(images_dir).name / Path(image_filename)
61
+ lines.append(f"\n![Step {event.step_num}]({rel_path})\n")
62
+ else:
63
+ lines.append(
64
+ f"\n*[Screenshot captured - {len(event.image)} bytes]*\n"
65
+ )
66
+ elif isinstance(event.image, str):
67
+ lines.append(f"\n**Screenshot URL:** {event.image}\n")
68
+
69
+ if event.step.reason:
70
+ lines.append(f"\n**Reasoning:**\n> {event.step.reason}\n")
71
+
72
+ if event.step.actions:
73
+ lines.append("\n**Planned Actions:**\n")
74
+ for action in event.step.actions:
75
+ count_str = (
76
+ f" (x{action.count})"
77
+ if action.count and action.count > 1
78
+ else ""
79
+ )
80
+ lines.append(
81
+ f"- `{action.type.value}`: {action.argument}{count_str}\n"
82
+ )
83
+
84
+ if event.step.stop:
85
+ lines.append("\n**Status:** Task Complete\n")
86
+
87
+ case ActionEvent():
88
+ lines.append(f"\n### Actions Executed ({timestamp})\n")
89
+ if event.error:
90
+ lines.append(f"\n**Error:** {event.error}\n")
91
+ else:
92
+ lines.append("\n**Result:** Success\n")
93
+
94
+ case LogEvent():
95
+ lines.append(f"\n> **Log ({timestamp}):** {event.message}\n")
96
+
97
+ case SplitEvent():
98
+ if event.label:
99
+ lines.append(f"\n---\n\n### {event.label}\n")
100
+ else:
101
+ lines.append("\n---\n")
102
+
103
+ case ImageEvent():
104
+ pass
105
+
106
+ case PlanEvent():
107
+ phase_titles = {
108
+ "initial": "Initial Planning",
109
+ "reflection": "Reflection",
110
+ "summary": "Summary",
111
+ }
112
+ phase_title = phase_titles.get(event.phase, event.phase.capitalize())
113
+ lines.append(f"\n### {phase_title} ({timestamp})\n")
114
+
115
+ if event.image:
116
+ if isinstance(event.image, bytes):
117
+ if images_dir:
118
+ image_counter += 1
119
+ image_filename = f"plan_{event.phase}_{image_counter}.png"
120
+ image_path = Path(images_dir) / image_filename
121
+ image_path.write_bytes(event.image)
122
+ rel_path = Path(images_dir).name / Path(image_filename)
123
+ lines.append(f"\n![{phase_title}]({rel_path})\n")
124
+ else:
125
+ lines.append(
126
+ f"\n*[Screenshot captured - {len(event.image)} bytes]*\n"
127
+ )
128
+ elif isinstance(event.image, str):
129
+ lines.append(f"\n**Screenshot URL:** {event.image}\n")
130
+
131
+ if event.reasoning:
132
+ lines.append(f"\n**Reasoning:**\n> {event.reasoning}\n")
133
+
134
+ if event.result:
135
+ lines.append(f"\n**Result:** {event.result}\n")
136
+
137
+ output_path.write_text("".join(lines))
138
+
139
+
140
+ def export_to_html(events: list[ObserverEvent], path: str) -> None:
141
+ """Export events to a self-contained HTML file.
142
+
143
+ Args:
144
+ events: List of events to export.
145
+ path: Path to the output HTML file.
146
+ """
147
+ output_path = Path(path)
148
+ output_path.parent.mkdir(parents=True, exist_ok=True)
149
+
150
+ html_parts: list[str] = [_get_html_header()]
151
+
152
+ for event in events:
153
+ timestamp = event.timestamp.strftime("%H:%M:%S")
154
+
155
+ match event:
156
+ case StepEvent():
157
+ html_parts.append('<div class="step">')
158
+ html_parts.append(f"<h2>Step {event.step_num}</h2>")
159
+ html_parts.append(f'<span class="timestamp">{timestamp}</span>')
160
+
161
+ if isinstance(event.image, bytes):
162
+ b64_image = base64.b64encode(event.image).decode("utf-8")
163
+ html_parts.append(
164
+ f'<img src="data:image/png;base64,{b64_image}" '
165
+ f'alt="Step {event.step_num}" class="screenshot"/>'
166
+ )
167
+ elif isinstance(event.image, str):
168
+ html_parts.append(
169
+ f'<p class="url">Screenshot URL: <a href="{event.image}">{event.image}</a></p>'
170
+ )
171
+
172
+ if event.step.reason:
173
+ html_parts.append('<div class="reasoning">')
174
+ html_parts.append(
175
+ f"<strong>Reasoning:</strong><p>{_escape_html(event.step.reason)}</p>"
176
+ )
177
+ html_parts.append("</div>")
178
+
179
+ if event.step.actions:
180
+ html_parts.append('<div class="actions">')
181
+ html_parts.append("<strong>Planned Actions:</strong><ul>")
182
+ for action in event.step.actions:
183
+ count_str = (
184
+ f" (x{action.count})"
185
+ if action.count and action.count > 1
186
+ else ""
187
+ )
188
+ html_parts.append(
189
+ f"<li><code>{action.type.value}</code>: "
190
+ f"{_escape_html(action.argument)}{count_str}</li>"
191
+ )
192
+ html_parts.append("</ul></div>")
193
+
194
+ if event.step.stop:
195
+ html_parts.append('<div class="complete">Task Complete</div>')
196
+
197
+ html_parts.append("</div>")
198
+
199
+ case ActionEvent():
200
+ html_parts.append('<div class="action-result">')
201
+ html_parts.append(f'<span class="timestamp">{timestamp}</span>')
202
+ if event.error:
203
+ html_parts.append(
204
+ f'<div class="error">Error: {_escape_html(event.error)}</div>'
205
+ )
206
+ else:
207
+ html_parts.append(
208
+ '<div class="success">Actions executed successfully</div>'
209
+ )
210
+ html_parts.append("</div>")
211
+
212
+ case LogEvent():
213
+ html_parts.append('<div class="log">')
214
+ html_parts.append(f'<span class="timestamp">{timestamp}</span>')
215
+ html_parts.append(f"<p>{_escape_html(event.message)}</p>")
216
+ html_parts.append("</div>")
217
+
218
+ case SplitEvent():
219
+ if event.label:
220
+ html_parts.append(
221
+ f'<div class="split"><h3>{_escape_html(event.label)}</h3></div>'
222
+ )
223
+ else:
224
+ html_parts.append('<hr class="split-line"/>')
225
+
226
+ case ImageEvent():
227
+ pass
228
+
229
+ case PlanEvent():
230
+ phase_titles = {
231
+ "initial": "Initial Planning",
232
+ "reflection": "Reflection",
233
+ "summary": "Summary",
234
+ }
235
+ phase_title = phase_titles.get(event.phase, event.phase.capitalize())
236
+ html_parts.append('<div class="plan">')
237
+ html_parts.append(f"<h3>{phase_title}</h3>")
238
+ html_parts.append(f'<span class="timestamp">{timestamp}</span>')
239
+
240
+ if event.image:
241
+ if isinstance(event.image, bytes):
242
+ b64_image = base64.b64encode(event.image).decode("utf-8")
243
+ html_parts.append(
244
+ f'<img src="data:image/png;base64,{b64_image}" '
245
+ f'alt="{phase_title}" class="screenshot"/>'
246
+ )
247
+ elif isinstance(event.image, str):
248
+ html_parts.append(
249
+ f'<p class="url">Screenshot URL: '
250
+ f'<a href="{event.image}">{event.image}</a></p>'
251
+ )
252
+
253
+ if event.reasoning:
254
+ html_parts.append('<div class="reasoning">')
255
+ html_parts.append(
256
+ f"<strong>Reasoning:</strong><p>{_escape_html(event.reasoning)}</p>"
257
+ )
258
+ html_parts.append("</div>")
259
+
260
+ if event.result:
261
+ html_parts.append(
262
+ f'<div class="plan-result"><strong>Result:</strong> '
263
+ f"{_escape_html(event.result)}</div>"
264
+ )
265
+
266
+ html_parts.append("</div>")
267
+
268
+ html_parts.append(_get_html_footer())
269
+ output_path.write_text("".join(html_parts))
270
+
271
+
272
+ def _escape_html(text: str) -> str:
273
+ """Escape HTML special characters."""
274
+ return (
275
+ text.replace("&", "&amp;")
276
+ .replace("<", "&lt;")
277
+ .replace(">", "&gt;")
278
+ .replace('"', "&quot;")
279
+ .replace("'", "&#39;")
280
+ )
281
+
282
+
283
+ def _get_html_header() -> str:
284
+ """Get HTML document header with CSS styles."""
285
+ return """<!DOCTYPE html>
286
+ <html lang="en">
287
+ <head>
288
+ <meta charset="UTF-8">
289
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
290
+ <title>Agent Execution Report</title>
291
+ <style>
292
+ body {
293
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
294
+ max-width: 1200px;
295
+ margin: 0 auto;
296
+ padding: 20px;
297
+ background: #f5f5f5;
298
+ }
299
+ h1 {
300
+ color: #333;
301
+ border-bottom: 2px solid #007bff;
302
+ padding-bottom: 10px;
303
+ }
304
+ .step {
305
+ background: white;
306
+ border-radius: 8px;
307
+ padding: 20px;
308
+ margin: 20px 0;
309
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
310
+ }
311
+ .step h2 {
312
+ margin-top: 0;
313
+ color: #007bff;
314
+ }
315
+ .timestamp {
316
+ color: #666;
317
+ font-size: 0.9em;
318
+ }
319
+ .screenshot {
320
+ max-width: 100%;
321
+ border: 1px solid #ddd;
322
+ border-radius: 4px;
323
+ margin: 10px 0;
324
+ }
325
+ .reasoning {
326
+ background: #f8f9fa;
327
+ padding: 10px;
328
+ border-left: 3px solid #007bff;
329
+ margin: 10px 0;
330
+ }
331
+ .actions {
332
+ margin: 10px 0;
333
+ }
334
+ .actions ul {
335
+ margin: 5px 0;
336
+ padding-left: 20px;
337
+ }
338
+ .actions code {
339
+ background: #e9ecef;
340
+ padding: 2px 6px;
341
+ border-radius: 3px;
342
+ }
343
+ .complete {
344
+ background: #d4edda;
345
+ color: #155724;
346
+ padding: 10px;
347
+ border-radius: 4px;
348
+ margin-top: 10px;
349
+ }
350
+ .action-result {
351
+ padding: 10px;
352
+ margin: 5px 0;
353
+ }
354
+ .success {
355
+ color: #155724;
356
+ }
357
+ .error {
358
+ color: #721c24;
359
+ background: #f8d7da;
360
+ padding: 10px;
361
+ border-radius: 4px;
362
+ }
363
+ .log {
364
+ background: #fff3cd;
365
+ padding: 10px;
366
+ margin: 10px 0;
367
+ border-radius: 4px;
368
+ }
369
+ .split {
370
+ text-align: center;
371
+ margin: 30px 0;
372
+ }
373
+ .split h3 {
374
+ color: #666;
375
+ }
376
+ .split-line {
377
+ border: none;
378
+ border-top: 2px dashed #ccc;
379
+ margin: 30px 0;
380
+ }
381
+ .url {
382
+ word-break: break-all;
383
+ }
384
+ .plan {
385
+ background: #e7f3ff;
386
+ border-radius: 8px;
387
+ padding: 20px;
388
+ margin: 20px 0;
389
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
390
+ }
391
+ .plan h3 {
392
+ margin-top: 0;
393
+ color: #0056b3;
394
+ }
395
+ .plan-result {
396
+ background: #d1ecf1;
397
+ color: #0c5460;
398
+ padding: 10px;
399
+ border-radius: 4px;
400
+ margin-top: 10px;
401
+ }
402
+ </style>
403
+ </head>
404
+ <body>
405
+ <h1>Agent Execution Report</h1>
406
+ """
407
+
408
+
409
+ def _get_html_footer() -> str:
410
+ """Get HTML document footer."""
411
+ return """
412
+ </body>
413
+ </html>
414
+ """
415
+
416
+
417
+ def export_to_json(events: list[ObserverEvent], path: str) -> None:
418
+ """Export events to a JSON file.
419
+
420
+ Args:
421
+ events: List of events to export.
422
+ path: Path to the output JSON file.
423
+ """
424
+ output_path = Path(path)
425
+ output_path.parent.mkdir(parents=True, exist_ok=True)
426
+
427
+ # Convert events to JSON-serializable format
428
+ json_events = []
429
+ for event in events:
430
+ # Handle bytes images before model_dump to avoid UTF-8 decode error
431
+ if isinstance(event, (StepEvent, ImageEvent, PlanEvent)) and isinstance(
432
+ getattr(event, "image", None), bytes
433
+ ):
434
+ # Dump without json mode first, then handle bytes manually
435
+ event_dict = event.model_dump()
436
+ event_dict["image"] = base64.b64encode(event.image).decode("utf-8")
437
+ event_dict["image_encoding"] = "base64"
438
+ # Convert datetime to string
439
+ if "timestamp" in event_dict:
440
+ event_dict["timestamp"] = event_dict["timestamp"].isoformat()
441
+ else:
442
+ event_dict = event.model_dump(mode="json")
443
+ json_events.append(event_dict)
444
+
445
+ output_path.write_text(json.dumps(json_events, indent=2, default=str))
@@ -0,0 +1,12 @@
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) OpenAGI Foundation
3
+ # All rights reserved.
4
+ #
5
+ # This file is part of the official API project.
6
+ # Licensed under the MIT License.
7
+ # -----------------------------------------------------------------------------
8
+
9
+ # Re-export from types for convenience
10
+ from ...types import AsyncObserver
11
+
12
+ __all__ = ["AsyncObserver"]
oagi/agent/protocol.py ADDED
@@ -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
+ from typing import Protocol
10
+
11
+ from ..types import ActionHandler, AsyncActionHandler, AsyncImageProvider, ImageProvider
12
+
13
+
14
+ class Agent(Protocol):
15
+ """Protocol for synchronous task execution agents."""
16
+
17
+ def execute(
18
+ self,
19
+ instruction: str,
20
+ action_handler: ActionHandler,
21
+ image_provider: ImageProvider,
22
+ ) -> bool:
23
+ """Execute a task with the given handlers.
24
+
25
+ Args:
26
+ instruction: Task instruction to execute
27
+ action_handler: Handler for executing actions
28
+ image_provider: Provider for capturing images
29
+
30
+ Returns:
31
+ True if task completed successfully, False otherwise
32
+ """
33
+ ...
34
+
35
+
36
+ class AsyncAgent(Protocol):
37
+ """Protocol for asynchronous task execution agents."""
38
+
39
+ async def execute(
40
+ self,
41
+ instruction: str,
42
+ action_handler: AsyncActionHandler,
43
+ image_provider: AsyncImageProvider,
44
+ ) -> bool:
45
+ """Asynchronously execute a task with the given handlers.
46
+
47
+ Args:
48
+ instruction: Task instruction to execute
49
+ action_handler: Handler for executing actions
50
+ image_provider: Provider for capturing images
51
+
52
+ Returns:
53
+ True if task completed successfully, False otherwise
54
+ """
55
+ ...
oagi/agent/registry.py ADDED
@@ -0,0 +1,155 @@
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) OpenAGI Foundation
3
+ # All rights reserved.
4
+ #
5
+ # This file is part of the official API project.
6
+ # Licensed under the MIT License.
7
+ # -----------------------------------------------------------------------------
8
+
9
+ import inspect
10
+ from collections.abc import Callable
11
+ from typing import Any
12
+
13
+ from .protocol import AsyncAgent
14
+
15
+ # Type alias for agent factory functions
16
+ AgentFactory = Callable[..., AsyncAgent]
17
+
18
+ # Global registry mapping mode names to factory functions
19
+ _agent_registry: dict[str, AgentFactory] = {}
20
+
21
+
22
+ def async_agent_register(mode: str) -> Callable[[AgentFactory], AgentFactory]:
23
+ """Decorator to register agent factory functions for specific modes.
24
+
25
+ The decorator performs the following:
26
+ 1. Registers the factory function under the specified mode name
27
+ 2. Validates that duplicate modes are not registered
28
+ 3. Enables runtime validation of returned AsyncAgent instances
29
+
30
+ Args:
31
+ mode: The agent mode identifier (e.g., "actor", "planner", "todo")
32
+
33
+ Returns:
34
+ Decorator function that registers the factory
35
+
36
+ Raises:
37
+ ValueError: If the mode is already registered
38
+ """
39
+
40
+ def decorator(func: AgentFactory) -> AgentFactory:
41
+ # Check if mode is already registered
42
+ if mode in _agent_registry:
43
+ raise ValueError(
44
+ f"Agent mode '{mode}' is already registered. "
45
+ f"Cannot register the same mode twice."
46
+ )
47
+
48
+ # Register the factory
49
+ _agent_registry[mode] = func
50
+ return func
51
+
52
+ return decorator
53
+
54
+
55
+ def get_agent_factory(mode: str) -> AgentFactory:
56
+ """Get the registered agent factory for a mode.
57
+
58
+ Args:
59
+ mode: The agent mode identifier
60
+
61
+ Returns:
62
+ The registered factory function
63
+
64
+ Raises:
65
+ ValueError: If the mode is not registered
66
+ """
67
+ if mode not in _agent_registry:
68
+ available_modes = list(_agent_registry.keys())
69
+ raise ValueError(
70
+ f"Unknown agent mode: '{mode}'. Available modes: {available_modes}"
71
+ )
72
+ return _agent_registry[mode]
73
+
74
+
75
+ def list_agent_modes() -> list[str]:
76
+ """List all registered agent modes.
77
+
78
+ Returns:
79
+ List of registered mode names
80
+ """
81
+ return list(_agent_registry.keys())
82
+
83
+
84
+ def create_agent(mode: str, **kwargs: Any) -> AsyncAgent:
85
+ """Create an agent instance using the registered factory for the given mode.
86
+
87
+ This function automatically introspects the factory's signature and only passes
88
+ parameters that the factory accepts. This allows factories to have flexible
89
+ signatures while callers can provide a standard set of parameters.
90
+
91
+ Standard parameters typically include:
92
+ - api_key: OAGI API key
93
+ - base_url: OAGI API base URL
94
+ - model: Model identifier (e.g., "lux-actor-1")
95
+ - max_steps: Maximum number of steps to execute
96
+ - temperature: Sampling temperature
97
+
98
+ Args:
99
+ mode: The agent mode identifier
100
+ **kwargs: Parameters to pass to the factory function
101
+
102
+ Returns:
103
+ AsyncAgent instance created by the factory
104
+
105
+ Raises:
106
+ ValueError: If the mode is not registered
107
+ TypeError: If the factory returns an object that doesn't implement AsyncAgent
108
+
109
+ Example:
110
+ agent = create_agent(
111
+ mode="actor",
112
+ api_key="...",
113
+ base_url="...",
114
+ model="lux-actor-1",
115
+ max_steps=30,
116
+ temperature=0.0,
117
+ )
118
+ """
119
+ factory = get_agent_factory(mode)
120
+
121
+ # Introspect factory signature to determine which parameters it accepts
122
+ sig = inspect.signature(factory)
123
+
124
+ # Check if factory has **kwargs parameter (VAR_KEYWORD)
125
+ has_var_keyword = any(
126
+ param.kind == inspect.Parameter.VAR_KEYWORD for param in sig.parameters.values()
127
+ )
128
+
129
+ if has_var_keyword:
130
+ # If factory has **kwargs, pass all parameters
131
+ filtered_kwargs = kwargs
132
+ else:
133
+ # Otherwise, filter kwargs to only include parameters the factory accepts
134
+ accepted_params = set(sig.parameters.keys())
135
+ filtered_kwargs = {
136
+ key: value for key, value in kwargs.items() if key in accepted_params
137
+ }
138
+
139
+ agent = factory(**filtered_kwargs)
140
+
141
+ if not hasattr(agent, "execute"):
142
+ raise TypeError(
143
+ f"Factory for mode '{mode}' returned an object that doesn't "
144
+ f"implement AsyncAgent protocol. Expected an object with an "
145
+ f"'execute' method, got {type(agent).__name__}"
146
+ )
147
+
148
+ if not inspect.iscoroutinefunction(agent.execute):
149
+ raise TypeError(
150
+ f"Factory for mode '{mode}' returned an object with a non-async "
151
+ f"'execute' method. AsyncAgent protocol requires 'execute' to be "
152
+ f"an async method."
153
+ )
154
+
155
+ return agent
@@ -0,0 +1,33 @@
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 .memory import PlannerMemory
10
+ from .models import (
11
+ Action,
12
+ PlannerOutput,
13
+ ReflectionOutput,
14
+ Todo,
15
+ TodoHistory,
16
+ TodoStatus,
17
+ )
18
+ from .planner import Planner
19
+ from .taskee_agent import TaskeeAgent
20
+ from .tasker_agent import TaskerAgent
21
+
22
+ __all__ = [
23
+ "TaskerAgent",
24
+ "TaskeeAgent",
25
+ "PlannerMemory",
26
+ "Planner",
27
+ "Todo",
28
+ "TodoStatus",
29
+ "Action",
30
+ "TodoHistory",
31
+ "PlannerOutput",
32
+ "ReflectionOutput",
33
+ ]