python-codex 0.1.1__tar.gz → 0.1.2__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.2/.github/workflows/test.yml +65 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/PKG-INFO +1 -1
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/cli.py +8 -1
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/runtime_services.py +3 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/exec_tool.py +1 -1
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/unified_exec_manager.py +19 -2
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/utils/get_env.py +23 -4
- {python_codex-0.1.1 → python_codex-0.1.2}/pyproject.toml +2 -2
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/test_agent.py +24 -7
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/test_builtin_tools.py +34 -2
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/test_cli.py +32 -1
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/test_model.py +24 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/.github/workflows/publish.yml +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/.gitignore +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/AGENTS.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/LICENSE +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/README.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/README_ZH.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/docs/ALIGNMENT.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/docs/CONTEXT.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/docs/responses_server/README.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/__init__.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/agent.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/collaboration.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/context.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/doctor.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/model.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/portable.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/portable_server.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/collaboration_default.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/collaboration_plan.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/default_base_instructions.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/exec_tools.json +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/models.json +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/permissions/approval_policy/never.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/permissions/approval_policy/on_failure.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/permissions/approval_policy/on_request.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/permissions/approval_policy/on_request_rule_request_permission.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/permissions/approval_policy/unless_trusted.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/permissions/sandbox_mode/danger_full_access.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/permissions/sandbox_mode/read_only.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/permissions/sandbox_mode/workspace_write.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/subagent_tools.json +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/protocol.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/runtime.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/__init__.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/agent_tool_schemas.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/apply_patch_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/base_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/close_agent_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/code_mode_manager.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/exec_command_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/exec_runtime.js +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/grep_files_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/list_dir_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/read_file_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/request_permissions_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/request_user_input_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/resume_agent_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/send_input_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/shell_command_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/shell_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/spawn_agent_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/update_plan_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/view_image_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/wait_agent_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/wait_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/web_search_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/tools/write_stdin_tool.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/utils/__init__.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/utils/dotenv.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/utils/random_ids.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/pycodex/utils/visualize.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/responses_server/__init__.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/responses_server/__main__.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/responses_server/app.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/responses_server/config.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/responses_server/payload_processors.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/responses_server/server.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/responses_server/session_store.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/responses_server/stream_router.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/responses_server/tools/__init__.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/responses_server/tools/custom_adapter.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/responses_server/tools/web_search.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/TESTS.md +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/__init__.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/compare_request_user_input_roundtrip.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/compare_steer_request_bodies.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/compare_tool_schemas.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/fake_responses_server.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/fakes.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/responses_server/fake_chat_completions_server.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/responses_server/test_server.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/test_context.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/test_doctor.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/test_fake_responses_server.py +0 -0
- {python_codex-0.1.1 → python_codex-0.1.2}/tests/test_portable.py +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
pull_request:
|
|
6
|
+
push:
|
|
7
|
+
branches:
|
|
8
|
+
- main
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
pytest-py310:
|
|
12
|
+
name: pytest (Python 3.10)
|
|
13
|
+
runs-on: ubuntu-22.04
|
|
14
|
+
steps:
|
|
15
|
+
- name: Check out repository
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.10"
|
|
22
|
+
|
|
23
|
+
- name: Set up uv
|
|
24
|
+
uses: astral-sh/setup-uv@v5
|
|
25
|
+
|
|
26
|
+
- name: Sync dependencies
|
|
27
|
+
run: uv sync --dev
|
|
28
|
+
|
|
29
|
+
- name: Run pytest
|
|
30
|
+
run: uv run pytest
|
|
31
|
+
|
|
32
|
+
pytest-py36:
|
|
33
|
+
name: pytest (Python 3.6)
|
|
34
|
+
runs-on: ubuntu-22.04
|
|
35
|
+
container:
|
|
36
|
+
image: python:3.6.15-slim-bullseye
|
|
37
|
+
steps:
|
|
38
|
+
- name: Install system dependencies
|
|
39
|
+
run: |
|
|
40
|
+
apt-get update
|
|
41
|
+
apt-get install -y --no-install-recommends git
|
|
42
|
+
|
|
43
|
+
- name: Check out repository
|
|
44
|
+
uses: actions/checkout@v4
|
|
45
|
+
|
|
46
|
+
- name: Mark workspace as safe for git
|
|
47
|
+
run: git config --global --add safe.directory "$PWD"
|
|
48
|
+
|
|
49
|
+
- name: Set up Node.js
|
|
50
|
+
uses: actions/setup-node@v4
|
|
51
|
+
with:
|
|
52
|
+
node-version: "20"
|
|
53
|
+
|
|
54
|
+
- name: Set up uv
|
|
55
|
+
uses: astral-sh/setup-uv@v5
|
|
56
|
+
|
|
57
|
+
- name: Show runtime versions
|
|
58
|
+
run: python --version
|
|
59
|
+
- run: node --version
|
|
60
|
+
|
|
61
|
+
- name: Sync dependencies
|
|
62
|
+
run: uv sync --dev
|
|
63
|
+
|
|
64
|
+
- name: Run pytest
|
|
65
|
+
run: uv run pytest
|
|
@@ -21,7 +21,6 @@ from .protocol import AgentEvent
|
|
|
21
21
|
from .runtime import AgentRuntime
|
|
22
22
|
from .runtime_services import RuntimeEnvironment, create_runtime_environment
|
|
23
23
|
from .utils import CliSessionView, load_codex_dotenv
|
|
24
|
-
from responses_server import launch_chat_completion_compat_server
|
|
25
24
|
|
|
26
25
|
EXIT_COMMANDS = {"/exit", "/quit"}
|
|
27
26
|
HISTORY_COMMAND = "/history"
|
|
@@ -33,6 +32,14 @@ LOCAL_RESPONSES_SERVER_API_KEY_ENV = "PYCODEX_LOCAL_RESPONSES_SERVER_KEY"
|
|
|
33
32
|
CLI_ORIGINATOR = "codex-tui"
|
|
34
33
|
|
|
35
34
|
|
|
35
|
+
def launch_chat_completion_compat_server(*args, **kwargs):
|
|
36
|
+
from responses_server import (
|
|
37
|
+
launch_chat_completion_compat_server as launch_compat_server,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return launch_compat_server(*args, **kwargs)
|
|
41
|
+
|
|
42
|
+
|
|
36
43
|
def configure_loguru() -> None:
|
|
37
44
|
try:
|
|
38
45
|
from loguru import logger
|
|
@@ -346,6 +346,9 @@ class SubAgentManager:
|
|
|
346
346
|
managed.state = "completed"
|
|
347
347
|
finally:
|
|
348
348
|
managed.pending_submission_ids.discard(submission_id)
|
|
349
|
+
if managed.pending_submission_ids and managed.error_message is None:
|
|
350
|
+
managed.completed_message = None
|
|
351
|
+
managed.state = "running"
|
|
349
352
|
async with self._condition:
|
|
350
353
|
self._condition.notify_all()
|
|
351
354
|
|
|
@@ -17,7 +17,7 @@ from ..protocol import JSONValue
|
|
|
17
17
|
from .base_tool import BaseTool, ToolContext
|
|
18
18
|
from .code_mode_manager import CodeModeManager
|
|
19
19
|
|
|
20
|
-
EXEC_FREEFORM_GRAMMAR = """start: pragma_source | plain_source
|
|
20
|
+
EXEC_FREEFORM_GRAMMAR = r"""start: pragma_source | plain_source
|
|
21
21
|
pragma_source: PRAGMA_LINE NEWLINE SOURCE
|
|
22
22
|
plain_source: SOURCE
|
|
23
23
|
|
|
@@ -184,6 +184,9 @@ class _HeadTailBuffer:
|
|
|
184
184
|
self.tail.clear()
|
|
185
185
|
return combined
|
|
186
186
|
|
|
187
|
+
def has_data(self) -> bool:
|
|
188
|
+
return bool(self.head or self.tail)
|
|
189
|
+
|
|
187
190
|
|
|
188
191
|
@dataclass(slots=True)
|
|
189
192
|
class UnifiedExecSession:
|
|
@@ -194,6 +197,7 @@ class UnifiedExecSession:
|
|
|
194
197
|
tty: bool
|
|
195
198
|
unread_output: _HeadTailBuffer = field(default_factory=_HeadTailBuffer)
|
|
196
199
|
reader_task: asyncio.Task | None = None
|
|
200
|
+
output_event: asyncio.Event = field(default_factory=asyncio.Event)
|
|
197
201
|
|
|
198
202
|
|
|
199
203
|
class UnifiedExecManager:
|
|
@@ -294,11 +298,22 @@ class UnifiedExecManager:
|
|
|
294
298
|
if session is None:
|
|
295
299
|
return f"Error: session_id {session_id} is not running."
|
|
296
300
|
|
|
297
|
-
|
|
301
|
+
loop = asyncio.get_running_loop()
|
|
302
|
+
start_wait = loop.time()
|
|
298
303
|
try:
|
|
299
304
|
await asyncio.wait_for(session.process.wait(), timeout=yield_time_ms / 1000.0)
|
|
300
305
|
except asyncio.TimeoutError:
|
|
301
|
-
|
|
306
|
+
remaining_seconds = (yield_time_ms / 1000.0) - (loop.time() - start_wait)
|
|
307
|
+
if (
|
|
308
|
+
session.process.returncode is None
|
|
309
|
+
and not session.unread_output.has_data()
|
|
310
|
+
and remaining_seconds > 0
|
|
311
|
+
):
|
|
312
|
+
session.output_event.clear()
|
|
313
|
+
try:
|
|
314
|
+
await asyncio.wait_for(session.output_event.wait(), timeout=remaining_seconds)
|
|
315
|
+
except asyncio.TimeoutError:
|
|
316
|
+
pass
|
|
302
317
|
|
|
303
318
|
if session.reader_task is not None and session.process.returncode is not None:
|
|
304
319
|
await session.reader_task
|
|
@@ -345,6 +360,8 @@ class UnifiedExecManager:
|
|
|
345
360
|
if not chunk:
|
|
346
361
|
break
|
|
347
362
|
session.unread_output.push_chunk(chunk)
|
|
363
|
+
session.output_event.set()
|
|
364
|
+
session.output_event.set()
|
|
348
365
|
|
|
349
366
|
def _resolve_workdir(self, workdir: str | None) -> Path:
|
|
350
367
|
if not workdir:
|
|
@@ -83,10 +83,15 @@ def get_package_version() -> str:
|
|
|
83
83
|
detected = _detect_upstream_codex_version()
|
|
84
84
|
if detected is not None:
|
|
85
85
|
return detected
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
for distribution_name in ("python-codex", "pycodex"):
|
|
87
|
+
try:
|
|
88
|
+
return importlib.metadata.version(distribution_name)
|
|
89
|
+
except importlib.metadata.PackageNotFoundError:
|
|
90
|
+
continue
|
|
91
|
+
local_version = _read_local_package_version()
|
|
92
|
+
if local_version is not None:
|
|
93
|
+
return local_version
|
|
94
|
+
return "0.1.0"
|
|
90
95
|
|
|
91
96
|
|
|
92
97
|
def get_os_info() -> tuple[str, str]:
|
|
@@ -178,6 +183,20 @@ def _normalize_os_version(version: str) -> str:
|
|
|
178
183
|
return version
|
|
179
184
|
|
|
180
185
|
|
|
186
|
+
def _read_local_package_version() -> str | None:
|
|
187
|
+
pyproject_path = Path(__file__).resolve().parents[2] / "pyproject.toml"
|
|
188
|
+
if not pyproject_path.is_file():
|
|
189
|
+
return None
|
|
190
|
+
match = re.search(
|
|
191
|
+
r'^\s*version\s*=\s*"([^"]+)"\s*$',
|
|
192
|
+
pyproject_path.read_text(encoding="utf-8"),
|
|
193
|
+
flags=re.MULTILINE,
|
|
194
|
+
)
|
|
195
|
+
if match is None:
|
|
196
|
+
return None
|
|
197
|
+
return match.group(1).strip() or None
|
|
198
|
+
|
|
199
|
+
|
|
181
200
|
def _tmux_display_message(fmt: str) -> str | None:
|
|
182
201
|
try:
|
|
183
202
|
output = subprocess.run(
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-codex"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "A minimal Python extraction of Codex's main agent loop"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -34,7 +34,7 @@ package = true
|
|
|
34
34
|
default-groups = []
|
|
35
35
|
|
|
36
36
|
[tool.hatch.build.targets.wheel]
|
|
37
|
-
packages = ["pycodex"]
|
|
37
|
+
packages = ["pycodex", "responses_server"]
|
|
38
38
|
|
|
39
39
|
[tool.pytest.ini_options]
|
|
40
40
|
pythonpath = ["."]
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
import time
|
|
5
4
|
|
|
6
5
|
import pytest
|
|
7
6
|
|
|
@@ -44,6 +43,25 @@ class SlowTool(BaseTool):
|
|
|
44
43
|
return "done"
|
|
45
44
|
|
|
46
45
|
|
|
46
|
+
class CoordinatedParallelTool(BaseTool):
|
|
47
|
+
description = "Tool that proves both calls entered before either finished."
|
|
48
|
+
input_schema = {"type": "object"}
|
|
49
|
+
supports_parallel = True
|
|
50
|
+
|
|
51
|
+
def __init__(self, name: str, entered: list[str], both_started: asyncio.Event) -> None:
|
|
52
|
+
self.name = name
|
|
53
|
+
self._entered = entered
|
|
54
|
+
self._both_started = both_started
|
|
55
|
+
|
|
56
|
+
async def run(self, context, args):
|
|
57
|
+
del context, args
|
|
58
|
+
self._entered.append(self.name)
|
|
59
|
+
if len(self._entered) == 2:
|
|
60
|
+
self._both_started.set()
|
|
61
|
+
await asyncio.wait_for(self._both_started.wait(), timeout=0.2)
|
|
62
|
+
return "done"
|
|
63
|
+
|
|
64
|
+
|
|
47
65
|
class WaitAgentNotificationTool(BaseTool):
|
|
48
66
|
name = "wait_agent"
|
|
49
67
|
description = "Returns a completed sub-agent status."
|
|
@@ -109,17 +127,16 @@ async def test_parallel_tools_share_one_model_round() -> None:
|
|
|
109
127
|
)
|
|
110
128
|
|
|
111
129
|
tools = ToolRegistry()
|
|
112
|
-
|
|
113
|
-
|
|
130
|
+
entered: list[str] = []
|
|
131
|
+
both_started = asyncio.Event()
|
|
132
|
+
tools.register(CoordinatedParallelTool("slow_a", entered, both_started))
|
|
133
|
+
tools.register(CoordinatedParallelTool("slow_b", entered, both_started))
|
|
114
134
|
|
|
115
135
|
agent = AgentLoop(model, tools)
|
|
116
|
-
|
|
117
|
-
started = time.perf_counter()
|
|
118
136
|
result = await agent.run_turn(["并行跑两个工具"])
|
|
119
|
-
elapsed = time.perf_counter() - started
|
|
120
137
|
|
|
121
138
|
assert result.output_text == "两个工具都执行完了"
|
|
122
|
-
assert
|
|
139
|
+
assert entered == ["slow_a", "slow_b"] or entered == ["slow_b", "slow_a"]
|
|
123
140
|
|
|
124
141
|
|
|
125
142
|
@pytest.mark.asyncio
|
|
@@ -259,7 +259,9 @@ async def test_exec_command_tool_returns_session_for_long_running_process(tmp_pa
|
|
|
259
259
|
|
|
260
260
|
assert closed.is_error is False
|
|
261
261
|
assert "Process exited with code 0" in closed.output
|
|
262
|
-
|
|
262
|
+
combined_output = result.output + closed.output
|
|
263
|
+
assert "start" in combined_output
|
|
264
|
+
assert "end" in combined_output
|
|
263
265
|
|
|
264
266
|
|
|
265
267
|
@pytest.mark.asyncio
|
|
@@ -374,9 +376,27 @@ async def test_write_stdin_tool_defaults_to_upstream_truncation_budget(tmp_path)
|
|
|
374
376
|
ToolContext(turn_id="turn_9_write_finish", history=()),
|
|
375
377
|
)
|
|
376
378
|
|
|
379
|
+
body = poll.output.split("Output:\n", 1)[1]
|
|
380
|
+
for _ in range(5):
|
|
381
|
+
if "tokens truncated" in body:
|
|
382
|
+
break
|
|
383
|
+
if "Process exited with code 0" in poll.output:
|
|
384
|
+
break
|
|
385
|
+
poll = await registry.execute(
|
|
386
|
+
ToolCall(
|
|
387
|
+
call_id="call_9_write_finish_poll",
|
|
388
|
+
name="write_stdin",
|
|
389
|
+
arguments={
|
|
390
|
+
"session_id": session_id,
|
|
391
|
+
"yield_time_ms": 200,
|
|
392
|
+
},
|
|
393
|
+
),
|
|
394
|
+
ToolContext(turn_id="turn_9_write_finish_poll", history=()),
|
|
395
|
+
)
|
|
396
|
+
body = poll.output.split("Output:\n", 1)[1]
|
|
397
|
+
|
|
377
398
|
assert poll.is_error is False
|
|
378
399
|
assert "Original token count: " in poll.output
|
|
379
|
-
body = poll.output.split("Output:\n", 1)[1]
|
|
380
400
|
assert body.startswith("Total output lines: 1\n\nHEAD")
|
|
381
401
|
assert "tokens truncated" in body
|
|
382
402
|
assert body.endswith("TAIL")
|
|
@@ -899,6 +919,18 @@ async def test_spawn_agent_send_input_wait_and_close_round_trip() -> None:
|
|
|
899
919
|
),
|
|
900
920
|
ToolContext(turn_id="turn_wait", history=()),
|
|
901
921
|
)
|
|
922
|
+
if waited.output == {
|
|
923
|
+
"status": {agent_id: {"completed": "done one"}},
|
|
924
|
+
"timed_out": False,
|
|
925
|
+
}:
|
|
926
|
+
waited = await registry.execute(
|
|
927
|
+
ToolCall(
|
|
928
|
+
call_id="call_wait_again",
|
|
929
|
+
name="wait_agent",
|
|
930
|
+
arguments={"ids": [agent_id], "timeout_ms": 1000},
|
|
931
|
+
),
|
|
932
|
+
ToolContext(turn_id="turn_wait_again", history=()),
|
|
933
|
+
)
|
|
902
934
|
|
|
903
935
|
assert waited.output == {
|
|
904
936
|
"status": {agent_id: {"completed": "done two"}},
|
|
@@ -344,9 +344,23 @@ def test_build_runtime_overrides_provider_for_managed_vllm_mode(
|
|
|
344
344
|
@pytest.mark.asyncio
|
|
345
345
|
async def test_run_cli_launches_managed_responses_server_for_vllm_endpoint(
|
|
346
346
|
monkeypatch,
|
|
347
|
+
tmp_path,
|
|
347
348
|
) -> None:
|
|
348
349
|
started = {}
|
|
349
350
|
registered = {}
|
|
351
|
+
config_path = tmp_path / "config.toml"
|
|
352
|
+
config_path.write_text(
|
|
353
|
+
"\n".join(
|
|
354
|
+
[
|
|
355
|
+
'model = "demo-model"',
|
|
356
|
+
'model_provider = "demo"',
|
|
357
|
+
'[model_providers.demo]',
|
|
358
|
+
'base_url = "https://example.com/v1"',
|
|
359
|
+
'env_key = "DUMMY_KEY"',
|
|
360
|
+
]
|
|
361
|
+
)
|
|
362
|
+
)
|
|
363
|
+
monkeypatch.setenv("DUMMY_KEY", "test-key")
|
|
350
364
|
|
|
351
365
|
class _FakeManagedServer:
|
|
352
366
|
base_url = "http://127.0.0.1:18001/v1"
|
|
@@ -415,6 +429,8 @@ async def test_run_cli_launches_managed_responses_server_for_vllm_endpoint(
|
|
|
415
429
|
|
|
416
430
|
args = build_parser().parse_args(
|
|
417
431
|
[
|
|
432
|
+
"--config",
|
|
433
|
+
str(config_path),
|
|
418
434
|
"--vllm-endpoint",
|
|
419
435
|
"http://127.0.0.1:18000",
|
|
420
436
|
"Reply with exactly OK.",
|
|
@@ -2588,7 +2604,22 @@ async def test_prompt_request_permissions_supports_session_scope() -> None:
|
|
|
2588
2604
|
async def test_run_cli_returns_non_zero_on_single_turn_error(
|
|
2589
2605
|
monkeypatch: pytest.MonkeyPatch,
|
|
2590
2606
|
capsys: pytest.CaptureFixture[str],
|
|
2607
|
+
tmp_path: Path,
|
|
2591
2608
|
) -> None:
|
|
2609
|
+
config_path = tmp_path / "config.toml"
|
|
2610
|
+
config_path.write_text(
|
|
2611
|
+
"\n".join(
|
|
2612
|
+
[
|
|
2613
|
+
'model = "demo-model"',
|
|
2614
|
+
'model_provider = "demo"',
|
|
2615
|
+
'[model_providers.demo]',
|
|
2616
|
+
'base_url = "https://example.com/v1"',
|
|
2617
|
+
'env_key = "DUMMY_KEY"',
|
|
2618
|
+
]
|
|
2619
|
+
)
|
|
2620
|
+
)
|
|
2621
|
+
monkeypatch.setenv("DUMMY_KEY", "test-key")
|
|
2622
|
+
|
|
2592
2623
|
class _FakeRuntime:
|
|
2593
2624
|
def __init__(self):
|
|
2594
2625
|
self._stopped = asyncio.Event()
|
|
@@ -2609,7 +2640,7 @@ async def test_run_cli_returns_non_zero_on_single_turn_error(
|
|
|
2609
2640
|
monkeypatch.setattr("pycodex.cli.build_runtime", lambda *args, **kwargs: _FakeRuntime())
|
|
2610
2641
|
monkeypatch.setattr("sys.stdin.read", lambda: "")
|
|
2611
2642
|
|
|
2612
|
-
args = build_parser().parse_args(["hello"])
|
|
2643
|
+
args = build_parser().parse_args(["--config", str(config_path), "hello"])
|
|
2613
2644
|
code = await run_cli(args)
|
|
2614
2645
|
|
|
2615
2646
|
assert code == 1
|
|
@@ -8,6 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
10
10
|
|
|
11
|
+
import pycodex.utils.get_env as get_env
|
|
11
12
|
from pycodex import (
|
|
12
13
|
AssistantMessage,
|
|
13
14
|
ContextMessage,
|
|
@@ -541,6 +542,29 @@ def test_responses_model_client_builds_tui_user_agent(monkeypatch) -> None:
|
|
|
541
542
|
assert re.match(r'^codex-tui/.+ \(.+; .+\) .+ \(codex-tui; .+\)$', headers['user-agent'])
|
|
542
543
|
|
|
543
544
|
|
|
545
|
+
def test_get_package_version_reads_distribution_name(monkeypatch) -> None:
|
|
546
|
+
def fake_version(name: str) -> str:
|
|
547
|
+
if name == 'python-codex':
|
|
548
|
+
return '0.1.2'
|
|
549
|
+
raise get_env.importlib.metadata.PackageNotFoundError
|
|
550
|
+
|
|
551
|
+
monkeypatch.setattr(get_env, '_detect_upstream_codex_version', lambda: None)
|
|
552
|
+
monkeypatch.setattr(get_env.importlib.metadata, 'version', fake_version)
|
|
553
|
+
|
|
554
|
+
assert get_env.get_package_version() == '0.1.2'
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def test_get_package_version_falls_back_to_local_pyproject(monkeypatch) -> None:
|
|
558
|
+
def fake_missing_version(_name: str) -> str:
|
|
559
|
+
raise get_env.importlib.metadata.PackageNotFoundError
|
|
560
|
+
|
|
561
|
+
monkeypatch.setattr(get_env, '_detect_upstream_codex_version', lambda: None)
|
|
562
|
+
monkeypatch.setattr(get_env.importlib.metadata, 'version', fake_missing_version)
|
|
563
|
+
monkeypatch.setattr(get_env, '_read_local_package_version', lambda: '0.1.2')
|
|
564
|
+
|
|
565
|
+
assert get_env.get_package_version() == '0.1.2'
|
|
566
|
+
|
|
567
|
+
|
|
544
568
|
def test_responses_model_client_serializes_prompt_turn_metadata(monkeypatch) -> None:
|
|
545
569
|
provider = ResponsesProviderConfig(
|
|
546
570
|
model='demo-model',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/permissions/approval_policy/never.md
RENAMED
|
File without changes
|
{python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/permissions/approval_policy/on_failure.md
RENAMED
|
File without changes
|
{python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/permissions/approval_policy/on_request.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_codex-0.1.1 → python_codex-0.1.2}/pycodex/prompts/permissions/sandbox_mode/read_only.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_codex-0.1.1 → python_codex-0.1.2}/tests/responses_server/fake_chat_completions_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|