python-codex 0.2.6__tar.gz → 0.2.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. {python_codex-0.2.6 → python_codex-0.2.7}/.github/workflows/publish.yml +6 -2
  2. {python_codex-0.2.6 → python_codex-0.2.7}/.github/workflows/test.yml +6 -2
  3. {python_codex-0.2.6 → python_codex-0.2.7}/AGENTS.md +5 -3
  4. {python_codex-0.2.6 → python_codex-0.2.7}/PKG-INFO +29 -15
  5. {python_codex-0.2.6 → python_codex-0.2.7}/README.md +25 -12
  6. {python_codex-0.2.6 → python_codex-0.2.7}/README_ZH.md +8 -4
  7. {python_codex-0.2.6 → python_codex-0.2.7}/docs/ALIGNMENT.md +11 -6
  8. {python_codex-0.2.6 → python_codex-0.2.7}/docs/CONTEXT.md +5 -1
  9. {python_codex-0.2.6 → python_codex-0.2.7}/docs/responses_server/README.md +20 -4
  10. {python_codex-0.2.6 → python_codex-0.2.7}/packages/pycodex-ws/pyproject.toml +2 -2
  11. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/__init__.py +4 -0
  12. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/agent.py +54 -14
  13. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/cli.py +110 -36
  14. python_codex-0.2.7/pycodex/image_utils.py +79 -0
  15. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/model.py +7 -1
  16. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/portable.py +14 -7
  17. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/prompts/models.json +170 -0
  18. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/runtime.py +2 -0
  19. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/__init__.py +3 -0
  20. python_codex-0.2.7/pycodex/tools/clock_tool.py +168 -0
  21. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/view_image_tool.py +7 -9
  22. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/utils/compactor.py +17 -2
  23. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/utils/session_persist.py +50 -2
  24. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/utils/visualize.py +11 -4
  25. {python_codex-0.2.6 → python_codex-0.2.7}/pyproject.toml +4 -3
  26. {python_codex-0.2.6 → python_codex-0.2.7}/responses_server/config.py +4 -1
  27. {python_codex-0.2.6 → python_codex-0.2.7}/responses_server/messages_api.py +49 -0
  28. {python_codex-0.2.6 → python_codex-0.2.7}/responses_server/payload_processors.py +1 -0
  29. {python_codex-0.2.6 → python_codex-0.2.7}/responses_server/stream_router.py +108 -22
  30. {python_codex-0.2.6 → python_codex-0.2.7}/responses_server/trajectory_dump.py +18 -2
  31. {python_codex-0.2.6 → python_codex-0.2.7}/tests/TESTS.md +18 -1
  32. {python_codex-0.2.6 → python_codex-0.2.7}/tests/responses_server/test_server.py +291 -9
  33. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_agent.py +112 -3
  34. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_builtin_tools.py +87 -0
  35. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_cli.py +106 -6
  36. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_model.py +25 -0
  37. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_portable.py +10 -0
  38. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_workspace_server.py +68 -0
  39. {python_codex-0.2.6 → python_codex-0.2.7}/tools/feishu_oauth.py +15 -1
  40. {python_codex-0.2.6 → python_codex-0.2.7}/workspace_server/app.py +12 -12
  41. {python_codex-0.2.6 → python_codex-0.2.7}/workspace_server/workspace.html +7 -4
  42. {python_codex-0.2.6 → python_codex-0.2.7}/workspace_server/workspaces.py +34 -24
  43. {python_codex-0.2.6 → python_codex-0.2.7}/.gitignore +0 -0
  44. {python_codex-0.2.6 → python_codex-0.2.7}/LICENSE +0 -0
  45. {python_codex-0.2.6 → python_codex-0.2.7}/packages/pycodex-ws/README.md +0 -0
  46. {python_codex-0.2.6 → python_codex-0.2.7}/packages/pycodex-ws/pycodex_ws_meta/__init__.py +0 -0
  47. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/collaboration.py +0 -0
  48. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/compat.py +0 -0
  49. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/context.py +0 -0
  50. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/doctor.py +0 -0
  51. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/feishu_card.py +0 -0
  52. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/feishu_link.py +0 -0
  53. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/interactive_session.py +0 -0
  54. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/model_metadata.py +0 -0
  55. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/portable_server.py +0 -0
  56. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/prompts/collaboration_default.md +0 -0
  57. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/prompts/collaboration_plan.md +0 -0
  58. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/prompts/default_base_instructions.md +0 -0
  59. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/prompts/permissions/approval_policy/never.md +0 -0
  60. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/prompts/permissions/approval_policy/on_failure.md +0 -0
  61. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/prompts/permissions/approval_policy/on_request.md +0 -0
  62. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/prompts/permissions/approval_policy/on_request_rule_request_permission.md +0 -0
  63. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/prompts/permissions/approval_policy/unless_trusted.md +0 -0
  64. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/prompts/permissions/sandbox_mode/danger_full_access.md +0 -0
  65. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/prompts/permissions/sandbox_mode/read_only.md +0 -0
  66. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/prompts/permissions/sandbox_mode/workspace_write.md +0 -0
  67. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/protocol.py +0 -0
  68. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/runtime_services.py +0 -0
  69. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/agent_tool_schemas.py +0 -0
  70. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/apply_patch_tool.py +0 -0
  71. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/base_tool.py +0 -0
  72. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/close_agent_tool.py +0 -0
  73. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/code_mode_manager.py +0 -0
  74. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/exec_command_tool.py +0 -0
  75. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/exec_runtime.js +0 -0
  76. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/exec_tool.py +0 -0
  77. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/grep_files_tool.py +0 -0
  78. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/ipython_tool.py +0 -0
  79. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/list_dir_tool.py +0 -0
  80. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/read_file_tool.py +0 -0
  81. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/request_permissions_tool.py +0 -0
  82. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/request_user_input_tool.py +0 -0
  83. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/resume_agent_tool.py +0 -0
  84. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/send_input_tool.py +0 -0
  85. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/shell_command_tool.py +0 -0
  86. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/shell_tool.py +0 -0
  87. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/spawn_agent_tool.py +0 -0
  88. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/unified_exec_manager.py +0 -0
  89. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/update_plan_tool.py +0 -0
  90. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/wait_agent_tool.py +0 -0
  91. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/wait_tool.py +0 -0
  92. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/web_search_tool.py +0 -0
  93. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/tools/write_stdin_tool.py +0 -0
  94. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/utils/__init__.py +0 -0
  95. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/utils/async_bridge.py +0 -0
  96. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/utils/debug.py +0 -0
  97. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/utils/dotenv.py +0 -0
  98. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/utils/get_env.py +0 -0
  99. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/utils/random_ids.py +0 -0
  100. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/utils/toolcall_visualize.py +0 -0
  101. {python_codex-0.2.6 → python_codex-0.2.7}/pycodex/utils/truncation.py +0 -0
  102. {python_codex-0.2.6 → python_codex-0.2.7}/responses_server/__init__.py +0 -0
  103. {python_codex-0.2.6 → python_codex-0.2.7}/responses_server/__main__.py +0 -0
  104. {python_codex-0.2.6 → python_codex-0.2.7}/responses_server/app.py +0 -0
  105. {python_codex-0.2.6 → python_codex-0.2.7}/responses_server/server.py +0 -0
  106. {python_codex-0.2.6 → python_codex-0.2.7}/responses_server/session_store.py +0 -0
  107. {python_codex-0.2.6 → python_codex-0.2.7}/responses_server/tools/__init__.py +0 -0
  108. {python_codex-0.2.6 → python_codex-0.2.7}/responses_server/tools/custom_adapter.py +0 -0
  109. {python_codex-0.2.6 → python_codex-0.2.7}/responses_server/tools/web_search.py +0 -0
  110. {python_codex-0.2.6 → python_codex-0.2.7}/tests/__init__.py +0 -0
  111. {python_codex-0.2.6 → python_codex-0.2.7}/tests/compare_request_user_input_roundtrip.py +0 -0
  112. {python_codex-0.2.6 → python_codex-0.2.7}/tests/compare_steer_request_bodies.py +0 -0
  113. {python_codex-0.2.6 → python_codex-0.2.7}/tests/compare_tool_schemas.py +0 -0
  114. {python_codex-0.2.6 → python_codex-0.2.7}/tests/fake_responses_server.py +0 -0
  115. {python_codex-0.2.6 → python_codex-0.2.7}/tests/fakes.py +0 -0
  116. {python_codex-0.2.6 → python_codex-0.2.7}/tests/responses_server/fake_chat_completions_server.py +0 -0
  117. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_async_bridge.py +0 -0
  118. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_compactor.py +0 -0
  119. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_context.py +0 -0
  120. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_doctor.py +0 -0
  121. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_fake_responses_server.py +0 -0
  122. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_feishu_card.py +0 -0
  123. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_feishu_link.py +0 -0
  124. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_ipython_tool.py +0 -0
  125. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_packaging.py +0 -0
  126. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_py36_syntax.py +0 -0
  127. {python_codex-0.2.6 → python_codex-0.2.7}/tests/test_visualize.py +0 -0
  128. {python_codex-0.2.6 → python_codex-0.2.7}/workspace_server/__init__.py +0 -0
  129. {python_codex-0.2.6 → python_codex-0.2.7}/workspace_server/__main__.py +0 -0
  130. {python_codex-0.2.6 → python_codex-0.2.7}/workspace_server/workspaces.html +0 -0
