python-codex 0.1.0__py3-none-any.whl → 0.1.1__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.
@@ -0,0 +1,205 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import hashlib
5
+ import json
6
+ import threading
7
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
8
+ from pathlib import Path
9
+ from urllib.parse import unquote, urlparse
10
+
11
+ from .portable import (
12
+ DEFAULT_STORAGE_SERVER,
13
+ HEALTHCHECK_PATH,
14
+ STORAGE_API_PREFIX,
15
+ _call_id_from_payload,
16
+ )
17
+
18
+
19
+ class CodexStorageServer:
20
+ def __init__(
21
+ self,
22
+ root: str | Path,
23
+ host: str = "127.0.0.1",
24
+ port: int = 5577,
25
+ ) -> None:
26
+ self._root = Path(root).resolve()
27
+ self._root.mkdir(parents=True, exist_ok=True)
28
+ self._objects_dir = self._root / "objects"
29
+ self._objects_dir.mkdir(parents=True, exist_ok=True)
30
+ self._server = ThreadingHTTPServer((host, port), self._build_handler())
31
+ self._thread: threading.Thread | None = None
32
+
33
+ @property
34
+ def host(self) -> str:
35
+ return str(self._server.server_address[0])
36
+
37
+ @property
38
+ def port(self) -> int:
39
+ return int(self._server.server_address[1])
40
+
41
+ @property
42
+ def server_address(self) -> str:
43
+ return f"{self.host}:{self.port}"
44
+
45
+ @property
46
+ def base_url(self) -> str:
47
+ return f"http://{self.server_address}{STORAGE_API_PREFIX}"
48
+
49
+ @property
50
+ def root(self) -> Path:
51
+ return self._root
52
+
53
+ def start(self) -> None:
54
+ if self._thread is not None:
55
+ return
56
+ self._thread = threading.Thread(
57
+ target=self._server.serve_forever,
58
+ name="pycodex-storage-server",
59
+ daemon=True,
60
+ )
61
+ self._thread.start()
62
+
63
+ def stop(self) -> None:
64
+ self._server.shutdown()
65
+ self._server.server_close()
66
+ if self._thread is not None:
67
+ self._thread.join(timeout=5.0)
68
+ self._thread = None
69
+
70
+ def _build_handler(self):
71
+ server = self
72
+
73
+ class Handler(BaseHTTPRequestHandler):
74
+ def do_GET(self) -> None: # noqa: N802
75
+ path = urlparse(self.path).path
76
+ if path == HEALTHCHECK_PATH:
77
+ self._send_json(200, {"ok": True})
78
+ return
79
+ if not path.startswith(f"{STORAGE_API_PREFIX}/call/"):
80
+ self._send_json(404, {"error": "not found"})
81
+ return
82
+ call_id = unquote(path[len(f"{STORAGE_API_PREFIX}/call/") :]).strip()
83
+ if not call_id:
84
+ self._send_json(400, {"error": "missing call_id"})
85
+ return
86
+ object_path = server._object_path(call_id)
87
+ if not object_path.is_file():
88
+ self._send_json(404, {"error": "not found"})
89
+ return
90
+ payload = object_path.read_bytes()
91
+ print(
92
+ "[server] call: "
93
+ f"client={self.client_address[0]} call_id={call_id} path={object_path}",
94
+ flush=True,
95
+ )
96
+ self.send_response(200)
97
+ self.send_header("Content-Type", "application/octet-stream")
98
+ self.send_header("Content-Length", str(len(payload)))
99
+ self.send_header("X-Pycodex-Sha256", hashlib.sha256(payload).hexdigest())
100
+ self.send_header("X-Pycodex-Call-Id", call_id)
101
+ self.end_headers()
102
+ self.wfile.write(payload)
103
+
104
+ def do_POST(self) -> None: # noqa: N802
105
+ path = urlparse(self.path).path
106
+ if path != f"{STORAGE_API_PREFIX}/put":
107
+ self._send_json(404, {"error": "not found"})
108
+ return
109
+ content_length = int(self.headers.get("Content-Length", "0") or "0")
110
+ if content_length <= 0:
111
+ self._send_json(400, {"error": "empty body"})
112
+ return
113
+ payload = self.rfile.read(content_length)
114
+ sha256 = hashlib.sha256(payload).hexdigest()
115
+ expected_sha256 = self.headers.get("X-Pycodex-Sha256", "").strip().lower()
116
+ if expected_sha256 and expected_sha256 != sha256:
117
+ self._send_json(400, {"error": "checksum mismatch"})
118
+ return
119
+ call_id = _call_id_from_payload(payload)
120
+ object_path = server._object_path(call_id)
121
+ if not object_path.is_file():
122
+ object_path.write_bytes(payload)
123
+ status = "stored"
124
+ else:
125
+ status = "reused"
126
+ print(
127
+ "[server] put: "
128
+ f"client={self.client_address[0]} "
129
+ f"call_id={call_id} status={status} path={object_path}",
130
+ flush=True,
131
+ )
132
+ host_header = self.headers.get("Host", server.server_address).strip() or server.server_address
133
+ self._send_json(
134
+ 200,
135
+ {
136
+ "call_id": call_id,
137
+ "call": f"{call_id}@{host_header}",
138
+ },
139
+ )
140
+
141
+ def log_message(self, _format: str, *_args) -> None:
142
+ return
143
+
144
+ def _send_json(self, status: int, payload: dict[str, object]) -> None:
145
+ body = json.dumps(payload).encode("utf-8")
146
+ self.send_response(status)
147
+ self.send_header("Content-Type", "application/json")
148
+ self.send_header("Content-Length", str(len(body)))
149
+ self.end_headers()
150
+ self.wfile.write(body)
151
+
152
+ return Handler
153
+
154
+ def _object_path(self, call_id: str) -> Path:
155
+ return self._objects_dir / f"{call_id}.bin"
156
+
157
+
158
+ def build_parser() -> argparse.ArgumentParser:
159
+ parser = argparse.ArgumentParser(
160
+ prog="python -m pycodex.portable_server",
161
+ description="Run a pycodex remote storage service for --put/--call testing.",
162
+ )
163
+ parser.add_argument(
164
+ "--root",
165
+ default=str(Path(".tmp") / "pycodex_storage"),
166
+ help="Directory used to store uploaded encrypted bundles.",
167
+ )
168
+ parser.add_argument(
169
+ "--host",
170
+ default=DEFAULT_STORAGE_SERVER.split(":", 1)[0],
171
+ help="Host interface to bind.",
172
+ )
173
+ parser.add_argument(
174
+ "--port",
175
+ type=int,
176
+ default=int(DEFAULT_STORAGE_SERVER.split(":", 1)[1]),
177
+ help="Port to bind.",
178
+ )
179
+ return parser
180
+
181
+
182
+ def main(argv: list[str] | None = None) -> int:
183
+ parser = build_parser()
184
+ args = parser.parse_args(argv)
185
+ server = CodexStorageServer(args.root, host=args.host, port=args.port)
186
+ server.start()
187
+ print(f"storage server listening on {server.base_url}", flush=True)
188
+ print(f"storage root: {server.root}", flush=True)
189
+ print(f"put current home: pycodex --put @{server.server_address}", flush=True)
190
+ print(
191
+ f"put custom home: pycodex --put /data/.codex/@{server.server_address}",
192
+ flush=True,
193
+ )
194
+ try:
195
+ if server._thread is not None:
196
+ server._thread.join()
197
+ except KeyboardInterrupt:
198
+ return 130
199
+ finally:
200
+ server.stop()
201
+ return 0
202
+
203
+
204
+ if __name__ == "__main__":
205
+ raise SystemExit(main())
pycodex/runtime.py CHANGED
@@ -3,12 +3,15 @@ from __future__ import annotations
3
3
  import asyncio
