python-codex 0.4.5__tar.gz → 0.4.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. {python_codex-0.4.5 → python_codex-0.4.7}/PKG-INFO +13 -7
  2. {python_codex-0.4.5 → python_codex-0.4.7}/README.md +12 -6
  3. {python_codex-0.4.5 → python_codex-0.4.7}/README_ZH.md +7 -3
  4. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/cli.py +5 -2
  5. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/events.py +6 -3
  6. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/model.py +26 -0
  7. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/runtime.py +22 -15
  8. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/event_helpers.py +4 -2
  9. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/session_persist.py +11 -3
  10. {python_codex-0.4.5 → python_codex-0.4.7}/pyproject.toml +2 -2
  11. {python_codex-0.4.5 → python_codex-0.4.7}/.gitignore +0 -0
  12. {python_codex-0.4.5 → python_codex-0.4.7}/LICENSE +0 -0
  13. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/__init__.py +0 -0
  14. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/__main__.py +0 -0
  15. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/agent.py +0 -0
  16. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/bootstrap.py +0 -0
  17. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/context.py +0 -0
  18. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/portable/__init__.py +0 -0
  19. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/portable/client.py +0 -0
  20. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/portable/server.py +0 -0
  21. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/prompts/default_base_instructions.md +0 -0
  22. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/prompts/models.json +0 -0
  23. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/prompts/permissions/approval_policy/never.md +0 -0
  24. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/prompts/permissions/approval_policy/on_failure.md +0 -0
  25. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/prompts/permissions/approval_policy/on_request.md +0 -0
  26. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/prompts/permissions/approval_policy/on_request_rule_request_permission.md +0 -0
  27. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/prompts/permissions/approval_policy/unless_trusted.md +0 -0
  28. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/prompts/permissions/sandbox_mode/danger_full_access.md +0 -0
  29. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/prompts/permissions/sandbox_mode/read_only.md +0 -0
  30. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/prompts/permissions/sandbox_mode/workspace_write.md +0 -0
  31. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/protocol.py +0 -0
  32. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/__init__.py +0 -0
  33. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/agent_tool_schemas.py +0 -0
  34. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/apply_patch_tool.py +0 -0
  35. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/base_tool.py +0 -0
  36. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/clock_tool.py +0 -0
  37. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/close_agent_tool.py +0 -0
  38. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/code_mode_manager.py +0 -0
  39. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/exec_command_tool.py +0 -0
  40. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/exec_runtime.js +0 -0
  41. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/exec_tool.py +0 -0
  42. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/grep_files_tool.py +0 -0
  43. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/list_dir_tool.py +0 -0
  44. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/read_file_tool.py +0 -0
  45. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/request_permissions_tool.py +0 -0
  46. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/request_user_input_tool.py +0 -0
  47. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/resume_agent_tool.py +0 -0
  48. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/runtime_services.py +0 -0
  49. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/send_input_tool.py +0 -0
  50. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/shell_command_tool.py +0 -0
  51. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/shell_tool.py +0 -0
  52. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/spawn_agent_tool.py +0 -0
  53. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/unified_exec_manager.py +0 -0
  54. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/update_plan_tool.py +0 -0
  55. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/view_image_tool.py +0 -0
  56. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/wait_agent_tool.py +0 -0
  57. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/wait_tool.py +0 -0
  58. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/web_search_tool.py +0 -0
  59. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/tools/write_stdin_tool.py +0 -0
  60. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/__init__.py +0 -0
  61. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/_aes_gcm/LICENSE +0 -0
  62. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/_aes_gcm/README.md +0 -0
  63. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/_aes_gcm/__init__.py +0 -0
  64. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/_aes_gcm/aes.py +0 -0
  65. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/_aes_gcm/gcm.py +0 -0
  66. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/_aes_gcm/source-sha256.json +0 -0
  67. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/async_bridge.py +0 -0
  68. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/compactor.py +0 -0
  69. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/compat.py +0 -0
  70. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/crypto.py +0 -0
  71. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/debug.py +0 -0
  72. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/doctor.py +0 -0
  73. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/dotenv.py +0 -0
  74. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/get_env.py +0 -0
  75. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/image_utils.py +0 -0
  76. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/model_metadata.py +0 -0
  77. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/random_ids.py +0 -0
  78. {python_codex-0.4.5 → python_codex-0.4.7}/pycodex/utils/truncation.py +0 -0
  79. {python_codex-0.4.5 → python_codex-0.4.7}/responses_server/__init__.py +0 -0
  80. {python_codex-0.4.5 → python_codex-0.4.7}/responses_server/__main__.py +0 -0
  81. {python_codex-0.4.5 → python_codex-0.4.7}/responses_server/app.py +0 -0
  82. {python_codex-0.4.5 → python_codex-0.4.7}/responses_server/config.py +0 -0
  83. {python_codex-0.4.5 → python_codex-0.4.7}/responses_server/messages_api.py +0 -0
  84. {python_codex-0.4.5 → python_codex-0.4.7}/responses_server/payload_processors.py +0 -0
  85. {python_codex-0.4.5 → python_codex-0.4.7}/responses_server/server.py +0 -0
  86. {python_codex-0.4.5 → python_codex-0.4.7}/responses_server/session_store.py +0 -0
  87. {python_codex-0.4.5 → python_codex-0.4.7}/responses_server/stream_router.py +0 -0
  88. {python_codex-0.4.5 → python_codex-0.4.7}/responses_server/tools/__init__.py +0 -0
  89. {python_codex-0.4.5 → python_codex-0.4.7}/responses_server/tools/custom_adapter.py +0 -0
  90. {python_codex-0.4.5 → python_codex-0.4.7}/responses_server/tools/web_search.py +0 -0
  91. {python_codex-0.4.5 → python_codex-0.4.7}/responses_server/trajectory_dump.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-codex
