python-codex 0.2.4__tar.gz → 0.2.6__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 (128) hide show
  1. {python_codex-0.2.4 → python_codex-0.2.6}/.github/workflows/publish.yml +32 -4
  2. {python_codex-0.2.4 → python_codex-0.2.6}/AGENTS.md +5 -1
  3. {python_codex-0.2.4 → python_codex-0.2.6}/PKG-INFO +34 -6
  4. {python_codex-0.2.4 → python_codex-0.2.6}/README.md +33 -5
  5. {python_codex-0.2.4 → python_codex-0.2.6}/README_ZH.md +17 -2
  6. python_codex-0.2.6/packages/pycodex-ws/README.md +13 -0
  7. python_codex-0.2.6/packages/pycodex-ws/pycodex_ws_meta/__init__.py +1 -0
  8. python_codex-0.2.6/packages/pycodex-ws/pyproject.toml +16 -0
  9. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/context.py +4 -18
  10. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/interactive_session.py +15 -1
  11. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/model.py +153 -11
  12. python_codex-0.2.6/pycodex/model_metadata.py +31 -0
  13. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/prompts/models.json +334 -0
  14. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/unified_exec_manager.py +32 -1
  15. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/utils/compactor.py +5 -87
  16. {python_codex-0.2.4 → python_codex-0.2.6}/pyproject.toml +1 -1
  17. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_agent.py +7 -14
  18. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_builtin_tools.py +58 -0
  19. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_cli.py +32 -5
  20. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_compactor.py +9 -9
  21. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_context.py +10 -0
  22. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_model.py +186 -0
  23. python_codex-0.2.6/tests/test_packaging.py +27 -0
  24. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_workspace_server.py +182 -0
  25. {python_codex-0.2.4 → python_codex-0.2.6}/workspace_server/app.py +57 -1
  26. {python_codex-0.2.4 → python_codex-0.2.6}/workspace_server/workspace.html +309 -72
  27. {python_codex-0.2.4 → python_codex-0.2.6}/.github/workflows/test.yml +0 -0
  28. {python_codex-0.2.4 → python_codex-0.2.6}/.gitignore +0 -0
  29. {python_codex-0.2.4 → python_codex-0.2.6}/LICENSE +0 -0
  30. {python_codex-0.2.4 → python_codex-0.2.6}/docs/ALIGNMENT.md +0 -0
  31. {python_codex-0.2.4 → python_codex-0.2.6}/docs/CONTEXT.md +0 -0
  32. {python_codex-0.2.4 → python_codex-0.2.6}/docs/responses_server/README.md +0 -0
  33. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/__init__.py +0 -0
  34. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/agent.py +0 -0
  35. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/cli.py +0 -0
  36. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/collaboration.py +0 -0
  37. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/compat.py +0 -0
  38. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/doctor.py +0 -0
  39. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/feishu_card.py +0 -0
  40. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/feishu_link.py +0 -0
  41. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/portable.py +0 -0
  42. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/portable_server.py +0 -0
  43. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/prompts/collaboration_default.md +0 -0
  44. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/prompts/collaboration_plan.md +0 -0
  45. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/prompts/default_base_instructions.md +0 -0
  46. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/prompts/permissions/approval_policy/never.md +0 -0
  47. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/prompts/permissions/approval_policy/on_failure.md +0 -0
  48. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/prompts/permissions/approval_policy/on_request.md +0 -0
  49. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/prompts/permissions/approval_policy/on_request_rule_request_permission.md +0 -0
  50. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/prompts/permissions/approval_policy/unless_trusted.md +0 -0
  51. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/prompts/permissions/sandbox_mode/danger_full_access.md +0 -0
  52. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/prompts/permissions/sandbox_mode/read_only.md +0 -0
  53. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/prompts/permissions/sandbox_mode/workspace_write.md +0 -0
  54. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/protocol.py +0 -0
  55. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/runtime.py +0 -0
  56. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/runtime_services.py +0 -0
  57. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/__init__.py +0 -0
  58. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/agent_tool_schemas.py +0 -0
  59. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/apply_patch_tool.py +0 -0
  60. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/base_tool.py +0 -0
  61. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/close_agent_tool.py +0 -0
  62. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/code_mode_manager.py +0 -0
  63. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/exec_command_tool.py +0 -0
  64. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/exec_runtime.js +0 -0
  65. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/exec_tool.py +0 -0
  66. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/grep_files_tool.py +0 -0
  67. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/ipython_tool.py +0 -0
  68. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/list_dir_tool.py +0 -0
  69. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/read_file_tool.py +0 -0
  70. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/request_permissions_tool.py +0 -0
  71. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/request_user_input_tool.py +0 -0
  72. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/resume_agent_tool.py +0 -0
  73. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/send_input_tool.py +0 -0
  74. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/shell_command_tool.py +0 -0
  75. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/shell_tool.py +0 -0
  76. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/spawn_agent_tool.py +0 -0
  77. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/update_plan_tool.py +0 -0
  78. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/view_image_tool.py +0 -0
  79. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/wait_agent_tool.py +0 -0
  80. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/wait_tool.py +0 -0
  81. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/web_search_tool.py +0 -0
  82. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/tools/write_stdin_tool.py +0 -0
  83. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/utils/__init__.py +0 -0
  84. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/utils/async_bridge.py +0 -0
  85. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/utils/debug.py +0 -0
  86. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/utils/dotenv.py +0 -0
  87. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/utils/get_env.py +0 -0
  88. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/utils/random_ids.py +0 -0
  89. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/utils/session_persist.py +0 -0
  90. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/utils/toolcall_visualize.py +0 -0
  91. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/utils/truncation.py +0 -0
  92. {python_codex-0.2.4 → python_codex-0.2.6}/pycodex/utils/visualize.py +0 -0
  93. {python_codex-0.2.4 → python_codex-0.2.6}/responses_server/__init__.py +0 -0
  94. {python_codex-0.2.4 → python_codex-0.2.6}/responses_server/__main__.py +0 -0
  95. {python_codex-0.2.4 → python_codex-0.2.6}/responses_server/app.py +0 -0
  96. {python_codex-0.2.4 → python_codex-0.2.6}/responses_server/config.py +0 -0
  97. {python_codex-0.2.4 → python_codex-0.2.6}/responses_server/messages_api.py +0 -0
  98. {python_codex-0.2.4 → python_codex-0.2.6}/responses_server/payload_processors.py +0 -0
  99. {python_codex-0.2.4 → python_codex-0.2.6}/responses_server/server.py +0 -0
  100. {python_codex-0.2.4 → python_codex-0.2.6}/responses_server/session_store.py +0 -0
  101. {python_codex-0.2.4 → python_codex-0.2.6}/responses_server/stream_router.py +0 -0
  102. {python_codex-0.2.4 → python_codex-0.2.6}/responses_server/tools/__init__.py +0 -0
  103. {python_codex-0.2.4 → python_codex-0.2.6}/responses_server/tools/custom_adapter.py +0 -0
  104. {python_codex-0.2.4 → python_codex-0.2.6}/responses_server/tools/web_search.py +0 -0
  105. {python_codex-0.2.4 → python_codex-0.2.6}/responses_server/trajectory_dump.py +0 -0
  106. {python_codex-0.2.4 → python_codex-0.2.6}/tests/TESTS.md +0 -0
  107. {python_codex-0.2.4 → python_codex-0.2.6}/tests/__init__.py +0 -0
  108. {python_codex-0.2.4 → python_codex-0.2.6}/tests/compare_request_user_input_roundtrip.py +0 -0
  109. {python_codex-0.2.4 → python_codex-0.2.6}/tests/compare_steer_request_bodies.py +0 -0
  110. {python_codex-0.2.4 → python_codex-0.2.6}/tests/compare_tool_schemas.py +0 -0
  111. {python_codex-0.2.4 → python_codex-0.2.6}/tests/fake_responses_server.py +0 -0
  112. {python_codex-0.2.4 → python_codex-0.2.6}/tests/fakes.py +0 -0
  113. {python_codex-0.2.4 → python_codex-0.2.6}/tests/responses_server/fake_chat_completions_server.py +0 -0
  114. {python_codex-0.2.4 → python_codex-0.2.6}/tests/responses_server/test_server.py +0 -0
  115. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_async_bridge.py +0 -0
  116. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_doctor.py +0 -0
  117. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_fake_responses_server.py +0 -0
  118. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_feishu_card.py +0 -0
  119. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_feishu_link.py +0 -0
  120. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_ipython_tool.py +0 -0
  121. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_portable.py +0 -0
  122. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_py36_syntax.py +0 -0
  123. {python_codex-0.2.4 → python_codex-0.2.6}/tests/test_visualize.py +0 -0
  124. {python_codex-0.2.4 → python_codex-0.2.6}/tools/feishu_oauth.py +0 -0
  125. {python_codex-0.2.4 → python_codex-0.2.6}/workspace_server/__init__.py +0 -0
  126. {python_codex-0.2.4 → python_codex-0.2.6}/workspace_server/__main__.py +0 -0
  127. {python_codex-0.2.4 → python_codex-0.2.6}/workspace_server/workspaces.html +0 -0
  128. {python_codex-0.2.4 → python_codex-0.2.6}/workspace_server/workspaces.py +0 -0