4
4
  from collections import deque
5
5
  from dataclasses import dataclass
6
- from typing import Literal
6
+ from typing import TYPE_CHECKING, Literal
7
7
 
8
8
  from .agent import AgentLoop, EventHandler, NOOP_EVENT_HANDLER, TurnInterrupted
9
9
  from .protocol import AgentEvent, Operation, ShutdownOp, Submission, TurnResult, UserTurnOp
10
10
  from .utils import uuid7_string
11
11
 
12
+ if TYPE_CHECKING:
13
+ from .runtime_services import RuntimeEnvironment
14
+
12
15
 
13
16
  @dataclass(slots=True)
14
17
  class _QueuedSubmission:
@@ -20,8 +23,9 @@ class _QueuedSubmission:
20
23
  class AgentRuntime:
21
24
  """Thin outer queue that mirrors the Rust `submission_loop` shape."""
22
25
 
23
- def __init__(self, agent_loop: AgentLoop) -> None:
26
+ def __init__(self, agent_loop: AgentLoop, runtime_environment: RuntimeEnvironment | None = None) -> None:
24
27
  self._agent_loop = agent_loop
28
+ self.runtime_environment = runtime_environment
25
29
  self._enqueue_queue: deque[_QueuedSubmission] = deque()
26
30
  self._steer_queue: deque[_QueuedSubmission] = deque()
