codex-agent-framework 0.1.56__tar.gz → 0.1.57__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.56 → codex_agent_framework-0.1.57}/CHANGELOG.md +11 -0
- {codex_agent_framework-0.1.56/codex_agent_framework.egg-info → codex_agent_framework-0.1.57}/PKG-INFO +1 -1
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/stream_utils.py +42 -75
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/utils.py +30 -57
- codex_agent_framework-0.1.57/codex_agent/version.py +1 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57/codex_agent_framework.egg-info}/PKG-INFO +1 -1
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/pyproject.toml +1 -1
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_utils.py +87 -1
- codex_agent_framework-0.1.56/codex_agent/version.py +0 -1
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/LICENSE +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/MANIFEST.in +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/README.md +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/__main__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/agent.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/agent_runtime.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/ai.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/browser.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_commands.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/bash/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/bash/requirements.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/browser/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/browser/requirements.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/content/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/content/requirements.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/content/system.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/content/vision.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/context/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/desktop/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/desktop/requirements.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/environment/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/environment/requirements.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/files/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/files/requirements.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/files/tools.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/image_generation/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/image_generation/image_generation_system_prompt.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/image_generation/requirements.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/interface/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/interface/requirements.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/memory/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/memory/requirements.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/planner/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/planner/requirements.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/python/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/python/requirements.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/realtime/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/realtime/audio.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/realtime/bridge.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/realtime/manager.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/realtime/requirements.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/realtime/session.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/scheduler/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/scheduler/requirements.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/subagents/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/subagents/profiles/explorer.json +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/subagents/requirements.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/tts/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/tts/requirements.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_plugins/web_search/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/cli/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/cli/headless.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/cli/main.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/cli/output.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/cli/root.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/cli/runner.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/client.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/command.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/config.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/context.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/desktop.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/event.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/get_text/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/get_text/default_gitignore +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/get_text/get_text.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/get_text/simpler_get_text.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/hooks.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/image.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/latex.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/mainloop.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/message.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/plugin.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/prompts/system_prompt.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/provider.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/runtime.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/scheduler.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/scripts/install-system-dependencies.sh +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/server/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/server/app.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/server/core.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/service.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/sessions.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/status.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/stream.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/tool.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/tray.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/tui/__init__.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/tui/app.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/tui/chat.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/tui/lifecycle.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/tui/log.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/tui/perf.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/tui/state.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/tui/status_bar.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/tui/stream_debug.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/tui/style.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/tui/transcript.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/tui/widgets.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/worker.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent_framework.egg-info/SOURCES.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent_framework.egg-info/entry_points.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent_framework.egg-info/requires.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent_framework.egg-info/top_level.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/dependencies.txt +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/setup.cfg +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_audio_hooks.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_commands.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_context.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_desktop.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_environment.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_image_generation.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_observe.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_plugins.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_providers_config.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_realtime.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_response.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_runtime.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_sessions.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_subagents.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_tools.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_turns.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_ai.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_browser.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_builtin_config.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_chat.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_cli.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_cli_headless.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_cli_root.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_client.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_events.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_get_text_browser.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_hooks.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_image_message.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_local_desktop.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_memory.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_messages.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_planner.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_scheduler.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_server.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_service.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_tui.py +0 -0
- {codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_worker.py +0 -0
|
@@ -4,6 +4,17 @@ 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.57] - 2026-05-17
|
|
8
|
+
### Fixed
|
|
9
|
+
- Fix stream helper fan-out edge cases so `Tee`, `Splitter`, and `MappingStreamSplitter` readers keep their own queues and reusable mapping splitters do not redirect old readers to new streams.
|
|
10
|
+
- Make `utils.Logger` create its parent directory before truncating the log file so first use works on fresh paths.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Simplify small utility helpers for line-preserving truncation, message packing, and fenced stream pattern construction while preserving their existing behavior.
|
|
14
|
+
|
|
15
|
+
### Tests
|
|
16
|
+
- Add focused regression coverage for the stream helpers, logger initialization, and utility formatting/truncation helpers; validate with the full offline CI suite (580 passed).
|
|
17
|
+
|
|
7
18
|
## [0.1.56] - 2026-05-17
|
|
8
19
|
### Changed
|
|
9
20
|
- Replace the TUI chat surface with a widget-based timeline inspired by Textual chat patterns so live assistant output updates an active message widget instead of mutating a monolithic transcript buffer.
|
|
@@ -53,17 +53,16 @@ class Tee:
|
|
|
53
53
|
for k in self.queues:
|
|
54
54
|
self.queues[k].put(END)
|
|
55
55
|
|
|
56
|
-
def tee(self,stream, n=2):
|
|
57
|
-
self.queues={k:Queue() for k in range(n)}
|
|
58
|
-
self.thread=Thread(target=self.target, args=(stream,),
|
|
56
|
+
def tee(self, stream, n=2):
|
|
57
|
+
self.queues = {k: Queue() for k in range(n)}
|
|
58
|
+
self.thread = Thread(target=self.target, args=(stream,), daemon=True)
|
|
59
59
|
self.thread.start()
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return tuple(readers.values())
|
|
60
|
+
|
|
61
|
+
def reader(queue):
|
|
62
|
+
while (processed_chunk := queue.get()) is not END:
|
|
63
|
+
yield processed_chunk
|
|
64
|
+
|
|
65
|
+
return tuple(reader(queue) for queue in self.queues.values())
|
|
67
66
|
|
|
68
67
|
class Splitter:
|
|
69
68
|
|
|
@@ -86,17 +85,16 @@ class Splitter:
|
|
|
86
85
|
self.queue_false.put(END)
|
|
87
86
|
|
|
88
87
|
def split(self, stream):
|
|
89
|
-
self.queue_true=Queue()
|
|
90
|
-
self.queue_false=Queue()
|
|
91
|
-
self.thread=Thread(target=self.target, args=(stream,),daemon=True)
|
|
88
|
+
self.queue_true = Queue()
|
|
89
|
+
self.queue_false = Queue()
|
|
90
|
+
self.thread = Thread(target=self.target, args=(stream,), daemon=True)
|
|
92
91
|
self.thread.start()
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def reader_false():
|
|
97
|
-
while (chunk:=self.queue_false.get()) is not END:
|
|
92
|
+
|
|
93
|
+
def reader(queue):
|
|
94
|
+
while (chunk := queue.get()) is not END:
|
|
98
95
|
yield chunk
|
|
99
|
-
|
|
96
|
+
|
|
97
|
+
return reader(self.queue_true), reader(self.queue_false)
|
|
100
98
|
|
|
101
99
|
class Streamer:
|
|
102
100
|
|
|
@@ -159,61 +157,27 @@ class StreamLogger(Streamer):
|
|
|
159
157
|
with open(self.file,'a', encoding='utf-8') as f:
|
|
160
158
|
f.write(output)
|
|
161
159
|
|
|
162
|
-
def fenced(start: Union[str,regex.Pattern,re.Pattern], end: Union[str,regex.Pattern,re.Pattern], flags=0) -> regex.Pattern:
|
|
163
|
-
r"""
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
The end delimiter can include backreferences to captured groups from the opening fence into placeholders in the closing fence.
|
|
168
|
-
|
|
169
|
-
The closing fence pattern may include placeholders in the form of "#1#" for the first captured group,
|
|
170
|
-
"#2#" for the second, or "#name#" for a named capturing group; these will be replaced by the appropriate
|
|
171
|
-
backreference. This mechanism is completely generic and does not enforce any specific delimiter shapes.
|
|
172
|
-
|
|
173
|
-
Example:
|
|
174
|
-
# Using a numeric placeholder:
|
|
175
|
-
pattern = fenced(r"START (\w+)", r"END #1#", flags=regex.DOTALL)
|
|
176
|
-
text = "START content END content"
|
|
177
|
-
match = pattern.match(text)
|
|
178
|
-
if match:
|
|
179
|
-
print(match.group())
|
|
180
|
-
|
|
181
|
-
# Using a named capturing group:
|
|
182
|
-
pattern = fenced(r"BEGIN (?P<tag>\w+)", r"FIN #tag#", flags=regex.DOTALL)
|
|
183
|
-
text = "BEGIN example FIN example"
|
|
184
|
-
match = pattern.match(text)
|
|
185
|
-
if match:
|
|
186
|
-
print(match.group())
|
|
187
|
-
|
|
188
|
-
:param start: Opening delimiter (str or regex.Pattern). May contain capturing groups.
|
|
189
|
-
:param end: Closing delimiter (str or regex.Pattern) which may include placeholders (e.g., "#1#", "#name#").
|
|
190
|
-
:param flags: Regex flags to modify behavior (e.g., regex.DOTALL, regex.MULTILINE).
|
|
191
|
-
:return: A compiled regex pattern.
|
|
160
|
+
def fenced(start: Union[str, regex.Pattern, re.Pattern], end: Union[str, regex.Pattern, re.Pattern], flags=0) -> regex.Pattern:
|
|
161
|
+
r"""Compile a fenced-content pattern with the inner text captured as ``content``.
|
|
162
|
+
|
|
163
|
+
``end`` may reference capture groups from ``start`` with ``#1#`` or
|
|
164
|
+
``#name#`` placeholders, which are converted to regex backreferences.
|
|
192
165
|
"""
|
|
193
|
-
def
|
|
194
|
-
if isinstance(pattern, (regex.Pattern,re.Pattern)):
|
|
195
|
-
return pattern.pattern
|
|
166
|
+
def pattern_source(pattern):
|
|
196
167
|
if isinstance(pattern, str):
|
|
197
168
|
return pattern
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
# Replace placeholders in end pattern with regex backrefs
|
|
204
|
-
def replace_placeholder(match):
|
|
169
|
+
if isinstance(pattern, (regex.Pattern, re.Pattern)):
|
|
170
|
+
return pattern.pattern
|
|
171
|
+
raise ValueError("`start` and `end` must be strings or regex Pattern objects")
|
|
172
|
+
|
|
173
|
+
def placeholder_to_backref(match):
|
|
205
174
|
key = match.group(1)
|
|
206
|
-
if key.isdigit()
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
# Named content group
|
|
213
|
-
inner = rf'(?P<content>(?:(?!{end_regex})[\s\S])*)'
|
|
214
|
-
|
|
215
|
-
full_pattern = rf'{start_regex}{inner}(?:{end_regex}|$)'
|
|
216
|
-
return regex.compile(full_pattern, flags)
|
|
175
|
+
return rf"\{key}" if key.isdigit() else rf"(?P={key})"
|
|
176
|
+
|
|
177
|
+
start_regex = pattern_source(start)
|
|
178
|
+
end_regex = regex.sub(r"#(\w+)#", placeholder_to_backref, pattern_source(end))
|
|
179
|
+
inner = rf"(?P<content>(?:(?!{end_regex})[\s\S])*)"
|
|
180
|
+
return regex.compile(rf"{start_regex}{inner}(?:{end_regex}|$)", flags)
|
|
217
181
|
|
|
218
182
|
def is_compiled_pattern(pattern):
|
|
219
183
|
if isinstance(pattern, (re.Pattern,regex.Pattern)):
|
|
@@ -528,16 +492,19 @@ class MappingStreamSplitter:
|
|
|
528
492
|
def maybe_init_readers(self, data):
|
|
529
493
|
for key in data.keys():
|
|
530
494
|
if key not in self.readers:
|
|
531
|
-
self.queues[key]=Queue()
|
|
532
|
-
|
|
533
|
-
|
|
495
|
+
queue = self.queues[key] = Queue()
|
|
496
|
+
|
|
497
|
+
def reader(queue=queue):
|
|
498
|
+
while (value := queue.get()) is not END:
|
|
534
499
|
if isinstance(value, StreamError):
|
|
535
500
|
raise value.error
|
|
536
501
|
yield value
|
|
537
|
-
|
|
502
|
+
|
|
503
|
+
self.readers[key] = reader()
|
|
538
504
|
|
|
539
505
|
def process(self,stream):
|
|
540
|
-
self.
|
|
506
|
+
self.readers = {}
|
|
507
|
+
self.queues = {}
|
|
541
508
|
self.done=False
|
|
542
509
|
if self.defaults:
|
|
543
510
|
self.maybe_init_readers(self.defaults)
|
|
@@ -230,69 +230,39 @@ def type_hierarchy(cls, root):
|
|
|
230
230
|
|
|
231
231
|
|
|
232
232
|
def truncate(string, max_tokens=2000, start_line=1, model=None):
|
|
233
|
-
"""
|
|
234
|
-
Truncate a string to a maximum number of tokens using line-based chunking.
|
|
235
|
-
|
|
236
|
-
Args:
|
|
237
|
-
string: The string to truncate
|
|
238
|
-
max_tokens: Maximum number of tokens to keep
|
|
239
|
-
start_line: The line number of the first line in the string (1-indexed).
|
|
240
|
-
Used for skipping content and tracking line numbers in truncation messages.
|
|
241
|
-
|
|
242
|
-
Returns:
|
|
243
|
-
Truncated string with indication of removed content
|
|
244
|
-
"""
|
|
245
|
-
# line-based pagination
|
|
233
|
+
"""Return a line-preserving prefix that fits within ``max_tokens``."""
|
|
246
234
|
lines = string.splitlines(keepends=True)
|
|
247
|
-
if start_line >
|
|
248
|
-
if start_line >
|
|
249
|
-
return ""
|
|
250
|
-
string = ''.join(lines[start_line - 1:])
|
|
251
|
-
lines = string.splitlines(keepends=True)
|
|
252
|
-
|
|
253
|
-
# Quick check - if already under limit after pagination, return as-is
|
|
254
|
-
tokens = tokenize(string, model=model)
|
|
255
|
-
if len(tokens) <= max_tokens:
|
|
256
|
-
return string
|
|
235
|
+
if start_line > len(lines):
|
|
236
|
+
return "" if start_line > 1 else string
|
|
257
237
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
result_lines = []
|
|
263
|
-
current_tokens = 0
|
|
238
|
+
lines = lines[max(0, start_line - 1):]
|
|
239
|
+
string = "".join(lines)
|
|
240
|
+
if len(tokenize(string, model=model)) <= max_tokens:
|
|
241
|
+
return string
|
|
264
242
|
|
|
243
|
+
kept_lines = []
|
|
244
|
+
kept_tokens = 0
|
|
265
245
|
for line in lines:
|
|
266
246
|
line_tokens = token_count(line, model=model)
|
|
267
|
-
if
|
|
268
|
-
result_lines.append(line)
|
|
269
|
-
current_tokens += line_tokens
|
|
270
|
-
else:
|
|
247
|
+
if kept_tokens + line_tokens > max_tokens:
|
|
271
248
|
break
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return result
|
|
287
|
-
else:
|
|
288
|
-
return f"[Lines {start_line}-{start_line + remaining_lines - 1} truncated - first line exceeds token limit]\n"
|
|
249
|
+
kept_lines.append(line)
|
|
250
|
+
kept_tokens += line_tokens
|
|
251
|
+
|
|
252
|
+
last_line = start_line + len(lines) - 1
|
|
253
|
+
if not kept_lines:
|
|
254
|
+
return f"[Lines {start_line}-{last_line} truncated - first line exceeds token limit]\n"
|
|
255
|
+
|
|
256
|
+
result = "".join(kept_lines)
|
|
257
|
+
first_truncated = start_line + len(kept_lines)
|
|
258
|
+
if first_truncated <= last_line:
|
|
259
|
+
if not result.endswith("\n"):
|
|
260
|
+
result += "\n"
|
|
261
|
+
result += f"\n...\n\n[Lines {first_truncated}-{last_line} truncated]\n"
|
|
262
|
+
return result
|
|
289
263
|
|
|
290
264
|
def pack_msgs(messages):
|
|
291
|
-
|
|
292
|
-
for message in messages:
|
|
293
|
-
text += message.name + ':\n'
|
|
294
|
-
text += message.content.strip() + '\n\n'
|
|
295
|
-
return text
|
|
265
|
+
return "".join(f"{message.name}:\n{message.content.strip()}\n\n" for message in messages)
|
|
296
266
|
|
|
297
267
|
def msg_token_count(msg, model=None):
|
|
298
268
|
return msg.api_token_count(model=model)
|
|
@@ -468,8 +438,11 @@ def upload_file_to_folder(source, folder, name=None):
|
|
|
468
438
|
class Logger:
|
|
469
439
|
|
|
470
440
|
def __init__(self,file=None):
|
|
471
|
-
self.file=file or root_join('logs',"log.txt")
|
|
472
|
-
|
|
441
|
+
self.file = os.fspath(file or root_join('logs', "log.txt"))
|
|
442
|
+
folder = os.path.dirname(self.file)
|
|
443
|
+
if folder:
|
|
444
|
+
os.makedirs(folder, exist_ok=True)
|
|
445
|
+
open(self.file, 'w', encoding='utf-8').close()
|
|
473
446
|
|
|
474
447
|
def log(self,message):
|
|
475
448
|
with open(self.file,'a',encoding="utf-8") as f:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.57"
|
|
@@ -6,7 +6,7 @@ from PIL import Image as PILImage
|
|
|
6
6
|
from codex_agent.message import AssistantMessage, CompactionMessage, DeveloperMessage, InterruptMessage, ServerToolResponseMessage, ToolResultMessage, UserMessage
|
|
7
7
|
from codex_agent import utils
|
|
8
8
|
from codex_agent.image import ImageMessage, image_input_token_count
|
|
9
|
-
from codex_agent.stream_utils import MappingStreamProcessor
|
|
9
|
+
from codex_agent.stream_utils import MappingStreamProcessor, MappingStreamSplitter, Splitter, Tee, fenced
|
|
10
10
|
from codex_agent.utils import split_history_turns
|
|
11
11
|
from codex_agent.utils import project_python_env
|
|
12
12
|
|
|
@@ -29,6 +29,28 @@ def test_split_history_turns_groups_complete_agent_loop():
|
|
|
29
29
|
assert remainder == []
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
def test_pack_msgs_formats_named_messages():
|
|
33
|
+
class Message:
|
|
34
|
+
def __init__(self, name, content):
|
|
35
|
+
self.name = name
|
|
36
|
+
self.content = content
|
|
37
|
+
|
|
38
|
+
messages = [
|
|
39
|
+
Message("user", " hello "),
|
|
40
|
+
Message("assistant", "done"),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
assert utils.pack_msgs(messages) == "user:\nhello\n\nassistant:\ndone\n\n"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_logger_creates_parent_directory_and_truncates_file(tmp_path):
|
|
47
|
+
log_file = tmp_path / "logs" / "log.txt"
|
|
48
|
+
|
|
49
|
+
utils.Logger(file=log_file)
|
|
50
|
+
|
|
51
|
+
assert log_file.read_text(encoding="utf-8") == ""
|
|
52
|
+
|
|
53
|
+
|
|
32
54
|
def test_project_python_env_prefers_running_interpreter():
|
|
33
55
|
env = project_python_env(env={"PATH": "/usr/bin:/bin"}, executable="/opt/project-python/bin/python")
|
|
34
56
|
|
|
@@ -37,6 +59,30 @@ def test_project_python_env_prefers_running_interpreter():
|
|
|
37
59
|
assert env["PYTHON_EXECUTABLE"] == "/opt/project-python/bin/python"
|
|
38
60
|
|
|
39
61
|
|
|
62
|
+
def test_tee_fans_out_stream_to_independent_readers():
|
|
63
|
+
first, second = Tee().tee(iter(["a", "b"]), n=2)
|
|
64
|
+
|
|
65
|
+
assert list(first) == ["a", "b"]
|
|
66
|
+
assert list(second) == ["a", "b"]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_splitter_routes_stream_to_independent_readers():
|
|
70
|
+
even, odd = Splitter(lambda number: number % 2 == 0).split(iter([1, 2, 3, 4]))
|
|
71
|
+
|
|
72
|
+
assert list(even) == [2, 4]
|
|
73
|
+
assert list(odd) == [1, 3]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_mapping_stream_splitter_can_be_reused():
|
|
77
|
+
splitter = MappingStreamSplitter(threaded=False)
|
|
78
|
+
|
|
79
|
+
first = splitter.split(iter([{"text": "a"}]))
|
|
80
|
+
second = splitter.split(iter([{"text": "b"}]))
|
|
81
|
+
|
|
82
|
+
assert list(first["text"]) == ["a"]
|
|
83
|
+
assert list(second["text"]) == ["b"]
|
|
84
|
+
|
|
85
|
+
|
|
40
86
|
def test_mapping_stream_processor_is_direct_passthrough_without_processors():
|
|
41
87
|
source = iter([{"content": "a"}, {"content": "b"}])
|
|
42
88
|
processor = MappingStreamProcessor(processors={"content": []}, threaded=True)
|
|
@@ -47,6 +93,24 @@ def test_mapping_stream_processor_is_direct_passthrough_without_processors():
|
|
|
47
93
|
assert list(result) == [{"content": "a"}, {"content": "b"}]
|
|
48
94
|
|
|
49
95
|
|
|
96
|
+
def test_fenced_reuses_numbered_capture_in_closing_pattern():
|
|
97
|
+
pattern = fenced(r"BEGIN (\w+)\n", r"\nEND #1#")
|
|
98
|
+
|
|
99
|
+
match = pattern.fullmatch("BEGIN tag\nhello\nEND tag")
|
|
100
|
+
|
|
101
|
+
assert match is not None
|
|
102
|
+
assert match.group("content") == "hello"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_fenced_reuses_named_capture_in_closing_pattern():
|
|
106
|
+
pattern = fenced(r"<(?P<tag>\w+)>", r"</#tag#>")
|
|
107
|
+
|
|
108
|
+
match = pattern.fullmatch("<note>hello</note>")
|
|
109
|
+
|
|
110
|
+
assert match is not None
|
|
111
|
+
assert match.group("content") == "hello"
|
|
112
|
+
|
|
113
|
+
|
|
50
114
|
def test_read_document_content_adds_line_numbers_by_default(tmp_path):
|
|
51
115
|
file = tmp_path / "sample.txt"
|
|
52
116
|
file.write_text("alpha\nbeta\ngamma", encoding="utf-8")
|
|
@@ -390,6 +454,28 @@ def test_image_message_token_count_ignores_base64_text_volume():
|
|
|
390
454
|
assert message.estimated_token_count["gpt-5"]["count"] == count
|
|
391
455
|
|
|
392
456
|
|
|
457
|
+
def test_truncate_returns_requested_page_when_under_token_limit(monkeypatch):
|
|
458
|
+
monkeypatch.setattr(utils, "tokenize", lambda text, model=None: list(text))
|
|
459
|
+
monkeypatch.setattr(utils, "token_count", lambda text, model=None: len(text))
|
|
460
|
+
|
|
461
|
+
assert utils.truncate("a\nb\nc\n", max_tokens=10, start_line=2) == "b\nc\n"
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def test_truncate_appends_line_range_notice(monkeypatch):
|
|
465
|
+
monkeypatch.setattr(utils, "tokenize", lambda text, model=None: list(text))
|
|
466
|
+
monkeypatch.setattr(utils, "token_count", lambda text, model=None: len(text))
|
|
467
|
+
|
|
468
|
+
assert utils.truncate("aa\nbb\ncc\n", max_tokens=3) == "aa\n\n...\n\n[Lines 2-3 truncated]\n"
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def test_truncate_reports_first_line_when_it_exceeds_limit(monkeypatch):
|
|
472
|
+
monkeypatch.setattr(utils, "tokenize", lambda text, model=None: list(text))
|
|
473
|
+
monkeypatch.setattr(utils, "token_count", lambda text, model=None: len(text))
|
|
474
|
+
|
|
475
|
+
assert utils.truncate("aaaa\nbb\n", max_tokens=2, start_line=3) == ""
|
|
476
|
+
assert utils.truncate("aaaa\nbb\n", max_tokens=2) == "[Lines 1-2 truncated - first line exceeds token limit]\n"
|
|
477
|
+
|
|
478
|
+
|
|
393
479
|
def test_load_runtime_env_reads_runtime_dotenv(isolated_runtime_dir, monkeypatch):
|
|
394
480
|
isolated_runtime_dir.mkdir(parents=True, exist_ok=True)
|
|
395
481
|
env_file = isolated_runtime_dir / ".env"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.56"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/builtin_commands.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/get_text/__init__.py
RENAMED
|
File without changes
|
{codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/get_text/default_gitignore
RENAMED
|
File without changes
|
{codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/get_text/get_text.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/prompts/system_prompt.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/server/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/codex_agent/tui/stream_debug.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_audio_hooks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_environment.py
RENAMED
|
File without changes
|
{codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_image_generation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_agent_providers_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_agent_framework-0.1.56 → codex_agent_framework-0.1.57}/tests/test_get_text_browser.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|