@@ -54,7 +54,10 @@ jobs:
54
54
  steps:
55
55
  - name: Install system dependencies
56
56
  run: |
57
- apt-get update
57
+ printf '%s\n' \
58
+ 'deb [check-valid-until=no] https://archive.debian.org/debian bullseye main' \
59
+ > /etc/apt/sources.list
60
+ apt-get -o APT::Update::Error-Mode=any update
58
61
  apt-get install -y --no-install-recommends git nodejs npm
59
62
 
60
63
  - name: Check out repository
@@ -80,7 +83,8 @@ jobs:
80
83
  "requests>=2.27.1" \
81
84
  "prompt-toolkit>=3.0.36,<3.1" \
82
85
  "loguru>=0.7.3,<1" \
83
- "cryptography>=40.0.2,<41" \
86
+ "pycryptodomex>=3.20" \
87
+ "pillow>=8.4.0,<9" \
84
88
  "fastapi>=0.83,<0.84" \
85
89
  "uvicorn>=0.16,<0.17" \
86
90
  "pytest>=6.2.5,<7" \
@@ -44,7 +44,10 @@ jobs:
44
44
  steps:
45
45
  - name: Install system dependencies
46
46
  run: |
47
- apt-get update
47
+ printf '%s\n' \
48
+ 'deb [check-valid-until=no] https://archive.debian.org/debian bullseye main' \
49
+ > /etc/apt/sources.list
50
+ apt-get -o APT::Update::Error-Mode=any update
48
51
  apt-get install -y --no-install-recommends git nodejs npm
49
52
 
50
53
  - name: Check out repository
@@ -70,7 +73,8 @@ jobs:
70
73
  "requests>=2.27.1" \
71
74
  "prompt-toolkit>=3.0.36,<3.1" \
72
75
  "loguru>=0.7.3,<1" \
73
- "cryptography>=40.0.2,<41" \
76
+ "pycryptodomex>=3.20" \
77
+ "pillow>=8.4.0,<9" \
74
78
  "fastapi>=0.83,<0.84" \
75
79
  "uvicorn>=0.16,<0.17" \
76
80
  "pytest>=6.2.5,<7" \
@@ -19,7 +19,7 @@
19
19
  - `responses_server` 如果要兼容下游 `/v1/messages`,也优先保持这条边界:内部继续用 canonical chat request / chat-like chunk 流,只有真正发请求和读取 SSE 时才做 messages 适配,这样 tool hydration、mock `web_search` follow-up、provider payload post-process 都能复用。
20
20
  - 真实 vLLM `0.19.0` 的 `/v1/messages` 会对缺失 `max_tokens` 直接返回 `400`;messages 适配层必须总是补这个字段。当前约定是优先透传请求里的 `max_output_tokens`/`max_tokens`,否则回退到默认 `32000`。
21
21
  - 对 vLLM chat-completions 打开 `return_token_ids=true` 时,streaming `prompt_token_ids` 只出现在首个 chunk,后续每个 chunk 的 `choices[*].token_ids` 都是 decode delta;要在 `responses_server` 侧导出 trajectory 时,按“首个 `prompt_token_ids` + 按序拼接所有 chunk 的 `token_ids`”重建即可。
22
- - `pycodex` 默认是最小交互 CLI;无 prompt 时进入 REPL,并通过 `AgentRuntime` 跑外层提交循环。当前会显示最小事件流、assistant 流式输出、简单 title/history(`/title`, `/history`),并默认注册一组与原版一一对应的本地工具子集。
22
+ - `pycodex` 默认是最小交互 CLI;无 prompt 时进入 REPL,并通过 `AgentRuntime` 跑外层提交循环。当前会显示最小事件流、assistant 流式输出、简单 title/history(`/title`, `/history`),并默认注册上游对齐工具子集和明确标记的本地扩展。
23
23
  - Web workspace lives in the standalone `workspace_server/` package and is launched with `pycodex-ws --listen <host:port> --board <html>`, not through `pycodex` CLI dispatch. CLI and web share `pycodex.interactive_session.run_interactive_session`; slash-command semantics such as `/resume`, `/compact`, `/model`, and `/link` belong to that shared interactive shell loop, while workspace only supplies a web view/input adapter and tab/session lifecycle.
24
24
  - Relative images in board HTML resolve to HTTP siblings such as `/w/<workspace-id>/<image>` (or `/<image>` in single-workspace mode), not to `/board/...`. Keep the image fallback routes after concrete workspace/API routes, resolve files from the board's directory, and reject non-`image/*` files or paths that escape that directory.
25
25
  - 交互 CLI 的事件流展示优先表达用户可感知的阶段(例如工具开始/完成、模型回看工具结果),不要直接把内部 `iteration` 计数暴露成主要状态文案;`iterations` 应继续保留在 `TurnResult` 等程序化结果里。