27
31
  self._queue_lock = asyncio.Lock()
@@ -397,11 +397,12 @@ class RuntimeEnvironment:
397
397
  self.request_user_input_manager = RequestUserInputManager()
398
398
  self.request_permissions_manager = RequestPermissionsManager()
399
399
 
400
- def configure_runtime_builder(self, builder: RuntimeBuilder | None) -> None:
401
- self.subagent_manager.set_runtime_builder(builder)
402
400
 
401
+ def create_runtime_environment() -> RuntimeEnvironment:
402
+ return RuntimeEnvironment()
403
403
 
404
- _RUNTIME_ENV = RuntimeEnvironment()
404
+
405
+ _RUNTIME_ENV = create_runtime_environment()
405
406
 
406
407
 
407
408
  def get_runtime_environment() -> RuntimeEnvironment:
@@ -0,0 +1,355 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-codex
3
+ Version: 0.1.1
4
+ Summary: A minimal Python extraction of Codex's main agent loop
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: cryptography>=3.4
8
+ Requires-Dist: fastapi>=0.115
9
+ Requires-Dist: loguru>=0.7.3
10
+ Requires-Dist: prompt-toolkit>=3.0
11
+ Requires-Dist: requests>=2.31
12
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
13
+ Requires-Dist: uvicorn>=0.32
14
+ Description-Content-Type: text/markdown
15
+
16
+ # pycodex
17
+
18
+ English README. Chinese version: `README_ZH.md`
19
+
20
+ PyPI distribution name: `python-codex`
21
+ Import path and CLI command remain `pycodex`.
22
+
23
+ This repository extracts the core Codex agent loop from upstream Codex
24
+ (`https://github.com/openai/codex`) into a deliberately small Python version,
25
+ while preserving the two most important layers:
26
+
27
+ - `submission_loop`: sequentially consumes submitted operations.
28
+ - `run_turn`: keeps executing `model sample -> tool call -> feed tool result
29
+ back into the model` inside a single turn until a final answer is reached.
30
+
31
+ Relevant Rust reference points:
32
+
33
+ - `codex-rs/core/src/codex.rs` -> `submission_loop`
34
+ - `codex-rs/core/src/codex.rs` -> `run_turn`
35
+ - `codex-rs/core/src/codex.rs` -> `run_sampling_request`
36
+ - `codex-rs/core/src/tools/router.rs` -> `ToolRouter`
37
+ - `codex-rs/core/src/stream_events_utils.rs` -> `handle_output_item_done`
38
+
39
+ ## Quick Start
40
+
41
+ Install dependencies first:
42
+
43
+ ```bash
44
+ uv sync
45
+ ```
46
+
47
+ Try the real entry points:
48
+
49
+ ```bash
50
+ uv run pycodex "Reply with exactly OK."
51
+ uv run pycodex
52
+ ```
53
+
54
+ ## Design Tradeoffs
55
+
56
+ This is not a 1:1 port of the Rust implementation. The current goal is a
57
+ minimal reusable kernel that converges on the upstream behavior over time:
58
+
59
+ 1. Use a thin `ModelClient` protocol to abstract the model side.
60
+ 2. Use `ToolRegistry` to manage tool specs and executors.
61
+ 3. Use `AgentLoop` to implement the core closed loop.
62
+ 4. Use `AgentRuntime` to preserve the outer submission queue so it can keep
63
+ converging toward Rust's `submission_loop` later.
64
+
65
+ Intentionally not included yet:
66
+
67
+ - TUI / streaming incremental rendering
68
+ - MCP / connectors / sandbox / approvals
69
+ - memory / compact / hooks / review mode
70
+ - a full production OpenAI adapter surface
71
+
72
+ All of those can be layered on later. For now, the project is focused on
73
+ nailing the core tool-augmented reasoning loop first.
74
+
75
+ ## Layout
76
+
77
+ - `pycodex/protocol.py`: minimal conversation item / prompt / event protocol
78
+ - `pycodex/model.py`: model client protocol and Responses API adapter
79
+ - `pycodex/cli.py`: single-turn and interactive `pycodex` CLI entry points
80
+ - `pycodex/tools/base_tool.py`: `BaseTool`, `ToolRegistry`, `ToolContext`
81
+ - `pycodex/tools/`: concrete tool implementations
82
+ - `pycodex/agent.py`: inner turn loop
83
+ - `pycodex/runtime.py`: outer submission queue
84
+ - `tests/test_agent.py`: core behavior tests
85
+
86
+ ## Current Alignment Status
87
+
88
+ Current progress is easiest to read in layers:
89
+
90
+ - prompt/context alignment:
91
+ - on the non-interactive `exec` path, `instructions` and `input` already
92
+ match upstream Codex;
93
+ - this layer is now mainly handled by `pycodex/context.py` plus vendored
94
+ prompt data.
95
+ - turn-loop semantic alignment:
96
+ - `AgentLoop` no longer uses a fixed 12-iteration cap by default;
97
+ - like upstream, it now converges naturally based on whether there is still
98
+ follow-up work or tool handoff to do;
99
+ - the local iteration-limit parameter is gone.
100
+ - request-level alignment:
101
+ - the non-interactive `exec` request body is mostly aligned;
102
+ - the default CLI non-exec first request now also follows the upstream
103
+ `codex-tui` + `<collaboration_mode>` path;
104
+ - the default CLI two-turn main-thread request/header behavior has also been
105
+ captured and aligned, including omitting `workspaces` on later turns;
106
+ - the remaining work is now more about outer behavior branches than this
107
+ already-compared request/header path.
108
+ - tool round-trip alignment:
109
+ - the Default-mode unavailable path for `request_user_input` is aligned to
110
+ real upstream captures;
111
+ - the Plan-mode happy path is also aligned at the tool/protocol layer based
112
+ on upstream source: it forces `isOther=true`, requires non-empty `options`,
113
+ and returns structured answers as a JSON string plus `success=true`;
114
+ - there is now a deterministic round-trip comparison helper,
115
+ `tests/compare_request_user_input_roundtrip.py`, built on the proxy mode in
116
+ `tests/fake_responses_server.py`; against the locally installed
117
+ `codex-cli 0.115.0`, the only remaining Plan-mode live-capture schema
118
+ difference is that `pycodex` includes `success=true` in
119
+ `function_call_output`.
120
+
121
+ See `docs/ALIGNMENT.md` for more detailed notes.
122
+
123
+ ## Live Model Integration
124
+
125
+ If this machine already has a Codex CLI configuration, `pycodex` can reuse the
126
+ `model`, `model_provider`, `base_url`, and `env_key` from
127
+ `~/.codex/config.toml` directly:
128
+
129
+ ```python
130
+ from pycodex import ResponsesModelClient
131
+
132
+ client = ResponsesModelClient.from_codex_config()
133
+ ```
134
+
135
+ The current implementation uses the streaming OpenAI-compatible `/responses`
136
+ endpoint. This path has already been validated against the local
137
+ `~/.codex/config.toml` setup.
138
+
139
+ When launched through the CLI, `pycodex` also loads `.env` from the same
140
+ configuration directory before reading config (typically `~/.codex/.env`), so
141
+ provider keys and similar environment variables can live there. To match
142
+ upstream Codex, variables starting with `CODEX_` are not imported from `.env`.
143
+
144
+ ## pycodex CLI
145
+
146
+ `pycodex` now defaults to a minimal interactive entry point. Internally it uses
147
+ `AgentRuntime` to drive the turn submission loop and reuses
148
+ `~/.codex/config.toml` by default:
149
+
150
+ ```bash
151
+ pycodex
152
+ pycodex "Summarize this repo in one sentence."
153
+ printf 'Reply with exactly OK.' | pycodex
154
+ pycodex --json "Reply with exactly OK."
155
+ pycodex --profile model_proxy "Reply with exactly OK."
156
+ pycodex --vllm-endpoint http://127.0.0.1:18000 "Reply with exactly OK."
157
+ pycodex --put @127.0.0.1:5577
158
+ pycodex --put /data/.codex/@127.0.0.1:5577
159
+ pycodex --call SECRET-CALLID@127.0.0.1:5577 "Reply with exactly OK."
160
+ pycodex doctor
161
+ ```
162
+
163
+ Current behavior:
164
+
165
+ - with no argv prompt and a TTY stdin, enter interactive mode
166
+ - with an argv prompt or piped stdin, run a single turn
167
+ - interactive mode supports `/exit` and `/quit`
168
+ - interactive mode shows a compact event stream for user-visible phases such as
169
+ tool execution and model follow-up after tool results
170
+ - assistant text is printed from streaming deltas directly
171
+ - interactive mode supports `/history`, `/title`, and `/model`
172
+ - `/model <name>` switches the model used by later turns in the current
173
+ interactive session; `/model` shows the current model and available choices
174
+ - steer is enabled by default in interactive mode: normal input goes into the
175
+ runtime steer path, the current request stops at the next safe boundary, and
176
+ later steer text is appended to the next model request's `input` in order;
177
+ for explicit queueing, use `/queue <message>`, which prints
178
+ `[steer] queued: ...` and later `[steer] inserted: ...`
179
+ - the default built-in tool subset currently exposed as local tools is:
180
+ `shell`, `shell_command`, `exec_command`, `write_stdin`, `exec`, `wait`,
181
+ `web_search`, `update_plan`, `request_user_input`, `request_permissions`,
182
+ `spawn_agent`, `send_input`, `resume_agent`, `wait_agent`, `close_agent`,
183
+ `apply_patch`, `grep_files`, `read_file`, `list_dir`, `view_image`
184
+ - `--vllm-endpoint http://host:port` automatically launches a local
185
+ `responses_server` compatibility layer; when the URL path is empty it is
186
+ normalized to `/v1`, and `/responses` requests are still forwarded to the
187
+ downstream `/v1/chat/completions` endpoint. For `model_provider = "vllm"`,
188
+ reasoning is now preserved across this path: chat chunks with `reasoning` or
189
+ `reasoning_content` are translated back into Responses `reasoning` items, and
190
+ historical `reasoning` items are replayed into downstream assistant messages
191
+ via the `reasoning` field. Streaming token usage is also requested from vLLM
192
+ and forwarded to the final `response.completed.response.usage`
193
+ - `pycodex doctor` checks config, `.env`, API keys, DNS, TCP/TLS, and an
194
+ optional live Responses API request
195
+
196
+ Current primary uses:
197
+
198
+ - verify provider / model / auth configuration
199
+ - debug `ResponsesModelClient`
200
+ - run minimal single-turn and multi-turn smoke tests
201
+
202
+ `doctor` examples:
203
+
204
+ ```bash
205
+ pycodex doctor
206
+ pycodex doctor --skip-live
207
+ pycodex doctor --json
208
+ ```
209
+
210
+ ## Portable Mode
211
+
212
+ `Portable Mode` is the quickest way to bring your usual `pycodex` setup into a
213
+ fresh machine, container, or debug image.
214
+
215
+ Use it like this:
216
+
217
+ ```bash
218
+ pycodex --put @127.0.0.1:5577
219
+ pycodex --put /data/.codex/@127.0.0.1:5577
220
+ ```
221
+
222
+ - `--put` prints a reusable `SECRET-CALLID@host:port` plus a final one-line
223
+ `pycodex --call ...` command
224
+ - on the new environment or image, run that printed `--call` command directly
225
+ - quickly restoring your usual `config.toml`, `.env`, `AGENTS.md`, and
226
+ `skills/` into a clean debug environment
227
+ - keeping a new image focused on the bug you are debugging instead of spending
228
+ time rebuilding local Codex setup by hand
229
+ - bootstrapping `pycodex` even when the target environment does not already
230
+ have a populated `~/.codex`
231
+ - bare `--put` uses the current user's `~/.codex`
232
+ - `--put /path/.codex/@host:port` lets you publish a different Codex home
233
+
234
+ ## Example
235
+
236
+ ```python
237
+ import asyncio
238
+
239
+ from pycodex import (
240
+ AgentLoop,
241
+ BaseTool,
242
+ ContextManager,
243
+ ResponsesModelClient,
244
+ ToolRegistry,
245
+ )
246
+
247
+
248
+ class EchoTool(BaseTool):
249
+ name = "echo"
250
+ description = "Echo the provided text."
251
+ input_schema = {
252
+ "type": "object",
253
+ "properties": {"text": {"type": "string"}},
254
+ "required": ["text"],
255
+ }
256
+
257
+ async def run(self, context, args):
258
+ del context
259
+ return args["text"]
260
+
261
+
262
+ async def main() -> None:
263
+ model = ResponsesModelClient.from_codex_config()
264
+ context_manager = ContextManager.from_codex_config()
265
+
266
+ tools = ToolRegistry()
267
+ tools.register(EchoTool())
268
+
269
+ agent = AgentLoop(model, tools, context_manager)
270
+ result = await agent.run_turn(
271
+ ["Call the echo tool with text=hello, then tell me what it returned."]
272
+ )
273
+ print(result.output_text)
274
+
275
+
276
+ asyncio.run(main())
277
+ ```
278
+
279
+ ## Alignment Checklist
280
+
281
+ See `docs/ALIGNMENT.md` for more detail. This section keeps a high-level
282
+ checklist for quick status scanning.
283
+
284
+ ### Tool Alignment
285
+
286
+ Official upstream tools:
287
+
288
+ - [x] `shell` - run shell commands in argv form.
289
+ - [x] `shell_command` - run shell scripts in string form.
290
+ - [x] `exec_command` - start long-running commands with a session.
291
+ - [x] `write_stdin` - write stdin to an existing execution session or poll
292
+ output.
293
+ - [x] `web_search` - expose provider-native web search capability.
294
+ - [x] `update_plan` - update the task plan and maintain step status.
295
+ - [x] `request_user_input` - ask the user structured questions and wait for an
296
+ answer.
297
+ - [x] `request_permissions` - request extra permissions before continuing.
298
+ - [x] `spawn_agent` - create and start a sub-agent.
299
+ - [x] `send_input` - continue feeding input to an existing sub-agent.
300
+ - [x] `resume_agent` - reopen a closed sub-agent.
301
+ - [x] `wait_agent` - wait for a sub-agent to reach a terminal state.
302
+ - [x] `close_agent` - close a sub-agent that is no longer needed.
303
+ - [x] `apply_patch` - edit files precisely with a freeform patch.
304
+ - [x] `grep_files` - search file contents by pattern.
305
+ - [x] `read_file` - read file slices while preserving line-number semantics.
306
+ - [x] `list_dir` - list directory tree slices.
307
+ - [x] `view_image` - turn a local image into model-visible input.
308
+
309
+ Upstream low-frequency / special-mode tools not yet modeled separately:
310
+
311
+ - [ ] `wait_infinite` - long blocking wait for external events or later input.
312
+ - [ ] `spawn_agents_on_csv` - create sub-agent jobs in bulk from CSV.
313
+ - [ ] `report_agent_job_result` - report batch agent job results.
314
+ - [ ] `js_repl` - JavaScript REPL / code-mode primary entry point.
315
+ - [ ] `js_repl_reset` - reset `js_repl` state.
316
+ - [ ] `artifacts` - generate or manage structured artifact outputs.
317
+ - [ ] `list_mcp_resources` - list MCP resources.
318
+ - [ ] `list_mcp_resource_templates` - list MCP resource templates.
319
+ - [ ] `read_mcp_resource` - read MCP resource contents.
320
+ - [ ] `multi_tool_use.parallel` - parallel wrapper around multiple developer
321
+ tool calls.
322
+
323
+ Repository-specific compatibility / transition tools:
324
+
325
+ - [x] `exec` - current local approximation of code mode.
326
+ - [x] `wait` - current local approximation of code-mode waiting behavior.
327
+
328
+ ### Behavior Alignment
329
+
330
+ - [x] `AgentLoop` / `AgentRuntime` main loop skeleton - turn loop and submission
331
+ queue are in place.
332
+ - [x] non-interactive `exec` `instructions` alignment - base instructions match
333
+ upstream.
334
+ - [x] non-interactive `exec` `input` alignment - prompt input matches upstream.
335
+ - [x] developer/contextual-user message shape alignment - message/content shape
336
+ matches upstream.
337
+ - [x] `AGENTS.md` + `<environment_context>` injection alignment - context
338
+ assembly order matches upstream.
339
+ - [x] non-interactive `exec` tool subset alignment - the model-visible tool set
340
+ has converged.
341
+ - [x] `include = ["reasoning.encrypted_content"]` - reasoning include field is
342
+ aligned.
343
+ - [x] `prompt_cache_key` - request-level prompt cache key is implemented.
344
+ - [x] `x-client-request-id` - request id header is implemented.
345
+ - [x] `x-codex-turn-metadata` - turn id / sandbox header is implemented.
346
+ - [x] `originator` - mode-aware originator header is implemented.
347
+ - [x] exact `user-agent` string alignment - aligned on the non-interactive
348
+ `exec` path.
349
+ - [x] field-by-field exec-mode tool schema alignment - currently reuses the
350
+ upstream snapshot directly through the tool layer.
351
+ - [ ] full interactive-mode and non-`exec` behavior alignment - the non-exec
352
+ first-turn context is now on the `codex-tui` path, but continuous REPL
353
+ multi-turn behavior is not fully verified yet.
354
+ - [ ] sandbox / approvals / compact / memory and other outer behavior alignment
355
+ - these systems are still in later scope.
@@ -1,13 +1,15 @@
1
- pycodex/__init__.py,sha256=Cjeqgi3mmkmxE_xcTcPl4heS5k4UGBCG3qFGbwNm7PQ,2986
1
+ pycodex/__init__.py,sha256=T11JU1QHEk81TchhrTAOqVkvUUiQGlesk9PNaivjPrU,3052
2
2
  pycodex/agent.py,sha256=ApIneWSqDxryf9hdmTRFL65AH4e-sn0MWuuR80951Ec,10069