3
- Version: 0.4.5
3
+ Version: 0.4.7
4
4
  Summary: A minimal Python extraction of Codex's main agent loop
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.6.2
@@ -369,22 +369,28 @@ Current behavior:
369
369
  Open views survive reloads and server restarts. See
370
370
  [workspace content views](docs/RUNTIME.md#workspace-content-views) for the
371
371
  complete syntax and limits.
372
- - interactive mode exits through `/exit`, `/quit`, Ctrl+D on an empty prompt,
373
- or a single Ctrl+C; accepted work finishes before cleanup and normal exit.
372
+ - interactive mode exits through Ctrl+D on an empty prompt or a single Ctrl+C;
373
+ accepted work finishes before cleanup and normal exit.
374
374
  A `[closing]` message explains the wait. Press Ctrl+C again while closing
375
375
  to immediately exit the CLI with status 130. This skips remaining cleanup;
376
376
  external tool processes may keep running.
377
377
  - interactive mode shows a compact event stream for user-visible phases such as
378
378
  tool execution and model follow-up after tool results
379
+ - startup shows a welcome, model, effort, endpoint and available commands.
379
380
  - assistant text is printed from streaming deltas directly
380
- - interactive mode supports `/history`, `/title`, `/model`, `/resume`, `/compact`,
381
- and `/fork`
381
+ - interactive mode supports `/history`, `/title`, `/model`, `/effort`, `/resume`,
382
+ `/compact`, and `/fork`
382
383
  - `/model <name>` switches the model used by later turns in the current
383
384
  interactive session; `/model` shows the current model and available choices
385
+ - `/effort low` switches effort while idle; `/effort` shows the current value,
386
+ and `/effort default` restores the model default. Frontend labels show
387
+ `model:effort`.
384
388
  - `/resume` with no argument lists the currently resumable sessions by their
385
389
  first user-message preview; `/resume 1` resumes the first listed session
386
- - `/resume <number>` replaces the in-memory history with the selected recorded
387
- Codex rollout from `CODEX_HOME/sessions`. Tool calls without recorded results
390
+ - `/resume <session_id>` accepts a full session UUID, including sessions older
391
+ than the displayed list. Both forms search `CODEX_HOME/sessions` and
392
+ `CODEX_HOME/archived_sessions` and replace the in-memory history with the
393
+ selected recorded Codex rollout. Tool calls without recorded results
388
394
  are omitted during restore; later saved messages and completed tools remain
389
395
  available, and the source file is left intact.
390
396
  - `/compact` synthesizes a local handoff summary, replaces the in-memory
@@ -349,22 +349,28 @@ Current behavior:
349
349
  Open views survive reloads and server restarts. See
350
350
  [workspace content views](docs/RUNTIME.md#workspace-content-views) for the
351
351
  complete syntax and limits.
352
- - interactive mode exits through `/exit`, `/quit`, Ctrl+D on an empty prompt,
353
- or a single Ctrl+C; accepted work finishes before cleanup and normal exit.
352
+ - interactive mode exits through Ctrl+D on an empty prompt or a single Ctrl+C;
353
+ accepted work finishes before cleanup and normal exit.
354
354
  A `[closing]` message explains the wait. Press Ctrl+C again while closing
355
355
  to immediately exit the CLI with status 130. This skips remaining cleanup;
356
356
  external tool processes may keep running.
357
357
  - interactive mode shows a compact event stream for user-visible phases such as
358
358
  tool execution and model follow-up after tool results
359
+ - startup shows a welcome, model, effort, endpoint and available commands.
359
360
  - assistant text is printed from streaming deltas directly
360
- - interactive mode supports `/history`, `/title`, `/model`, `/resume`, `/compact`,
361
- and `/fork`
361
+ - interactive mode supports `/history`, `/title`, `/model`, `/effort`, `/resume`,
362
+ `/compact`, and `/fork`
362
363
  - `/model <name>` switches the model used by later turns in the current
363
364
  interactive session; `/model` shows the current model and available choices
365
+ - `/effort low` switches effort while idle; `/effort` shows the current value,
366
+ and `/effort default` restores the model default. Frontend labels show
367
+ `model:effort`.
364
368
  - `/resume` with no argument lists the currently resumable sessions by their
365
369
  first user-message preview; `/resume 1` resumes the first listed session
366
- - `/resume <number>` replaces the in-memory history with the selected recorded
367
- Codex rollout from `CODEX_HOME/sessions`. Tool calls without recorded results
370
+ - `/resume <session_id>` accepts a full session UUID, including sessions older
371
+ than the displayed list. Both forms search `CODEX_HOME/sessions` and
372
+ `CODEX_HOME/archived_sessions` and replace the in-memory history with the
373
+ selected recorded Codex rollout. Tool calls without recorded results
368
374
  are omitted during restore; later saved messages and completed tools remain
369
375
  available, and the source file is left intact.
370
376
  - `/compact` synthesizes a local handoff summary, replaces the in-memory
@@ -304,15 +304,19 @@ pycodex doctor
304
304
 
305
305
  - 没有 argv prompt 且 stdin 是 TTY 时,进入交互模式
306
306
  - 有 argv prompt 或 stdin 管道输入时,执行单轮请求
307
- - 交互模式通过 `/exit`、`/quit`、空提示符上的 Ctrl+D 或一次 Ctrl+C 正常退出;
307
+ - 交互模式通过空提示符上的 Ctrl+D 或一次 Ctrl+C 正常退出;
308
308
  `[closing]` 提示表示正在等待已接收的工作结束并清理资源,不取消模型或工具调用。
309
309
  - 交互模式下会显示简洁阶段事件流,例如工具执行状态和模型回看工具结果
310
+ - 开场保留欢迎语,显示 model、effort、endpoint 和命令列表。
310
311
  - assistant 文本会按流式 delta 直接打印
311
- - 交互模式下支持 `/history`、`/title`、`/model` 和 `/resume`
312
+ - 交互模式下支持 `/history`、`/title`、`/model`、`/effort` 和 `/resume`
312
313
  - `/model <name>` 会切换当前交互会话后续请求使用的模型;`/model` 会显示当前模型和可选模型
314
+ - `/effort low` 在空闲时切换 effort,`/effort` 查看当前值,`/effort default` 恢复模型默认值;前端模型标签显示 `model:effort`。
313
315
  - `/resume` 不带参数时会按首条用户消息预览列出当前可恢复的 session;`/resume 1`
314
316
  会恢复列表里的第 1 个 session
315
- - `/resume <数字>` 会从 `CODEX_HOME/sessions` 读取选中的已记录 Codex rollout,
317
+ - `/resume <session_id>` 支持完整的会话 UUID,不受当前列表条数限制。
318
+ 编号和 UUID 两种方式都会查找 `CODEX_HOME/sessions` 与
319
+ `CODEX_HOME/archived_sessions`,读取选中的已记录 Codex rollout,
316
320
  并直接替换当前内存里的会话 history
317
321
  - 新 session 现在会自动保存到 `CODEX_HOME/sessions/.../rollout-*.jsonl`,
318
322
  使用稳定的 session/thread id,并按 item 级别 append + flush,和 `/resume`
@@ -19,7 +19,7 @@ from prompt_toolkit.patch_stdout import patch_stdout
19
19
 
20
20
  from .bootstrap import build_agent, build_model, build_runtime, configure_loguru
21
21
  from .events import DEFAULT_MAIN_PROMPT, EventDisplay
22
- from .model import DEFAULT_CODEX_CONFIG_PATH
22
+ from .model import DEFAULT_CODEX_CONFIG_PATH, ModelControl
23
23
  from .portable import bootstrap_called_home, upload_codex_home
24
24
  from .utils import get_debug_dir
25
25
  from .utils.event_helpers import format_error, render_result
@@ -216,7 +216,10 @@ async def run_interactive_session(runtime, json_mode, config_path=None, view=Non
216
216
  if not future.cancelled() and future.exception() is None:
217
217
  render_result("turn", future.result(), True, view.write_line)
218
218
 
219
- view.display.start(runtime.commands())
219
+ client = typing.cast(ModelControl, runtime.agent.model_client)
220
+ view.display.start(
221
+ client.model, client.reasoning_effort, client.endpoint, runtime.commands()
222
+ )
220
223
  with _cli_sigint_handler(runtime) as install_sigint_handler:
221
224
  try:
222
225
  while not view.display.closed:
@@ -48,11 +48,14 @@ class EventDisplay:
48
48
  self.closed = False
49
49
  self.set_context_window_tokens(context_window_tokens)
50
50
 
51
- def start(self, commands):
51
+ def start(self, model, reasoning_effort, endpoint, commands):
52
+ self.log("Welcome to PyCodex")
52
53
  self.log(
53
- "pycodex interactive mode. Type /exit or press Ctrl+C to quit; "
54
- "press Ctrl+C again while closing to force exit."
54
+ "model: {0} | effort: {1}".format(
55
+ model, reasoning_effort or "provider default"
56
+ )
55
57
  )
58
+ self.log("endpoint: " + endpoint)
56
59
  self.log(format_command_result({"kind": "help", "commands": commands}))
57
60
 
58
61
  def write(self, text, kind=""):
@@ -67,6 +67,11 @@ class ModelClient(Protocol):
67
67
 
68
68
  class ModelControl(Protocol):
69
69
  model: "str"
70
+ reasoning_effort: "typing.Union[str, None]"
71
+
72
+ @property
73
+ def endpoint(self) -> "str":
74
+ """Return the active API base URL, safe for display."""
70
75
 
71
76
  async def list_models(self) -> "typing.List[str]":
72
77
  """List the models available to an interactive session."""
@@ -398,6 +403,27 @@ class ResponsesModelClient:
398
403
  def model(self, model: "str") -> "None":
399
404
  self._config = replace(self._config, model=model)
400
405
 
406
+ @property
407
+ def reasoning_effort(self) -> "typing.Union[str, None]":
408
+ """Return the effective effort; assign None to restore the model default."""
409
+ return self._config.effective_reasoning_effort()
410
+
411
+ @reasoning_effort.setter
412
+ def reasoning_effort(self, effort: "typing.Union[str, None]") -> "None":
413
+ if effort is not None and (
414
+ not effort or any(char.isspace() for char in effort)
415
+ ):
416
+ raise ValueError("reasoning effort must be a single non-empty value")
417
+ self._config = replace(self._config, reasoning_effort=effort)
418
+
419
+ @property
420
+ def endpoint(self) -> "str":
421
+ """Return the active API base URL without credentials, query or fragment."""
422
+ url = urllib.parse.urlsplit(self._config.base_url)
423
+ return urllib.parse.urlunsplit(
424
+ (url.scheme, url.netloc.rsplit("@", 1)[-1], url.path, "", "")
425
+ )
426
+
401
427
  @classmethod
402
428
  def from_codex_config(
403
429
  cls,
@@ -25,6 +25,7 @@ from .events import (
25
25
  TurnInterruptedEvent,
26
26
  TurnStartedEvent,
27
27
  )
28
+ from .model import ModelControl
28
29
  from .protocol import TurnResult
29
30
  from .utils import uuid7_string
30
31
  from .utils.event_helpers import shorten_title
@@ -124,12 +125,13 @@ class AgentRuntime:
124
125
  "history",
125
126
  "title",
126
127
  "model",
128
+ "effort",
127
129
  "resume",
128
130
  "compact",
129
131
  "fork",
130
132
  )
131
133
  + tuple(self.command_handlers)
132
- + ("queue", "exit", "quit")
134
+ + ("queue",)
133
135
  )
134
136
 
135
137
  def snapshot(self):
@@ -141,7 +143,10 @@ class AgentRuntime:
141
143
  "recorded_rollout_path": (
142
144
  str(recorded_path) if recorded_path is not None else None
143
145
  ),
144
- "model": self.agent.model_name,
146
+ "model": "{0}:{1}".format(
147
+ self.agent.model_name,
148
+ getattr(self.agent.model_client, "reasoning_effort", None) or "default",
149
+ ),
145
150
  "title": self.title,
146
151
  "history": conversation_history_to_turns(self.agent.history),
147
152
  "busy": self.is_busy,
@@ -250,13 +255,10 @@ class AgentRuntime:
250
255
  submission_id = uuid7_string()
251
256
  future = asyncio.get_running_loop().create_future()
252
257
  try:
253
- if command in {"/exit", "/quit"}:
258
+ async with self._get_command_lock():
259
+ if not self.agent.accepts_input:
260
+ raise RuntimeError("agent is shutting down")
254
261
  result = await self._execute_command(command[1:], argument.strip())
255
- else:
256
- async with self._get_command_lock():
257
- if not self.agent.accepts_input:
258
- raise RuntimeError("agent is shutting down")
259
- result = await self._execute_command(command[1:], argument.strip())
260
262
  except Exception as exc:
261
263
  future.set_exception(exc)
262
264
  self._publish(CommandFailedEvent(submission_id, command, str(exc), sender))
@@ -267,10 +269,7 @@ class AgentRuntime:
267
269
  return SubmittedInput(submission_id, "command", future)
268
270
 
269
271
  async def _execute_command(self, command, argument):
270
- if (
271
- command in {"help", "history", "compact", "fork", "exit", "quit"}
272
- and argument
273
- ):
272
+ if command in {"help", "history", "compact", "fork"} and argument:
274
273
  raise ValueError("Usage: /{0}".format(command))
275
274
  if command == "help":
276
275
  return {"kind": "help", "commands": self.commands()}
@@ -292,6 +291,17 @@ class AgentRuntime:
292
291
  "model": self.agent.model_name,
293
292
  "models": await self.agent.model_client.list_models(),
294
293
  }
294
+ if command == "effort":
295
+ client = typing.cast(ModelControl, self.agent.model_client)
296
+ effort = client.reasoning_effort
297
+ if argument:
298
+ if len(argument.split()) != 1:
299
+ raise ValueError("Usage: /effort [<value>|default]")
300
+ self.require_idle("change reasoning effort")
301
+ client.reasoning_effort = None if argument == "default" else argument
302
+ effort = client.reasoning_effort
303
+ self.publish_state("effort")
304
+ return {"kind": "effort", "reasoning_effort": effort}
295
305
  if command == "resume":
296
306
  if not argument:
297
307
  return {
@@ -317,9 +327,6 @@ class AgentRuntime:
317
327
  if command == "fork":
318
328
  self.fork()
319
329
  return {"kind": "forked", "session_id": self.agent.session_id}
320
- if command in {"exit", "quit"}:
321
- await self.close()
322
- return {"kind": "closed"}
323
330
  handler = self.command_handlers.get(command)
324
331
  if handler is None:
325
332
  raise ValueError("Unknown command: /{0}".format(command))
@@ -35,6 +35,10 @@ def format_command_result(result: "typing.Dict[str, object]") -> "str":
35
35
  )
36
36
  if kind == "model_changed":
37
37
  return "Switched model to {0}.".format(result["model"])
38
+ if kind == "effort":
39
+ return "Reasoning effort: {0}".format(
40
+ result["reasoning_effort"] or "provider default"
41
+ )
38
42
  if kind == "sessions":
39
43
  if not result["sessions"]:
40
44
  return "No resumable sessions found."
@@ -67,8 +71,6 @@ def format_command_result(result: "typing.Dict[str, object]") -> "str":
67
71
  )