@@ -33,7 +33,7 @@
33
33
  - `README.md` 和 `docs/` 属于对齐工作的一部分:只要实现状态、对齐结论或使用方式发生实质变化,就应及时更新,不要让文档滞后于当前代码。
34
34
  - 新工具必须继承 `BaseTool`,然后通过 `ToolRegistry.register(tool_instance)` 接入;不要再给 registry 传散装 name/description/handler 参数。
35
35
  - request/tool schema 对齐应落在实际建模层(`ToolSpec` / `BaseTool` / request builder)本身,不要再引入 prompt 级别的 `serialized_tools` 旁路覆盖。
36
- - 当前已接入的内置工具子集:`shell`、`shell_command`、`exec_command`、`write_stdin`、`exec`、`wait`、`web_search`、`update_plan`、`request_user_input`、`request_permissions`、`spawn_agent`、`send_input`、`resume_agent`、`wait_agent`、`close_agent`、`apply_patch`、`grep_files`、`read_file`、`list_dir`、`view_image`。后续继续新增时,仍然按原版 Codex 内置工具逐个补齐。
36
+ - 当前已接入的默认工具集:`shell`、`shell_command`、`exec_command`、`write_stdin`、`clock`、`exec`、`wait`、`web_search`、`update_plan`、`request_user_input`、`request_permissions`、`spawn_agent`、`send_input`、`resume_agent`、`wait_agent`、`close_agent`、`apply_patch`、`grep_files`、`read_file`、`list_dir`、`view_image`。其中 `clock` 是明确的 pycodex 扩展;其余新增工具仍优先逐个对齐原版 Codex。
37
37
  - `exec_command` / `write_stdin` 的 unified-exec 对齐要注意两层默认截断语义:省略 `max_output_tokens` 时也要按 upstream 默认 `10_000` token 预算裁剪 tool response;同时长时间未轮询的未读输出缓冲不能无限累积,需保留 upstream 同款 `1 MiB` head/tail。
38
38
  - 当前协议层已经支持两类原版工具载荷:普通 function tools,以及 `apply_patch` 这类 freeform/custom tools;工具结果也支持结构化 `input_image` content items,用于 `view_image` 这类会把图片喂回模型的工具。
39
39
  - 当前本地 runtime 已支持一个最小的 in-process sub-agent 管理层:`spawn_agent` / `send_input` / `resume_agent` / `wait_agent` / `close_agent` 通过共享的 `SubAgentManager` 驱动新的 `AgentRuntime` 实例,不依赖 CLI harness 自带的多 agent 基础设施。
@@ -41,7 +41,7 @@
41
41
  - `request_user_input` 对齐上游时要注意两层:Default mode 默认返回固定错误 `request_user_input is unavailable in Default mode`;Plan mode happy path 则要求每个问题都有非空 `options`、handler 会补 `isOther=true`,并把结构化答案序列化成 JSON 字符串加 `success=true` 塞回 `function_call_output`。
42
42
  - 在 `/data/pycodex` 本机已安装的 `codex-cli 0.115.0` 上,用 `tests/compare_request_user_input_roundtrip.py` 做 Plan-mode deterministic proxy live capture 时,upstream 的 second request `function_call_output` 当前不带 `success`;而 GitHub `openai/codex` `main` 源码里的 `FunctionCallOutputPayload` / `request_user_input` handler 支持传递 `success`。写对齐结论时要明确区分“installed CLI live capture”与“upstream main 源码建模”。
43
43
  - 当前 `exec` / `wait` 已有一个最小 code-mode 实现:底层通过 Node 子进程运行 JavaScript,自带 `text` / `image` / `store` / `load` / `exit` / `notify` / `yield_control` helper,并允许通过 `tools.<name>(...)` 调回本地已注册工具;`web_search` 当前作为 Responses API provider-native tool declaration 暴露给模型,不经过本地 ToolRegistry 执行。
44
- - 我们支持的 tools 必须和原版 Codex 内置 tools 一一对应:名称、定位、参数形状、交互模型、输出语义都应尽量对齐;不要混合多个原版工具的语义做一个“折中工具”。
44
+ - 除明确记录的 pycodex 扩展(当前为 `clock`)外,我们支持的 tools 必须和原版 Codex 内置 tools 一一对应:名称、定位、参数形状、交互模型、输出语义都应尽量对齐;不要混合多个原版工具的语义做一个“折中工具”。
45
45
  - 代码风格上不要使用 `*` 定义 keyword-only 参数;接口默认允许位置参数。
46
46
  - runtime 包和会在 import 阶段执行的测试辅助代码需要保持 Python 3.6.2 语法兼容;不要引入 walrus `:=`、`match` 等仅 3.8+/3.10+ 可解析的新语法,哪怕分支在运行时不会走到。
47
47
  - 本仓库使用 `uv`;本地默认没有预装测试依赖,开始工作前先跑 `uv sync --dev`,验证用 `uv run pytest`。
@@ -62,6 +62,7 @@
62
62
  - 对接真实 `~/.codex/sessions/.../rollout-*.jsonl` 时,不要假设它一定是严格的一行一个 JSON object:本机样本可能包含 pretty-printed 多行对象,且文件尾部偶尔带未完成记录。恢复历史时用 concatenated-JSON 方式读取,并容忍尾部残缺。
63
63
  - `pycodex` 本地 session 保存现在也按上游思路走:新 session 一开始就分配稳定的 uuidv7 thread/session id,并把历史增量追加到 `CODEX_HOME/sessions/.../rollout-*.jsonl`;`/resume` 列表应只展示至少有真实 user message 的 rollout,避免空白新 session 污染恢复列表。
64
64
  - auto-compact 对齐上游配置名 `model_auto_compact_token_limit`;为空时关闭,触发依据是最近一次模型上报的 `usage.total_tokens`,pre-turn 压缩上一轮历史,mid-turn 压缩工具 follow-up 前的当前历史,并继续复用现有 compacted rollout 记录。
65
+ - Local compact templates intentionally diverge from upstream: the prompt explicitly preserves the latest real user request/language, and the summary prefix tells the next model to continue instead of merely acknowledging the handoff. Do not mechanically replace these additions with `codex-rs/core/templates/compact/{prompt.md,summary_prefix.md}`. Real `compacted` rollout entries remain append-only checkpoints whose `replacement_history` can be summary-only; the old transcript stays on disk for audit but is not part of the next active prompt.
65
66
  - Responses streaming 里的 `response.incomplete` 不是连接断开:不要让 `ResponsesModelClient` 把它当 retryable incomplete stream 反复重连。普通 turn 应明确报 `response.incomplete`;compact 请求如果已经收到 assistant partial summary,可以用这个 partial summary 完成 replacement history,避免 midturn auto-compact 卡在 5 次 retry。
66
67
  - 上游 Codex Responses 请求当前不传模型级 `max_output_tokens`,也没有读取 `model_max_output_tokens` 这个 config key;这个名字在上游主要用于工具输出截断,不要为了上游对齐把它加进模型请求。
67
68
  - `service_tier = "fast"` 在 config 中是 Fast mode 的持久写法,但 Responses wire 值是 `priority`;只对模型 metadata 的 `service_tiers` 明确包含该 id 的模型发送,`default` 和不支持的 tier 都省略。
