codex-agent-framework 0.1.12__tar.gz → 0.1.13__tar.gz

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 (91) hide show
  1. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/CHANGELOG.md +8 -0
  2. {codex_agent_framework-0.1.12/codex_agent_framework.egg-info → codex_agent_framework-0.1.13}/PKG-INFO +8 -3
  3. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/README.md +5 -2
  4. codex_agent_framework-0.1.13/codex_agent/__init__.py +136 -0
  5. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/__main__.py +1 -2
  6. codex_agent_framework-0.1.13/codex_agent/agent.py +425 -0
  7. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/agent_runtime.py +60 -24
  8. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/ai.py +7 -6
  9. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/builtin_commands.py +5 -5
  10. codex_agent_framework-0.1.13/codex_agent/builtin_plugins/__init__.py +10 -0
  11. codex_agent_framework-0.1.13/codex_agent/builtin_plugins/browser/__init__.py +208 -0
  12. codex_agent_framework-0.1.12/codex_agent/browser.py → codex_agent_framework-0.1.13/codex_agent/builtin_plugins/browser/controller.py +58 -19
  13. codex_agent_framework-0.1.13/codex_agent/builtin_plugins/desktop/__init__.py +605 -0
  14. codex_agent_framework-0.1.13/codex_agent/builtin_plugins/desktop/controller.py +411 -0
  15. codex_agent_framework-0.1.13/codex_agent/builtin_plugins/desktop/screenshot.py +44 -0
  16. codex_agent_framework-0.1.13/codex_agent/builtin_plugins/memory/__init__.py +191 -0
  17. codex_agent_framework-0.1.13/codex_agent/builtin_plugins/scheduler/__init__.py +155 -0
  18. codex_agent_framework-0.1.13/codex_agent/builtin_providers.py +17 -0
  19. codex_agent_framework-0.1.13/codex_agent/builtin_tools/__init__.py +58 -0
  20. codex_agent_framework-0.1.13/codex_agent/builtin_tools/files.py +274 -0
  21. codex_agent_framework-0.1.13/codex_agent/builtin_tools/server_tools.py +4 -0
  22. codex_agent_framework-0.1.13/codex_agent/builtin_tools/shell.py +55 -0
  23. codex_agent_framework-0.1.13/codex_agent/builtin_tools/system.py +72 -0
  24. codex_agent_framework-0.1.13/codex_agent/builtin_tools/vision.py +206 -0
  25. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/chat.py +38 -21
  26. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/client.py +11 -2
  27. codex_agent_framework-0.1.13/codex_agent/command.py +240 -0
  28. codex_agent_framework-0.1.13/codex_agent/config.py +127 -0
  29. codex_agent_framework-0.1.13/codex_agent/context.py +231 -0
  30. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/event.py +8 -0
  31. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/get_text/get_text.py +10 -10
  32. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/get_text/simpler_get_text.py +0 -1
  33. codex_agent_framework-0.1.13/codex_agent/mainloop.py +374 -0
  34. codex_agent_framework-0.1.13/codex_agent/plugin.py +96 -0
  35. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/provider.py +4 -1
  36. codex_agent_framework-0.1.13/codex_agent/registry.py +190 -0
  37. codex_agent_framework-0.1.13/codex_agent/runtime.py +34 -0
  38. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/scheduler.py +17 -4
  39. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/server.py +34 -12
  40. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/service.py +3 -2
  41. codex_agent_framework-0.1.13/codex_agent/sessions.py +360 -0
  42. codex_agent_framework-0.1.13/codex_agent/status.py +257 -0
  43. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/stream_utils.py +26 -10
  44. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/tool.py +20 -1
  45. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/tray.py +62 -1
  46. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/utils.py +14 -7
  47. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/voice.py +13 -25
  48. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13/codex_agent_framework.egg-info}/PKG-INFO +8 -3
  49. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent_framework.egg-info/SOURCES.txt +23 -2
  50. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent_framework.egg-info/requires.txt +2 -0
  51. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/pyproject.toml +3 -1
  52. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_agent.py +802 -79
  53. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_browser.py +31 -3
  54. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_chat.py +70 -16
  55. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_client.py +20 -2
  56. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_image_message.py +25 -36
  57. codex_agent_framework-0.1.13/tests/test_local_desktop.py +85 -0
  58. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_memory.py +58 -7
  59. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_messages.py +5 -5
  60. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_scheduler.py +6 -5
  61. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_server.py +123 -19
  62. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_service.py +3 -1
  63. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_tray.py +35 -0
  64. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_utils.py +37 -1
  65. codex_agent_framework-0.1.12/codex_agent/__init__.py +0 -51
  66. codex_agent_framework-0.1.12/codex_agent/agent.py +0 -1965
  67. codex_agent_framework-0.1.12/codex_agent/builtin_providers.py +0 -74
  68. codex_agent_framework-0.1.12/codex_agent/builtin_tools.py +0 -770
  69. codex_agent_framework-0.1.12/codex_agent/command.py +0 -19
  70. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/LICENSE +0 -0
  71. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/MANIFEST.in +0 -0
  72. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/get_text/__init__.py +0 -0
  73. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/get_text/default_gitignore +0 -0
  74. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/image.py +0 -0
  75. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/latex.py +0 -0
  76. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/memory.py +0 -0
  77. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/message.py +0 -0
  78. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/prompts/image_generation_system_prompt.txt +0 -0
  79. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/prompts/system_prompt.txt +0 -0
  80. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/tui.py +0 -0
  81. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/worker.py +0 -0
  82. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
  83. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent_framework.egg-info/entry_points.txt +0 -0
  84. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent_framework.egg-info/top_level.txt +0 -0
  85. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/setup.cfg +0 -0
  86. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_ai.py +0 -0
  87. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_cli.py +0 -0
  88. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_events.py +0 -0
  89. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_get_text_browser.py +0 -0
  90. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_tui.py +0 -0
  91. {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_worker.py +0 -0
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  This project loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and uses semantic versioning where practical.
6
6
 
7
+ ## [0.1.13] - 2026-05-07
8
+ ### Changed
9
+ - Reorganize built-in tools into a `codex_agent.builtin_tools` package with separate modules for file, shell, vision, system, and server-tool responsibilities.
10
+ - Keep the public `codex_agent.builtin_tools` import surface compatible by re-exporting the existing built-in tools and helpers from the package initializer.
11
+
12
+ ### Tests
13
+ - Update built-in tool regression coverage for the package layout and validate the full suite at 376 passing tests.
14
+
7
15
  ## [0.1.12] - 2026-05-05
8
16
  ### Added
9
17
  - Add a persistent Playwright/Chromium browser controller with tools to open/close the browser, select tabs, navigate, click, fill, select options, press keys, inspect action snapshots, and capture screenshots.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-agent-framework
3
- Version: 0.1.12
3
+ Version: 0.1.13
4
4
  Summary: A lightweight event-driven Codex agent runtime.
5
5
  Author: Baptiste
6
6
  License-Expression: MIT
@@ -21,6 +21,7 @@ Requires-Dist: codex-backend-sdk
21
21
  Requires-Dist: fastapi
22
22
  Requires-Dist: filetype
23
23
  Requires-Dist: modict
24
+ Requires-Dist: mss
24
25
  Requires-Dist: numpy
25
26
  Requires-Dist: odfpy
26
27
  Requires-Dist: openai
@@ -31,6 +32,7 @@ Requires-Dist: playwright
31
32
  Requires-Dist: pydub
32
33
  Requires-Dist: pypdf
33
34
  Requires-Dist: pynteract
35
+ Requires-Dist: pywinctl
34
36
  Requires-Dist: python-docx
35
37
  Requires-Dist: PyYAML
36
38
  Requires-Dist: regex
@@ -361,9 +363,11 @@ Configuration is persisted to `agent_config.json` in the runtime directory when
361
363
  ```text
362
364
  codex_agent/ Python package
363
365
  codex_agent/agent.py Agent, AgentConfig, AgentSession
364
- codex_agent/builtin_tools.py Built-in local tools
366
+ codex_agent/plugin.py Stateful plugin base class
367
+ codex_agent/builtin_tools/ Built-in local tools package
365
368
  codex_agent/builtin_commands.py Built-in slash commands
366
369
  codex_agent/builtin_providers.py Built-in context providers
370
+ codex_agent/builtin_plugins/ Auto-loaded stateful plugin packages
367
371
  codex_agent/prompts/ Packaged prompt templates
368
372
  codex_agent/get_text/ Document extraction helpers
369
373
  tests/ Test suite
@@ -384,7 +388,7 @@ The tests isolate `AGENT_RUNTIME_DIR` automatically, so they should not create o
384
388
  Current baseline:
385
389
 
386
390
  ```text
387
- 293 passed
391
+ 376 passed
388
392
  ```
389
393
 
390
394
  ## Packaging
@@ -399,6 +403,7 @@ python -m build
399
403
  The distribution includes prompt text files and `codex_agent/get_text/default_gitignore` through package data and `MANIFEST.in`.
400
404
 
401
405
  ## Recent changes
406
+ - `0.1.13`: reorganize built-in tools into a `codex_agent.builtin_tools` package, keeping the public import surface compatible while separating file, shell, vision, system, and server-tool modules.
402
407
  - `0.1.12`: add a persistent Playwright/Chromium browser controller with tab navigation, DOM/action snapshots, screenshots, form/click/key tools, and `browser_goto(url)` for active-tab navigation.
403
408
  - `0.1.11`: split strict line-numbered UTF-8 `read` from unnumbered extracted `view`, preserve blank lines in read snippets, and show persistent+temporary message counts in the TUI status bar.
404
409
  - `0.1.10`: persist only backend compaction summaries, drop bulky compacted conversation payloads, and refresh context status after compaction.
@@ -314,9 +314,11 @@ Configuration is persisted to `agent_config.json` in the runtime directory when
314
314
  ```text
315
315
  codex_agent/ Python package
316
316
  codex_agent/agent.py Agent, AgentConfig, AgentSession
317
- codex_agent/builtin_tools.py Built-in local tools
317
+ codex_agent/plugin.py Stateful plugin base class
318
+ codex_agent/builtin_tools/ Built-in local tools package
318
319
  codex_agent/builtin_commands.py Built-in slash commands
319
320
  codex_agent/builtin_providers.py Built-in context providers
321
+ codex_agent/builtin_plugins/ Auto-loaded stateful plugin packages
320
322
  codex_agent/prompts/ Packaged prompt templates
321
323
  codex_agent/get_text/ Document extraction helpers
322
324
  tests/ Test suite
@@ -337,7 +339,7 @@ The tests isolate `AGENT_RUNTIME_DIR` automatically, so they should not create o
337
339
  Current baseline:
338
340
 
339
341
  ```text
340
- 293 passed
342
+ 376 passed
341
343
  ```
342
344
 
343
345
  ## Packaging
@@ -352,6 +354,7 @@ python -m build
352
354
  The distribution includes prompt text files and `codex_agent/get_text/default_gitignore` through package data and `MANIFEST.in`.
353
355
 
354
356
  ## Recent changes
357
+ - `0.1.13`: reorganize built-in tools into a `codex_agent.builtin_tools` package, keeping the public import surface compatible while separating file, shell, vision, system, and server-tool modules.
355
358
  - `0.1.12`: add a persistent Playwright/Chromium browser controller with tab navigation, DOM/action snapshots, screenshots, form/click/key tools, and `browser_goto(url)` for active-tab navigation.
356
359
  - `0.1.11`: split strict line-numbered UTF-8 `read` from unnumbered extracted `view`, preserve blank lines in read snippets, and show persistent+temporary message counts in the TUI status bar.
357
360
  - `0.1.10`: persist only backend compaction summaries, drop bulky compacted conversation payloads, and refresh context status after compaction.
@@ -0,0 +1,136 @@
1
+ from .agent import Agent
2
+ from .client import AgentClient
3
+ from .config import AgentConfig, ConfigManager, DEFAULT_SYSTEM_PROMPT
4
+ from .context import ContextManager
5
+ from .event import (
6
+ AgentInterruptRequestedEvent,
7
+ AgentInterrupted,
8
+ AgentInterruptedEvent,
9
+ AgentTurnEndEvent,
10
+ AgentTurnErrorEvent,
11
+ AgentTurnStartEvent,
12
+ AssistantStepEndEvent,
13
+ AssistantStepStartEvent,
14
+ AssistantTurnEndEvent,
15
+ AssistantTurnStartEvent,
16
+ AudioPlaybackEvent,
17
+ CompactionCompletedEvent,
18
+ CompactionRequestedEvent,
19
+ Event,
20
+ EventBus,
21
+ MessageAddedEvent,
22
+ MessageSubmittedEvent,
23
+ ResponseContentDeltaEvent,
24
+ ResponseDoneEvent,
25
+ ResponseMessageDeltaEvent,
26
+ ResponseOutputItemEvent,
27
+ ResponseReasoningDeltaEvent,
28
+ ResponseStartEvent,
29
+ ResponseToolCallsDeltaEvent,
30
+ ServerToolCallEvent,
31
+ SessionDeletedEvent,
32
+ SessionLoadedEvent,
33
+ ToolCallDoneEvent,
34
+ ToolCallStartEvent,
35
+ )
36
+ from .message import (
37
+ AssistantMessage,
38
+ CompactionMessage,
39
+ DeveloperMessage,
40
+ Message,
41
+ MessageChunk,
42
+ ProviderMessage,
43
+ ServerToolResponseMessage,
44
+ SystemMessage,
45
+ ToolResultMessage,
46
+ ToolResultWrapper,
47
+ UserMessage,
48
+ )
49
+ from .image import ImageMessage
50
+ from .mainloop import MainLoopManager
51
+ from .memory import MemoryEntry, RAGMemory, RAGMemoryConfig
52
+ from .plugin import Plugin, PluginsManager
53
+ from .registry import RegistryManager
54
+ from .command import AgentCommand, AgentFuture, CommandManager, command
55
+ from .provider import provider
56
+ from .runtime import RuntimeManager
57
+ from .sessions import AgentSession, SessionsManager
58
+ from .status import StatusManager
59
+ from .tool import ImageGenerationTool, ServerTool, Tool, WebSearchTool, get_agent, get_current_call_id, tool
60
+ from .worker import SummarizerWorker, TurnSummaryWorker, Worker
61
+
62
+ __all__ = [
63
+ "Agent",
64
+ "AgentClient",
65
+ "AgentConfig",
66
+ "ConfigManager",
67
+ "DEFAULT_SYSTEM_PROMPT",
68
+ "ContextManager",
69
+ "AgentInterruptRequestedEvent",
70
+ "AgentInterrupted",
71
+ "AgentInterruptedEvent",
72
+ "AgentTurnEndEvent",
73
+ "AgentTurnErrorEvent",
74
+ "AgentTurnStartEvent",
75
+ "AssistantStepEndEvent",
76
+ "AssistantStepStartEvent",
77
+ "AssistantTurnEndEvent",
78
+ "AssistantTurnStartEvent",
79
+ "AudioPlaybackEvent",
80
+ "CompactionCompletedEvent",
81
+ "CompactionRequestedEvent",
82
+ "Event",
83
+ "EventBus",
84
+ "MessageAddedEvent",
85
+ "MessageSubmittedEvent",
86
+ "ResponseContentDeltaEvent",
87
+ "ResponseDoneEvent",
88
+ "ResponseMessageDeltaEvent",
89
+ "ResponseOutputItemEvent",
90
+ "ResponseReasoningDeltaEvent",
91
+ "ResponseStartEvent",
92
+ "ResponseToolCallsDeltaEvent",
93
+ "ServerToolCallEvent",
94
+ "SessionDeletedEvent",
95
+ "SessionLoadedEvent",
96
+ "ToolCallDoneEvent",
97
+ "ToolCallStartEvent",
98
+ "AssistantMessage",
99
+ "CompactionMessage",
100
+ "DeveloperMessage",
101
+ "Message",
102
+ "MessageChunk",
103
+ "ProviderMessage",
104
+ "ServerToolResponseMessage",
105
+ "SystemMessage",
106
+ "ToolResultMessage",
107
+ "ToolResultWrapper",
108
+ "UserMessage",
109
+ "ImageMessage",
110
+ "MainLoopManager",
111
+ "MemoryEntry",
112
+ "RAGMemory",
113
+ "RAGMemoryConfig",
114
+ "Plugin",
115
+ "PluginsManager",
116
+ "RegistryManager",
117
+ "AgentCommand",
118
+ "AgentFuture",
119
+ "CommandManager",
120
+ "command",
121
+ "provider",
122
+ "RuntimeManager",
123
+ "AgentSession",
124
+ "SessionsManager",
125
+ "StatusManager",
126
+ "ImageGenerationTool",
127
+ "ServerTool",
128
+ "Tool",
129
+ "WebSearchTool",
130
+ "get_agent",
131
+ "get_current_call_id",
132
+ "tool",
133
+ "SummarizerWorker",
134
+ "TurnSummaryWorker",
135
+ "Worker",
136
+ ]
@@ -26,8 +26,7 @@ def default_agent():
26
26
 
27
27
 
28
28
  def configure_server_url(agent, host, port):
29
- if hasattr(agent, "update_config"):
30
- agent.update_config(server_url=f"http://{host}:{port}", save=False)
29
+ agent.config_manager.update(server_url=f"http://{host}:{port}", save=False)
31
30
  return agent
32
31
 
33
32
 
@@ -0,0 +1,425 @@
1
+ from .utils import text_content, ensure_runtime_dir, upload_file_to_folder, project_python_env
2
+ from modict import modict
3
+ import os
4
+ import sys
5
+ from .message import DeveloperMessage, ServerToolResponseMessage, SystemMessage, ToolResultMessage, UserMessage, message_from_data
6
+ from .image import ImageMessage
7
+ from .command import CommandManager
8
+ from .config import ConfigManager
9
+ from .context import ContextManager
10
+ from .mainloop import MainLoopManager
11
+ from .plugin import PluginsManager
12
+ from .registry import RegistryManager
13
+ from .runtime import RuntimeManager
14
+ from .sessions import SessionsManager
15
+ from .status import StatusManager
16
+ from .tool import ImageGenerationTool, reset_current_agent, set_current_agent
17
+ from .ai import AIClient
18
+ from .voice import VoiceProcessor
19
+ from .latex import LaTeXProcessor
20
+ from .event import AgentInterruptRequestedEvent, AgentInterrupted, AgentInterruptedEvent, AudioPlaybackEvent, EventBus
21
+ from pynteract import Shell
22
+ from threading import Event as ThreadEvent
23
+
24
+ class Agent:
25
+
26
+ def __init__(self,shell=None,session="latest",**kwargs):
27
+ """Initializes the Agent instance with configuration, events, tools, and message history.
28
+
29
+ Args:
30
+ shell: Optional shell instance for code execution. If None, creates a new Shell.
31
+ session: Session id or JSON session file to resume. Defaults to
32
+ "latest"; pass "new" to force a fresh session.
33
+ **kwargs: Additional keyword arguments to update the agent configuration.
34
+ """
35
+ ensure_runtime_dir()
36
+ self.init_execution_environment()
37
+ self.config_manager=ConfigManager(self)
38
+ self.config=self.config_manager.config
39
+ self.config_manager.load()
40
+ self.config_manager.update(kwargs, save=False)
41
+ self.events=EventBus()
42
+ self.registry=RegistryManager(self)
43
+ self.tools=self.registry.tools
44
+ self.providers=self.registry.providers
45
+ self.command_manager=CommandManager(self)
46
+ self.commands=self.command_manager.commands
47
+ self.context_manager=ContextManager(self)
48
+ self.plugins_manager=PluginsManager(self)
49
+ self.plugins=self.plugins_manager.plugins
50
+ self.sessions_manager=SessionsManager(self)
51
+ self.runtime=RuntimeManager(self)
52
+ self.hooks=modict()
53
+ self.on(AudioPlaybackEvent, self.handle_audio_playback)
54
+ self.mainloop_manager=MainLoopManager(self)
55
+ self._interrupt_requested=ThreadEvent()
56
+ self.current_session_id=None
57
+ self.session=None
58
+ self.status_manager=StatusManager(self)
59
+ self.shell=None
60
+ self.init_shell(shell=shell)
61
+ self.init_workfolder()
62
+ self.sessions_manager.init_folder()
63
+ self.registry.init_tools_folder()
64
+ self.registry.init_providers_folder()
65
+ self.command_manager.init_folder()
66
+ self.ai=AIClient(self)
67
+ self.voice=VoiceProcessor(self)
68
+ self.latex=LaTeXProcessor()
69
+ self.content_processors=[self.voice]
70
+ self.registry.init_builtin_tools()
71
+ self.registry.init_builtin_providers()
72
+ self.command_manager.init_builtin()
73
+ self.plugins_manager.init_builtin()
74
+ self.registry.init_runtime_tools()
75
+ self.registry.init_runtime_providers()
76
+ self.command_manager.init_runtime()
77
+ self.registry.init_server_tools()
78
+ self.start_new_session() if session == "new" else self.load_session(session)
79
+
80
+ def on(self, event_type, handler=None):
81
+ """Register an event handler.
82
+
83
+ Can be used as ``agent.on(ResponseContentDeltaEvent, handler)`` or as
84
+ ``@agent.on(ResponseContentDeltaEvent)``.
85
+ """
86
+ return self.events.on(event_type, handler)
87
+
88
+ def emit(self, event_type, **data):
89
+ return self.events.emit(event_type, **data)
90
+
91
+ @property
92
+ def busy(self):
93
+ return self.mainloop_manager.busy
94
+
95
+ @property
96
+ def current_turn_id(self):
97
+ return self.mainloop_manager.current_turn_id
98
+
99
+ def start(self):
100
+ return self.mainloop_manager.start()
101
+
102
+ def stop(self, timeout=None):
103
+ return self.mainloop_manager.stop(timeout=timeout)
104
+
105
+ def submit(self, command, **payload):
106
+ return self.mainloop_manager.submit(command, **payload)
107
+
108
+ def start_turn(self):
109
+ return self.mainloop_manager.start_turn()
110
+
111
+ def submit_command(self, prompt):
112
+ return self.command_manager.submit(prompt)
113
+
114
+ def submit_message(self, message):
115
+ return self.mainloop_manager.submit_message(message)
116
+
117
+ def interrupt(self, reason="user"):
118
+ abort_error = None
119
+ if not self._interrupt_requested.is_set():
120
+ self._interrupt_requested.set()
121
+ if getattr(self, "ai", None) is not None:
122
+ try:
123
+ self.ai.abort()
124
+ except Exception as exc:
125
+ abort_error = exc
126
+ self.emit(AgentInterruptedEvent, reason=reason)
127
+ self.emit(AgentInterruptRequestedEvent, reason=reason, interrupted=True, error=str(abort_error) if abort_error else "")
128
+ return True
129
+
130
+ def clear_interrupt(self):
131
+ self._interrupt_requested.clear()
132
+
133
+ def is_interrupted(self):
134
+ return self._interrupt_requested.is_set()
135
+
136
+ def check_interrupt(self):
137
+ if self.is_interrupted():
138
+ raise AgentInterrupted()
139
+
140
+ def init_workfolder(self):
141
+ if not os.path.isdir(self.config.workfolder):
142
+ os.makedirs(self.config.workfolder,exist_ok=True)
143
+
144
+ def init_execution_environment(self):
145
+ os.environ.update(project_python_env())
146
+
147
+ def init_shell(self,shell=None):
148
+ if shell is None:
149
+ shell=Shell(silent=True)
150
+ shell.update_namespace(
151
+ agent=self,
152
+ os=os,
153
+ sys=sys,
154
+ python_executable=sys.executable,
155
+ )
156
+ self.shell=shell
157
+
158
+ def execution_env(self):
159
+ return project_python_env()
160
+
161
+ def get_sessions(self):
162
+ return self.sessions_manager.list()
163
+
164
+ def latest_session_id(self):
165
+ return self.sessions_manager.latest_id()
166
+
167
+ def load_session(self, session):
168
+ return self.sessions_manager.load(session)
169
+
170
+ def start_new_session(self):
171
+ return self.sessions_manager.start_new()
172
+
173
+ def delete_session(self, session):
174
+ return self.sessions_manager.delete(session)
175
+
176
+ def navigate_session(self, direction):
177
+ return self.sessions_manager.navigate(direction)
178
+
179
+ def save_session(self):
180
+ return self.sessions_manager.save()
181
+
182
+ def compact_session(self, client=None, model=None, instructions=None):
183
+ return self.sessions_manager.compact(client=client, model=model, instructions=instructions)
184
+
185
+ def add_message(self,msg,pending=False):
186
+ """Queue a message for the next pending flush, except tool results."""
187
+ if isinstance(msg, ToolResultMessage):
188
+ return self.sessions_manager.append_message(msg)
189
+ if self.mainloop_manager.dumping_pending:
190
+ return self.sessions_manager.append_message(msg)
191
+ return self.mainloop_manager.add_pending_message(msg)
192
+
193
+
194
+ def new_message(self,pending=False,**kwargs):
195
+ """Helper to create and add a message based on provided keyword arguments.
196
+
197
+ Args:
198
+ pending: If True, stores message in pending list instead of main messages.
199
+ **kwargs: Message fields. Must include an explicit ``type``.
200
+
201
+ Returns:
202
+ The created message object.
203
+ """
204
+ msg=message_from_data(kwargs)
205
+ return self.add_message(msg,pending=pending)
206
+
207
+ def add_image(self,source,pending=False,**kwargs):
208
+ if source:
209
+ img=ImageMessage(
210
+ content=source,
211
+ **kwargs
212
+ )
213
+ if not img.is_valid():
214
+ raise ValueError(f"Invalid or inaccessible image source: {source!r}")
215
+ self.add_message(img,pending=pending)
216
+ return img
217
+
218
+ def add_tool(self,func=None,name=None,description=None,parameters=None,required=None,tool_type=None,format=None):
219
+ return self.registry.add_tool(
220
+ func=func,
221
+ name=name,
222
+ description=description,
223
+ parameters=parameters,
224
+ required=required,
225
+ tool_type=tool_type,
226
+ format=format,
227
+ )
228
+
229
+ def add_provider(self, func=None, name=None):
230
+ return self.registry.add_provider(func=func, name=name)
231
+
232
+ def add_command(self, func=None, name=None):
233
+ return self.command_manager.add(func, name=name)
234
+
235
+ def add_hook(self, key, func=None):
236
+ """Register a runtime hook.
237
+
238
+ Hooks are callable extension points keyed by name. They are runtime
239
+ functions stored outside persisted payloads, and can be used as
240
+ ``@agent.add_hook("audio_playback_hook")``.
241
+ """
242
+ if func is None:
243
+ def decorator(f):
244
+ return self.add_hook(key, f)
245
+ return decorator
246
+
247
+ if hasattr(self, "mainloop_manager") and self.mainloop_manager.should_queue_state_command():
248
+ return self.submit("add_hook", key=key, func=func).wait()
249
+
250
+ self.hooks.setdefault(key, []).append(func)
251
+ return func
252
+
253
+ def has_hook(self, key):
254
+ return bool(self.hooks.get(key))
255
+
256
+ def run_hooks(self, key, *args):
257
+ results = []
258
+ for hook in list(self.hooks.get(key, ())):
259
+ agent_context = set_current_agent(self)
260
+ try:
261
+ results.append(hook(*args))
262
+ finally:
263
+ reset_current_agent(agent_context)
264
+ return results
265
+
266
+ def get_messages(self,filter=None):
267
+ return self.sessions_manager.messages(filter=filter)
268
+
269
+ def get_status(self):
270
+ return self.status_manager.get_status()
271
+
272
+ def create_image(
273
+ self,
274
+ prompt,
275
+ input_images=None,
276
+ model="gpt-5.4",
277
+ reasoning_effort="medium",
278
+ verbosity="medium",
279
+ output_format="png",
280
+ size=None,
281
+ quality=None,
282
+ background=None,
283
+ moderation=None,
284
+ ):
285
+ """Generate an image directly and return the saved image path."""
286
+ content = [{"type": "text", "text": prompt}]
287
+ for source in input_images or []:
288
+ content.extend(ImageMessage(content=source).content_to_response_parts())
289
+
290
+ system_prompt = text_content(os.path.join(os.path.dirname(__file__), "prompts", "image_generation_system_prompt.txt"))
291
+ message = self.ai.run(
292
+ messages=[SystemMessage(content=system_prompt), UserMessage(content=content)],
293
+ tools=[ImageGenerationTool(
294
+ output_format=output_format,
295
+ size=size,
296
+ quality=quality,
297
+ background=background,
298
+ moderation=moderation,
299
+ ).to_response_tool()],
300
+ model=model,
301
+ reasoning_effort=reasoning_effort,
302
+ verbosity=verbosity,
303
+ )
304
+ for item in message.output_items:
305
+ if item.get("type") == "image_generation_call":
306
+ return ServerToolResponseMessage(item=item).content
307
+ else:
308
+ raise RuntimeError("The image_generation tool did not return an image.")
309
+
310
+ def get_response(self):
311
+ """Run the model, emit response events, and store the final message.
312
+
313
+ Returns:
314
+ Message: The aggregated assistant message with content and/or tool calls.
315
+ """
316
+ messages = self.context_manager.get_context()
317
+ if self.context_manager.should_auto_compact(messages):
318
+ self.compact_session(
319
+ model=self.config.model,
320
+ instructions=self.config_manager.system_message().content,
321
+ )
322
+ messages = self.context_manager.get_context()
323
+
324
+ message=self.ai.run(
325
+ messages=messages,
326
+ tools=self.registry.get_response_tools(),
327
+ prompt_cache_key=self.current_session_id,
328
+ **self.config.extract('model','reasoning_effort','verbosity','parallel_tool_calls')
329
+ )
330
+ self.sessions_manager.append_message(message)
331
+ for item in message.output_items:
332
+ if self.registry.server_tool_for_item(item):
333
+ self.sessions_manager.append_message(ServerToolResponseMessage(item=item))
334
+ return message
335
+
336
+ def speak(self,text,**kwargs):
337
+ audio=self.ai.text_to_audio(text,**kwargs)
338
+ if audio is not None:
339
+ self.emit(AudioPlaybackEvent, audio=audio)
340
+
341
+ def handle_audio_playback(self, event):
342
+ audio = event.get("audio")
343
+ if audio is None:
344
+ return None
345
+ if self.has_hook("audio_playback_hook"):
346
+ return self.run_hooks("audio_playback_hook", audio)
347
+ from .voice import silent_play
348
+ return silent_play(audio)
349
+
350
+ def listen(self, source, language=None, **kwargs):
351
+ """
352
+ description: |
353
+ Listen to audio input and transcribe it to text, then process it as a user message.
354
+ Accepts either audio data (BytesIO/bytes) or a file path.
355
+ parameters:
356
+ source:
357
+ description: Audio data as file_path string, BytesIO object or bytes
358
+ language:
359
+ description: ISO-639-1 language code (e.g., "en", "fr")
360
+ kwargs:
361
+ description: Additional parameters for the transcription (model, prompt, temperature, etc.)
362
+ """
363
+
364
+ transcribed_text = self.transcribe(source, language, **kwargs)
365
+
366
+ # Add transcribed text as a user message
367
+ self.add_message(UserMessage(content=transcribed_text))
368
+
369
+ def transcribe(self, source, language=None, **kwargs):
370
+ """
371
+ description: |
372
+ Transcribe audio to text.
373
+ Accepts either audio data (BytesIO/bytes) or a file path.
374
+ parameters:
375
+ source:
376
+ description: Audio data as file_path string, BytesIO object or bytes
377
+ language:
378
+ description: ISO-639-1 language code (e.g., "en", "fr")
379
+ kwargs:
380
+ description: Additional parameters for the transcription (model, prompt, temperature, etc.)
381
+ """
382
+ if source is None:
383
+ raise ValueError("An audio source must be provided")
384
+
385
+ # Transcribe audio to text
386
+ transcribed_text = self.ai.audio_to_text(
387
+ source=source,
388
+ language=language,
389
+ **kwargs
390
+ )
391
+
392
+ return transcribed_text
393
+
394
+ def upload_file(self, source, name=None):
395
+ """
396
+ description: |
397
+ Upload a file to the agent's workfolder and inform the agent via system message.
398
+ Supports file paths (str), BytesIO objects, or raw bytes.
399
+ parameters:
400
+ source:
401
+ description: File source - can be a file path (str), BytesIO object, or bytes
402
+ name:
403
+ description: Optional name for the file. Required when source is bytes without a name attribute.
404
+ Can include extension to override detection.
405
+ returns:
406
+ description: Absolute path to the uploaded file
407
+ """
408
+ dest_path = upload_file_to_folder(source, self.config.workfolder, name=name)
409
+ dest_filename = os.path.basename(dest_path)
410
+
411
+ self.add_message(DeveloperMessage(content=f"File '{dest_filename}' has been uploaded to the workfolder at: {dest_path}"))
412
+
413
+ return dest_path
414
+
415
+ def __call__(self,prompt=None)->bool:
416
+ return self.mainloop_manager.call(prompt=prompt)
417
+
418
+ def interact(self):
419
+ """Starts the interactive Textual chat UI."""
420
+ from .utils import set_root_path
421
+ import os
422
+ if os.environ.get('AGENT_ROOT_PATH') is None:
423
+ set_root_path(file=__file__)
424
+ from .chat import Chat
425
+ Chat(self).run()