python-codex 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.
- {python_codex-0.1.12 → python_codex-0.1.13}/.gitignore +1 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/AGENTS.md +3 -1
- {python_codex-0.1.12 → python_codex-0.1.13}/PKG-INFO +1 -1
- {python_codex-0.1.12 → python_codex-0.1.13}/docs/ALIGNMENT.md +1 -1
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/__init__.py +10 -8
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/agent.py +41 -17
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/cli.py +198 -145
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/compat.py +8 -4
- python_codex-0.1.13/pycodex/feishu_card.py +693 -0
- python_codex-0.1.13/pycodex/feishu_link.py +342 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/model.py +89 -7
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/prompts/models.json +4 -4
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/protocol.py +17 -17
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/runtime.py +9 -14
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/runtime_services.py +45 -23
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/apply_patch_tool.py +11 -12
- python_codex-0.1.13/pycodex/tools/ipython_tool.py +144 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/unified_exec_manager.py +3 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/utils/__init__.py +2 -13
- python_codex-0.1.13/pycodex/utils/async_bridge.py +54 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/utils/compactor.py +22 -9
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/utils/session_persist.py +57 -38
- python_codex-0.1.13/pycodex/utils/toolcall_visualize.py +713 -0
- python_codex-0.1.13/pycodex/utils/visualize.py +534 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pyproject.toml +1 -1
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/compare_request_user_input_roundtrip.py +114 -100
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/test_agent.py +199 -27
- python_codex-0.1.13/tests/test_async_bridge.py +38 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/test_builtin_tools.py +183 -8
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/test_cli.py +339 -289
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/test_context.py +4 -4
- python_codex-0.1.13/tests/test_feishu_card.py +338 -0
- python_codex-0.1.13/tests/test_feishu_link.py +25 -0
- python_codex-0.1.13/tests/test_ipython_tool.py +121 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/test_model.py +101 -0
- python_codex-0.1.13/tests/test_visualize.py +37 -0
- python_codex-0.1.13/tools/feishu_oauth.py +188 -0
- python_codex-0.1.12/pycodex/utils/visualize.py +0 -1172
- {python_codex-0.1.12 → python_codex-0.1.13}/.github/workflows/publish.yml +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/.github/workflows/test.yml +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/LICENSE +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/README.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/README_ZH.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/docs/CONTEXT.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/docs/responses_server/README.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/collaboration.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/context.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/doctor.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/portable.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/portable_server.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/prompts/collaboration_default.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/prompts/collaboration_plan.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/prompts/default_base_instructions.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/prompts/exec_tools.json +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/prompts/permissions/approval_policy/never.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/prompts/permissions/approval_policy/on_failure.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/prompts/permissions/approval_policy/on_request.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/prompts/permissions/approval_policy/on_request_rule_request_permission.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/prompts/permissions/approval_policy/unless_trusted.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/prompts/permissions/sandbox_mode/danger_full_access.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/prompts/permissions/sandbox_mode/read_only.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/prompts/permissions/sandbox_mode/workspace_write.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/prompts/subagent_tools.json +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/__init__.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/agent_tool_schemas.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/base_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/close_agent_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/code_mode_manager.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/exec_command_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/exec_runtime.js +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/exec_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/grep_files_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/list_dir_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/read_file_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/request_permissions_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/request_user_input_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/resume_agent_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/send_input_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/shell_command_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/shell_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/spawn_agent_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/update_plan_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/view_image_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/wait_agent_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/wait_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/web_search_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/tools/write_stdin_tool.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/utils/debug.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/utils/dotenv.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/utils/get_env.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/pycodex/utils/random_ids.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/responses_server/__init__.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/responses_server/__main__.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/responses_server/app.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/responses_server/config.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/responses_server/messages_api.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/responses_server/payload_processors.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/responses_server/server.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/responses_server/session_store.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/responses_server/stream_router.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/responses_server/tools/__init__.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/responses_server/tools/custom_adapter.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/responses_server/tools/web_search.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/responses_server/trajectory_dump.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/TESTS.md +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/__init__.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/compare_steer_request_bodies.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/compare_tool_schemas.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/fake_responses_server.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/fakes.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/responses_server/fake_chat_completions_server.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/responses_server/test_server.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/test_compactor.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/test_doctor.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/test_fake_responses_server.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/test_portable.py +0 -0
- {python_codex-0.1.12 → python_codex-0.1.13}/tests/test_py36_syntax.py +0 -0
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
- 对需要 model-specific prompt 的本地 model slug,直接在 vendored `pycodex/prompts/models.json` 补条目;当前 `step-3.5-flash` / `step-3.5-flash-2603` / `step-3.6` 已按这个方式接入。
|
|
24
24
|
- 交互 REPL 的 context 用量提示也应尽量贴近上游语义:展示“剩余 context 百分比”而不是原始 token 数;计算时按上游同款 `BASELINE_TOKENS=12000` 做归一化,并在模型元数据只有 `context_window` 时默认按 `95%` effective window 处理。只要当前模型能解析出 context window,初始 prompt 就先显示 `100%`,等首个 usage 回来后再刷新成真实值。
|
|
25
25
|
- 对交互 REPL 的 context 指示器,`model_context_window` 的取值优先级也要贴近上游:先吃 `config.toml` / profile 里的 `model_context_window` override,再回退到 vendored `models.json` 的 `context_window`;effective percent 继续沿用模型元数据,没有时默认 `95%`。
|
|
26
|
-
- `pyco(<percent>)` 正常只来自模型流里最近一次 `response.completed.response.usage.total_tokens`;如果大 tool output 之后的下一次请求被下游 `context_length_exceeded` 拒绝,rollout 不会单独记录 usage。遇到这类错误时应从错误文案的 `requested ... tokens (... in the messages, ... in the completion)` 提取真实请求 token,作为失败请求的 `token_count` 事件回灌,并立即触发 compact
|
|
26
|
+
- `pyco(<percent>)` 正常只来自模型流里最近一次 `response.completed.response.usage.total_tokens`;如果大 tool output 之后的下一次请求被下游 `context_length_exceeded` 拒绝,rollout 不会单独记录 usage。遇到这类错误时应从错误文案的 `requested ... tokens (... in the messages, ... in the completion)` 提取真实请求 token,作为失败请求的 `token_count` 事件回灌,并立即触发 compact 后重试一次。若服务端只返回 `Your input exceeds the context window...` 这类无 token 数的 `response.failed`,仍应触发 compact+retry,只是不要伪造 `token_count`。若 compact 请求本身也超长,先循环删除最旧的 `ToolResult` 及其配对 `ToolCall` 再重试 compact。
|
|
27
27
|
- `AgentLoop` 的 turn-loop 语义要跟上游 `codex-rs/core/src/codex.rs` 一致:按 follow-up / tool handoff 自然收敛,不要加固定 12 轮之类的 hard cap,也不要保留本地专用的 iteration-limit 参数。
|
|
28
28
|
- `README.md` 和 `docs/` 属于对齐工作的一部分:只要实现状态、对齐结论或使用方式发生实质变化,就应及时更新,不要让文档滞后于当前代码。
|
|
29
29
|
- 新工具必须继承 `BaseTool`,然后通过 `ToolRegistry.register(tool_instance)` 接入;不要再给 registry 传散装 name/description/handler 参数。
|
|
@@ -57,3 +57,5 @@
|
|
|
57
57
|
- 对接真实 `~/.codex/sessions/.../rollout-*.jsonl` 时,不要假设它一定是严格的一行一个 JSON object:本机样本可能包含 pretty-printed 多行对象,且文件尾部偶尔带未完成记录。恢复历史时用 concatenated-JSON 方式读取,并容忍尾部残缺。
|
|
58
58
|
- `pycodex` 本地 session 保存现在也按上游思路走:新 session 一开始就分配稳定的 uuidv7 thread/session id,并把历史增量追加到 `CODEX_HOME/sessions/.../rollout-*.jsonl`;`/resume` 列表应只展示至少有真实 user message 的 rollout,避免空白新 session 污染恢复列表。
|
|
59
59
|
- auto-compact 对齐上游配置名 `model_auto_compact_token_limit`;为空时关闭,触发依据是最近一次模型上报的 `usage.total_tokens`,pre-turn 压缩上一轮历史,mid-turn 压缩工具 follow-up 前的当前历史,并继续复用现有 compacted rollout 记录。
|
|
60
|
+
- Responses streaming 里的 `response.incomplete` 不是连接断开:不要让 `ResponsesModelClient` 把它当 retryable incomplete stream 反复重连。普通 turn 应明确报 `response.incomplete`;compact 请求如果已经收到 assistant partial summary,可以用这个 partial summary 完成 replacement history,避免 midturn auto-compact 卡在 5 次 retry。
|
|
61
|
+
- Feishu card tests read `~/.codex/.feishu_refresh_token` through production code; when running `tests/test_feishu_card.py` locally, isolate HOME (for example `HOME=/tmp/pycodex-empty-home env -u VIRTUAL_ENV uv run pytest tests/test_feishu_card.py tests/test_feishu_link.py`) unless the test itself controls `HOME`.
|
|
@@ -554,7 +554,7 @@ Those are the next alignment target after the prompt/context pass.
|
|
|
554
554
|
|
|
555
555
|
- `pycodex` 现在已经补上最小的 provider 级 stream retry:`ResponsesProviderConfig`
|
|
556
556
|
支持 `stream_max_retries` / `stream_idle_timeout_ms`,默认值对齐 upstream 的
|
|
557
|
-
`
|
|
557
|
+
`300_000 ms` SSE idle timeout;代码在 `pycodex/model.py`
|
|
558
558
|
- 当前实现会把 `response.failed`、stream 在 `response.completed` 前断开、以及
|
|
559
559
|
`requests` 侧的读流异常统一视为 retryable stream error,并在
|
|
560
560
|
`ResponsesModelClient.complete(...)` 里按 backoff 重试;重试前会向外发
|
|
@@ -2,12 +2,13 @@ from .compat import patch_asyncio
|
|
|
2
2
|
|
|
3
3
|
patch_asyncio()
|
|
4
4
|
|
|
5
|
-
from .agent import
|
|
5
|
+
from .agent import Agent
|
|
6
6
|
from .context import ContextConfig, ContextManager
|
|
7
7
|
from .model import (
|
|
8
8
|
ModelClient,
|
|
9
9
|
NOOP_MODEL_STREAM_EVENT_HANDLER,
|
|
10
10
|
ResponsesApiError,
|
|
11
|
+
ResponsesIncompleteError,
|
|
11
12
|
ResponsesModelClient,
|
|
12
13
|
ResponsesProviderConfig,
|
|
13
14
|
)
|
|
@@ -26,14 +27,14 @@ from .protocol import (
|
|
|
26
27
|
TurnResult,
|
|
27
28
|
UserMessage,
|
|
28
29
|
)
|
|
29
|
-
from .runtime import
|
|
30
|
+
from .runtime import CliSubmissionQueue
|
|
30
31
|
from .runtime_services import (
|
|
31
32
|
PlanStore,
|
|
32
33
|
RequestPermissionsManager,
|
|
33
34
|
RequestUserInputManager,
|
|
34
35
|
SubAgentManager,
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
create_agent_runtime_environment,
|
|
37
|
+
get_agent_runtime_environment,
|
|
37
38
|
)
|
|
38
39
|
from .tools import (
|
|
39
40
|
ApplyPatchTool,
|
|
@@ -90,13 +91,13 @@ def debug(stop: 'bool' = False):
|
|
|
90
91
|
|
|
91
92
|
__all__ = [
|
|
92
93
|
"AgentEvent",
|
|
93
|
-
"
|
|
94
|
-
"
|
|
94
|
+
"Agent",
|
|
95
|
+
"CliSubmissionQueue",
|
|
95
96
|
"ApplyPatchTool",
|
|
96
97
|
"AssistantMessage",
|
|
97
98
|
"BaseTool",
|
|
98
99
|
"CloseAgentTool",
|
|
99
|
-
"
|
|
100
|
+
"create_agent_runtime_environment",
|
|
100
101
|
"CodeModeManager",
|
|
101
102
|
"ContextConfig",
|
|
102
103
|
"ContextManager",
|
|
@@ -120,6 +121,7 @@ __all__ = [
|
|
|
120
121
|
"RequestUserInputManager",
|
|
121
122
|
"ResumeAgentTool",
|
|
122
123
|
"ResponsesApiError",
|
|
124
|
+
"ResponsesIncompleteError",
|
|
123
125
|
"ResponsesModelClient",
|
|
124
126
|
"ResponsesProviderConfig",
|
|
125
127
|
"SendInputTool",
|
|
@@ -142,5 +144,5 @@ __all__ = [
|
|
|
142
144
|
"WaitTool",
|
|
143
145
|
"WebSearchTool",
|
|
144
146
|
"WriteStdinTool",
|
|
145
|
-
"
|
|
147
|
+
"get_agent_runtime_environment",
|
|
146
148
|
]
|
|
@@ -23,10 +23,11 @@ import typing
|
|
|
23
23
|
|
|
24
24
|
if typing.TYPE_CHECKING:
|
|
25
25
|
from .utils.session_persist import SessionRolloutRecorder
|
|
26
|
+
from .runtime_services import AgentRuntimeEnvironment
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
EventHandler = Callable[[AgentEvent], None]
|
|
29
|
-
|
|
30
|
+
BASE_EVENT_HANDLER: 'EventHandler' = lambda _event: None
|
|
30
31
|
_REQUESTED_TOKENS_RE = re.compile(
|
|
31
32
|
r"requested\s+([0-9,]+)\s+tokens",
|
|
32
33
|
re.IGNORECASE,
|
|
@@ -39,13 +40,19 @@ _MAX_CONTEXT_TOKENS_RE = re.compile(
|
|
|
39
40
|
r"maximum\s+context\s+length\s+is\s+([0-9,]+)\s+tokens",
|
|
40
41
|
re.IGNORECASE,
|
|
41
42
|
)
|
|
43
|
+
_CONTEXT_LENGTH_ERROR_MARKERS = (
|
|
44
|
+
"context_length_exceeded",
|
|
45
|
+
"maximum context length",
|
|
46
|
+
"exceeds the context window",
|
|
47
|
+
"exceeded the context window",
|
|
48
|
+
)
|
|
42
49
|
|
|
43
50
|
|
|
44
51
|
class TurnInterrupted(RuntimeError):
|
|
45
52
|
pass
|
|
46
53
|
|
|
47
54
|
|
|
48
|
-
class
|
|
55
|
+
class Agent:
|
|
49
56
|
"""Minimal Python port of Codex's turn loop.
|
|
50
57
|
|
|
51
58
|
The core idea mirrors the Rust implementation:
|
|
@@ -60,9 +67,10 @@ class AgentLoop:
|
|
|
60
67
|
tool_registry: 'ToolRegistry',
|
|
61
68
|
context_manager: 'typing.Union[ContextManager, None]' = None,
|
|
62
69
|
parallel_tool_calls: 'bool' = True,
|
|
63
|
-
event_handler: 'EventHandler' =
|
|
70
|
+
event_handler: 'EventHandler' = BASE_EVENT_HANDLER,
|
|
64
71
|
initial_history: 'typing.Tuple[ConversationItem, ...]' = (),
|
|
65
72
|
rollout_recorder: 'typing.Union[SessionRolloutRecorder, None]' = None,
|
|
73
|
+
runtime_environment: 'AgentRuntimeEnvironment' = None,
|
|
66
74
|
) -> 'None':
|
|
67
75
|
self._model_client = model_client
|
|
68
76
|
self._tool_registry = tool_registry
|
|
@@ -75,6 +83,7 @@ class AgentLoop:
|
|
|
75
83
|
self._context_manager.resolve_auto_compact_token_limit()
|
|
76
84
|
)
|
|
77
85
|
self._last_total_usage_tokens: 'typing.Union[int, None]' = None
|
|
86
|
+
self.runtime_environment = runtime_environment
|
|
78
87
|
self.interrupt_asap = False
|
|
79
88
|
|
|
80
89
|
@property
|
|
@@ -82,7 +91,7 @@ class AgentLoop:
|
|
|
82
91
|
return tuple(self._history)
|
|
83
92
|
|
|
84
93
|
def set_event_handler(
|
|
85
|
-
self, event_handler: 'EventHandler' =
|
|
94
|
+
self, event_handler: 'EventHandler' = BASE_EVENT_HANDLER
|
|
86
95
|
) -> 'None':
|
|
87
96
|
self._event_handler = event_handler
|
|
88
97
|
|
|
@@ -98,6 +107,11 @@ class AgentLoop:
|
|
|
98
107
|
) -> 'None':
|
|
99
108
|
self._rollout_recorder = rollout_recorder
|
|
100
109
|
|
|
110
|
+
def ask(self, text: 'str') -> 'TurnResult':
|
|
111
|
+
from .utils.async_bridge import run_async
|
|
112
|
+
|
|
113
|
+
return run_async(self.run_turn([text]))
|
|
114
|
+
|
|
101
115
|
def _raise_if_interrupt_requested(
|
|
102
116
|
self,
|
|
103
117
|
turn_id: 'str',
|
|
@@ -342,17 +356,26 @@ class AgentLoop:
|
|
|
342
356
|
lambda event: self._handle_model_stream_event(turn_id, event),
|
|
343
357
|
)
|
|
344
358
|
except Exception as exc:
|
|
345
|
-
|
|
346
|
-
if
|
|
359
|
+
error_message = str(exc)
|
|
360
|
+
if (
|
|
361
|
+
not _is_context_length_error_message(error_message)
|
|
362
|
+
or attempted_context_compact
|
|
363
|
+
):
|
|
347
364
|
raise
|
|
348
365
|
attempted_context_compact = True
|
|
349
|
-
|
|
350
|
-
|
|
366
|
+
context_usage = _usage_from_context_length_error(error_message)
|
|
367
|
+
if context_usage is not None:
|
|
368
|
+
self._remember_token_usage(context_usage)
|
|
369
|
+
self._emit("token_count", turn_id, usage=context_usage)
|
|
351
370
|
await self._run_auto_compact(
|
|
352
371
|
turn_id,
|
|
353
372
|
phase="context_length_exceeded",
|
|
354
|
-
total_tokens=
|
|
355
|
-
|
|
373
|
+
total_tokens=(
|
|
374
|
+
context_usage.get("total_tokens")
|
|
375
|
+
if context_usage is not None
|
|
376
|
+
else None
|
|
377
|
+
),
|
|
378
|
+
token_limit=_context_length_error_token_limit(error_message),
|
|
356
379
|
prune_tool_results_on_context_error=True,
|
|
357
380
|
)
|
|
358
381
|
self._raise_if_interrupt_requested(turn_id, iteration)
|
|
@@ -385,7 +408,7 @@ class AgentLoop:
|
|
|
385
408
|
token_limit: 'typing.Union[int, None]' = None,
|
|
386
409
|
prune_tool_results_on_context_error: 'bool' = False,
|
|
387
410
|
) -> 'None':
|
|
388
|
-
from .utils.compactor import
|
|
411
|
+
from .utils.compactor import compact_agent
|
|
389
412
|
|
|
390
413
|
payload: 'typing.Dict[str, object]' = {"phase": phase}
|
|
391
414
|
if total_tokens is not None:
|
|
@@ -403,7 +426,7 @@ class AgentLoop:
|
|
|
403
426
|
self._emit("stream_error", turn_id, **event.payload)
|
|
404
427
|
|
|
405
428
|
try:
|
|
406
|
-
compact_result = await
|
|
429
|
+
compact_result = await compact_agent(
|
|
407
430
|
self,
|
|
408
431
|
handle_compact_stream_event,
|
|
409
432
|
prune_tool_results_on_context_error,
|
|
@@ -477,11 +500,7 @@ class AgentLoop:
|
|
|
477
500
|
def _usage_from_context_length_error(
|
|
478
501
|
message: 'str',
|
|
479
502
|
) -> 'typing.Union[typing.Dict[str, int], None]':
|
|
480
|
-
|
|
481
|
-
if (
|
|
482
|
-
"context_length_exceeded" not in lower
|
|
483
|
-
and "maximum context length" not in lower
|
|
484
|
-
):
|
|
503
|
+
if not _is_context_length_error_message(message):
|
|
485
504
|
return None
|
|
486
505
|
|
|
487
506
|
requested_match = _REQUESTED_TOKENS_RE.search(message)
|
|
@@ -498,6 +517,11 @@ def _usage_from_context_length_error(
|
|
|
498
517
|
return usage
|
|
499
518
|
|
|
500
519
|
|
|
520
|
+
def _is_context_length_error_message(message: 'str') -> 'bool':
|
|
521
|
+
lower = message.lower()
|
|
522
|
+
return any(marker in lower for marker in _CONTEXT_LENGTH_ERROR_MARKERS)
|
|
523
|
+
|
|
524
|
+
|
|
501
525
|
def _context_length_error_token_limit(message: 'str') -> 'typing.Union[int, None]':
|
|
502
526
|
limit_match = _MAX_CONTEXT_TOKENS_RE.search(message)
|
|
503
527
|
if limit_match is None:
|