@@ -69,4 +70,5 @@
69
70
  - 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`.
70
71
  - `lark_oapi.ws.client` creates a module-level asyncio `loop` at import time and `Client.start()` always uses that global. For `/link` long-connection listeners, bind that SDK global to a listener-thread-owned loop before constructing the client, and stop it through private `_disconnect()` plus `loop.stop()` on `/unlink`; otherwise unlink/link can reuse a still-running SDK loop and fail with `RuntimeError: This event loop is already running`.
71
72
  - `exec_command` background completion auto-resume is intentionally Agent-idle-only: when a session exits, it may call `Agent.maybe_invoke(...)` and start a synthetic `<exec_command_completed>` turn only if that Agent is not already running a turn. Do not enqueue/cache these events in `CliSubmissionQueue`; direct Agent/IPython use should share the same Agent-level hook.
73
+ - `clock` is a per-Agent/session pycodex extension with one global period value. Reuse `Agent.maybe_invoke(...)` for `<clock_tick>` wake-ups and include the timezone-aware ISO 8601 `current_time` captured when the timer expires: cancel the pending countdown when any turn starts, restart it only after a successful reply, and cancel it on `clock(null)` or runtime shutdown. Terminal events expose only aggregate `background_work_count`, which includes running exec sessions and an enabled clock after a successful reply; CLI/workspace idle status should use that single field. Do not add a second submission queue or shell-based sleep loop.
72
74
  - The tool description JSON fallbacks (`pycodex/prompts/exec_tools.json` and `pycodex/prompts/subagent_tools.json`) were deleted after moving schemas into class-level `BaseTool` specs. `ToolSpec.serialize()` intentionally skips function-tool `output_schema`, matching upstream `ResponsesApiTool.output_schema #[serde(skip)]`; keep output schemas as local metadata only unless upstream wire format changes.
@@ -1,17 +1,18 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-codex
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: A minimal Python extraction of Codex's main agent loop
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.6.2
7
- Requires-Dist: cryptography<41,>=40.0.2; python_version < '3.7'
8
- Requires-Dist: cryptography>=40.0.2; python_version >= '3.7'
9
7
  Requires-Dist: dataclasses>=0.8; python_version < '3.7'
10
8
  Requires-Dist: fastapi<0.84,>=0.83.0; python_version < '3.7'
11
9
  Requires-Dist: fastapi>=0.83.0; python_version >= '3.7'
12
10
  Requires-Dist: importlib-metadata>=4.8.3; python_version < '3.8'
13
11
  Requires-Dist: loguru>=0.7.3
12
+ Requires-Dist: pillow>=8.4.0; python_version < '3.7'
13
+ Requires-Dist: pillow>=9.0.0; python_version >= '3.7'
14
14
  Requires-Dist: prompt-toolkit>=3.0.36
15
+ Requires-Dist: pycryptodomex>=3.20
15
16
  Requires-Dist: requests>=2.27.1
16
17
  Requires-Dist: tomli<2,>=1.2.3; python_version < '3.11'
17
18
  Requires-Dist: typing-extensions>=4.1.1; python_version < '3.8'
@@ -254,17 +255,29 @@ Current behavior:
254
255
  later steer text is appended to the next model request's `input` in order;
255
256
  for explicit queueing, use `/queue <message>`, which prints
256
257
  `[steer] queued: ...` and later `[steer] inserted: ...`
257
- - the default built-in tool subset currently exposed as local tools is:
258
- `shell`, `shell_command`, `exec_command`, `write_stdin`, `exec`, `wait`,
259
- `web_search`, `update_plan`, `request_user_input`, `request_permissions`,
260
- `spawn_agent`, `send_input`, `resume_agent`, `wait_agent`, `close_agent`,
261
- `apply_patch`, `grep_files`, `read_file`, `list_dir`, `view_image`
258
+ - the default local tool set includes the upstream-aligned subset plus the
259
+ pycodex `clock` extension: `shell`, `shell_command`, `exec_command`,
260
+ `write_stdin`, `clock`, `exec`, `wait`, `web_search`, `update_plan`,
261
+ `request_user_input`, `request_permissions`, `spawn_agent`, `send_input`,
262
+ `resume_agent`, `wait_agent`, `close_agent`, `apply_patch`, `grep_files`,
263
+ `read_file`, `list_dir`, `view_image`
264
+ - `clock(period_m)` sets one periodic clock for the current Agent session;
265
+ `null` cancels it. The countdown restarts after each reply and wakes the
266
+ Agent with a `<clock_tick>` message containing the current timezone-aware
267
+ time when it expires.
268
+ - while a background command or clock is pending, the idle status is
269
+ `idle: sleeping`
270
+ - only the active workspace tab shows its close button
262
271
  - `--vllm-endpoint http://host:port` automatically launches a local
263
272
  `responses_server` compatibility layer; when the URL path is empty it is
264
273
  normalized to `/v1`, and `/responses` requests are still forwarded to the
265
- downstream `/v1/chat/completions` endpoint. For `model_provider = "vllm"`,
266
- reasoning is now preserved across this path: chat chunks with `reasoning` or
267
- `reasoning_content` are translated back into Responses `reasoning` items, and
274
+ downstream `/v1/chat/completions` endpoint. This local compat path always
275
+ uses the canonical Responses request shape, even when the selected model's
276
+ metadata enables `responses_lite`. With `--vllm-endpoint`, startup also reads
277
+ `/v1/models` and uses the last returned model id for the downstream request.
278
+ For `model_provider = "vllm"`, reasoning is preserved across this path:
279
+ chat chunks with `reasoning` or `reasoning_content` are translated back into
280
+ Responses `reasoning` items, and
268
281
  historical `reasoning` items are replayed into downstream assistant messages
269
282
  via the `reasoning` field. Streaming token usage is also requested from vLLM
270
283
  and forwarded to the final `response.completed.response.usage`. If a
@@ -408,6 +421,7 @@ Upstream low-frequency / special-mode tools not yet modeled separately:
408
421
 
409
422
  Repository-specific compatibility / transition tools:
410
423
 
424
+ - [x] `clock` - pycodex periodic Agent wake-up extension.
411
425
  - [x] `exec` - current local approximation of code mode.
412
426
  - [x] `wait` - current local approximation of code-mode waiting behavior.
413
427
 
@@ -422,8 +436,8 @@ Repository-specific compatibility / transition tools:
422
436
  matches upstream.
423
437
  - [x] `AGENTS.md` + `<environment_context>` injection alignment - context
424
438
  assembly order matches upstream.
425
- - [x] non-interactive `exec` tool subset alignment - the model-visible tool set
426
- has converged.
439
+ - [x] non-interactive `exec` upstream tool subset alignment - the aligned
440
+ subset has converged; pycodex additionally exposes `clock`.
427
441
  - [x] `include = ["reasoning.encrypted_content"]` - reasoning include field is
428
442
  aligned.
429
443
  - [x] `prompt_cache_key` - request-level prompt cache key is implemented.
@@ -432,8 +446,8 @@ Repository-specific compatibility / transition tools:
432
446
  - [x] `originator` - mode-aware originator header is implemented.