68
72
  if kind == "forked":
69
73
  return "Forked session: " + result["session_id"]
70
- if kind == "closed":
71
- return ""
72
74
  return "\n".join(result["lines"])
73
75
 
74
76
 
@@ -163,6 +163,7 @@ class SessionRolloutRecorder:
163
163
  def list_resumable_sessions(
164
164
  codex_home: "Path",
165
165
  limit: "int" = 20,
166
+ session_id: "typing.Union[str, None]" = None,
166
167
  ) -> "typing.Tuple[typing.Dict[str, str], ...]":
167
168
  latest_rollouts_by_id: "typing.Dict[str, Path]" = {}
168
169
  for dirname in ROLLUP_SESSION_DIRNAMES:
@@ -173,6 +174,8 @@ def list_resumable_sessions(
173
174
  thread_id = _thread_id_from_rollout_path(path)
174
175
  if thread_id is None:
175
176
  continue
177
+ if session_id is not None and thread_id.lower() != session_id.lower():
178
+ continue
176
179
  previous = latest_rollouts_by_id.get(thread_id)
177
180
  if previous is None or path.stat().st_mtime > previous.stat().st_mtime:
178
181
  latest_rollouts_by_id[thread_id] = path
@@ -202,11 +205,16 @@ def list_resumable_sessions(
202
205
 
203
206
  def select_resumable_session(
204
207
  codex_home: "Path",
205
- resume_index_text: "str",
208
+ resume_target: "str",
206
209
  ) -> "typing.Dict[str, str]":
207
- normalized_target = resume_index_text.strip()
210
+ normalized_target = resume_target.strip()
211
+ if UUID_PATTERN.fullmatch(normalized_target):
212
+ sessions = list_resumable_sessions(codex_home, 1, normalized_target)
213
+ if not sessions:
214
+ raise ValueError(f"Session not found: {normalized_target}")
215
+ return sessions[0]
208
216
  if not normalized_target.isdigit():
209
- raise ValueError("Usage: /resume <number>")
217
+ raise ValueError("Usage: /resume <number|session_id>")
210
218
 
211
219
  sessions = list_resumable_sessions(codex_home)
212
220
  resume_index = int(normalized_target)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "python-codex"
7
- version = "0.4.5"
7
+ version = "0.4.7"
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"
@@ -40,7 +40,7 @@ test = [
40
40
  ]
41
41
  dev = [
42
42
  { include-group = "test" },
43
- "pycodex-ws==0.4.5",
43
+ "pycodex-ws==0.4.7",
44
44
  "black>=22.8.0",
45
45
  "debugpy>=1.8.20; python_version >= '3.8'",
46
46
  "isort>=5.10.1",
File without changes
File without changes