3
- pycodex/cli.py,sha256=d3ug7fR9GeMDDe_BR37RBeUW6w5QnaFRTkGiNgvAkqA,21748
3
+ pycodex/cli.py,sha256=JKHedrIgFaQgAc9h3P1MHvJTvUjL5dv7gxgHh-LCsDs,24520
4
4
  pycodex/collaboration.py,sha256=XAM2enljzHMjzZVlLxbOQF0JhWgKW4qaaDfVcUdE47g,632
5
5
  pycodex/context.py,sha256=8-Eg1TE4-GVbEfW0fNZjDWhjLypK3jBlKZY1haYYVPY,23143
6
6
  pycodex/doctor.py,sha256=VN-qetM2qJCNRNTZXBMe44VSrEOu8kUXE01luLMF050,10357
7
7
  pycodex/model.py,sha256=ZqXSucpzBm0kn2XfhBdKebdwvJQH1Jc9xMqBfPwOKGM,19672
8
+ pycodex/portable.py,sha256=Y2pY08pDiWITY0QYgH3F9YKpOe2EYtxE0qqSmrCkp_g,15260
9
+ pycodex/portable_server.py,sha256=xhEwySCJ41WnsowXM-Db6kkmCOVM02Lmd4pbN6hZzh0,7232
8
10
  pycodex/protocol.py,sha256=8mQ7I-y9bxYueSr7d_yGj2Tw69t47OCgwvmxhwihdFw,10807