@@ -107,8 +107,10 @@ jobs:
107
107
 
108
108
  - name: Build distributions
109
109
  run: |
110
- python -m pip install --upgrade build
111
- python -m build
110
+ python -m pip install --upgrade build twine
111
+ python -m build --outdir dist/python-codex .
112
+ python -m build --outdir dist/pycodex-ws packages/pycodex-ws
113
+ python -m twine check dist/python-codex/* dist/pycodex-ws/*
112
114
 
113
115
  - name: Upload distributions
114
116
  uses: actions/upload-artifact@v4
@@ -124,7 +126,7 @@ jobs:
124
126
  id-token: write
125
127
  environment:
126
128
  name: testpypi
127
- url: https://test.pypi.org/project/pycodex/
129
+ url: https://test.pypi.org/project/python-codex/
128
130
  steps:
129
131
  - name: Download distributions
130
132
  uses: actions/download-artifact@v4
@@ -135,6 +137,7 @@ jobs:
135
137
  - name: Publish to TestPyPI
136
138
  uses: pypa/gh-action-pypi-publish@release/v1
137
139
  with:
140
+ packages-dir: dist/python-codex/
138
141
  repository-url: https://test.pypi.org/legacy/
139
142
 
140
143
  publish-pypi:
@@ -145,7 +148,7 @@ jobs:
145
148
  id-token: write
146
149
  environment:
147
150
  name: pypi
148
- url: https://pypi.org/project/pycodex/
151
+ url: https://pypi.org/project/python-codex/
149
152
  steps:
150
153
  - name: Download distributions
151
154
  uses: actions/download-artifact@v4
@@ -155,3 +158,28 @@ jobs:
155
158
 
156
159
  - name: Publish to PyPI
157
160
  uses: pypa/gh-action-pypi-publish@release/v1
161
+ with:
162
+ packages-dir: dist/python-codex/
163
+
164
+ publish-pycodex-ws:
165
+ if: github.event_name == 'release' || github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.repository == 'pypi')
166
+ needs:
167
+ - build
168
+ - publish-pypi
169
+ runs-on: ubuntu-latest
170
+ permissions:
171
+ id-token: write
172
+ environment:
173
+ name: pypi
174
+ url: https://pypi.org/project/pycodex-ws/
175
+ steps:
176
+ - name: Download distributions
177
+ uses: actions/download-artifact@v4
178
+ with:
179
+ name: python-package-distributions
180
+ path: dist/
181
+
182
+ - name: Publish pycodex-ws to PyPI
183
+ uses: pypa/gh-action-pypi-publish@release/v1
184
+ with:
185
+ packages-dir: dist/pycodex-ws/
@@ -3,7 +3,9 @@
3
3
  ## Repo Priors
4
4
 
5
5
  - 这个仓库的目标是尽可能准确地复现上游 Codex(`https://github.com/openai/codex`),不是发明一个“类似 Codex”的简化替代品;凡是行为差异都应优先朝原版 Codex 收敛。
6
- - 发布到 PyPI 时,distribution name 用 `python-codex`;代码包目录、`import` 路径和 CLI 命令仍保持 `pycodex`。
6
+ - PyPI 主 distribution name 用 `python-codex`;代码包目录、`import` 路径和 CLI 命令仍保持 `pycodex`。
7
+ - `pycodex-ws` 是与 `python-codex` 锁步同版本的薄 metapackage,只依赖精确同版本的 `python-codex`;不要在两个 wheel 中重复打包 `pycodex/`、`workspace_server/` 或重复声明 `pycodex-ws` console script。
8
+ - 双包发布时先发布 `python-codex`,成功后再发布 `pycodex-ws`,避免新 metapackage 短暂指向尚不存在的核心版本。
7
9
  - 包结构直接放在 repo 根目录下的 `pycodex/`,不使用 `src/` layout。
8
10
  - Rust 到 Python 的主映射:`submission_loop` -> `pycodex/runtime.py`,`run_turn` / `run_sampling_request` -> `pycodex/agent.py`,`ToolRouter` -> `pycodex/tools/base_tool.py` + `pycodex/tools/`.
9
11
  - 会话协议先收敛到 4 类 item:`UserMessage`、`AssistantMessage`、`ToolCall`、`ToolResult`;只有这层稳定后再扩 richer event model。
@@ -19,6 +21,7 @@
19
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`”重建即可。
20
22
  - `pycodex` 默认是最小交互 CLI;无 prompt 时进入 REPL,并通过 `AgentRuntime` 跑外层提交循环。当前会显示最小事件流、assistant 流式输出、简单 title/history(`/title`, `/history`),并默认注册一组与原版一一对应的本地工具子集。
21
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
+ - 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.
22
25
  - 交互 CLI 的事件流展示优先表达用户可感知的阶段(例如工具开始/完成、模型回看工具结果),不要直接把内部 `iteration` 计数暴露成主要状态文案;`iterations` 应继续保留在 `TurnResult` 等程序化结果里。
23
26
  - 在交互 CLI 里,`stream_error` 表示当前 Responses stream attempt 失败且模型客户端可能马上自动重试;不要在这个事件上 `finish_stream()` 输出当前 assistant delta buffer,否则第一次失败 attempt 的文本和重试成功后的最终回复会重复显示。真正 fatal 的失败仍由 `turn_failed` 走通用 flush,保留 partial 输出。
24
27
  - prompt/context 相关逻辑统一放在 `pycodex/context.py`:`AgentLoop` 只维护真实会话历史;每轮请求前由 `ContextManager` 注入 base instructions、developer message、`AGENTS.md` 指令和 `<environment_context>`,且这些注入项不写回 history。
@@ -61,6 +64,7 @@
61
64
  - auto-compact 对齐上游配置名 `model_auto_compact_token_limit`;为空时关闭,触发依据是最近一次模型上报的 `usage.total_tokens`,pre-turn 压缩上一轮历史,mid-turn 压缩工具 follow-up 前的当前历史,并继续复用现有 compacted rollout 记录。
62
65
  - Responses streaming 里的 `response.incomplete` 不是连接断开:不要让 `ResponsesModelClient` 把它当 retryable incomplete stream 反复重连。普通 turn 应明确报 `response.incomplete`;compact 请求如果已经收到 assistant partial summary,可以用这个 partial summary 完成 replacement history,避免 midturn auto-compact 卡在 5 次 retry。
63
66
  - 上游 Codex Responses 请求当前不传模型级 `max_output_tokens`,也没有读取 `model_max_output_tokens` 这个 config key;这个名字在上游主要用于工具输出截断,不要为了上游对齐把它加进模型请求。
67
+ - `service_tier = "fast"` 在 config 中是 Fast mode 的持久写法,但 Responses wire 值是 `priority`;只对模型 metadata 的 `service_tiers` 明确包含该 id 的模型发送,`default` 和不支持的 tier 都省略。
64
68
  - 普通 turn 遇到 `ResponsesIncompleteError(reason="max_output_tokens")` 时,上游语义是保留异常前已经收到的 `response.output_item.done`;pycodex 因为模型客户端按整轮返回,需要在异常路径把这些 done assistant/reasoning items 写入 history 和 rollout,才能让用户下一句 `continue` 接上。不要把纯 `response.output_text.delta` 合成 history,也不要持久化没有 tool result 的 tool call。
65
69
  - 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`.
66
70
  - `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`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-codex
3
- Version: 0.2.4
3
+ Version: 0.2.6
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
@@ -23,8 +23,17 @@ Description-Content-Type: text/markdown
23
23
 
24
24
  English README. Chinese version: `README_ZH.md`
25
25
 
26
- PyPI distribution name: `python-codex`
27
- Import path and CLI command remain `pycodex`.
26
+ PyPI distributions:
27
+
28
+ - Primary package: `python-codex`
29
+ - Workspace install alias: `pycodex-ws`
30
+
31
+ The import path remains `pycodex`; the CLI commands are `pycodex` and
32
+ `pycodex-ws`.
33
+
34
+ `pycodex-ws` is a thin metapackage that depends on the exact matching
35
+ `python-codex` version. The implementation and console script remain owned by
36
+ `python-codex`, so the two distributions never install duplicate modules.
28
37
 
29
38
  This repository extracts the core Codex agent loop from upstream Codex
30
39
  (`https://github.com/openai/codex`) into a deliberately small Python version,
@@ -44,6 +53,13 @@ Relevant Rust reference points:
44
53
 
45
54
  ## Quick Start
46
55
 
56
+ Install the full package or the workspace-oriented alias:
57
+
58
+ ```bash
59
+ pip install python-codex
60
+ pip install pycodex-ws
61
+ ```
62
+
47
63
  Install dependencies first:
48
64
 
49
65
  ```bash
@@ -179,7 +195,8 @@ Current behavior:
179
195
  - interactive mode shows a compact event stream for user-visible phases such as
180
196
  tool execution and model follow-up after tool results
181
197
  - assistant text is printed from streaming deltas directly
182
- - interactive mode supports `/history`, `/title`, `/model`, `/resume`, and `/compact`
198
+ - interactive mode supports `/history`, `/title`, `/model`, `/resume`, `/compact`,
199
+ and `/fork`
183
200
  - `/model <name>` switches the model used by later turns in the current
184
201
  interactive session; `/model` shows the current model and available choices
185
202
  - `/resume` with no argument lists the currently resumable sessions by their
@@ -189,9 +206,15 @@ Current behavior:
189
206
  - `/compact` synthesizes a local handoff summary, replaces the in-memory
190
207
  conversation history with the compacted view, and appends a compacted-history
191
208
  entry to the rollout so later `/resume` sees the same state
209
+ - `/fork` generates a new model session id while preserving the current history,
210
+ rollout, and workspace tab
192
211
  - `model_auto_compact_token_limit = <tokens>` in `config.toml` enables the same
193
212
  compaction path automatically when the latest reported usage reaches that
194
213
  threshold before a follow-up sampling request or the next user turn
214
+ - `service_tier = "fast"` enables Fast mode for models whose vendored metadata
215
+ advertises the `priority` service tier; pycodex follows upstream Codex by
216
+ sending `service_tier = "priority"` on the Responses request, while
217
+ `service_tier = "default"` or unsupported tiers are omitted
195
218
  - if a model request fails with `context_length_exceeded`, pycodex now treats
196
219
  the provider-reported requested token count as a failed-request usage sample,
197
220
  triggers the same compact path immediately, and retries the request once; if
@@ -219,8 +242,13 @@ Current behavior:
219
242
  `delete(name)` controls; omitted names become `workspace-1`, `workspace-2`,
220
243
  etc. If `board` is omitted when adding a workspace, pycodex assigns a random
221
244
  writable `/tmp/pcws-*.html` board path. Add/delete actions and later
222
- session-state saves refresh the JSON file. `--password <value>` enables a
223
- password-only login page for workspace pages, APIs, and websocket connections.
245
+ session-state saves refresh the JSON file. Board HTML can reference local
246
+ images beside the board (including nested paths) with relative URLs; only
247
+ `image/*` files contained by the board directory are served.
248
+ Assistant Markdown supports KaTeX formulas with `$...$`, `$$...$$`,
249
+ `\(...\)`, and `\[...\]` delimiters.
250
+ `--password <value>` enables a password-only login page for workspace pages,
251
+ APIs, and websocket connections.
224
252
  - steer is enabled by default in interactive mode: normal input goes into the
225
253
  runtime steer path, the current request stops at the next safe boundary, and
226
254
  later steer text is appended to the next model request's `input` in order;
@@ -2,8 +2,17 @@
2
2
 
3
3
  English README. Chinese version: `README_ZH.md`
4
4
 
5
- PyPI distribution name: `python-codex`
6
- Import path and CLI command remain `pycodex`.
5
+ PyPI distributions:
6
+
7
+ - Primary package: `python-codex`
8
+ - Workspace install alias: `pycodex-ws`
9
+
10
+ The import path remains `pycodex`; the CLI commands are `pycodex` and
11
+ `pycodex-ws`.
12
+
13
+ `pycodex-ws` is a thin metapackage that depends on the exact matching
14
+ `python-codex` version. The implementation and console script remain owned by
15
+ `python-codex`, so the two distributions never install duplicate modules.
7
16
 
8
17
  This repository extracts the core Codex agent loop from upstream Codex
9
18
  (`https://github.com/openai/codex`) into a deliberately small Python version,
@@ -23,6 +32,13 @@ Relevant Rust reference points:
23
32
 
24
33
  ## Quick Start
25
34
 
35
+ Install the full package or the workspace-oriented alias:
36
+
37
+ ```bash
38
+ pip install python-codex
39
+ pip install pycodex-ws
40
+ ```
41
+
26
42
  Install dependencies first:
27
43
 
28
44
  ```bash
@@ -158,7 +174,8 @@ Current behavior:
158
174
  - interactive mode shows a compact event stream for user-visible phases such as
159
175
  tool execution and model follow-up after tool results
160
176
  - assistant text is printed from streaming deltas directly
161
- - interactive mode supports `/history`, `/title`, `/model`, `/resume`, and `/compact`
177
+ - interactive mode supports `/history`, `/title`, `/model`, `/resume`, `/compact`,
178
+ and `/fork`
162
179
  - `/model <name>` switches the model used by later turns in the current
163
180
  interactive session; `/model` shows the current model and available choices
164
181
  - `/resume` with no argument lists the currently resumable sessions by their
@@ -168,9 +185,15 @@ Current behavior:
168
185
  - `/compact` synthesizes a local handoff summary, replaces the in-memory
169
186
  conversation history with the compacted view, and appends a compacted-history
170
187
  entry to the rollout so later `/resume` sees the same state
188
+ - `/fork` generates a new model session id while preserving the current history,
189
+ rollout, and workspace tab
171
190
  - `model_auto_compact_token_limit = <tokens>` in `config.toml` enables the same
172
191
  compaction path automatically when the latest reported usage reaches that
173
192
  threshold before a follow-up sampling request or the next user turn
193
+ - `service_tier = "fast"` enables Fast mode for models whose vendored metadata
194
+ advertises the `priority` service tier; pycodex follows upstream Codex by
195
+ sending `service_tier = "priority"` on the Responses request, while
196
+ `service_tier = "default"` or unsupported tiers are omitted
174
197
  - if a model request fails with `context_length_exceeded`, pycodex now treats
175
198
  the provider-reported requested token count as a failed-request usage sample,
176
199
  triggers the same compact path immediately, and retries the request once; if
@@ -198,8 +221,13 @@ Current behavior:
198
221
  `delete(name)` controls; omitted names become `workspace-1`, `workspace-2`,
199
222
  etc. If `board` is omitted when adding a workspace, pycodex assigns a random
200
223
  writable `/tmp/pcws-*.html` board path. Add/delete actions and later
201
- session-state saves refresh the JSON file. `--password <value>` enables a
202
- password-only login page for workspace pages, APIs, and websocket connections.
224
+ session-state saves refresh the JSON file. Board HTML can reference local
225
+ images beside the board (including nested paths) with relative URLs; only
226
+ `image/*` files contained by the board directory are served.
227
+ Assistant Markdown supports KaTeX formulas with `$...$`, `$$...$$`,
228
+ `\(...\)`, and `\[...\]` delimiters.
229
+ `--password <value>` enables a password-only login page for workspace pages,
230
+ APIs, and websocket connections.
203
231
  - steer is enabled by default in interactive mode: normal input goes into the
204
232
  runtime steer path, the current request stops at the next safe boundary, and
205
233
  later steer text is appended to the next model request's `input` in order;
@@ -2,8 +2,16 @@
2
2
 
3
3
  中文 README。English version: `README.md`
4
4
 
5
- PyPI distribution name: `python-codex`
6
- Import path and CLI command remain `pycodex`.
5
+ PyPI distributions:
6
+
7
+ - 主包:`python-codex`
8
+ - Workspace 安装别名:`pycodex-ws`
9
+
10
+ Import 路径仍保持 `pycodex`;CLI 命令为 `pycodex` 和 `pycodex-ws`。
11
+
12
+ `pycodex-ws` 是一个薄 metapackage,精确依赖同版本的 `python-codex`。
13
+ 实际代码和 console script 继续由 `python-codex` 提供,两个 distribution
14
+ 不会重复安装同名模块。
7
15
 
8
16
  这个仓库把上游 Codex(`https://github.com/openai/codex`)里最核心的 agent
9
17
  闭环先抽成一个尽量小的 Python 版本,重点保留两层结构:
@@ -21,6 +29,13 @@ Import path and CLI command remain `pycodex`.
21
29
 
22
30
  ## 快速开始
23
31
 
32
+ 安装完整包或 workspace 安装别名:
33
+
34
+ ```bash
35
+ pip install python-codex
36
+ pip install pycodex-ws
37
+ ```
38
+
24
39
  先安装开发依赖:
25
40
 
26
41
  ```bash
@@ -0,0 +1,13 @@
1
+ # pycodex-ws
2
+
3
+ `pycodex-ws` is the workspace-oriented installation name for
4
+ [`python-codex`](https://pypi.org/project/python-codex/).
5
+
6
+ ```bash
7
+ pip install pycodex-ws
8
+ pycodex-ws --help
9
+ ```
10
+
11
+ This distribution intentionally contains only package metadata. It depends on
12
+ the exact matching `python-codex` version, which provides the `workspace_server`
13
+ implementation and the `pycodex-ws` console script.
@@ -0,0 +1 @@
1
+ """Metadata-only package for the pycodex-ws distribution."""
@@ -0,0 +1,16 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.17.1,<1.18"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pycodex-ws"
7
+ version = "0.2.6"
8
+ description = "Browser workspace install alias for python-codex"
9
+ readme = "README.md"
10
+ requires-python = ">=3.6.2"
11
+ dependencies = [
12
+ "python-codex==0.2.6",
13
+ ]
14
+
15
+ [tool.hatch.build.targets.wheel]
16
+ packages = ["pycodex_ws_meta"]
@@ -1,8 +1,6 @@
1
1
 
2
2
  from dataclasses import dataclass
3
3
  from datetime import datetime
4
- from functools import lru_cache
5
- import json
6
4
  from pathlib import Path
7
5
  import typing
8
6
 
@@ -12,6 +10,7 @@ except ModuleNotFoundError: # pragma: no cover - Python 3.10 path
12
10
  import tomli as tomllib
13
11
 
14
12
  from .collaboration import DEFAULT_COLLABORATION_MODE, CollaborationMode
13
+ from .model_metadata import load_models_by_slug
15
14
  from .protocol import ContextMessage, ConversationItem, JSONDict, Prompt, ToolSpec
16
15
  from .utils.get_env import (
17
16
  get_sandbox_tag,
@@ -23,7 +22,6 @@ from .utils.get_env import (
23
22
  DEFAULT_BASE_INSTRUCTIONS_PATH = (
24
23
  Path(__file__).resolve().parent / "prompts" / "default_base_instructions.md"
25
24
  )
26
- DEFAULT_MODELS_PATH = Path(__file__).resolve().parent / "prompts" / "models.json"
27
25
  DEFAULT_COLLABORATION_INSTRUCTIONS_PATH = (
28
26
  Path(__file__).resolve().parent / "prompts" / "collaboration_default.md"
29
27
  )
@@ -270,7 +268,7 @@ class ContextManager:
270
268
  model_metadata = None
271
269
  model_slug = self._config.model
272
270
  if model_slug is not None:
273
- model_metadata = _load_models_by_slug().get(model_slug)
271
+ model_metadata = load_models_by_slug().get(model_slug)
274
272
 
275
273
  context_window = self._config.model_context_window
276
274
  if context_window is None and model_metadata is not None:
@@ -293,7 +291,7 @@ class ContextManager:
293
291
  model_slug = self._config.model
294
292
  if model_slug is None:
295
293
  return None
296
- model_metadata = _load_models_by_slug().get(model_slug)
294
+ model_metadata = load_models_by_slug().get(model_slug)
297
295
  if model_metadata is None:
298
296
  return None
299
297
  return _normalize_int(model_metadata.get("auto_compact_token_limit"))
@@ -302,7 +300,7 @@ class ContextManager:
302
300
  model_slug = self._config.model
303
301
  if model_slug is None:
304
302
  return None
305
- model_metadata = _load_models_by_slug().get(model_slug)
303
+ model_metadata = load_models_by_slug().get(model_slug)
306
304
  if model_metadata is None:
307
305
  return None
308
306
 
@@ -560,18 +558,6 @@ def _read_first_instruction_file(base: 'Path') -> 'typing.Union[str, None]':
560
558
  return None
561
559
 
562
560
 
563
- @lru_cache(maxsize=1)
564
- def _load_models_by_slug() -> 'typing.Dict[str, JSONDict]':
565
- payload = json.loads(DEFAULT_MODELS_PATH.read_text(encoding="utf-8"))
566
- models = payload.get("models", [])
567
- by_slug: 'typing.Dict[str, JSONDict]' = {}
568
- for model in models:
569
- slug = model.get("slug")
570
- if isinstance(slug, str):
571
- by_slug[slug] = model
572
- return by_slug
573
-
574
-
575
561
  def _resolve_personality_message(variables, personality: 'typing.Union[str, None]') -> 'str':
576
562
  if not isinstance(variables, dict):
577
563
  return ""
@@ -25,10 +25,11 @@ MODEL_COMMAND = "/model"
25
25
  QUEUE_COMMAND = "/queue"
26
26
  RESUME_COMMAND = "/resume"
27
27
  COMPACT_COMMAND = "/compact"
28
+ FORK_COMMAND = "/fork"
28
29
  LINK_COMMAND = "/link"
29
30
  UNLINK_COMMAND = "/unlink"
30
31
  EXTRA_COMMANDS_LINE = (
31
- "Extra commands: /help, /history, /title, /model, /resume, /compact, /link, /unlink"
32
+ "Extra commands: /help, /history, /title, /model, /resume, /compact, /fork, /link, /unlink"
32
33
  )
33
34
 
34
35
 
@@ -301,6 +302,19 @@ async def run_interactive_session(
301
302
  except Exception as exc: # pragma: no cover - defensive surface
302
303
  view.show_error(str(exc))
303
304
  continue
305
+ if prompt_text == FORK_COMMAND:
306
+ if has_pending_turn_tasks():
307
+ view.write_line(
308
+ "Cannot fork while work is running or queued."
309
+ )
310
+ continue
311
+ if not hasattr(model_client, "_session_id"):
312
+ view.write_line("Current model does not support session IDs.")
313
+ continue
314
+ new_session_id = uuid7_string()
315
+ model_client._session_id = new_session_id
316
+ view.write_line(f"Forked session: {new_session_id}")
317
+ continue
304
318
  if prompt_text.startswith(f"{LINK_COMMAND} "):
305
319
  link_target = prompt_text[len(LINK_COMMAND) :].strip()
306
320
  if not link_target:
@@ -19,16 +19,19 @@ except ModuleNotFoundError: # pragma: no cover - Python 3.10 path
19
19
 
20
20
  from .protocol import (
21
21
  AssistantMessage,
22
+ JSONDict,
22
23
  ModelResponse,
23
24
  ModelStreamEvent,
24
25
  Prompt,
25
26
  ReasoningItem,
26
27
  ToolCall,
27
28
  )
29
+ from .model_metadata import model_metadata
28
30
  from .utils import build_user_agent, uuid7_string
29
31
 
30
32
  DEFAULT_CODEX_CONFIG_PATH = Path.home() / ".codex" / "config.toml"
31
33
  DEFAULT_ORIGINATOR = "pycodex"
34
+ RESPONSES_LITE_HEADER = "x-openai-internal-codex-responses-lite"
32
35
  ModelStreamEventHandler = Callable[[ModelStreamEvent], None]
33
36
  NOOP_MODEL_STREAM_EVENT_HANDLER: 'ModelStreamEventHandler' = lambda _event: None
34
37
  DEFAULT_STREAM_MAX_RETRIES = 5
@@ -65,6 +68,7 @@ class ResponsesProviderConfig:
65
68
  beta_features_header: 'typing.Union[str, None]' = None
66
69
  stream_max_retries: 'typing.Union[int, None]' = None
67
70
  stream_idle_timeout_ms: 'typing.Union[int, None]' = None
71
+ service_tier: 'typing.Union[str, None]' = None
68
72
 
69
73
  @classmethod
70
74
  def from_codex_config(
@@ -116,6 +120,7 @@ class ResponsesProviderConfig:
116
120
  reasoning_effort=selected.get("model_reasoning_effort"),
117
121
  reasoning_summary=selected.get("model_reasoning_summary"),
118
122
  verbosity=selected.get("model_verbosity"),
123
+ service_tier=selected.get("service_tier"),
119
124
  sandbox_mode=selected.get("sandbox_mode"),
120
125
  beta_features_header=",".join(beta_features) or None,
121
126
  stream_max_retries=_optional_int(provider.get("stream_max_retries")),
@@ -157,6 +162,65 @@ class ResponsesProviderConfig:
157
162
  return DEFAULT_STREAM_IDLE_TIMEOUT_MS / 1000.0
158
163
  return max(int(self.stream_idle_timeout_ms), 1) / 1000.0
159
164
 
165
+ def metadata(self) -> 'typing.Union[JSONDict, None]':
166
+ return model_metadata(self.model)
167
+
168
+ def use_responses_lite(self) -> 'bool':
169
+ metadata = self.metadata()
170
+ if metadata is None:
171
+ return False
172
+ return metadata.get("use_responses_lite") is True
173
+
174
+ def effective_reasoning_effort(self) -> 'typing.Union[str, None]':
175
+ if self.reasoning_effort is not None:
176
+ return str(self.reasoning_effort)
177
+ metadata = self.metadata()
178
+ if not _metadata_supports_reasoning(metadata):
179
+ return None
180
+ return _optional_metadata_string(metadata, "default_reasoning_level")
181
+
182
+ def effective_reasoning_summary(self) -> 'typing.Union[str, None]':
183
+ summary = self.reasoning_summary
184
+ if summary is None:
185
+ metadata = self.metadata()
186
+ if not _metadata_supports_reasoning(metadata):
187
+ return None
188
+ summary = _optional_metadata_string(metadata, "default_reasoning_summary")
189
+ if summary is None:
190
+ return None
191
+ if str(summary).strip().lower() == "none":
192
+ return None
193
+ return str(summary)
194
+
195
+ def effective_verbosity(self) -> 'typing.Union[str, None]':
196
+ if self.verbosity is not None:
197
+ return str(self.verbosity)
198
+ metadata = self.metadata()
199
+ if metadata is None or metadata.get("support_verbosity") is not True:
200
+ return None
201
+ return _optional_metadata_string(metadata, "default_verbosity")
202
+
203
+ def effective_service_tier(self) -> 'typing.Union[str, None]':
204
+ service_tier = self.service_tier
205
+ if service_tier is None:
206
+ return None
207
+ service_tier = str(service_tier).strip()
208
+ if service_tier == "fast":
209
+ service_tier = "priority"
210
+ if not service_tier or service_tier == "default":
211
+ return None
212
+
213
+ metadata = self.metadata()
214
+ if metadata is None:
215
+ return None
216
+ supported = metadata.get("service_tiers")
217
+ if not isinstance(supported, list):
218
+ return None
219
+ for tier in supported:
220
+ if isinstance(tier, dict) and tier.get("id") == service_tier:
221
+ return service_tier
222
+ return None
223
+
160
224
 
161
225
  def _optional_bool(value: 'typing.Union[bool, str, int, None]') -> 'typing.Union[bool, None]':
162
226
  if value is None:
@@ -171,6 +235,43 @@ def _optional_bool(value: 'typing.Union[bool, str, int, None]') -> 'typing.Union
171
235
  raise ValueError(f"invalid boolean config value: {value!r}")
172
236
 
173
237
 
238
+ def _metadata_supports_reasoning(
239
+ metadata: 'typing.Union[JSONDict, None]',
240
+ ) -> 'bool':
241
+ return metadata is not None and metadata.get("supports_reasoning_summaries") is True
242
+
243
+
244
+ def _optional_metadata_string(
245
+ metadata: 'typing.Union[JSONDict, None]',
246
+ key: 'str',
247
+ ) -> 'typing.Union[str, None]':
248
+ if metadata is None:
249
+ return None
250
+ value = metadata.get(key)
251
+ if value is None:
252
+ return None
253
+ text = str(value).strip()
254
+ return text or None
255
+
256
+
257
+ def _strip_image_details(items: 'typing.Iterable[object]') -> 'None':
258
+ for item in items:
259
+ if not isinstance(item, dict):
260
+ continue
261
+ content = item.get("content")
262
+ if isinstance(content, list):
263
+ _strip_image_detail_from_content_items(content)
264
+ output = item.get("output")
265
+ if isinstance(output, list):
266
+ _strip_image_detail_from_content_items(output)
267
+
268
+
269
+ def _strip_image_detail_from_content_items(items: 'typing.Iterable[object]') -> 'None':
270
+ for item in items:
271
+ if isinstance(item, dict) and item.get("type") == "input_image":
272
+ item.pop("detail", None)
273
+
274
+
174
275
  class ResponsesApiError(RuntimeError):
175
276
  pass
176
277
 
@@ -392,34 +493,73 @@ class ResponsesModelClient:
392
493
  ) from exc
393
494
 
394
495
  def _build_payload(self, prompt: 'Prompt') -> 'typing.Dict[str, object]':
496
+ use_responses_lite = self._config.use_responses_lite()
497
+ input_items = [item.serialize() for item in prompt.input]
498
+ if use_responses_lite:
499
+ _strip_image_details(input_items)
500
+
501
+ tools = [tool.serialize() for tool in prompt.tools]
395
502
  payload: 'typing.Dict[str, object]' = {
396
503
  "model": self.model,
397
- "instructions": prompt.base_instructions or "",
398
- "input": [item.serialize() for item in prompt.input],
399
- "tools": [tool.serialize() for tool in prompt.tools],
400
- "parallel_tool_calls": prompt.parallel_tool_calls,
504
+ "input": input_items,
505
+ "parallel_tool_calls": prompt.parallel_tool_calls and not use_responses_lite,
401
506
  "store": False,
402
507
  "stream": True,
403
508
  "include": ["reasoning.encrypted_content"],
404
509
  "prompt_cache_key": self._session_id,
405
510
  }
406
- if prompt.tools:
511
+ if use_responses_lite:
512
+ prefix: 'typing.List[typing.Dict[str, object]]' = [
513
+ {
514
+ "type": "additional_tools",
515
+ "role": "developer",
516
+ "tools": tools,
517
+ }
518
+ ]
519
+ if prompt.base_instructions:
520
+ prefix.append(
521
+ {
522
+ "type": "message",
523
+ "role": "developer",
524
+ "content": [
525
+ {
526
+ "type": "input_text",
527
+ "text": prompt.base_instructions,
528
+ }
529
+ ],
530
+ }
531
+ )
532
+ payload["input"] = prefix + input_items
533
+ else:
534
+ payload["instructions"] = prompt.base_instructions or ""
535
+ payload["tools"] = tools
536
+
537
+ if prompt.tools or use_responses_lite:
407
538
  payload["tool_choice"] = "auto"
408
539
 
409
540
  reasoning: 'typing.Dict[str, str]' = {}
410
- if self._config.reasoning_effort is not None:
411
- reasoning["effort"] = self._config.reasoning_effort
412
- if self._config.reasoning_summary is not None:
413
- reasoning["summary"] = self._config.reasoning_summary
541
+ reasoning_effort = self._config.effective_reasoning_effort()
542
+ reasoning_summary = self._config.effective_reasoning_summary()
543
+ if reasoning_effort is not None:
544
+ reasoning["effort"] = reasoning_effort
545
+ if reasoning_summary is not None:
546
+ reasoning["summary"] = reasoning_summary
547
+ if use_responses_lite and reasoning:
548
+ reasoning["context"] = "all_turns"
414
549
  if reasoning:
415
550
  payload["reasoning"] = reasoning
416
551
 
417
552
  text = None
418
- if self._config.verbosity is not None:
419
- text = {"verbosity": self._config.verbosity}
553
+ verbosity = self._config.effective_verbosity()
554
+ if verbosity is not None:
555
+ text = {"verbosity": verbosity}
420
556
  if text is not None:
421
557
  payload["text"] = text
422
558
 
559
+ service_tier = self._config.effective_service_tier()
560
+ if service_tier is not None:
561
+ payload["service_tier"] = service_tier
562
+
423
563
  return payload
424
564
 
425
565
  def _list_models_sync(self) -> 'typing.List[str]':
@@ -483,6 +623,8 @@ class ResponsesModelClient:
483
623
  headers["authorization"] = f"Bearer {api_key}"
484
624
  if self._config.beta_features_header is not None:
485
625
  headers["x-codex-beta-features"] = self._config.beta_features_header
626
+ if self._config.use_responses_lite():
627
+ headers[RESPONSES_LITE_HEADER] = "true"
486
628
  if self._openai_subagent is not None:
487
629
  headers["x-openai-subagent"] = self._openai_subagent
488
630
  if prompt.turn_metadata is not None: