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.
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/CHANGELOG.md +8 -0
- {codex_agent_framework-0.1.12/codex_agent_framework.egg-info → codex_agent_framework-0.1.13}/PKG-INFO +8 -3
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/README.md +5 -2
- codex_agent_framework-0.1.13/codex_agent/__init__.py +136 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/__main__.py +1 -2
- codex_agent_framework-0.1.13/codex_agent/agent.py +425 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/agent_runtime.py +60 -24
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/ai.py +7 -6
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/builtin_commands.py +5 -5
- codex_agent_framework-0.1.13/codex_agent/builtin_plugins/__init__.py +10 -0
- codex_agent_framework-0.1.13/codex_agent/builtin_plugins/browser/__init__.py +208 -0
- 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
- codex_agent_framework-0.1.13/codex_agent/builtin_plugins/desktop/__init__.py +605 -0
- codex_agent_framework-0.1.13/codex_agent/builtin_plugins/desktop/controller.py +411 -0
- codex_agent_framework-0.1.13/codex_agent/builtin_plugins/desktop/screenshot.py +44 -0
- codex_agent_framework-0.1.13/codex_agent/builtin_plugins/memory/__init__.py +191 -0
- codex_agent_framework-0.1.13/codex_agent/builtin_plugins/scheduler/__init__.py +155 -0
- codex_agent_framework-0.1.13/codex_agent/builtin_providers.py +17 -0
- codex_agent_framework-0.1.13/codex_agent/builtin_tools/__init__.py +58 -0
- codex_agent_framework-0.1.13/codex_agent/builtin_tools/files.py +274 -0
- codex_agent_framework-0.1.13/codex_agent/builtin_tools/server_tools.py +4 -0
- codex_agent_framework-0.1.13/codex_agent/builtin_tools/shell.py +55 -0
- codex_agent_framework-0.1.13/codex_agent/builtin_tools/system.py +72 -0
- codex_agent_framework-0.1.13/codex_agent/builtin_tools/vision.py +206 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/chat.py +38 -21
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/client.py +11 -2
- codex_agent_framework-0.1.13/codex_agent/command.py +240 -0
- codex_agent_framework-0.1.13/codex_agent/config.py +127 -0
- codex_agent_framework-0.1.13/codex_agent/context.py +231 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/event.py +8 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/get_text/get_text.py +10 -10
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/get_text/simpler_get_text.py +0 -1
- codex_agent_framework-0.1.13/codex_agent/mainloop.py +374 -0
- codex_agent_framework-0.1.13/codex_agent/plugin.py +96 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/provider.py +4 -1
- codex_agent_framework-0.1.13/codex_agent/registry.py +190 -0
- codex_agent_framework-0.1.13/codex_agent/runtime.py +34 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/scheduler.py +17 -4
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/server.py +34 -12
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/service.py +3 -2
- codex_agent_framework-0.1.13/codex_agent/sessions.py +360 -0
- codex_agent_framework-0.1.13/codex_agent/status.py +257 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/stream_utils.py +26 -10
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/tool.py +20 -1
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/tray.py +62 -1
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/utils.py +14 -7
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/voice.py +13 -25
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13/codex_agent_framework.egg-info}/PKG-INFO +8 -3
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent_framework.egg-info/SOURCES.txt +23 -2
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent_framework.egg-info/requires.txt +2 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/pyproject.toml +3 -1
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_agent.py +802 -79
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_browser.py +31 -3
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_chat.py +70 -16
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_client.py +20 -2
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_image_message.py +25 -36
- codex_agent_framework-0.1.13/tests/test_local_desktop.py +85 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_memory.py +58 -7
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_messages.py +5 -5
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_scheduler.py +6 -5
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_server.py +123 -19
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_service.py +3 -1
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_tray.py +35 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_utils.py +37 -1
- codex_agent_framework-0.1.12/codex_agent/__init__.py +0 -51
- codex_agent_framework-0.1.12/codex_agent/agent.py +0 -1965
- codex_agent_framework-0.1.12/codex_agent/builtin_providers.py +0 -74
- codex_agent_framework-0.1.12/codex_agent/builtin_tools.py +0 -770
- codex_agent_framework-0.1.12/codex_agent/command.py +0 -19
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/LICENSE +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/MANIFEST.in +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/get_text/__init__.py +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/get_text/default_gitignore +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/image.py +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/latex.py +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/memory.py +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/message.py +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/prompts/image_generation_system_prompt.txt +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/prompts/system_prompt.txt +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/tui.py +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent/worker.py +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent_framework.egg-info/entry_points.txt +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/codex_agent_framework.egg-info/top_level.txt +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/setup.cfg +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_ai.py +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_cli.py +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_events.py +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_get_text_browser.py +0 -0
- {codex_agent_framework-0.1.12 → codex_agent_framework-0.1.13}/tests/test_tui.py +0 -0
- {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.
|
|
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/
|
|
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
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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()
|