433
447
  - [x] exact `user-agent` string alignment - aligned on the non-interactive
434
448
  `exec` path.
435
- - [x] field-by-field exec-mode tool schema alignment - currently reuses the
436
- upstream snapshot directly through the tool layer.
449
+ - [x] field-by-field upstream exec-mode tool schema alignment - aligned tools
450
+ use class-level specs; `clock` is documented separately as an extension.
437
451
  - [ ] full interactive-mode and non-`exec` behavior alignment - the non-exec
438
452
  first-turn context is now on the `codex-tui` path, but continuous REPL
439
453
  multi-turn behavior is not fully verified yet.
@@ -233,17 +233,29 @@ Current behavior:
233
233
  later steer text is appended to the next model request's `input` in order;
234
234
  for explicit queueing, use `/queue <message>`, which prints
235
235
  `[steer] queued: ...` and later `[steer] inserted: ...`
236
- - the default built-in tool subset currently exposed as local tools is:
237
- `shell`, `shell_command`, `exec_command`, `write_stdin`, `exec`, `wait`,
238
- `web_search`, `update_plan`, `request_user_input`, `request_permissions`,
239
- `spawn_agent`, `send_input`, `resume_agent`, `wait_agent`, `close_agent`,
240
- `apply_patch`, `grep_files`, `read_file`, `list_dir`, `view_image`
236
+ - the default local tool set includes the upstream-aligned subset plus the
237
+ pycodex `clock` extension: `shell`, `shell_command`, `exec_command`,
238
+ `write_stdin`, `clock`, `exec`, `wait`, `web_search`, `update_plan`,
239
+ `request_user_input`, `request_permissions`, `spawn_agent`, `send_input`,
240
+ `resume_agent`, `wait_agent`, `close_agent`, `apply_patch`, `grep_files`,
241
+ `read_file`, `list_dir`, `view_image`
242
+ - `clock(period_m)` sets one periodic clock for the current Agent session;
243
+ `null` cancels it. The countdown restarts after each reply and wakes the
244
+ Agent with a `<clock_tick>` message containing the current timezone-aware
245
+ time when it expires.
246
+ - while a background command or clock is pending, the idle status is
247
+ `idle: sleeping`
248
+ - only the active workspace tab shows its close button
241
249
  - `--vllm-endpoint http://host:port` automatically launches a local
242
250
  `responses_server` compatibility layer; when the URL path is empty it is
243
251
  normalized to `/v1`, and `/responses` requests are still forwarded to the
244
- downstream `/v1/chat/completions` endpoint. For `model_provider = "vllm"`,
245
- reasoning is now preserved across this path: chat chunks with `reasoning` or
246
- `reasoning_content` are translated back into Responses `reasoning` items, and
252
+ downstream `/v1/chat/completions` endpoint. This local compat path always
253
+ uses the canonical Responses request shape, even when the selected model's
254
+ metadata enables `responses_lite`. With `--vllm-endpoint`, startup also reads
255
+ `/v1/models` and uses the last returned model id for the downstream request.
256
+ For `model_provider = "vllm"`, reasoning is preserved across this path:
257
+ chat chunks with `reasoning` or `reasoning_content` are translated back into
258
+ Responses `reasoning` items, and
247
259
  historical `reasoning` items are replayed into downstream assistant messages
248
260
  via the `reasoning` field. Streaming token usage is also requested from vLLM
249
261
  and forwarded to the final `response.completed.response.usage`. If a
@@ -387,6 +399,7 @@ Upstream low-frequency / special-mode tools not yet modeled separately:
387
399
 
388
400
  Repository-specific compatibility / transition tools:
389
401
 
402
+ - [x] `clock` - pycodex periodic Agent wake-up extension.
390
403
  - [x] `exec` - current local approximation of code mode.
391
404
  - [x] `wait` - current local approximation of code-mode waiting behavior.
392
405
 
@@ -401,8 +414,8 @@ Repository-specific compatibility / transition tools:
401
414
  matches upstream.
402
415
  - [x] `AGENTS.md` + `<environment_context>` injection alignment - context
403
416
  assembly order matches upstream.
404
- - [x] non-interactive `exec` tool subset alignment - the model-visible tool set
405
- has converged.
417
+ - [x] non-interactive `exec` upstream tool subset alignment - the aligned
418
+ subset has converged; pycodex additionally exposes `clock`.
406
419
  - [x] `include = ["reasoning.encrypted_content"]` - reasoning include field is
407
420
  aligned.
408
421
  - [x] `prompt_cache_key` - request-level prompt cache key is implemented.
@@ -411,8 +424,8 @@ Repository-specific compatibility / transition tools:
411
424
  - [x] `originator` - mode-aware originator header is implemented.
412
425
  - [x] exact `user-agent` string alignment - aligned on the non-interactive
413
426
  `exec` path.
414
- - [x] field-by-field exec-mode tool schema alignment - currently reuses the
415
- upstream snapshot directly through the tool layer.
427
+ - [x] field-by-field upstream exec-mode tool schema alignment - aligned tools
428
+ use class-level specs; `clock` is documented separately as an extension.
416
429
  - [ ] full interactive-mode and non-`exec` behavior alignment - the non-exec
417
430
  first-turn context is now on the `codex-tui` path, but continuous REPL
418
431
  multi-turn behavior is not fully verified yet.
@@ -157,8 +157,11 @@ pycodex doctor
157
157
  刚结束的 history fork 成一个不落盘的临时 follow-up 会话,并把文件内容作为下一条
158
158
  user 指令提交;适合做 Feishu 通知这类副作用收尾动作
159
159
  - 交互模式默认支持 steer:普通输入会走 runtime 的 steer 路径,当前请求会在下一个安全边界尽快停下,后续 steer 文本会按顺序并入下一次模型请求的 `input`;如需明确排队可用 `/queue <message>`,会打印 `[steer] queued: ...`,随后等该 turn 真正开始时再打印 `[steer] inserted: ...`
