python-codex 0.2.5__py3-none-any.whl → 0.2.7__py3-none-any.whl

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.
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import mmap
2
3
  import os
3
4
  import re
4
5
  from datetime import datetime
@@ -23,6 +24,8 @@ UUID_PATTERN = re.compile(
23
24
  re.IGNORECASE,
24
25
  )
25
26
  ROLLOUT_READ_CHUNK_SIZE = 1024 * 1024
27
+ COMPACTED_RECORD_MARKER = b',"type":"compacted","payload":'
28
+ ROLLOUT_RECORD_PREFIX = b'\n{"timestamp":"'
26
29
 
27
30
 
28
31
  def resolve_codex_home(
@@ -215,7 +218,14 @@ def load_resumed_session_path(
215
218
  saw_user_turn = False
216
219
  tool_names_by_call_id: 'typing.Dict[str, str]' = {}
217
220
 
218
- for entry in _iter_rollout_entries(rollout_path):
221
+ # A rollout is append-only, and a compacted entry replaces everything
222
+ # before it for the next request. Large tool outputs before the latest
223
+ # checkpoint are retained for audit, but do not need to be decoded while
224
+ # restoring the active conversation.
225
+ compacted_offset = _find_last_compacted_offset(rollout_path)
226
+ entry_start_offset = compacted_offset if compacted_offset is not None else 0
227
+
228
+ for entry in _iter_rollout_entries(rollout_path, entry_start_offset):
219
229
  item_type = str(entry.get("type", "")).strip()
220
230
  payload = entry.get("payload")
221
231
 
@@ -363,13 +373,51 @@ def _extract_first_user_message_preview(rollout_path: 'Path') -> 'typing.Union[s
363
373
  return None
364
374
 
365
375
 
366
- def _iter_rollout_entries(rollout_path: 'Path') -> 'typing.Iterable[typing.Dict[str, object]]':
376
+ def _find_last_compacted_offset(
377
+ rollout_path: 'Path',
378
+ ) -> 'typing.Union[int, None]':
379
+ """Find the latest recorder-format compact checkpoint."""
380
+
381
+ file_size = rollout_path.stat().st_size
382
+ if not file_size:
383
+ return None
384
+
385
+ with rollout_path.open("rb") as handle:
386
+ mapped = mmap.mmap(handle.fileno(), 0, access=mmap.ACCESS_READ)
387
+ try:
388
+ marker_offset = mapped.rfind(COMPACTED_RECORD_MARKER)
389
+ if marker_offset < 0:
390
+ return None
391
+ record_start = mapped.rfind(
392
+ ROLLOUT_RECORD_PREFIX,
393
+ 0,
394
+ marker_offset,
395
+ )
396
+ if record_start < 0:
397
+ return None
398
+ previous = record_start - 1
399
+ while previous >= 0 and mapped[previous] in b" \t\r\n":
400
+ previous -= 1
401
+ if previous >= 0 and mapped[previous] != ord("}"):
402
+ return None
403
+ return record_start + 1
404
+ finally:
405
+ mapped.close()
406
+ return None
407
+
408
+
409
+ def _iter_rollout_entries(
410
+ rollout_path: 'Path',
411
+ start_offset: 'int' = 0,
412
+ ) -> 'typing.Iterable[typing.Dict[str, object]]':
367
413
  decoder = json.JSONDecoder()
368
414
  buffer = ""
369
415
  start = 0
370
416
  parsed_entries = 0
371
417
 
372
418
  with rollout_path.open("r", encoding="utf-8", errors="replace") as handle:
419
+ if start_offset:
420
+ handle.seek(start_offset)
373
421
  while True:
374
422
  chunk = handle.read(ROLLOUT_READ_CHUNK_SIZE)
375
423
  eof = not chunk
@@ -17,7 +17,15 @@ import typing
17
17
  STATUS_FRAMES = ("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏")
18
18
  PROMPT_CONTEXT_BASELINE_TOKENS = 12_000
19
19
  DEFAULT_MAIN_PROMPT = "pycodex> "
20
- IDLE_LISTENING_STATUS = "idle: listening"
20
+ IDLE_SLEEPING_STATUS = "idle: sleeping"
21
+
22
+
23
+ def background_work_count(payload: "typing.Mapping[str, object]") -> "int":
24
+ value = payload.get("background_work_count", 0)
25
+ try:
26
+ return max(int(value), 0)
27
+ except (TypeError, ValueError):
28
+ return 0
21
29
 
22
30
 
23
31
  def shorten_title(text: "str", limit: "int" = 48) -> "str":
@@ -467,9 +475,8 @@ class CliSessionView:
467
475
  return f"pyco({self._context_remaining_percent}%)> "
468
476
 
469
477
  def _set_idle_status(self, event: "AgentEvent") -> "None":
470
- background_work_count = event.payload.get("background_exec_count", 0)
471
- if background_work_count > 0:
472
- self.prompter.set_status(IDLE_LISTENING_STATUS)
478
+ if background_work_count(event.payload) > 0:
479
+ self.prompter.set_status(IDLE_SLEEPING_STATUS)
473
480
  else:
474
481
  self.prompter.set_status(active=False)
475
482
 
@@ -1,17 +1,18 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-codex
3
- Version: 0.2.5
3
+ Version: 0.2.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
7
- Requires-Dist: cryptography<41,>=40.0.2; python_version < '3.7'
8
- Requires-Dist: cryptography>=40.0.2; python_version >= '3.7'
9
7
  Requires-Dist: dataclasses>=0.8; python_version < '3.7'
10
8
  Requires-Dist: fastapi<0.84,>=0.83.0; python_version < '3.7'
11
9
  Requires-Dist: fastapi>=0.83.0; python_version >= '3.7'
12
10
  Requires-Dist: importlib-metadata>=4.8.3; python_version < '3.8'
13
11
  Requires-Dist: loguru>=0.7.3
12
+ Requires-Dist: pillow>=8.4.0; python_version < '3.7'
13
+ Requires-Dist: pillow>=9.0.0; python_version >= '3.7'
14
14
  Requires-Dist: prompt-toolkit>=3.0.36
15
+ Requires-Dist: pycryptodomex>=3.20
15
16
  Requires-Dist: requests>=2.27.1
16
17
  Requires-Dist: tomli<2,>=1.2.3; python_version < '3.11'
17
18
  Requires-Dist: typing-extensions>=4.1.1; python_version < '3.8'
@@ -23,8 +24,17 @@ Description-Content-Type: text/markdown
23
24
 
24
25
  English README. Chinese version: `README_ZH.md`
25
26
 
26
- PyPI distribution name: `python-codex`
27
- Import path and CLI command remain `pycodex`.
27
+ PyPI distributions:
28
+
29
+ - Primary package: `python-codex`
30
+ - Workspace install alias: `pycodex-ws`
31
+
32
+ The import path remains `pycodex`; the CLI commands are `pycodex` and
33
+ `pycodex-ws`.
34
+
35
+ `pycodex-ws` is a thin metapackage that depends on the exact matching
36
+ `python-codex` version. The implementation and console script remain owned by
37
+ `python-codex`, so the two distributions never install duplicate modules.
28
38
 
29
39
  This repository extracts the core Codex agent loop from upstream Codex
30
40
  (`https://github.com/openai/codex`) into a deliberately small Python version,
@@ -44,6 +54,13 @@ Relevant Rust reference points:
44
54
 
45
55
  ## Quick Start
46
56
 
57
+ Install the full package or the workspace-oriented alias:
58
+
59
+ ```bash
60
+ pip install python-codex
61
+ pip install pycodex-ws
62
+ ```
63
+
47
64
  Install dependencies first:
48
65
 
49
66
  ```bash
@@ -238,17 +255,29 @@ Current behavior:
238
255
  later steer text is appended to the next model request's `input` in order;
239
256
  for explicit queueing, use `/queue <message>`, which prints
240
257
  `[steer] queued: ...` and later `[steer] inserted: ...`
241
- - the default built-in tool subset currently exposed as local tools is:
242
- `shell`, `shell_command`, `exec_command`, `write_stdin`, `exec`, `wait`,
243
- `web_search`, `update_plan`, `request_user_input`, `request_permissions`,
244
- `spawn_agent`, `send_input`, `resume_agent`, `wait_agent`, `close_agent`,
245
- `apply_patch`, `grep_files`, `read_file`, `list_dir`, `view_image`
258
+ - the default local tool set includes the upstream-aligned subset plus the
259
+ pycodex `clock` extension: `shell`, `shell_command`, `exec_command`,
260
+ `write_stdin`, `clock`, `exec`, `wait`, `web_search`, `update_plan`,
261
+ `request_user_input`, `request_permissions`, `spawn_agent`, `send_input`,
262
+ `resume_agent`, `wait_agent`, `close_agent`, `apply_patch`, `grep_files`,
263
+ `read_file`, `list_dir`, `view_image`
264
+ - `clock(period_m)` sets one periodic clock for the current Agent session;
265
+ `null` cancels it. The countdown restarts after each reply and wakes the
266
+ Agent with a `<clock_tick>` message containing the current timezone-aware
267
+ time when it expires.
268
+ - while a background command or clock is pending, the idle status is
269
+ `idle: sleeping`
270
+ - only the active workspace tab shows its close button
246
271
  - `--vllm-endpoint http://host:port` automatically launches a local
247
272
  `responses_server` compatibility layer; when the URL path is empty it is
248
273
  normalized to `/v1`, and `/responses` requests are still forwarded to the
249
- downstream `/v1/chat/completions` endpoint. For `model_provider = "vllm"`,
250
- reasoning is now preserved across this path: chat chunks with `reasoning` or
251
- `reasoning_content` are translated back into Responses `reasoning` items, and
274
+ downstream `/v1/chat/completions` endpoint. This local compat path always
275
+ uses the canonical Responses request shape, even when the selected model's
276
+ metadata enables `responses_lite`. With `--vllm-endpoint`, startup also reads
277
+ `/v1/models` and uses the last returned model id for the downstream request.
278
+ For `model_provider = "vllm"`, reasoning is preserved across this path:
279
+ chat chunks with `reasoning` or `reasoning_content` are translated back into
280
+ Responses `reasoning` items, and
252
281
  historical `reasoning` items are replayed into downstream assistant messages
253
282
  via the `reasoning` field. Streaming token usage is also requested from vLLM
254
283
  and forwarded to the final `response.completed.response.usage`. If a
@@ -392,6 +421,7 @@ Upstream low-frequency / special-mode tools not yet modeled separately:
392
421
 
393
422
  Repository-specific compatibility / transition tools:
394
423
 
424
+ - [x] `clock` - pycodex periodic Agent wake-up extension.
395
425
  - [x] `exec` - current local approximation of code mode.
396
426
  - [x] `wait` - current local approximation of code-mode waiting behavior.
397
427
 
@@ -406,8 +436,8 @@ Repository-specific compatibility / transition tools:
406
436
  matches upstream.
407
437
  - [x] `AGENTS.md` + `<environment_context>` injection alignment - context
408
438
  assembly order matches upstream.
409
- - [x] non-interactive `exec` tool subset alignment - the model-visible tool set
410
- has converged.
439
+ - [x] non-interactive `exec` upstream tool subset alignment - the aligned
440
+ subset has converged; pycodex additionally exposes `clock`.
411
441
  - [x] `include = ["reasoning.encrypted_content"]` - reasoning include field is
412
442
  aligned.
413
443
  - [x] `prompt_cache_key` - request-level prompt cache key is implemented.
@@ -416,8 +446,8 @@ Repository-specific compatibility / transition tools:
416
446
  - [x] `originator` - mode-aware originator header is implemented.
417
447
  - [x] exact `user-agent` string alignment - aligned on the non-interactive
418
448
  `exec` path.
419
- - [x] field-by-field exec-mode tool schema alignment - currently reuses the
420
- upstream snapshot directly through the tool layer.
449
+ - [x] field-by-field upstream exec-mode tool schema alignment - aligned tools
450
+ use class-level specs; `clock` is documented separately as an extension.
421
451
  - [ ] full interactive-mode and non-`exec` behavior alignment - the non-exec
422
452
  first-turn context is now on the `codex-tui` path, but continuous REPL
423
453
  multi-turn behavior is not fully verified yet.
@@ -1,24 +1,25 @@
1
- pycodex/__init__.py,sha256=I1P7OHkabjHP7g32A31o-L9NMd3lKYJ2xhV8AHLPYXA,3196
2
- pycodex/agent.py,sha256=HKzWVFRag_Le8LVR1qqfdfVzk23bRsXkykZV8Zq2hLE,21659
3
- pycodex/cli.py,sha256=1wTlt3ccgQqczrWBV3IjUVE0_L2QUlfPZEf6GB-tGZY,20983
1
+ pycodex/__init__.py,sha256=dzSorjXWi2XYzUU1l_zcCxO_WRb3B0a5OzMyCzQ9WC4,3266
2
+ pycodex/agent.py,sha256=sz0qAvCP52tpKlmPZc5LA1e9hY_vWWQ3tgHpvLNqojY,22968
3
+ pycodex/cli.py,sha256=XGiYLjK5s5ObvKRejuT3KQh-ua-TlmOw-BcT9K0VYF0,22809
4
4
  pycodex/collaboration.py,sha256=yQ6pBD-R3ZWR4_FAYQFoS7KF0m4LLD42otXIbPqw2ys,641
5
5
  pycodex/compat.py,sha256=l35JE0vGAOCn9NWbWbqwGURGk83HXddXQ5wJIfcG41o,3254
6
6
  pycodex/context.py,sha256=NRCJpYpEm76cAK1akid0BvQ-Z0mAvw1ZNboVxjPtm6U,26219
7
7
  pycodex/doctor.py,sha256=De3M4hRBJq8ZeqsUJgHz0vitqrH18YugrEnz7oHhTdQ,10572
8
8
  pycodex/feishu_card.py,sha256=De6pM--3MfhgGo-WcWfhm-fop5UtzjwKrZ4gI2Lls3w,26198
9
9
  pycodex/feishu_link.py,sha256=DDQCYXQXgo_2SpXPyWlK8KYcumNO1-sqFuQDsEMQpTg,16427
10
+ pycodex/image_utils.py,sha256=9TvowOPq0vTX3J4F-WLgz_Cfl7-jt3zLNM0oL3Vbhq8,2432
10
11
  pycodex/interactive_session.py,sha256=WzOD06d43ckF7SPer12JKzUPIK42Djw4kEjFRdM6_Lw,16420
11
- pycodex/model.py,sha256=2euikRqEuemeCv0gyyITCyuTdEOTK9yT50HIzd75Vtg,41808
12
+ pycodex/model.py,sha256=cgCFtOhB2GBFDiGU1OvqtBh5ngFmBhN53PphonCY5ek,42066
12
13
  pycodex/model_metadata.py,sha256=7fHntKSUXhG5eASJi4yw4jU_IyzGbYBp-WE6af19ATA,877
13
- pycodex/portable.py,sha256=gxl2E2h5uZJbasMEPPs-nyALFPIvX79T2ZYsu6vXZrg,15656
14
+ pycodex/portable.py,sha256=HdgfVwEoooh_eaZwiu4lqLXYes8ONyTnCMDqKuM8-Tc,15901
14
15
  pycodex/portable_server.py,sha256=6I3pQkWj3e_SFlDXY2mGdCPns1w_3PSxByBV9wv5epI,7331
15
16
  pycodex/protocol.py,sha256=4qiEcBQc3d3RZqsIKBjwORsHsQa78cqwvNljtnIuNbM,10795
16
- pycodex/runtime.py,sha256=dXxLkcsjufBZ8F7o4QKFXF--mkgq3cpxhyUbPPaBYy4,8684
17
+ pycodex/runtime.py,sha256=_-d0nMSFTlLK7UxMcGnjtktSCfVTxy8dCNl355j5D9Y,8758
17
18
  pycodex/runtime_services.py,sha256=6PQMI4MM8F9imijaVKBIz_0ADtoKDcdYs6vQ-c4frtQ,13931
18
19
  pycodex/prompts/collaboration_default.md,sha256=MBTmPuMubeWfZgIeFVj49wwnwD4n_o3fVYAbgWKwu6Q,955
19
20
  pycodex/prompts/collaboration_plan.md,sha256=IzjQAA5oHJz-3FmJdOjsJ4LHq6LW1tlEYMoy09n0HKk,8777
20
21
  pycodex/prompts/default_base_instructions.md,sha256=D65mcj6bo4CDvVom-D9cbJRJVNquo0NghKt164_fRsg,20923
21
- pycodex/prompts/models.json,sha256=Q3xyyqb6_c8_b3RN-0r0x2rpa-RUq5nxiCBtMdE8cZU,458204
22
+ pycodex/prompts/models.json,sha256=3J0FN5UUUj2oPBXWRR2AFo2LXB3-2PJ_5T52-F-eVh8,525055
22
23
  pycodex/prompts/permissions/approval_policy/never.md,sha256=QceTG6wjkaJARjYr0HYV1aPnPcpGcrkRUW-smWRr6MQ,120
23
24
  pycodex/prompts/permissions/approval_policy/on_failure.md,sha256=dfJjpXkpO6_ANdCKxbVJ8o4vyLxevrJWfKsGHTqtbkc,289
24
25
  pycodex/prompts/permissions/approval_policy/on_request.md,sha256=hVQalzh0FAdkKzw5u-N4H7-LtC9ijVDlYsh3OKsZKzo,3661
@@ -27,10 +28,11 @@ pycodex/prompts/permissions/approval_policy/unless_trusted.md,sha256=XHpi1Lfx1iI
27
28
  pycodex/prompts/permissions/sandbox_mode/danger_full_access.md,sha256=nZ7YHacBd3cAHKRZc9XClOOOnXJPXPh0WFBueh5C2D0,197
28
29
  pycodex/prompts/permissions/sandbox_mode/read_only.md,sha256=2rAPEXsBYCcuttI5j3euS-3uv_v97catIsnhxlSQSIM,173
29
30
  pycodex/prompts/permissions/sandbox_mode/workspace_write.md,sha256=lVN-LwrBbHqlv5yVjcd_mU8tzZW8jfKpTatJKIZu9HI,277
30
- pycodex/tools/__init__.py,sha256=aSLXrr_31KGQgDfRow5zVIc-2-KdXlHaCE6qUnE4HWI,1772
31
+ pycodex/tools/__init__.py,sha256=-d08bLB5pI0GzYKdY7OKpvj1kPk8u7xwD0_mqFoZpgY,1857
31
32
  pycodex/tools/agent_tool_schemas.py,sha256=8cL9ml4So0H6IIeK0ZzjAo9bk3VeprDOSYPpIamOqEU,2048
32
33
  pycodex/tools/apply_patch_tool.py,sha256=wLHk5sl_UkR7iv0yqMJBMLHbGkZlMmCuX9a0yTevzps,13802
33
34
  pycodex/tools/base_tool.py,sha256=0aQ69ygvSYKtDq1wHXr_j7EF3NtKIfmj0A9AbB-pD4Q,5431
35
+ pycodex/tools/clock_tool.py,sha256=lV4gtx51nGlbYyHRan-pvdOLzQWlJePA5o6xvWvApWg,5410
34
36
  pycodex/tools/close_agent_tool.py,sha256=79qr4ljPTvjnpH-Oe83yFTUGZdgNNnGoLZbpdqp4eOY,2031
35
37
  pycodex/tools/code_mode_manager.py,sha256=T8RHM8tyJvHWwS6AD2g9hsaOmvApMmlWuSCRfmDVMDE,19053
36
38
  pycodex/tools/exec_command_tool.py,sha256=KIt-pni7b2I41EMXGa1NW9YWJ1AoiWlPW_Ljlu7kLpk,4082
@@ -49,43 +51,43 @@ pycodex/tools/shell_tool.py,sha256=1m-Tcbn3His4ggyK5ec8Mkg6ihopqTvBd9F6fJlm6m4,4
49
51
  pycodex/tools/spawn_agent_tool.py,sha256=7Me4Frjz7_fTpjMMFCvNmDHPOK2G1qTfXUr2Ugs61bU,6194
50
52
  pycodex/tools/unified_exec_manager.py,sha256=YM0mKGygkprV51iAgt-A2YXJCG8Fyg3wuGGHktXuuQM,13957
51
53
  pycodex/tools/update_plan_tool.py,sha256=UsChtCBqI1RnVnPQbByPmD2LMZXMoCwfe6NpGqP8qu0,3174
52
- pycodex/tools/view_image_tool.py,sha256=2Xu5Vx7djVpz7-IV-LKrDKJjvVJc9AND-MFXp3CWplg,3892
54
+ pycodex/tools/view_image_tool.py,sha256=TbeO3vY0GlXCaonPGTut1TiJ91Co0I7V3qHuVku9aGc,3882
53
55
  pycodex/tools/wait_agent_tool.py,sha256=0Uj9-IrXe2dSvOtOMq-RAc0XzaidwFH5q7Mri3BXWyM,3135
54
56
  pycodex/tools/wait_tool.py,sha256=N6IrwzMp-fr1EohF-bxSwUlVs7KvZO2vV2csm3xCmS0,3205
55
57
  pycodex/tools/web_search_tool.py,sha256=mhiK_G6VC6K6-01KctmIpsr-BURDF8L4ZJyY6IFGlNA,964
56
58
  pycodex/tools/write_stdin_tool.py,sha256=K--XMp-AyEdp8yQ9EseYHsK3QrGrK4AgzOyvUk4swmw,3608
57
59
  pycodex/utils/__init__.py,sha256=p3jaERPxkimNDhmMIsm4glvKiazf3UMv1X-qoH5Zl3U,963
58
60
  pycodex/utils/async_bridge.py,sha256=d21Pjim-nsQbSG5pJddd0WaQ03CzA3w3TINWDmmjWbg,1815
59
- pycodex/utils/compactor.py,sha256=REmxBha4UH7EODDhd9oKl0GsVsqujHCJ_2LUKR_R1qY,6524
61
+ pycodex/utils/compactor.py,sha256=MqsJ_wcuDGPM8vndOGamLJ8j-51ivYDMlBek-Z7zZ8k,7111
60
62
  pycodex/utils/debug.py,sha256=JeEB5JfzYfbdG0fXlrWFmXyR1ts86fKsI_97IqgF6R0,296
61
63
  pycodex/utils/dotenv.py,sha256=rGKmurHjm7GdP4giyjHBPpSPv2Oi45qBqDB6HG3CnfA,1866
62
64
  pycodex/utils/get_env.py,sha256=5fNhcNhujOakWV6AS66rGW3jEA68WGpuE4YVXJZFE6U,7427
63
65
  pycodex/utils/random_ids.py,sha256=zBphjVGc7OXk9ZNExAbxRi_bk7ipyLG491qTv7hi8jM,380
64
- pycodex/utils/session_persist.py,sha256=LnyKbQQ2SyONXhZ_XF_5pUUz_ZqhWsA7dOL5pCcKsCg,18491
66
+ pycodex/utils/session_persist.py,sha256=X4SkBDWz9hIIETfZMkKrNUP4Lx7fi2agtGQtnXIypqo,20171
65
67
  pycodex/utils/toolcall_visualize.py,sha256=zIqmdsOfyYaLy_P4jpKnRxDsfTgYLRBx55R8m1P_lBE,24708
66
68
  pycodex/utils/truncation.py,sha256=B_RvfXC2-M1oKz--eQIqDLqMD0g7_J-MSQd3WD6Rh08,6110
67
- pycodex/utils/visualize.py,sha256=oTl1vqYW3nFBrdLcDyzwYfkNaPEW4OhVahJMHFSVkEg,20591
69
+ pycodex/utils/visualize.py,sha256=NfmAQdleK-yd62zJRB_oDX9SXii0wr3F3U313QlgNlU,20751
68
70
  responses_server/__init__.py,sha256=3yPv_zeGT7P11tTnmj5kXktISLNsNW-02MUnnbiZcb0,394
69
71
  responses_server/__main__.py,sha256=9SRp-Yw7ShGxc6DhSIXcDLKgGEdAVm3oBZ59rBOPjT0,62
70
72
  responses_server/app.py,sha256=ack2a0otiBwq_DpsFURqLMlQzcf9oJPwo8o6iJ1fuig,7885
71
- responses_server/config.py,sha256=leb3_uPrCyYdUIkyRyVPX4luGF88dQ62OkhRLPe7uxw,2718
72
- responses_server/messages_api.py,sha256=WgO6J1jz2pOJkI79rLXp-pS1yxtLARcwX8T6JX5Vkcc,16971
73
- responses_server/payload_processors.py,sha256=cbXGW8Xi-mliaWRg0_Af41X0vXV2W6R9VBzTE6DXfe4,3483
73
+ responses_server/config.py,sha256=9CR9gxIhNa5yAaH6u-jgJAS1EgweTejuP6EPx0vFC4I,2792
74
+ responses_server/messages_api.py,sha256=4jlLHJjoa50qy4ZnVN4NjMXu52qnIngldw2g55qx-A0,18835
75
+ responses_server/payload_processors.py,sha256=e0IqDGOtxpJElfQNvy6IiIfnjnMOOw9uaexhAdFpU1o,3546
74
76
  responses_server/server.py,sha256=Ko-Cqz_kW-uve091itucMklsPhEei77v-YcTjtjEdqU,2286
75
77
  responses_server/session_store.py,sha256=ZD3cH2aEOkWaQsu5qTzcal2mThTSFQPAhAhPUN9srgI,1115
76
- responses_server/stream_router.py,sha256=UiP-T4IKgJubD1L0AY93N3DqUh4K41fNcdONmC3Z-0A,37161
77
- responses_server/trajectory_dump.py,sha256=XCwYaZZmlAxSsSXOfhk3zRvyfDpOHX5R8KzspScNFUM,3435
78
+ responses_server/stream_router.py,sha256=3aEPqagupgP-_fiI6MC9C5IAnf3m7RAmj11NA1MAx1E,40906
79
+ responses_server/trajectory_dump.py,sha256=t9WO5h40h5MSs2bB_b6xdct84YUWQlp-5CiY_071Lhg,4006
78
80
  responses_server/tools/__init__.py,sha256=ivsBSEy0SBUhY-Uea5v1XMLXShkwHdCVl0id-1FwdZg,150
79
81
  responses_server/tools/custom_adapter.py,sha256=LxO7ldydvR-GWachDz8GKC0Q8KGGFoFPbZxM0QvxuZ0,8350
80
82
  responses_server/tools/web_search.py,sha256=pm4ZUiHUfxc0bGY1kEvt-BCzDrZIyP24xzPUcga2ul0,8908
81
83
  workspace_server/__init__.py,sha256=PRM4ONODb6hxjBUHkBD4TprE_pkTCIlT9sfftK5ic6M,866
82
84
  workspace_server/__main__.py,sha256=9SRp-Yw7ShGxc6DhSIXcDLKgGEdAVm3oBZ59rBOPjT0,62
83
- workspace_server/app.py,sha256=20lTENFBlercCzFmtAR4fIeMvqIZr8Gq_J2VotmNPck,56858
84
- workspace_server/workspace.html,sha256=xEcb5tVFM_5Ol6VFp1KzT3D95D8CBse5YyUkAxHugsM,42109
85
+ workspace_server/app.py,sha256=vRhnBzWRVRKxVrcLY2LLHssCaAw9tsVunCMUqE7697U,56797
86
+ workspace_server/workspace.html,sha256=zfnPejztriVQ3vyYSwAX5rQnUEM17fExDhMQRDuYklA,48598
85
87
  workspace_server/workspaces.html,sha256=GuYlFiOm1RJINrCKncClHt24D5i7XNABiP9C0xGxBA0,15679
86
- workspace_server/workspaces.py,sha256=Z-zoj0xgkOOP2CwbQl3vAI6TwLIDUInJKyobs0Jt3ps,19962
87
- python_codex-0.2.5.dist-info/METADATA,sha256=EbfCi71Or5nayWpWlbT5jnE1rEcmxmj-wvXxG6UOaZE,18852
88
- python_codex-0.2.5.dist-info/WHEEL,sha256=KGYbc1zXlYddvwxnNty23BeaKzh7YuoSIvIMO4jEhvw,87
89
- python_codex-0.2.5.dist-info/entry_points.txt,sha256=vkV2UWCtEKvQNMJuPNjt8HyBKiwp83JyqBatrBNGDp8,80
90
- python_codex-0.2.5.dist-info/licenses/LICENSE,sha256=0X8ifk312hYAORM4hlzg8wVSEXYKNmiPgWlB1YIy2Nw,10926
91
- python_codex-0.2.5.dist-info/RECORD,,
88
+ workspace_server/workspaces.py,sha256=4sZ7YDvsOeqCqO2azJkJD5Lw5BtQFcpHol4f7ZCWfi8,20393
89
+ python_codex-0.2.7.dist-info/METADATA,sha256=7YqrDbroneodIA_xL4ilQFjA5X4erzKTdOHGCgM2rYk,20124
90
+ python_codex-0.2.7.dist-info/WHEEL,sha256=KGYbc1zXlYddvwxnNty23BeaKzh7YuoSIvIMO4jEhvw,87
91
+ python_codex-0.2.7.dist-info/entry_points.txt,sha256=vkV2UWCtEKvQNMJuPNjt8HyBKiwp83JyqBatrBNGDp8,80
92
+ python_codex-0.2.7.dist-info/licenses/LICENSE,sha256=0X8ifk312hYAORM4hlzg8wVSEXYKNmiPgWlB1YIy2Nw,10926
93
+ python_codex-0.2.7.dist-info/RECORD,,
@@ -5,6 +5,9 @@ import urllib.parse
5
5
  import typing
6
6
 
7
7
 
8
+ DEFAULT_OUTCOMMING_TIMEOUT_SECONDS = 300.0
9
+
10
+
8
11
  @dataclass(frozen=True, )
9
12
  class CompatServerConfig:
10
13
  host: 'str' = "127.0.0.1"
@@ -13,7 +16,7 @@ class CompatServerConfig:
13
16
  outcomming_api: 'str' = "chat_completions"
14
17
  outcomming_api_key_env: 'typing.Union[str, None]' = None
15
18
  model_provider: 'typing.Union[str, None]' = None
16
- timeout_seconds: 'float' = 120.0
19
+ timeout_seconds: 'float' = DEFAULT_OUTCOMMING_TIMEOUT_SECONDS
17
20
 
18
21
  def outcomming_api_key(self) -> 'typing.Union[str, None]':
19
22
  if self.outcomming_api_key_env is None:
@@ -193,12 +193,61 @@ def saw_message_stop(state: 'typing.Dict[str, object]') -> 'bool':
193
193
 
194
194
 
195
195
  def _build_text_blocks(raw_content: 'object') -> 'typing.List[typing.Dict[str, object]]':
196
+ if isinstance(raw_content, list):
197
+ blocks: 'typing.List[typing.Dict[str, object]]' = []
198
+ for raw_part in raw_content:
199
+ if not isinstance(raw_part, dict):
200
+ raise MessagesAPIAdapterError("message content parts must be objects")
201
+ part_type = str(raw_part.get("type", "")).strip()
202
+ if part_type == "text":
203
+ text = str(raw_part.get("text", "") or "")
204
+ if text:
205
+ blocks.append({"type": "text", "text": text})
206
+ continue
207
+ if part_type == "image_url":
208
+ blocks.append(_build_image_block(raw_part))
209
+ continue
210
+ raise MessagesAPIAdapterError(
211
+ f"unsupported outcomming content part type for messages API: {part_type!r}"
212
+ )
213
+ return blocks
214
+
196
215
  text = str(raw_content or "")
197
216
  if not text:
198
217
  return []
199
218
  return [{"type": "text", "text": text}]
200
219
 
201
220
 
221
+ def _build_image_block(
222
+ raw_part: 'typing.Dict[str, object]',
223
+ ) -> 'typing.Dict[str, object]':
224
+ image_url = raw_part.get("image_url") or {}
225
+ if not isinstance(image_url, dict):
226
+ raise MessagesAPIAdapterError("`image_url` content parts must be objects")
227
+ url = str(image_url.get("url", "") or "").strip()
228
+ if not url:
229
+ raise MessagesAPIAdapterError(
230
+ "`image_url` content parts must carry a non-empty `url`"
231
+ )
232
+ if not url.startswith("data:"):
233
+ return {"type": "image", "source": {"type": "url", "url": url}}
234
+
235
+ header, _, data = url.partition(",")
236
+ media_type = header[len("data:"):].split(";")[0].strip()
237
+ if not media_type or not header.endswith(";base64"):
238
+ raise MessagesAPIAdapterError(
239
+ "`image_url` data URLs must be base64 encoded with a media type"
240
+ )
241
+ return {
242
+ "type": "image",
243
+ "source": {
244
+ "type": "base64",
245
+ "media_type": media_type,
246
+ "data": data,
247
+ },
248
+ }
249
+
250
+
202
251
  def _build_assistant_blocks(
203
252
  raw_message: 'typing.Dict[str, object]',
204
253
  ) -> 'typing.List[typing.Dict[str, object]]':
@@ -28,6 +28,7 @@ class OutgoingRequest(TypedDict):
28
28
  model: 'str'
29
29
  messages: 'typing.List[ChatMessage]'
30
30
  stream: 'bool'
31
+ chat_template_kwargs: 'Optional[typing.Dict[str, object]]'
31
32
  max_tokens: 'Optional[int]'
32
33
  tools: 'Optional[typing.List[typing.Dict[str, object]]]'
33
34
  tool_choice: 'Optional[object]'