9
- pycodex/runtime.py,sha256=Symum2pt6QibV_a3oWm9KWku50u22tLrmXQu76hJSH0,7560
10
- pycodex/runtime_services.py,sha256=Ir2gM7Qf-uSy9JPc8ahL85ifkY5JPdX09y-iHS_qYo8,12374
11
+ pycodex/runtime.py,sha256=tfEuyZmnTP625BQ0NMm-AGhjfQpXcv2EaZLtCJTnEmM,7757
12
+ pycodex/runtime_services.py,sha256=IIpv96YuxdWX2D1yu-HmtCx3Og-fYDPrA29vgAlyvJE,12331
11
13
  pycodex/prompts/collaboration_default.md,sha256=MBTmPuMubeWfZgIeFVj49wwnwD4n_o3fVYAbgWKwu6Q,955
12
14
  pycodex/prompts/collaboration_plan.md,sha256=IzjQAA5oHJz-3FmJdOjsJ4LHq6LW1tlEYMoy09n0HKk,8777
13
15
  pycodex/prompts/default_base_instructions.md,sha256=D65mcj6bo4CDvVom-D9cbJRJVNquo0NghKt164_fRsg,20923
@@ -53,8 +55,8 @@ pycodex/utils/dotenv.py,sha256=sOpu6PA1VrsPZK13ynh3nZg3-u9pdiCXkW648v3pwZQ,1789
53
55
  pycodex/utils/get_env.py,sha256=3l_KA8JCWW9mrKE9FiV2mTx10-e5MUbxaU8jbn3JaRs,6265