160
- - 当前默认注册一组与原版 Codex 一一对应的本地工具子集:`shell`、`shell_command`、`exec_command`、`write_stdin`、`exec`、`wait`、`web_search`、`update_plan`、`request_user_input`、`request_permissions`、`spawn_agent`、`send_input`、`resume_agent`、`wait_agent`、`close_agent`、`apply_patch`、`grep_files`、`read_file`、`list_dir`、`view_image`
161
- - `--vllm-endpoint http://host:port` 会自动拉起一个本地 `responses_server` compat 层;当 path 为空时会内部补 `/v1`,继续把 `/responses` 请求转到下游 `/v1/chat/completions`。当前对 `model_provider = "vllm"` 已补上 reasoning 兼容:会把 chat chunk 里的 `reasoning` / `reasoning_content` 翻回 Responses `reasoning` item,并把历史里的 `reasoning` item 回放成下游 assistant message 的 `reasoning` 字段;同时会向 vLLM 请求 streaming usage,并在最终 `response.completed.response.usage` 中回传
160
+ - 当前默认工具集由上游对齐子集和 pycodex 的 `clock` 扩展组成:`shell`、`shell_command`、`exec_command`、`write_stdin`、`clock`、`exec`、`wait`、`web_search`、`update_plan`、`request_user_input`、`request_permissions`、`spawn_agent`、`send_input`、`resume_agent`、`wait_agent`、`close_agent`、`apply_patch`、`grep_files`、`read_file`、`list_dir`、`view_image`
161
+ - `clock(period_m)` 为当前 Agent session 设置一个周期计时器,传 `null` 取消;每次回复后重新计时,到期后用包含当前时区时间的 `<clock_tick>` 消息唤醒 Agent
162
+ - 后台命令或 clock 正在等待时,空闲状态统一显示为 `idle: sleeping`
163
+ - workspace 只在当前活动 tab 上显示关闭按钮
164
+ - `--vllm-endpoint http://host:port` 会自动拉起一个本地 `responses_server` compat 层;当 path 为空时会内部补 `/v1`,继续把 `/responses` 请求转到下游 `/v1/chat/completions`。这条本地 compat 路径始终使用标准 Responses request shape,即使所选模型的 metadata 打开了 `responses_lite` 也不会切换成 lite wire format;启动时还会读取下游 `/v1/models`,自动使用返回列表中的最后一个 model id。当前对 `model_provider = "vllm"` 已补上 reasoning 兼容:会把 chat chunk 里的 `reasoning` / `reasoning_content` 翻回 Responses `reasoning` item,并把历史里的 `reasoning` item 回放成下游 assistant message 的 `reasoning` 字段;同时会向 vLLM 请求 streaming usage,并在最终 `response.completed.response.usage` 中回传
162
165
  - `pycodex doctor` 会检查配置、`.env`、API key、DNS、TCP/TLS,以及可选的 live Responses API 请求
163
166
 
164
167
  它目前主要用于:
@@ -280,6 +283,7 @@ asyncio.run(main())
280
283
 
281
284
  本仓库额外兼容层 / 过渡工具:
282
285
 
286
+ - [x] `clock` — pycodex 的周期性 Agent 唤醒扩展。
283
287
  - [x] `exec` — 当前对 code-mode 的本地近似实现。
284
288
  - [x] `wait` — 当前对 code-mode 等待行为的本地近似实现。
285
289
 
@@ -290,13 +294,13 @@ asyncio.run(main())
290
294
  - [x] 非交互 `exec` 路径的 `input` 对齐 — prompt input 已对齐上游。
291
295
  - [x] developer/contextual-user message 的 shape 对齐 — message/content 结构已对齐。
292
296
  - [x] `AGENTS.md` + `<environment_context>` 注入逻辑对齐 — 上下文拼接顺序已对齐。
293
- - [x] 非交互 `exec` 路径的工具子集对齐 — 暴露给模型的工具集合已收敛。
297
+ - [x] 非交互 `exec` 路径的上游工具子集对齐 — 对齐子集已收敛;pycodex 额外暴露 `clock`。
294
298
  - [x] `include = ["reasoning.encrypted_content"]` — reasoning include 字段已对齐。
295
299
  - [x] `prompt_cache_key` — 请求级 prompt cache key 已补齐。
296
300
  - [x] `x-client-request-id` — 请求 id header 已补齐。
297
301
  - [x] `x-codex-turn-metadata` — turn id / sandbox header 已补齐。
298
302
  - [x] `originator` — mode-aware originator header 已补齐。
299
303
  - [x] `user-agent` 精确字符串对齐 — 非交互 `exec` 路径已对齐上游字符串。
300
- - [x] exec-mode tool schema 的逐字段对齐 — 当前通过工具层直接复用上游 snapshot。
304
+ - [x] 上游 exec-mode tool schema 的逐字段对齐 — 对齐工具使用类内 spec;`clock` 作为扩展单独记录。
301
305
  - [ ] 交互模式与非 `exec` 路径的完整行为对齐 — non-exec 首轮 context 已切到 `codex-tui` 路径,但 REPL 连续多轮行为还未完全验证。
302
306
  - [ ] sandbox / approvals / compact / memory 等外围行为对齐 — 外围系统仍在后续范围。
@@ -126,9 +126,10 @@ At the time of writing:
126
126
  - class-level tool descriptions, input schemas, output schemas, and notable
127
127
  runtime result shapes have been refreshed across the default local tool set
128
128
 
129
- The current implementation already matches:
129
+ The current upstream-aligned subset already matches:
130
130
 
131
- - exec-mode tool subset size and membership for the compared path
131
+ - exec-mode upstream tool subset membership for the compared path; pycodex
132
+ additionally inserts its `clock` extension after `write_stdin`
132
133
  - `include = ["reasoning.encrypted_content"]`
133
134
  - model-visible prompt fields (`instructions` and `input`)
134
135
  - request-scoped `prompt_cache_key`
@@ -194,6 +195,8 @@ schema 一致的工具有:
194
195
  schema 对齐。
195
196
  - `request_user_input`:`pycodex` 按 upstream source main 建模,带
196
197
  `autoResolutionMs`;installed `codex-cli 0.138.0` 的 live capture 仍未带该字段。
198
+ - `clock`:pycodex 特有的 per-Agent/session 周期唤醒扩展,不属于 upstream
199
+ schema 对齐集合。
197
200
  - `spawn_agent` / `send_input` / `resume_agent` / `wait_agent` /
198
201
  `close_agent`:upstream 当前首轮 request 不再平铺暴露这些工具,而是暴露
199
202
  `tool_search`,并由 deferred discovery 加载 Multi-agent tools。`pycodex` 仍在首轮
@@ -375,6 +378,7 @@ schema 一致的工具有:
375
378
  | `shell_command` | `not exposed` | `class aligned` | 默认首轮路径不带;类内 desc/schema 已刷新为 shell-string command 语义 |
376
379
  | `exec_command` | `intentional approval-field/description delta; round-trip same` | `class aligned except skipped auth + local idle resume` | 删除 fallback 后不再暴露 `sandbox_permissions` / `justification` / `prefix_rule`,这是 pycodex 刻意跳过鉴权逻辑的差异;description 额外提示长任务可以先回复用户,任务完成时 agent 会被 invoke 来继续处理;其余参数按 schema 执行,`function_call` / `function_call_output` 外层 shape 一致;默认 `10_000` token 截断和未读输出 `1 MiB` head/tail cap 已补齐,仅剩动态值差异 |
377
380
  | `write_stdin` | `first-request same; round-trip same` | `class aligned` | 删除 fallback 后首轮 schema 相等;`function_call` / `function_call_output` 外层 shape 一致;默认 `10_000` token 截断和未读输出 `1 MiB` head/tail cap 已补齐,仅剩动态值差异 |
381
+ | `clock` | `pycodex-only` | `local extension` | 每个 Agent/session 一个周期计时器;每次成功回复后重新计时,到期复用 Agent idle auto-resume 注入带 `current_time` 的 `<clock_tick>`,传 `null` 或关闭 session 时取消 |
378
382
  | `exec` | `not exposed` | `class aligned` | 默认首轮路径不带;code-mode custom/freeform desc 和 grammar 已刷新,仍需 code-mode request-visible 抓包复测 |
