codex-agent-framework 0.1.58__tar.gz → 0.1.59__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.58 → codex_agent_framework-0.1.59}/CHANGELOG.md +13 -0
- {codex_agent_framework-0.1.58/codex_agent_framework.egg-info → codex_agent_framework-0.1.59}/PKG-INFO +2 -2
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/agent.py +1 -4
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/ai.py +10 -1
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_commands.py +0 -2
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/context/__init__.py +0 -2
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/image_generation/__init__.py +1 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/config.py +1 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/context.py +69 -26
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/message.py +85 -9
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/sessions.py +18 -8
- codex_agent_framework-0.1.59/codex_agent/token_counter.py +115 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/tool.py +86 -2
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/tui/app.py +5 -4
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/tui/chat.py +6 -1
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/tui/state.py +34 -8
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/tui/widgets.py +23 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/utils.py +1 -1
- codex_agent_framework-0.1.59/codex_agent/version.py +1 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59/codex_agent_framework.egg-info}/PKG-INFO +2 -2
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent_framework.egg-info/SOURCES.txt +2 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent_framework.egg-info/requires.txt +1 -1
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/pyproject.toml +2 -2
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_context.py +81 -14
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_chat.py +117 -64
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_messages.py +21 -1
- codex_agent_framework-0.1.59/tests/test_token_counter.py +211 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_utils.py +11 -6
- codex_agent_framework-0.1.58/codex_agent/version.py +0 -1
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/LICENSE +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/MANIFEST.in +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/README.md +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/__main__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/agent_runtime.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/browser.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/bash/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/bash/requirements.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/browser/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/browser/requirements.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/content/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/content/requirements.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/content/system.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/content/vision.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/desktop/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/desktop/requirements.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/environment/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/environment/requirements.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/files/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/files/requirements.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/files/tools.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/image_generation/image_generation_system_prompt.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/image_generation/requirements.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/interface/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/interface/requirements.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/memory/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/memory/requirements.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/planner/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/planner/requirements.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/python/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/python/requirements.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/realtime/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/realtime/audio.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/realtime/bridge.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/realtime/manager.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/realtime/requirements.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/realtime/session.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/scheduler/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/scheduler/requirements.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/subagents/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/subagents/profiles/explorer.json +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/subagents/requirements.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/tts/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/tts/requirements.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_plugins/web_search/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/cli/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/cli/headless.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/cli/main.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/cli/output.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/cli/root.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/cli/runner.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/client.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/command.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/desktop.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/event.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/get_text/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/get_text/default_gitignore +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/get_text/get_text.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/get_text/simpler_get_text.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/hooks.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/image.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/latex.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/mainloop.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/plugin.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/prompts/system_prompt.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/provider.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/runtime.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/scheduler.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/scripts/install-system-dependencies.sh +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/server/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/server/app.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/server/core.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/service.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/status.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/stream.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/stream_utils.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/tray.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/tui/__init__.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/tui/lifecycle.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/tui/log.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/tui/perf.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/tui/status_bar.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/tui/stream_debug.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/tui/style.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/tui/transcript.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/worker.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent_framework.egg-info/entry_points.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent_framework.egg-info/top_level.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/dependencies.txt +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/setup.cfg +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_audio_hooks.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_commands.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_desktop.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_environment.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_image_generation.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_observe.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_plugins.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_providers_config.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_realtime.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_response.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_runtime.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_sessions.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_subagents.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_tools.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_agent_turns.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_ai.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_browser.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_builtin_config.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_cli.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_cli_headless.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_cli_root.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_client.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_events.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_get_text_browser.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_hooks.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_image_message.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_local_desktop.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_memory.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_planner.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_scheduler.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_server.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_service.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_tui.py +0 -0
- {codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/tests/test_worker.py +0 -0
|
@@ -4,6 +4,19 @@ 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.59] - 2026-05-18
|
|
8
|
+
### Changed
|
|
9
|
+
- Make local context sizing rely on cached local token estimation for messages and tool schemas, with bounded smoothing against observed backend `usage.input_tokens` so status and auto-compaction decisions track reality more closely without a runtime token-counter dependency.
|
|
10
|
+
- Default session compaction to `gpt-5.4-mini` and stop injecting the main agent instructions into compaction requests unless explicit compaction instructions are provided.
|
|
11
|
+
- Bake stable token-count estimates for server tools and keep compact-session usage metadata available for later context accounting.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Preserve compaction usage data end-to-end in agent session state so compacted history can reuse backend-observed token accounting when available.
|
|
15
|
+
- Raise the minimum SDK dependency to `codex-backend-sdk>=0.3.5` so compact response usage is available to the agent runtime.
|
|
16
|
+
|
|
17
|
+
### Tests
|
|
18
|
+
- Add and update focused coverage for local token estimation, correction coefficient behavior, compaction defaults/overrides, compact-session usage accounting, and baked tool token counts.
|
|
19
|
+
|
|
7
20
|
## [0.1.58] - 2026-05-17
|
|
8
21
|
### Fixed
|
|
9
22
|
- Keep active SSE event subscribers attached even when the FastAPI request handler thread that opened the `StreamingResponse` has ended, preventing TUI streams from degrading into ping-only connections that miss real assistant/tool events.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-agent-framework
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.59
|
|
4
4
|
Summary: A lightweight event-driven Codex agent runtime.
|
|
5
5
|
Author: Baptiste
|
|
6
6
|
License-Expression: MIT
|
|
@@ -16,7 +16,7 @@ Description-Content-Type: text/markdown
|
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: audioop-lts; python_version >= "3.13"
|
|
18
18
|
Requires-Dist: beautifulsoup4
|
|
19
|
-
Requires-Dist: codex-backend-sdk>=0.3.
|
|
19
|
+
Requires-Dist: codex-backend-sdk>=0.3.5
|
|
20
20
|
Requires-Dist: fastapi
|
|
21
21
|
Requires-Dist: filetype
|
|
22
22
|
Requires-Dist: modict
|
|
@@ -461,10 +461,7 @@ class Agent:
|
|
|
461
461
|
self.status_manager.invalidate_context_cache()
|
|
462
462
|
messages = self.context_manager.get_context()
|
|
463
463
|
if self.context_manager.should_auto_compact(messages):
|
|
464
|
-
self.compact_session(
|
|
465
|
-
model=self.config.model,
|
|
466
|
-
instructions=self.config_manager.system_message().content,
|
|
467
|
-
)
|
|
464
|
+
self.compact_session()
|
|
468
465
|
messages = self.context_manager.get_context()
|
|
469
466
|
|
|
470
467
|
message = self.ai.run(
|
|
@@ -248,6 +248,10 @@ class AIClient:
|
|
|
248
248
|
|
|
249
249
|
try:
|
|
250
250
|
response_params = self._to_codex_response_params(params)
|
|
251
|
+
raw_input_estimate = self.agent.context_manager.raw_response_input_token_count(
|
|
252
|
+
params.get("messages", []),
|
|
253
|
+
response_tools=params.get("tools") or [],
|
|
254
|
+
)
|
|
251
255
|
response = self.codex_client.responses.create(**response_params, stream=True)
|
|
252
256
|
except Exception as e:
|
|
253
257
|
if self.is_bad_request_error(e):
|
|
@@ -265,8 +269,13 @@ class AIClient:
|
|
|
265
269
|
self.agent.mainloop_manager.touch_activity("response_stream")
|
|
266
270
|
if getattr(event, "type", None) == "response.completed":
|
|
267
271
|
completed = self._event_response(event)
|
|
272
|
+
usage = modict(completed.get("usage") or {})
|
|
273
|
+
self.agent.context_manager.apply_token_count_correction(
|
|
274
|
+
raw_input_estimate,
|
|
275
|
+
usage.get("input_tokens"),
|
|
276
|
+
)
|
|
268
277
|
self.agent.status_manager.cache_api_status(
|
|
269
|
-
usage=
|
|
278
|
+
usage=usage,
|
|
270
279
|
model=completed.get("model") or response_params.get("model"),
|
|
271
280
|
quota=self.get_quota_usage(),
|
|
272
281
|
)
|
{codex_agent_framework-0.1.58 → codex_agent_framework-0.1.59}/codex_agent/builtin_commands.py
RENAMED
|
@@ -38,8 +38,6 @@ def compact(max_compacted_turns=None):
|
|
|
38
38
|
agent = get_agent()
|
|
39
39
|
max_compacted_turns = None if max_compacted_turns in (None, "", "all") else int(max_compacted_turns)
|
|
40
40
|
message = agent.compact_session(
|
|
41
|
-
model=agent.config.model,
|
|
42
|
-
instructions=agent.config_manager.system_message().content,
|
|
43
41
|
max_compacted_turns=max_compacted_turns,
|
|
44
42
|
)
|
|
45
43
|
if message is None:
|
|
@@ -117,8 +117,6 @@ Tool result wrappers expose stable `wrapper_id` values in their XML representati
|
|
|
117
117
|
max_compacted_turns = int(max_compacted_turns)
|
|
118
118
|
before = self.context_public_status()
|
|
119
119
|
message = self.agent.compact_session(
|
|
120
|
-
model=self.agent.config.model,
|
|
121
|
-
instructions=self.agent.config_manager.system_message().content,
|
|
122
120
|
max_compacted_turns=max_compacted_turns,
|
|
123
121
|
)
|
|
124
122
|
after = self.context_public_status()
|
|
@@ -3,12 +3,17 @@ from modict import modict
|
|
|
3
3
|
from .image import ImageMessage
|
|
4
4
|
from .message import Message, ProviderMessage, SystemMessage, ToolResultWrapper
|
|
5
5
|
from .tool import reset_current_agent, set_current_agent
|
|
6
|
-
from .utils import sort, token_count_payload, truncate
|
|
6
|
+
from .utils import sort, token_count, token_count_payload, truncate
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class ContextManager:
|
|
10
|
+
TOKEN_COUNT_COEF_MIN = 0.5
|
|
11
|
+
TOKEN_COUNT_COEF_MAX = 2.0
|
|
12
|
+
TOKEN_COUNT_COEF_ALPHA = 0.8
|
|
13
|
+
|
|
10
14
|
def __init__(self, agent):
|
|
11
15
|
self.agent = agent
|
|
16
|
+
self.token_count_coef = 1.0
|
|
12
17
|
|
|
13
18
|
def provider_context_wrapper(self, provider_name, content):
|
|
14
19
|
return f'<context_provider name="{provider_name}">\n{content}\n</context_provider>'
|
|
@@ -149,13 +154,6 @@ class ContextManager:
|
|
|
149
154
|
compactable_messages = 0
|
|
150
155
|
compactable_tokens = 0
|
|
151
156
|
token_count_source = "local_estimate"
|
|
152
|
-
try:
|
|
153
|
-
current_count = self.agent.ai.count_input_tokens(
|
|
154
|
-
**self.response_input_token_payload(formatted_context, response_tools=response_tools)
|
|
155
|
-
)
|
|
156
|
-
token_count_source = "openai_input_tokens"
|
|
157
|
-
except Exception:
|
|
158
|
-
pass
|
|
159
157
|
input_token_limit = self.agent.config.input_token_limit
|
|
160
158
|
used_token_percent = (current_count / input_token_limit * 100) if input_token_limit else 0
|
|
161
159
|
sent_session_message_percent = (
|
|
@@ -216,11 +214,25 @@ class ContextManager:
|
|
|
216
214
|
return msg.get("turns_left", -1) != 0
|
|
217
215
|
|
|
218
216
|
def response_tools_token_count(self, response_tools=None):
|
|
217
|
+
model = self.agent.config.model
|
|
219
218
|
if response_tools is None:
|
|
220
|
-
|
|
219
|
+
return self.agent.tools_manager.response_tools_token_count(model=model)
|
|
221
220
|
if not response_tools:
|
|
222
221
|
return 0
|
|
223
|
-
|
|
222
|
+
tools = self.agent.tools_manager.get_tools()
|
|
223
|
+
by_name = {
|
|
224
|
+
(tool.get("name") or tool.get("type")): tool
|
|
225
|
+
for tool in tools
|
|
226
|
+
}
|
|
227
|
+
total = 0
|
|
228
|
+
for spec in response_tools:
|
|
229
|
+
name = spec.get("name") or spec.get("type")
|
|
230
|
+
tool = by_name.get(name)
|
|
231
|
+
if tool is not None:
|
|
232
|
+
total += tool.count_tokens(model=model)
|
|
233
|
+
else:
|
|
234
|
+
total += token_count_payload(spec, model=model)
|
|
235
|
+
return int(total)
|
|
224
236
|
|
|
225
237
|
def response_input_token_payload(self, messages, response_tools=None):
|
|
226
238
|
instructions = []
|
|
@@ -280,23 +292,54 @@ class ContextManager:
|
|
|
280
292
|
|
|
281
293
|
return sanitize(payload)
|
|
282
294
|
|
|
283
|
-
def
|
|
284
|
-
|
|
285
|
-
if
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
295
|
+
def estimate_token_count(self, raw_count):
|
|
296
|
+
raw_count = int(raw_count or 0)
|
|
297
|
+
if raw_count <= 0:
|
|
298
|
+
return 0
|
|
299
|
+
return max(0, int(round(raw_count * self.token_count_coef)))
|
|
300
|
+
|
|
301
|
+
def apply_token_count_correction(self, raw_estimate, observed_input_tokens):
|
|
302
|
+
raw_estimate = int(raw_estimate or 0)
|
|
303
|
+
observed_input_tokens = int(observed_input_tokens or 0)
|
|
304
|
+
if raw_estimate <= 0 or observed_input_tokens <= 0:
|
|
305
|
+
return self.token_count_coef
|
|
306
|
+
observed_ratio = observed_input_tokens / raw_estimate
|
|
307
|
+
observed_ratio = max(self.TOKEN_COUNT_COEF_MIN, min(self.TOKEN_COUNT_COEF_MAX, observed_ratio))
|
|
308
|
+
new_coef = (
|
|
309
|
+
self.token_count_coef * self.TOKEN_COUNT_COEF_ALPHA
|
|
310
|
+
+ observed_ratio * (1.0 - self.TOKEN_COUNT_COEF_ALPHA)
|
|
298
311
|
)
|
|
299
|
-
|
|
312
|
+
self.token_count_coef = max(self.TOKEN_COUNT_COEF_MIN, min(self.TOKEN_COUNT_COEF_MAX, new_coef))
|
|
313
|
+
return self.token_count_coef
|
|
314
|
+
|
|
315
|
+
def raw_response_input_token_count(self, messages, response_tools=None):
|
|
316
|
+
model = self.agent.config.model
|
|
317
|
+
instructions = []
|
|
318
|
+
history_messages = []
|
|
319
|
+
in_instruction_prefix = True
|
|
320
|
+
for msg in messages:
|
|
321
|
+
if in_instruction_prefix and isinstance(msg, SystemMessage):
|
|
322
|
+
instruction = msg.instruction_text()
|
|
323
|
+
if instruction:
|
|
324
|
+
instructions.append(instruction)
|
|
325
|
+
continue
|
|
326
|
+
in_instruction_prefix = False
|
|
327
|
+
history_messages.append(msg)
|
|
328
|
+
|
|
329
|
+
instruction_tokens = 0
|
|
330
|
+
if instructions:
|
|
331
|
+
instruction_tokens = token_count("\n\n".join(instructions), model=model)
|
|
332
|
+
|
|
333
|
+
history_tokens = sum(msg.count_tokens(model=model) for msg in history_messages)
|
|
334
|
+
tool_tokens = self.response_tools_token_count(response_tools=response_tools)
|
|
335
|
+
reasoning = self.agent.config.get("reasoning")
|
|
336
|
+
reasoning_tokens = token_count_payload({"reasoning": modict(reasoning)}, model=model) if reasoning else 0
|
|
337
|
+
tool_choice_tokens = token_count_payload({"tool_choice": "auto"}, model=model) if response_tools else 0
|
|
338
|
+
return instruction_tokens + history_tokens + tool_tokens + reasoning_tokens + tool_choice_tokens
|
|
339
|
+
|
|
340
|
+
def response_input_token_count(self, messages, response_tools=None):
|
|
341
|
+
raw_count = self.raw_response_input_token_count(messages, response_tools=response_tools)
|
|
342
|
+
return self.estimate_token_count(raw_count)
|
|
300
343
|
|
|
301
344
|
def should_auto_compact(self, messages):
|
|
302
345
|
if not self.agent.config.get("auto_compact", True):
|
|
@@ -36,6 +36,7 @@ class Message(modict):
|
|
|
36
36
|
timestamp = modict.factory(lambda: timestamp())
|
|
37
37
|
turns_left = -1
|
|
38
38
|
embedding = None
|
|
39
|
+
token_count = modict.factory(dict)
|
|
39
40
|
estimated_token_count = modict.factory(dict)
|
|
40
41
|
|
|
41
42
|
def __init__(self, *args, **kwargs):
|
|
@@ -91,24 +92,51 @@ class Message(modict):
|
|
|
91
92
|
def to_response_format(self):
|
|
92
93
|
raise NotImplementedError(f"{type(self).__name__} must implement to_response_format()")
|
|
93
94
|
|
|
94
|
-
def
|
|
95
|
+
def token_count_cache(self):
|
|
96
|
+
cache = self.get("token_count")
|
|
97
|
+
if isinstance(cache, dict) and cache:
|
|
98
|
+
return cache
|
|
99
|
+
legacy = self.get("estimated_token_count")
|
|
100
|
+
if isinstance(legacy, dict) and legacy:
|
|
101
|
+
self.token_count = dict(legacy)
|
|
102
|
+
return self.token_count
|
|
103
|
+
self.token_count = {}
|
|
104
|
+
return self.token_count
|
|
105
|
+
|
|
106
|
+
def cache_token_count(self, count, model=None):
|
|
107
|
+
model_key = str(model or "gpt-5")
|
|
108
|
+
cache = self.token_count_cache()
|
|
109
|
+
cache[model_key] = {
|
|
110
|
+
"fingerprint": self.api_token_fingerprint(),
|
|
111
|
+
"count": int(count or 0),
|
|
112
|
+
}
|
|
113
|
+
self.token_count = cache
|
|
114
|
+
self.estimated_token_count = dict(cache)
|
|
115
|
+
return int(count or 0)
|
|
116
|
+
|
|
117
|
+
def count_tokens(self, model=None):
|
|
95
118
|
model_key = str(model or "gpt-5")
|
|
96
119
|
fingerprint = self.api_token_fingerprint()
|
|
97
|
-
cache = self.
|
|
120
|
+
cache = self.token_count_cache()
|
|
98
121
|
cached = cache.get(model_key)
|
|
99
122
|
if isinstance(cached, dict) and cached.get("fingerprint") == fingerprint:
|
|
100
123
|
return int(cached.get("count") or 0)
|
|
101
124
|
|
|
102
|
-
count = self.compute_api_token_count(model=model)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
125
|
+
count = int(self.compute_api_token_count(model=model) or 0)
|
|
126
|
+
return self.cache_token_count(count, model=model)
|
|
127
|
+
|
|
128
|
+
def api_token_count(self, model=None):
|
|
129
|
+
return self.count_tokens(model=model)
|
|
106
130
|
|
|
107
131
|
@modict.computed()
|
|
108
|
-
def
|
|
132
|
+
def effective_token_count(self):
|
|
109
133
|
if self.get("turns_left", -1) == 0:
|
|
110
134
|
return 0
|
|
111
|
-
return self.
|
|
135
|
+
return self.count_tokens()
|
|
136
|
+
|
|
137
|
+
@modict.computed()
|
|
138
|
+
def effective_api_token_count(self):
|
|
139
|
+
return self.effective_token_count
|
|
112
140
|
|
|
113
141
|
def api_token_fingerprint(self):
|
|
114
142
|
payload = {
|
|
@@ -120,10 +148,21 @@ class Message(modict):
|
|
|
120
148
|
text = json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"), default=str)
|
|
121
149
|
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
122
150
|
|
|
151
|
+
def token_count_request(self, model=None):
|
|
152
|
+
item = self.to_response_format()
|
|
153
|
+
if item is None:
|
|
154
|
+
return None
|
|
155
|
+
if isinstance(item, list):
|
|
156
|
+
return {"model": str(model or "gpt-5"), "input": item}
|
|
157
|
+
return {"model": str(model or "gpt-5"), "input": [item]}
|
|
158
|
+
|
|
159
|
+
def token_count_payload(self):
|
|
160
|
+
return self.to_response_format()
|
|
161
|
+
|
|
123
162
|
def compute_api_token_count(self, model=None):
|
|
124
163
|
from .utils import token_count, token_count_payload
|
|
125
164
|
|
|
126
|
-
item = self.
|
|
165
|
+
item = self.token_count_payload()
|
|
127
166
|
if item is None:
|
|
128
167
|
return token_count(str(self.get("content") or ""), model=model)
|
|
129
168
|
return token_count_payload(item, model=model)
|
|
@@ -190,6 +229,9 @@ class SystemMessage(TextMessage):
|
|
|
190
229
|
parts.append(str(part))
|
|
191
230
|
return "\n".join(part for part in parts if part)
|
|
192
231
|
|
|
232
|
+
def token_count_request(self, model=None):
|
|
233
|
+
return {"model": str(model or "gpt-5"), "instructions": self.instruction_text()}
|
|
234
|
+
|
|
193
235
|
def compute_api_token_count(self, model=None):
|
|
194
236
|
from .utils import token_count
|
|
195
237
|
|
|
@@ -218,6 +260,7 @@ class CompactionMessage(DeveloperMessage):
|
|
|
218
260
|
content = "Previous conversation context was compacted by the backend."
|
|
219
261
|
response_id = ""
|
|
220
262
|
summaries = modict.factory(list)
|
|
263
|
+
usage = modict.factory(modict)
|
|
221
264
|
compacted_message_count = 0
|
|
222
265
|
|
|
223
266
|
def __init__(self, *args, **kwargs):
|
|
@@ -238,6 +281,39 @@ class CompactionMessage(DeveloperMessage):
|
|
|
238
281
|
def to_response_format(self):
|
|
239
282
|
return list(self.get("summaries") or [])
|
|
240
283
|
|
|
284
|
+
def observed_token_count(self):
|
|
285
|
+
usage = self.get("usage") or {}
|
|
286
|
+
if not isinstance(usage, dict):
|
|
287
|
+
return None
|
|
288
|
+
for key in ("input_tokens", "max_input_tokens"):
|
|
289
|
+
value = usage.get(key)
|
|
290
|
+
if isinstance(value, int) and value >= 0:
|
|
291
|
+
return value
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
def count_tokens(self, model=None):
|
|
295
|
+
observed = self.observed_token_count()
|
|
296
|
+
if observed is not None:
|
|
297
|
+
return int(observed)
|
|
298
|
+
return super().count_tokens(model=model)
|
|
299
|
+
|
|
300
|
+
def token_count_request(self, model=None):
|
|
301
|
+
if self.observed_token_count() is not None:
|
|
302
|
+
return None
|
|
303
|
+
payload = self.token_count_payload()
|
|
304
|
+
if not payload:
|
|
305
|
+
return {"model": str(model or "gpt-5"), "input": []}
|
|
306
|
+
return {"model": str(model or "gpt-5"), "input": payload}
|
|
307
|
+
|
|
308
|
+
def token_count_payload(self):
|
|
309
|
+
sanitized = []
|
|
310
|
+
for item in self.get("summaries") or []:
|
|
311
|
+
entry = dict(item)
|
|
312
|
+
if entry.get("type") == "compaction_summary" and entry.get("encrypted_content"):
|
|
313
|
+
entry["encrypted_content"] = "<encrypted_compaction_summary>"
|
|
314
|
+
sanitized.append(entry)
|
|
315
|
+
return sanitized
|
|
316
|
+
|
|
241
317
|
def as_string_attrs(self):
|
|
242
318
|
attrs = self.extract('name', 'type', 'timestamp')
|
|
243
319
|
attrs["response_id"] = self.get("response_id")
|
|
@@ -151,7 +151,7 @@ class AgentSession(modict):
|
|
|
151
151
|
self,
|
|
152
152
|
client=None,
|
|
153
153
|
model="gpt-5.4",
|
|
154
|
-
instructions=
|
|
154
|
+
instructions=None,
|
|
155
155
|
max_input_tokens=DEFAULT_COMPACTION_MAX_INPUT_TOKENS,
|
|
156
156
|
max_compacted_turns=None,
|
|
157
157
|
):
|
|
@@ -165,15 +165,26 @@ class AgentSession(modict):
|
|
|
165
165
|
return None
|
|
166
166
|
|
|
167
167
|
history = self.messages_to_response_history(plan.history_messages)
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
client_kwargs = modict(model=model)
|
|
169
|
+
if instructions is not None:
|
|
170
|
+
client_kwargs.instructions = instructions
|
|
171
|
+
client = client or CodexClient(**client_kwargs).authenticate()
|
|
172
|
+
compact_params = modict(
|
|
170
173
|
input=history,
|
|
171
174
|
model=model,
|
|
172
|
-
instructions=instructions,
|
|
173
175
|
)
|
|
176
|
+
if instructions is not None:
|
|
177
|
+
compact_params.instructions = instructions
|
|
178
|
+
result = client.responses.compact(**compact_params)
|
|
179
|
+
usage = getattr(result, "usage", None)
|
|
180
|
+
if hasattr(usage, "model_dump"):
|
|
181
|
+
usage = usage.model_dump()
|
|
182
|
+
elif usage is not None and not isinstance(usage, dict):
|
|
183
|
+
usage = dict(usage)
|
|
174
184
|
message = CompactionMessage(
|
|
175
185
|
response_id=result.id,
|
|
176
186
|
summaries=CompactionMessage.summary_items(result.output),
|
|
187
|
+
usage=usage or {},
|
|
177
188
|
compacted_message_count=len(compacted_messages),
|
|
178
189
|
compacted_turn_count=plan.compacted_turn_count,
|
|
179
190
|
preserved_turn_count=plan.preserved_turn_count,
|
|
@@ -797,9 +808,7 @@ class SessionsManager:
|
|
|
797
808
|
max_compacted_turns=max_compacted_turns,
|
|
798
809
|
).wait().result
|
|
799
810
|
|
|
800
|
-
model = model or self.agent.config.model
|
|
801
|
-
if instructions is None:
|
|
802
|
-
instructions = self.agent.config_manager.system_message().content
|
|
811
|
+
model = model or self.agent.config.get("compaction_model") or self.agent.config.model
|
|
803
812
|
max_input_tokens = self.agent.config.get(
|
|
804
813
|
"max_input_tokens",
|
|
805
814
|
DEFAULT_COMPACTION_MAX_INPUT_TOKENS,
|
|
@@ -821,9 +830,10 @@ class SessionsManager:
|
|
|
821
830
|
)
|
|
822
831
|
compact_kwargs = modict(
|
|
823
832
|
model=model,
|
|
824
|
-
instructions=instructions,
|
|
825
833
|
max_input_tokens=max_input_tokens,
|
|
826
834
|
)
|
|
835
|
+
if instructions is not None:
|
|
836
|
+
compact_kwargs.instructions = instructions
|
|
827
837
|
if max_compacted_turns is not None:
|
|
828
838
|
compact_kwargs.max_compacted_turns = max_compacted_turns
|
|
829
839
|
if client is not None:
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
class TokenCounter:
|
|
2
|
+
"""Count tokens for a single object using minimal Responses input_tokens requests.
|
|
3
|
+
|
|
4
|
+
The counter is object-oriented: each supported object provides a
|
|
5
|
+
``token_count_request(model=...)`` projection and stores its own cached
|
|
6
|
+
result locally in ``token_count``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, ai, default_model="gpt-5", baseline_text="PING"):
|
|
10
|
+
self.ai = ai
|
|
11
|
+
self.default_model = default_model
|
|
12
|
+
self.baseline_text = baseline_text
|
|
13
|
+
self._baseline_counts = {}
|
|
14
|
+
|
|
15
|
+
def count_object(self, obj, model=None):
|
|
16
|
+
model = str(model or self.default_model or "gpt-5")
|
|
17
|
+
|
|
18
|
+
cached = self._cached_count(obj, model=model)
|
|
19
|
+
if cached is not None:
|
|
20
|
+
return cached
|
|
21
|
+
|
|
22
|
+
observed = self._observed_count(obj)
|
|
23
|
+
if observed is not None:
|
|
24
|
+
return self._store_count(obj, observed, model=model)
|
|
25
|
+
|
|
26
|
+
request = getattr(obj, "token_count_request", lambda model=None: None)(model=model)
|
|
27
|
+
if request is None:
|
|
28
|
+
if hasattr(obj, "count_tokens"):
|
|
29
|
+
return int(obj.count_tokens(model=model))
|
|
30
|
+
raise TypeError(f"{type(obj).__name__} does not support token counting")
|
|
31
|
+
|
|
32
|
+
if self._needs_baseline(request):
|
|
33
|
+
baseline = self._baseline_request(model=model)
|
|
34
|
+
baseline_count = self._baseline_count(model=model)
|
|
35
|
+
payload = dict(request)
|
|
36
|
+
payload["input"] = baseline["input"]
|
|
37
|
+
count = int(self.ai.count_input_tokens(**payload)) - baseline_count
|
|
38
|
+
else:
|
|
39
|
+
count = int(self.ai.count_input_tokens(**request))
|
|
40
|
+
return self._store_count(obj, count, model=model)
|
|
41
|
+
|
|
42
|
+
def hydrate(self, objects, model=None):
|
|
43
|
+
return [self.count_object(obj, model=model) for obj in objects]
|
|
44
|
+
|
|
45
|
+
def _baseline_request(self, model=None):
|
|
46
|
+
return {
|
|
47
|
+
"model": str(model or self.default_model or "gpt-5"),
|
|
48
|
+
"input": [{
|
|
49
|
+
"type": "message",
|
|
50
|
+
"role": "user",
|
|
51
|
+
"content": [{"type": "input_text", "text": self.baseline_text}],
|
|
52
|
+
}],
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def _baseline_count(self, model=None):
|
|
56
|
+
model_key = str(model or self.default_model or "gpt-5")
|
|
57
|
+
cached = self._baseline_counts.get(model_key)
|
|
58
|
+
if cached is not None:
|
|
59
|
+
return int(cached)
|
|
60
|
+
count = int(self.ai.count_input_tokens(**self._baseline_request(model=model_key)))
|
|
61
|
+
self._baseline_counts[model_key] = count
|
|
62
|
+
return count
|
|
63
|
+
|
|
64
|
+
def _needs_baseline(self, request):
|
|
65
|
+
if not isinstance(request, dict):
|
|
66
|
+
return False
|
|
67
|
+
payload_input = request.get("input")
|
|
68
|
+
if payload_input:
|
|
69
|
+
return False
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
def _observed_count(self, obj):
|
|
73
|
+
observed = getattr(obj, "observed_token_count", None)
|
|
74
|
+
if callable(observed):
|
|
75
|
+
value = observed()
|
|
76
|
+
if value is not None:
|
|
77
|
+
return int(value)
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
def _fingerprint(self, obj):
|
|
81
|
+
if hasattr(obj, "api_token_fingerprint"):
|
|
82
|
+
return obj.api_token_fingerprint()
|
|
83
|
+
if hasattr(obj, "response_tool_fingerprint"):
|
|
84
|
+
return obj.response_tool_fingerprint()
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
def _cache_map(self, obj):
|
|
88
|
+
token_count_cache = getattr(obj, "token_count_cache", None)
|
|
89
|
+
if callable(token_count_cache):
|
|
90
|
+
return token_count_cache()
|
|
91
|
+
getter = getattr(obj, "get", None)
|
|
92
|
+
if callable(getter):
|
|
93
|
+
cache = getter("token_count") or {}
|
|
94
|
+
return cache if isinstance(cache, dict) else {}
|
|
95
|
+
return {}
|
|
96
|
+
|
|
97
|
+
def _cached_count(self, obj, model=None):
|
|
98
|
+
model_key = str(model or self.default_model or "gpt-5")
|
|
99
|
+
fingerprint = self._fingerprint(obj)
|
|
100
|
+
cache = self._cache_map(obj)
|
|
101
|
+
cached = cache.get(model_key)
|
|
102
|
+
if not isinstance(cached, dict):
|
|
103
|
+
return None
|
|
104
|
+
if fingerprint is not None and cached.get("fingerprint") != fingerprint:
|
|
105
|
+
return None
|
|
106
|
+
if "count" not in cached:
|
|
107
|
+
return None
|
|
108
|
+
return int(cached.get("count") or 0)
|
|
109
|
+
|
|
110
|
+
def _store_count(self, obj, count, model=None):
|
|
111
|
+
count = int(count or 0)
|
|
112
|
+
cache_method = getattr(obj, "cache_token_count", None)
|
|
113
|
+
if callable(cache_method):
|
|
114
|
+
return int(cache_method(count, model=model))
|
|
115
|
+
return count
|