54
56
  pycodex/utils/random_ids.py,sha256=vOEVgkwKeQXaHoEVU7IfsPPjKUABkGIeQ7lu9MZctU8,413
55
57
  pycodex/utils/visualize.py,sha256=fK79pTfOwMmRrQujAosGt0nGyyJjpz0GfpWY8BkK91c,35369
56
- python_codex-0.1.0.dist-info/METADATA,sha256=27msS6W8ibM1JsRSE7zMXpVBAvMxdFkBIPlehjwjtAc,11720
57
- python_codex-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
58
- python_codex-0.1.0.dist-info/entry_points.txt,sha256=sNUVakoVuTrzJH505ZgRTQxmtRRPUHV_EH0i6EbYTyM,45
59
- python_codex-0.1.0.dist-info/licenses/LICENSE,sha256=0X8ifk312hYAORM4hlzg8wVSEXYKNmiPgWlB1YIy2Nw,10926
60
- python_codex-0.1.0.dist-info/RECORD,,
58
+ python_codex-0.1.1.dist-info/METADATA,sha256=3w9Sv_prpkT9BQ7zYh4NUCDV3eVlcoaq2Pv6yAkacOc,13969
59
+ python_codex-0.1.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
60
+ python_codex-0.1.1.dist-info/entry_points.txt,sha256=sNUVakoVuTrzJH505ZgRTQxmtRRPUHV_EH0i6EbYTyM,45
61
+ python_codex-0.1.1.dist-info/licenses/LICENSE,sha256=0X8ifk312hYAORM4hlzg8wVSEXYKNmiPgWlB1YIy2Nw,10926
62
+ python_codex-0.1.1.dist-info/RECORD,,