379
383
  | `wait` | `not exposed` | `class aligned` | 默认首轮路径不带;code-mode wait schema/runtime 已刷新,仍需 code-mode request-visible 抓包复测 |
380
384
  | `web_search` | `first-request same; round-trip same` | `class aligned` | 删除 fallback 后 provider-native payload 相等,包含 `search_content_types=["text","image"]`;`web_search_call` shape 一致;provider-native tool 无单独客户端 `tool_result` |
@@ -402,7 +406,7 @@ same:
402
406
  - instructions
403
407
  - input
404
408
  - include
405
- - exec-mode tool subset membership
409
+ - upstream exec-mode tool subset membership
406
410
  - request context field presence
407
411
  - exec-mode tool schemas
408
412
  - current default-path schemas for `write_stdin`, `web_search`, `update_plan`,
@@ -411,6 +415,7 @@ same:
411
415
 
412
416
  different:
413
417
  - dynamic request metadata values
418
+ - pycodex inserts the local `clock` extension after `write_stdin`
414
419
  - intentional `exec_command` approval/sandbox field omission and idle-resume
415
420
  description in pycodex
416
421
  - transport-layer header casing / normalization
@@ -543,9 +548,9 @@ including the IANA timezone name (`Asia/Hong_Kong` rather than `HKT`).
543
548
 
544
549
  ### 7. Exec-mode tool exposure
545
550
 
546
- For non-interactive `pycodex`, `get_tools(exec_mode=True)` now matches the
547
- upstream `codex exec` tool subset so prompt comparison is done against the same
548
- tool surface.
551
+ For non-interactive `pycodex`, `get_tools(exec_mode=True)` keeps the upstream
552
+ `codex exec` subset and inserts the documented local `clock` extension after
553
+ `write_stdin`. Alignment comparisons exclude that extra tool.
549
554
 
550
555
  ## Files involved
551
556
 
