python-codex 0.1.5__tar.gz → 0.1.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 (102) hide show
  1. {python_codex-0.1.5 → python_codex-0.1.6}/AGENTS.md +2 -0
  2. {python_codex-0.1.5 → python_codex-0.1.6}/PKG-INFO +5 -1
  3. {python_codex-0.1.5 → python_codex-0.1.6}/README.md +4 -0
  4. {python_codex-0.1.5 → python_codex-0.1.6}/docs/responses_server/README.md +21 -2
  5. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/cli.py +19 -2
  6. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/utils/visualize.py +34 -7
  7. {python_codex-0.1.5 → python_codex-0.1.6}/pyproject.toml +1 -1
  8. {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/app.py +10 -1
  9. {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/config.py +12 -0
  10. python_codex-0.1.6/responses_server/messages_api.py +479 -0
  11. {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/payload_processors.py +1 -0
  12. {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/stream_router.py +94 -0
  13. {python_codex-0.1.5 → python_codex-0.1.6}/tests/responses_server/fake_chat_completions_server.py +196 -0
  14. {python_codex-0.1.5 → python_codex-0.1.6}/tests/responses_server/test_server.py +277 -0
  15. {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_cli.py +130 -2
  16. {python_codex-0.1.5 → python_codex-0.1.6}/.github/workflows/publish.yml +0 -0
  17. {python_codex-0.1.5 → python_codex-0.1.6}/.github/workflows/test.yml +0 -0
  18. {python_codex-0.1.5 → python_codex-0.1.6}/.gitignore +0 -0
  19. {python_codex-0.1.5 → python_codex-0.1.6}/LICENSE +0 -0
  20. {python_codex-0.1.5 → python_codex-0.1.6}/README_ZH.md +0 -0
  21. {python_codex-0.1.5 → python_codex-0.1.6}/docs/ALIGNMENT.md +0 -0
  22. {python_codex-0.1.5 → python_codex-0.1.6}/docs/CONTEXT.md +0 -0
  23. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/__init__.py +0 -0
  24. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/agent.py +0 -0
  25. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/collaboration.py +0 -0
  26. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/compat.py +0 -0
  27. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/context.py +0 -0
  28. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/doctor.py +0 -0
  29. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/model.py +0 -0
  30. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/portable.py +0 -0
  31. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/portable_server.py +0 -0
  32. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/collaboration_default.md +0 -0
  33. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/collaboration_plan.md +0 -0
  34. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/default_base_instructions.md +0 -0
  35. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/exec_tools.json +0 -0
  36. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/models.json +0 -0
  37. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/approval_policy/never.md +0 -0
  38. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/approval_policy/on_failure.md +0 -0
  39. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/approval_policy/on_request.md +0 -0
  40. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/approval_policy/on_request_rule_request_permission.md +0 -0
  41. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/approval_policy/unless_trusted.md +0 -0
  42. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/sandbox_mode/danger_full_access.md +0 -0
  43. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/sandbox_mode/read_only.md +0 -0
  44. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/sandbox_mode/workspace_write.md +0 -0
  45. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/subagent_tools.json +0 -0
  46. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/protocol.py +0 -0
  47. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/runtime.py +0 -0
  48. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/runtime_services.py +0 -0
  49. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/__init__.py +0 -0
  50. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/agent_tool_schemas.py +0 -0
  51. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/apply_patch_tool.py +0 -0
  52. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/base_tool.py +0 -0
  53. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/close_agent_tool.py +0 -0
  54. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/code_mode_manager.py +0 -0
  55. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/exec_command_tool.py +0 -0
  56. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/exec_runtime.js +0 -0
  57. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/exec_tool.py +0 -0
  58. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/grep_files_tool.py +0 -0
  59. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/list_dir_tool.py +0 -0
  60. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/read_file_tool.py +0 -0
  61. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/request_permissions_tool.py +0 -0
  62. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/request_user_input_tool.py +0 -0
  63. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/resume_agent_tool.py +0 -0
  64. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/send_input_tool.py +0 -0
  65. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/shell_command_tool.py +0 -0
  66. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/shell_tool.py +0 -0
  67. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/spawn_agent_tool.py +0 -0
  68. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/unified_exec_manager.py +0 -0
  69. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/update_plan_tool.py +0 -0
  70. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/view_image_tool.py +0 -0
  71. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/wait_agent_tool.py +0 -0
  72. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/wait_tool.py +0 -0
  73. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/web_search_tool.py +0 -0
  74. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/write_stdin_tool.py +0 -0
  75. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/utils/__init__.py +0 -0
  76. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/utils/compactor.py +0 -0
  77. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/utils/dotenv.py +0 -0
  78. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/utils/get_env.py +0 -0
  79. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/utils/random_ids.py +0 -0
  80. {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/utils/session_persist.py +0 -0
  81. {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/__init__.py +0 -0
  82. {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/__main__.py +0 -0
  83. {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/server.py +0 -0
  84. {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/session_store.py +0 -0
  85. {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/tools/__init__.py +0 -0
  86. {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/tools/custom_adapter.py +0 -0
  87. {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/tools/web_search.py +0 -0
  88. {python_codex-0.1.5 → python_codex-0.1.6}/tests/TESTS.md +0 -0
  89. {python_codex-0.1.5 → python_codex-0.1.6}/tests/__init__.py +0 -0
  90. {python_codex-0.1.5 → python_codex-0.1.6}/tests/compare_request_user_input_roundtrip.py +0 -0
  91. {python_codex-0.1.5 → python_codex-0.1.6}/tests/compare_steer_request_bodies.py +0 -0
  92. {python_codex-0.1.5 → python_codex-0.1.6}/tests/compare_tool_schemas.py +0 -0
  93. {python_codex-0.1.5 → python_codex-0.1.6}/tests/fake_responses_server.py +0 -0
  94. {python_codex-0.1.5 → python_codex-0.1.6}/tests/fakes.py +0 -0
  95. {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_agent.py +0 -0
  96. {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_builtin_tools.py +0 -0
  97. {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_compactor.py +0 -0
  98. {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_context.py +0 -0
  99. {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_doctor.py +0 -0
  100. {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_fake_responses_server.py +0 -0
  101. {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_model.py +0 -0
  102. {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_portable.py +0 -0
@@ -13,6 +13,8 @@
13
13
  - `responses_server` compat 层应透传请求里的 `model`;不要再做 “取 downstream /models 第一个 id 并强制覆盖请求模型” 这种兜底兼容。
14
14
  - 对 `model_provider = "vllm"`,`responses_server` 仍然走 `/v1/chat/completions` compat 路径,但要保留 reasoning:把 chat chunk 里的 `reasoning` / `reasoning_content` 翻回 Responses `reasoning` item,并把历史里的 Responses `reasoning` item 回放成下游 assistant message 的 `reasoning` 字段。
15
15
  - `responses_server` 的 provider-specific chat payload 定制统一放在 `responses_server/payload_processors.py`:使用 `CompatServerConfig.model_provider` 选择 `provider_name -> proc_fn(outcomming_request)` 映射,并且只在真正发出 downstream `/v1/chat/completions` 前 post-process;`StreamRouter` 内部继续保留 canonical payload,避免 tool hydration / mock web_search follow-up 被 provider 改写污染。
16
+ - `responses_server` 如果要兼容下游 `/v1/messages`,也优先保持这条边界:内部继续用 canonical chat request / chat-like chunk 流,只有真正发请求和读取 SSE 时才做 messages 适配,这样 tool hydration、mock `web_search` follow-up、provider payload post-process 都能复用。
17
+ - 真实 vLLM `0.19.0` 的 `/v1/messages` 会对缺失 `max_tokens` 直接返回 `400`;messages 适配层必须总是补这个字段。当前约定是优先透传请求里的 `max_output_tokens`/`max_tokens`,否则回退到默认 `32000`。
16
18
  - `pycodex` 默认是最小交互 CLI;无 prompt 时进入 REPL,并通过 `AgentRuntime` 跑外层提交循环。当前会显示最小事件流、assistant 流式输出、简单 title/history(`/title`, `/history`),并默认注册一组与原版一一对应的本地工具子集。
17
19
  - 交互 CLI 的事件流展示优先表达用户可感知的阶段(例如工具开始/完成、模型回看工具结果),不要直接把内部 `iteration` 计数暴露成主要状态文案;`iterations` 应继续保留在 `TurnResult` 等程序化结果里。
18
20
  - prompt/context 相关逻辑统一放在 `pycodex/context.py`:`AgentLoop` 只维护真实会话历史;每轮请求前由 `ContextManager` 注入 base instructions、developer message、`AGENTS.md` 指令和 `<environment_context>`,且这些注入项不写回 history。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-codex
3
- Version: 0.1.5
3
+ Version: 0.1.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
@@ -159,6 +159,7 @@ pycodex "Summarize this repo in one sentence."
159
159
  printf 'Reply with exactly OK.' | pycodex
160
160
  pycodex --json "Reply with exactly OK."
161
161
  pycodex --profile model_proxy "Reply with exactly OK."
162
+ pycodex --profile opus --use-messages "Reply with exactly OK."
162
163
  pycodex --vllm-endpoint http://127.0.0.1:18000 "Reply with exactly OK."
163
164
  pycodex --put @127.0.0.1:5577
164
165
  pycodex --put /data/.codex/@127.0.0.1:5577
@@ -211,6 +212,9 @@ Current behavior:
211
212
  historical `reasoning` items are replayed into downstream assistant messages
212
213
  via the `reasoning` field. Streaming token usage is also requested from vLLM
213
214
  and forwarded to the final `response.completed.response.usage`
215
+ - standalone `responses_server` now also supports downstream `/v1/messages`
216
+ backends via `--outcomming-api messages`, while keeping the internal
217
+ canonical request/route logic in chat-completions shape
214
218
  - `pycodex doctor` checks config, `.env`, API keys, DNS, TCP/TLS, and an
215
219
  optional live Responses API request
216
220
 
@@ -138,6 +138,7 @@ pycodex "Summarize this repo in one sentence."
138
138
  printf 'Reply with exactly OK.' | pycodex
139
139
  pycodex --json "Reply with exactly OK."
140
140
  pycodex --profile model_proxy "Reply with exactly OK."
141
+ pycodex --profile opus --use-messages "Reply with exactly OK."
141
142
  pycodex --vllm-endpoint http://127.0.0.1:18000 "Reply with exactly OK."
142
143
  pycodex --put @127.0.0.1:5577
143
144
  pycodex --put /data/.codex/@127.0.0.1:5577
@@ -190,6 +191,9 @@ Current behavior:
190
191
  historical `reasoning` items are replayed into downstream assistant messages
191
192
  via the `reasoning` field. Streaming token usage is also requested from vLLM
192
193
  and forwarded to the final `response.completed.response.usage`
194
+ - standalone `responses_server` now also supports downstream `/v1/messages`
195
+ backends via `--outcomming-api messages`, while keeping the internal
196
+ canonical request/route logic in chat-completions shape
193
197
  - `pycodex doctor` checks config, `.env`, API keys, DNS, TCP/TLS, and an
194
198
  optional live Responses API request
195
199
 
@@ -21,6 +21,7 @@
21
21
  - incomming `POST /v1/responses`
22
22
  - incomming `GET /v1/models`
23
23
  - outcomming `POST /v1/chat/completions`
24
+ - outcomming `POST /v1/messages`(通过边界适配复用同一套 canonical chat request / stream routing)
24
25
  - 流式 assistant 文本
25
26
  - vLLM chat-completions `reasoning` / `reasoning_content` -> Responses `reasoning` item 适配
26
27
  - vLLM 历史 `reasoning` item -> assistant message `reasoning` 字段回放
@@ -42,13 +43,13 @@
42
43
  ## Incomming / Outcomming 分层
43
44
 
44
45
  - incomming:面向 Codex 的 Responses 子集
45
- - outcomming:面向 backend 的 chat completions 子集
46
+ - outcomming:面向 backend 的 chat-completions / messages 兼容子集
46
47
 
47
48
  当前职责拆分:
48
49
 
49
50
  - `responses_server/app.py`:FastAPI app 和 CLI 入口
50
51
  - `responses_server/server.py`:`ResponseServer`,负责持有 `SessionStore` 和 `StreamRouter`
51
- - `responses_server/stream_router.py`:`StreamRouter`,负责 incomming 请求翻译、outcomming chat 请求和流路由;对 `model_provider = "vllm"` 额外适配 chat-level reasoning
52
+ - `responses_server/stream_router.py`:`StreamRouter`,负责 incomming 请求翻译、outcomming request 和流路由;对 `model_provider = "vllm"` 额外适配 chat-level reasoning
52
53
  - `responses_server/payload_processors.py`:按 `CompatServerConfig.model_provider` 选择 provider-specific payload `post_process`
53
54
  - `responses_server/tools/`:provider 侧工具适配层;当前放 mock `web_search` 和 custom-tool function wrapper
54
55
  - `responses_server/session_store.py`:最小隐藏状态存储
@@ -63,6 +64,15 @@ uv run python -m responses_server \
63
64
  --model-provider vllm
64
65
  ```
65
66
 
67
+ 如果下游不是 `/v1/chat/completions`,而是 Anthropic/Claude 风格的
68
+ `/v1/messages`,再额外加:
69
+
70
+ ```bash
71
+ uv run python -m responses_server \
72
+ --outcomming-base-url http://127.0.0.1:8000/v1 \
73
+ --outcomming-api messages
74
+ ```
75
+
66
76
  默认会在本地启动一个 incomming Responses 服务;真正监听地址由 `--host` 和 `--port`
67
77
  控制。
68
78
 
@@ -73,6 +83,15 @@ uv run python -m responses_server \
73
83
  当前内置规则里,`vllm` 仍走 chat-completions compat 路径,但会额外保留
74
84
  reasoning;`stepfun` 会删除所有 `developer` role。
75
85
 
86
+ `messages` compat 则故意不改这层 canonical request:仍然先构造 chat 风格
87
+ `outcomming_request`,只有在真正发请求和读 SSE 时,才在边界把它翻译成
88
+ messages request / event。这样 tool hydration、mock `web_search`
89
+ follow-up、provider payload post-process 仍然复用同一套主逻辑。
90
+
91
+ 当前 messages 边界还会补一个兼容性细节:下游如果像 vLLM `0.19.0` 一样要求
92
+ `max_tokens`,则优先透传上游请求里的 `max_output_tokens` / `max_tokens`;
93
+ 如果上游没给,当前默认补 `32000`,避免直接被下游 `400` 拒绝。
94
+
76
95
  ## 验证
77
96
 
78
97
  当前独立测试:
@@ -42,7 +42,6 @@ CliSessionMode = Literal["exec", "tui"]
42
42
  LOCAL_RESPONSES_SERVER_API_KEY_ENV = "PYCODEX_LOCAL_RESPONSES_SERVER_KEY"
43
43
  CLI_ORIGINATOR = "codex-tui"
44
44
 
45
-
46
45
  def launch_chat_completion_compat_server(*args, **kwargs):
47
46
  from responses_server import (
48
47
  launch_chat_completion_compat_server as launch_compat_server,
@@ -123,6 +122,15 @@ def build_parser() -> 'argparse.ArgumentParser':
123
122
  "When set, pycodex starts a local responses compat server for this session."
124
123
  ),
125
124
  )
125
+ parser.add_argument(
126
+ "--use-messages",
127
+ default=False,
128
+ action="store_true",
129
+ help=(
130
+ "When set, pycodex starts a local responses compat server and routes "
131
+ "to a downstream /v1/messages backend for this session."
132
+ ),
133
+ )
126
134
  parser.add_argument(
127
135
  "--system-prompt",
128
136
  default=None,
@@ -373,12 +381,17 @@ def _build_model_client(
373
381
  managed_responses_base_url: 'typing.Union[str, None]' = None,
374
382
  vllm_endpoint: 'typing.Union[str, None]' = None,
375
383
  use_chat_completion: 'bool' = False,
384
+ use_messages: 'bool' = False,
376
385
  ):
377
386
  load_codex_dotenv(config_path)
378
387
  provider_config = ResponsesProviderConfig.from_codex_config(
379
388
  config_path,
380
389
  profile,
381
390
  )
391
+ if use_chat_completion and use_messages:
392
+ raise ValueError("--use-chat-completion and --use-messages cannot be combined")
393
+ if vllm_endpoint and use_messages:
394
+ raise ValueError("--vllm-endpoint and --use-messages cannot be combined")
382
395
  url, key_env = provider_config.base_url, provider_config.api_key_env
383
396
  if managed_responses_base_url is not None:
384
397
  url, key_env = (
@@ -386,7 +399,7 @@ def _build_model_client(
386
399
  LOCAL_RESPONSES_SERVER_API_KEY_ENV,
387
400
  )
388
401
  os.environ.setdefault(LOCAL_RESPONSES_SERVER_API_KEY_ENV, "dummy")
389
- elif vllm_endpoint or use_chat_completion:
402
+ elif vllm_endpoint or use_chat_completion or use_messages:
390
403
  if vllm_endpoint:
391
404
  managed_server = launch_chat_completion_compat_server(
392
405
  vllm_endpoint,
@@ -397,6 +410,9 @@ def _build_model_client(
397
410
  provider_config.base_url,
398
411
  provider_config.api_key_env,
399
412
  model_provider=provider_config.provider_name,
413
+ outcomming_api=(
414
+ "messages" if use_messages else "chat_completions"
415
+ ),
400
416
  )
401
417
  atexit.register(managed_server.stop)
402
418
  url, key_env = (
@@ -755,6 +771,7 @@ async def run_cli(args: 'argparse.Namespace') -> 'int':
755
771
  args.timeout_seconds,
756
772
  vllm_endpoint=args.vllm_endpoint,
757
773
  use_chat_completion=args.use_chat_completion,
774
+ use_messages=args.use_messages,
758
775
  )
759
776
 
760
777
  runtime = build_runtime(
@@ -158,13 +158,29 @@ class Spinner:
158
158
  self._paused = False
159
159
 
160
160
  def clear(self) -> 'None':
161
- if not self._enabled or not self._visible:
162
- return
163
161
  with self._terminal_lock:
162
+ if not self._visible:
163
+ return
164
164
  self._raw_write("\r\x1b[2K")
165
165
  self._raw_flush()
166
166
  self._visible = False
167
167
 
168
+ def render_now(self) -> 'None':
169
+ if not self._turn_active or self._paused:
170
+ return
171
+ frame = colorize_cli_message(
172
+ build_cli_spinner_frame(self._index, self._label),
173
+ "status",
174
+ self._color_enabled,
175
+ )
176
+ self._index += 1
177
+ with self._terminal_lock:
178
+ if not self._turn_active or self._paused:
179
+ return
180
+ self._raw_write(f"\r\x1b[2K{frame}")
181
+ self._raw_flush()
182
+ self._visible = True
183
+
168
184
  def close(self) -> 'None':
169
185
  self.finish_turn()
170
186
  if self._thread is not None:
@@ -726,6 +742,7 @@ class CliSessionView:
726
742
  else:
727
743
  self._spinner.resume()
728
744
  self._spinner.set_label("running provider tools")
745
+ self._spinner.render_now()
729
746
  return
730
747
 
731
748
  if event.kind == "tool_started":
@@ -745,15 +762,11 @@ class CliSessionView:
745
762
  self._spinner.set_label(f"running {tool_name}")
746
763
  else:
747
764
  self._spinner.set_label("running provider tools")
765
+ self._spinner.render_now()
748
766
  return
749
767
 
750
768
  if event.kind == "tool_completed":
751
769
  self._finish_stream()
752
- if self._input_active:
753
- self._spinner.pause()
754
- else:
755
- self._spinner.resume()
756
- self._spinner.set_label("thinking")
757
770
  tool_name, summary, is_error = extract_tool_event_display(event.payload)
758
771
  summary = self._rewrite_agent_summary(tool_name, summary)
759
772
  if tool_name == "update_plan" and not is_error:
@@ -762,6 +775,12 @@ class CliSessionView:
762
775
  self._print_line(
763
776
  colorize_cli_message(line, "plan", self._color_enabled)
764
777
  )
778
+ if self._input_active:
779
+ self._spinner.pause()
780
+ else:
781
+ self._spinner.resume()
782
+ self._spinner.set_label("thinking")
783
+ self._spinner.render_now()
765
784
  return
766
785
  message = format_cli_tool_message(
767
786
  tool_name,
@@ -770,6 +789,12 @@ class CliSessionView:
770
789
  )
771
790
  self._remember_agent_name(tool_name, summary)
772
791
  self._print_line(self._colorize_formatted_tool_message(message))
792
+ if self._input_active:
793
+ self._spinner.pause()
794
+ else:
795
+ self._spinner.resume()
796
+ self._spinner.set_label("thinking")
797
+ self._spinner.render_now()
773
798
  return
774
799
 
775
800
  if event.kind == "turn_completed":
@@ -830,6 +855,8 @@ class CliSessionView:
830
855
 
831
856
  def resume_spinner(self) -> 'None':
832
857
  self._spinner.resume()
858
+ if not self._input_active:
859
+ self._spinner.render_now()
833
860
 
834
861
  def set_input_active(self, active: 'bool', resume_spinner: 'bool' = True) -> 'None':
835
862
  self._input_active = active
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "python-codex"
7
- version = "0.1.5"
7
+ version = "0.1.6"
8
8
  description = "A minimal Python extraction of Codex's main agent loop"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.6.2"
@@ -55,12 +55,18 @@ def build_parser() -> 'argparse.ArgumentParser':
55
55
  prog="python -m responses_server",
56
56
  description=(
57
57
  "Standalone localhost `/v1/responses` server that translates the "
58
- "Codex/Responses subset onto an outcomming `/v1/chat/completions` backend."
58
+ "Codex/Responses subset onto an outcomming `/v1/chat/completions` "
59
+ "or `/v1/messages` backend."
59
60
  ),
60
61
  )
61
62
  parser.add_argument("--host", default="127.0.0.1")
62
63
  parser.add_argument("--port", type=int, default=8001)
63
64
  parser.add_argument("--outcomming-base-url", required=True)
65
+ parser.add_argument(
66
+ "--outcomming-api",
67
+ default="chat_completions",
68
+ choices=["chat_completions", "messages"],
69
+ )
64
70
  parser.add_argument("--outcomming-api-key-env", default=None)
65
71
  parser.add_argument("--model-provider", default=None)
66
72
  parser.add_argument("--timeout-seconds", type=float, default=120.0)
@@ -80,10 +86,12 @@ def launch_chat_completion_compat_server(
80
86
  base_url: 'str',
81
87
  api_key_env: 'typing.Union[str, None]' = None,
82
88
  model_provider: 'typing.Union[str, None]' = None,
89
+ outcomming_api: 'str' = "chat_completions",
83
90
  ):
84
91
  config = CompatServerConfig.from_base_url(
85
92
  base_url,
86
93
  api_key_env,
94
+ outcomming_api=outcomming_api,
87
95
  model_provider=model_provider,
88
96
  )
89
97
  server = ManagedResponseServer(config)
@@ -209,6 +217,7 @@ def main() -> 'None':
209
217
  host=args.host,
210
218
  port=args.port,
211
219
  outcomming_base_url=args.outcomming_base_url,
220
+ outcomming_api=args.outcomming_api,
212
221
  outcomming_api_key_env=args.outcomming_api_key_env,
213
222
  model_provider=args.model_provider,
214
223
  timeout_seconds=args.timeout_seconds,
@@ -10,6 +10,7 @@ class CompatServerConfig:
10
10
  host: 'str' = "127.0.0.1"
11
11
  port: 'int' = 0
12
12
  outcomming_base_url: 'str' = "http://127.0.0.1:8000/v1"
13
+ outcomming_api: 'str' = "chat_completions"
13
14
  outcomming_api_key_env: 'typing.Union[str, None]' = None
14
15
  model_provider: 'typing.Union[str, None]' = None
15
16
  timeout_seconds: 'float' = 120.0
@@ -24,15 +25,24 @@ class CompatServerConfig:
24
25
  base = self.outcomming_base_url.rstrip("/")
25
26
  return f"{base}/chat/completions"
26
27
 
28
+ def outcomming_messages_url(self) -> 'str':
29
+ base = self.outcomming_base_url.rstrip("/")
30
+ return f"{base}/messages"
31
+
27
32
  def outcomming_models_url(self) -> 'str':
28
33
  base = self.outcomming_base_url.rstrip("/")
29
34
  return f"{base}/models"
30
35
 
36
+ def normalized_outcomming_api(self) -> 'str':
37
+ value = str(self.outcomming_api or "").strip().lower()
38
+ return value or "chat_completions"
39
+
31
40
  def with_ephemeral_port(self) -> 'CompatServerConfig':
32
41
  return CompatServerConfig(
33
42
  host=self.host,
34
43
  port=0,
35
44
  outcomming_base_url=self.outcomming_base_url,
45
+ outcomming_api=self.outcomming_api,
36
46
  outcomming_api_key_env=self.outcomming_api_key_env,
37
47
  model_provider=self.model_provider,
38
48
  timeout_seconds=self.timeout_seconds,
@@ -44,6 +54,7 @@ class CompatServerConfig:
44
54
  outcomming_base_url: 'str',
45
55
  api_key_env: 'typing.Union[str, None]' = None,
46
56
  model_provider: 'typing.Union[str, None]' = None,
57
+ outcomming_api: 'str' = "chat_completions",
47
58
  ) -> 'CompatServerConfig':
48
59
  parsed = urllib.parse.urlparse(outcomming_base_url)
49
60
  if not parsed.scheme or not parsed.netloc:
@@ -58,6 +69,7 @@ class CompatServerConfig:
58
69
  )
59
70
  return cls(
60
71
  outcomming_base_url=outcomming_base_url,
72
+ outcomming_api=outcomming_api,
61
73
  outcomming_api_key_env=api_key_env,
62
74
  model_provider=model_provider,
63
75
  )