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.
- {python_codex-0.1.5 → python_codex-0.1.6}/AGENTS.md +2 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/PKG-INFO +5 -1
- {python_codex-0.1.5 → python_codex-0.1.6}/README.md +4 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/docs/responses_server/README.md +21 -2
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/cli.py +19 -2
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/utils/visualize.py +34 -7
- {python_codex-0.1.5 → python_codex-0.1.6}/pyproject.toml +1 -1
- {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/app.py +10 -1
- {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/config.py +12 -0
- python_codex-0.1.6/responses_server/messages_api.py +479 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/payload_processors.py +1 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/stream_router.py +94 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/responses_server/fake_chat_completions_server.py +196 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/responses_server/test_server.py +277 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_cli.py +130 -2
- {python_codex-0.1.5 → python_codex-0.1.6}/.github/workflows/publish.yml +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/.github/workflows/test.yml +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/.gitignore +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/LICENSE +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/README_ZH.md +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/docs/ALIGNMENT.md +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/docs/CONTEXT.md +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/__init__.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/agent.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/collaboration.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/compat.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/context.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/doctor.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/model.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/portable.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/portable_server.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/collaboration_default.md +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/collaboration_plan.md +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/default_base_instructions.md +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/exec_tools.json +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/models.json +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/approval_policy/never.md +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/approval_policy/on_failure.md +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/approval_policy/on_request.md +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/approval_policy/on_request_rule_request_permission.md +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/approval_policy/unless_trusted.md +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/sandbox_mode/danger_full_access.md +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/sandbox_mode/read_only.md +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/permissions/sandbox_mode/workspace_write.md +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/prompts/subagent_tools.json +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/protocol.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/runtime.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/runtime_services.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/__init__.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/agent_tool_schemas.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/apply_patch_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/base_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/close_agent_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/code_mode_manager.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/exec_command_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/exec_runtime.js +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/exec_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/grep_files_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/list_dir_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/read_file_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/request_permissions_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/request_user_input_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/resume_agent_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/send_input_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/shell_command_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/shell_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/spawn_agent_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/unified_exec_manager.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/update_plan_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/view_image_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/wait_agent_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/wait_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/web_search_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/tools/write_stdin_tool.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/utils/__init__.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/utils/compactor.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/utils/dotenv.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/utils/get_env.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/utils/random_ids.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/pycodex/utils/session_persist.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/__init__.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/__main__.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/server.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/session_store.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/tools/__init__.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/tools/custom_adapter.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/responses_server/tools/web_search.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/TESTS.md +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/__init__.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/compare_request_user_input_roundtrip.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/compare_steer_request_bodies.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/compare_tool_schemas.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/fake_responses_server.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/fakes.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_agent.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_builtin_tools.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_compactor.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_context.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_doctor.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_fake_responses_server.py +0 -0
- {python_codex-0.1.5 → python_codex-0.1.6}/tests/test_model.py +0 -0
- {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.
|
|
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
|
|
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
|
|
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
|
|
@@ -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`
|
|
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
|
)
|