@@ -398,7 +398,8 @@ CodexNonExecConfirmedFacts = {
398
398
  当前代码已知差异:
399
399
 
400
400
  - `collaboration_mode` 内容来自 `./pycodex/prompts/collaboration_default.md` / `./pycodex/prompts/collaboration_plan.md`。
401
- - 默认 CLI 的 non-exec 路径当前使用 12 个 exec-mode tools。
401
+ - 默认 CLI 的 non-exec 路径在 12 个上游对齐 exec-mode tools 之外,还会在
402
+ `write_stdin` 后暴露 pycodex 扩展 `clock`。
402
403
  - REPL 连续多轮路径还没有单独 fake-server capture,所以现在不能声称它已经完全和 Codex 对齐。
403
404
 
404
405
  ## 4. Tool Schema
@@ -422,6 +423,9 @@ ExecModeToolOrder = [
422
423
  ]
423
424
  ```
424
425
 
426
+ `pycodex` 的实际默认顺序会在 `write_stdin` 后插入扩展工具 `clock`;上面的列表仍是
427
+ Codex 基准。
428
+
425
429
  ### 4.2 schema 形状
426
430
 
427
431
  ```python
@@ -8,6 +8,14 @@
8
8
  当前 HTTP backend 使用 FastAPI。
9
9
  当前独立测试里的 fake outcomming chat backend 也使用 FastAPI。
10
10
 
11
+ compat runtime 的下游 chat-completions / messages 流读取 timeout 默认是
12
+ `300` 秒,与 pycodex Responses client 的 `300_000 ms` SSE idle timeout
13
+ 对齐。
14
+
15
+ Responses 请求里的 `reasoning.effort` 会转换为下游 chat 请求的
16
+ `chat_template_kwargs.reasoning_effort`。这与 vLLM Responses renderer 的
17
+ prompt 渲染路径一致,也保留 provider 自定义 effort 值(例如 `max`)。
18
+
11
19
  ## 目录边界
12
20
 
13
21
  - incomming server 实现在 `responses_server/`
@@ -26,19 +34,19 @@
26
34
  - vLLM chat-completions `reasoning` / `reasoning_content` -> Responses `reasoning` item 适配
27
35
  - vLLM 历史 `reasoning` item -> assistant message `reasoning` 字段回放
28
36
  - vLLM streaming `usage` -> final `response.completed.response.usage`
29
- - 当环境变量 `PYCODEX_DUMP` 存在时,为每条 outcomming 请求附加 `return_token_ids = true`,并把抓到的 `prompt_token_ids` / `token_ids` 以 JSONL 追加到 `{PYCODEX_DUMP}/dump.jsonl`
37
+ - 当环境变量 `PYCODEX_DUMP` 存在时,为每条 outcomming 请求附加 `return_token_ids = true`,并把实际下发的 request body、下游 usage/cache、`prompt_token_ids` / `token_ids` 以 JSONL 追加到 `{PYCODEX_DUMP}/dump.jsonl`
30
38
  - 下游 chat stream 如果半路断开,会转成上游可解析的 `response.failed` 事件,而不是直接截断 HTTP body
31
39
  - 普通 function tools
32
40
  - custom tools 的 function-wrapper 兼容适配
33
41
  - mock `web_search` 接口对齐(返回空结果)
34
42
  - function / custom tool follow-up history 重建
43
+ - 结构化 `input_image` content items:user message 里的图片直接转成 chat `image_url` part;tool output 里的图片会在该 `tool` message 之后补一条只带图片的 `user` message(chat backend 普遍会丢弃 `tool` role 里的图片);`/v1/messages` 侧再转成 Anthropic `image` block
35
44
 
36
45
  ## 当前明确不支持
37
46
 
38
47
  这些能力当前会被显式拒绝,而不是静默降级:
39
48
 
40
49
  - 真正的 Responses `web_search` 执行;当前只做空结果 mock
41
- - 结构化 `input_image` tool output
42
50
  - 非流式 incomming 请求
43
51
 
44
52
  ## Incomming / Outcomming 分层
@@ -87,15 +95,23 @@ server 会为每条实际转发到下游的请求附上 `return_token_ids = true
87
95
  trajectory 追加到 `${PYCODEX_DUMP}/dump.jsonl`,当前记录格式是:
88
96
 
89
97
  ```json
90
- {"tokens":{"prefill":[1,2,3],"decode":[4,5,6]},"send_timestamp":2222.0}
98
+ {"request":{"model":"water18","messages":[],"stream":true,"return_token_ids":true},"usage":{"prompt_tokens":100,"completion_tokens":6,"total_tokens":106,"prompt_tokens_details":{"cached_tokens":64}},"tokens":{"prefill":[1,2,3],"decode":[4,5,6]},"send_timestamp":2222.0}
91
99
  ```
92
100
 
101
+ `request` 是 provider post-process 之后真正发出的 JSON body;HTTP headers 和
102
+ API key 不会写入 dump。对多轮 tool-call,可以逐条比较
103
+ `request.messages` 是否是上一轮的严格前缀扩展,并读取
104
+ `usage.prompt_tokens_details.cached_tokens` 核对下游 prefix-cache 命中。
105
+ 重试和 mock tool follow-up 都会各写一条独立记录。
106
+
93
107
  如果下游 provider 需要对 chat payload 做定制化改写,可以在
94
108
  `responses_server/payload_processors.py` 里注册对应 `model_provider -> proc_fn`
95
109
  映射;server 会在真正发出每一条 outcomming `/v1/chat/completions` 请求前,
96
110
  对 canonical `outcomming_request` 调一次这个 hook,默认按 `vllm` 处理。
97
111
  当前内置规则里,`vllm` 仍走 chat-completions compat 路径,但会额外保留
98
- reasoning;`stepfun` 会删除所有 `developer` role。
112
+ reasoning;`stepfun` 会把 `developer` role 转成 `system`。Step4 chat
113
+ template 不消费 `developer` role;使用该模板的 provider 应显式配置为
114
+ `stepfun`,否则 base instructions / AGENTS context 会被静默忽略。
99
115
 
100
116
  如果下游 chat stream 一轮结束时只给了 `reasoning` / `reasoning_content`,
101
117
  没有 assistant `content` 且没有 tool call,server 会丢弃这次 partial reasoning 并用
@@ -4,12 +4,12 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pycodex-ws"
7
- version = "0.2.6"
7
+ version = "0.2.7"
8
8
  description = "Browser workspace install alias for python-codex"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.6.2"
11
11
  dependencies = [
12
- "python-codex==0.2.6",
12
+ "python-codex==0.2.7",
13
13
  ]
14
14
 
15
15
  [tool.hatch.build.targets.wheel]
@@ -39,6 +39,8 @@ from .runtime_services import (
39
39
  from .tools import (
40
40
  ApplyPatchTool,
41
41
  BaseTool,
42
+ ClockManager,
43
+ ClockTool,
42
44
  CloseAgentTool,
43
45
  CodeModeManager,
44
46
  ExecTool,
@@ -96,6 +98,8 @@ __all__ = [
96
98
  "ApplyPatchTool",
97
99
  "AssistantMessage",
98
100
  "BaseTool",
101
+ "ClockManager",
102
+ "ClockTool",
99
103
  "CloseAgentTool",
100
104
  "create_agent_runtime_environment",
101
105
  "CodeModeManager",
@@ -17,7 +17,14 @@ from .protocol import (
17
17
  TurnResult,
18
18
  UserMessage,
19
19
  )
20
- from .tools import ExecCommandTool, ToolContext, ToolRegistry, UnifiedExecManager
20
+ from .tools import (
21
+ ClockManager,
22
+ ClockTool,
23
+ ExecCommandTool,
24
+ ToolContext,
25
+ ToolRegistry,
26
+ UnifiedExecManager,
27
+ )
21
28
  from .utils.truncation import truncate_tool_results_for_history
22
29
  from .utils import uuid7_string
23
30
  import typing
@@ -96,6 +103,14 @@ class Agent:
96
103
  )
97
104
  if self._exec_manager is not None:
98
105
  self._exec_manager.set_notify_hook(self.maybe_invoke)
106
+ clock_tool = self._tool_registry.get_tool("clock")
107
+ self._clock_manager: 'typing.Union[ClockManager, None]' = (
108
+ clock_tool._manager
109
+ if isinstance(clock_tool, ClockTool)
110
+ else None
111
+ )
112
+ if self._clock_manager is not None:
113
+ self._clock_manager.set_notify_hook(self.maybe_invoke)
99
114
 
100
115
  @property
101
116
  def history(self) -> 'typing.Tuple[ConversationItem, ...]':
@@ -141,6 +156,8 @@ class Agent:
141
156
  self, texts: 'typing.List[str]', turn_id: 'typing.Union[str, None]' = None
142
157
  ) -> 'TurnResult':
143
158
  self._turn_running = True
159
+ if self._clock_manager is not None:
160
+ self._clock_manager.turn_started()
144
161
  turn_id = turn_id or uuid7_string()
145
162
  self.interrupt_asap = False
146
163
  new_user_messages = [UserMessage(text=text) for text in texts]
@@ -198,6 +215,8 @@ class Agent:
198
215
  output_text=last_assistant_message,
199
216
  )
200
217
  self._turn_running = False
218
+ if self._clock_manager is not None:
219
+ self._clock_manager.arm_after_reply()
201
220
  return TurnResult(
202
221
  turn_id=turn_id,
203
222
  output_text=last_assistant_message,
@@ -240,17 +259,28 @@ class Agent:
240
259
  raise
241
260
 
242
261
  async def maybe_invoke(self, event: 'typing.Dict[str, object]') -> 'bool':
243
- if self._turn_running or event.get("type") != "exec_command_completed":
262
+ if self._turn_running:
263
+ return False
264
+ event_type = event.get("type")
265
+ if event_type == "exec_command_completed":
266
+ payload = {
267
+ "session_id": event.get("session_id"),
268
+ "exit_code": event.get("exit_code"),
269
+ "command": event.get("command"),
270
+ }
271
+ tag = "exec_command_completed"
272
+ elif event_type == "clock_tick":
273
+ payload = {
274
+ "period_m": event.get("period_m"),
275
+ "current_time": event.get("current_time"),
276
+ }
277
+ tag = "clock_tick"
278
+ else:
244
279
  return False
245
- payload = {
246
- "session_id": event.get("session_id"),
247
- "exit_code": event.get("exit_code"),
248
- "command": event.get("command"),
249
- }
250
280
  text = (
251
- "<exec_command_completed>\n"
281
+ f"<{tag}>\n"
252
282
  f"{json.dumps(payload, ensure_ascii=False, separators=(',', ':'))}\n"
253
- "</exec_command_completed>"
283
+ f"</{tag}>"
254
284
  )
255
285
  self._turn_running = True
256
286
  task = asyncio.create_task(self.run_turn([text]))
@@ -259,6 +289,10 @@ class Agent:
259
289
  )
260
290
  return True
261
291
 
292
+ def shutdown(self) -> 'None':
293
+ if self._clock_manager is not None:
294
+ self._clock_manager.cancel()
295
+
262
296
  async def _execute_tool_batch(
263
297
  self,
264
298
  turn_id: 'str',
@@ -328,16 +362,22 @@ class Agent:
328
362
 
329
363
  def _emit(self, kind: 'str', turn_id: 'str', **payload: 'object') -> 'None':
330
364
  if kind in TERMINAL_TURN_EVENTS:
331
- payload["background_exec_count"] = self._background_exec_count()
365
+ payload["background_work_count"] = self._background_work_count(kind)
332
366
  self._event_handler(
333
367
  AgentEvent(kind=kind, turn_id=turn_id, payload=dict(payload))
334
368
  )
335
369
 
336
- def _background_exec_count(self) -> 'int':
370
+ def _background_work_count(self, terminal_event: 'str') -> 'int':
337
371
  manager: 'typing.Union[UnifiedExecManager, None]' = self._exec_manager
338
- if manager is None:
339
- return 0
340
- return manager.running_session_count()
372
+ count = 0 if manager is None else manager.running_session_count()
373
+ clock_manager = self._clock_manager
374
+ if (
375
+ terminal_event == "turn_completed"
376
+ and clock_manager is not None
377
+ and clock_manager.enabled
378
+ ):
379
+ count += 1
380
+ return count
341
381
 
342
382
  def _persist_history_items(
343
383
  self,