python-codex 0.1.2__py3-none-any.whl → 0.1.3__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.
Files changed (56) hide show
  1. pycodex/__init__.py +5 -1
  2. pycodex/agent.py +39 -41
  3. pycodex/cli.py +43 -42
  4. pycodex/collaboration.py +6 -7
  5. pycodex/compat.py +99 -0
  6. pycodex/context.py +87 -87
  7. pycodex/doctor.py +40 -40
  8. pycodex/model.py +69 -69
  9. pycodex/portable.py +33 -33
  10. pycodex/portable_server.py +22 -21
  11. pycodex/protocol.py +84 -86
  12. pycodex/runtime.py +36 -35
  13. pycodex/runtime_services.py +69 -69
  14. pycodex/tools/agent_tool_schemas.py +0 -2
  15. pycodex/tools/apply_patch_tool.py +43 -44
  16. pycodex/tools/base_tool.py +35 -36
  17. pycodex/tools/close_agent_tool.py +2 -4
  18. pycodex/tools/code_mode_manager.py +61 -61
  19. pycodex/tools/exec_command_tool.py +5 -6
  20. pycodex/tools/exec_runtime.js +3 -3
  21. pycodex/tools/exec_tool.py +2 -4
  22. pycodex/tools/grep_files_tool.py +10 -11
  23. pycodex/tools/list_dir_tool.py +8 -9
  24. pycodex/tools/read_file_tool.py +13 -14
  25. pycodex/tools/request_permissions_tool.py +2 -4
  26. pycodex/tools/request_user_input_tool.py +13 -14
  27. pycodex/tools/resume_agent_tool.py +2 -4
  28. pycodex/tools/send_input_tool.py +8 -9
  29. pycodex/tools/shell_command_tool.py +5 -6
  30. pycodex/tools/shell_tool.py +5 -6
  31. pycodex/tools/spawn_agent_tool.py +4 -5
  32. pycodex/tools/unified_exec_manager.py +62 -61
  33. pycodex/tools/update_plan_tool.py +4 -5
  34. pycodex/tools/view_image_tool.py +4 -5
  35. pycodex/tools/wait_agent_tool.py +2 -4
  36. pycodex/tools/wait_tool.py +4 -5
  37. pycodex/tools/web_search_tool.py +1 -3
  38. pycodex/tools/write_stdin_tool.py +4 -5
  39. pycodex/utils/dotenv.py +6 -6
  40. pycodex/utils/get_env.py +37 -33
  41. pycodex/utils/random_ids.py +1 -2
  42. pycodex/utils/visualize.py +79 -79
  43. {python_codex-0.1.2.dist-info → python_codex-0.1.3.dist-info}/METADATA +15 -9
  44. python_codex-0.1.3.dist-info/RECORD +74 -0
  45. {python_codex-0.1.2.dist-info → python_codex-0.1.3.dist-info}/WHEEL +1 -1
  46. responses_server/app.py +29 -19
  47. responses_server/config.py +17 -17
  48. responses_server/payload_processors.py +16 -16
  49. responses_server/server.py +11 -11
  50. responses_server/session_store.py +10 -10
  51. responses_server/stream_router.py +58 -58
  52. responses_server/tools/custom_adapter.py +12 -12
  53. responses_server/tools/web_search.py +33 -33
  54. python_codex-0.1.2.dist-info/RECORD +0 -73
  55. {python_codex-0.1.2.dist-info → python_codex-0.1.3.dist-info}/entry_points.txt +0 -0
  56. {python_codex-0.1.2.dist-info → python_codex-0.1.3.dist-info}/licenses/LICENSE +0 -0
responses_server/app.py CHANGED
@@ -1,6 +1,6 @@
1
- from __future__ import annotations
2
1
 
3
2
  import argparse
3
+ import asyncio
4
4
  from dataclasses import replace
5
5
  import json
6
6
  import socket
@@ -15,14 +15,20 @@ import uvicorn
15
15
  from .config import CompatServerConfig
16
16
  from .server import ResponseServer
17
17
  from .stream_router import OutcommingChatError, UnsupportedIncommingFeature
18
+ import typing
18
19
 
19
20
 
20
- def _format_sse_event(event_name: str, payload: dict[str, object]) -> bytes:
21
+ def _run_uvicorn_server(server) -> 'None':
22
+ asyncio.set_event_loop(asyncio.new_event_loop())
23
+ server.run()
24
+
25
+
26
+ def _format_sse_event(event_name: 'str', payload: 'typing.Dict[str, object]') -> 'bytes':
21
27
  data = json.dumps(payload, ensure_ascii=False)
22
28
  return f"event: {event_name}\ndata: {data}\n\n".encode("utf-8")
23
29
 
24
30
 
25
- def _stream_events(response_server: ResponseServer, request_body: dict[str, object], request_headers: dict[str, str]) -> Iterator[bytes]:
31
+ def _stream_events(response_server: 'ResponseServer', request_body: 'typing.Dict[str, object]', request_headers: 'typing.Dict[str, str]') -> 'Iterator[bytes]':
26
32
  try:
27
33
  event_iter = response_server.start_response_stream(request_body, request_headers)
28
34
  for event_name, payload in event_iter:
@@ -41,7 +47,7 @@ def _stream_events(response_server: ResponseServer, request_body: dict[str, obje
41
47
  )
42
48
 
43
49
 
44
- def build_parser() -> argparse.ArgumentParser:
50
+ def build_parser() -> 'argparse.ArgumentParser':
45
51
  parser = argparse.ArgumentParser(
46
52
  prog="python -m responses_server",
47
53
  description=(
@@ -58,7 +64,7 @@ def build_parser() -> argparse.ArgumentParser:
58
64
  return parser
59
65
 
60
66
 
61
- def run_server(config: CompatServerConfig) -> None:
67
+ def run_server(config: 'CompatServerConfig') -> 'None':
62
68
  uvicorn.run(
63
69
  ManagedResponseServer.build_app(config),
64
70
  host=config.host,
@@ -68,9 +74,9 @@ def run_server(config: CompatServerConfig) -> None:
68
74
 
69
75
 
70
76
  def launch_chat_completion_compat_server(
71
- base_url: str,
72
- api_key_env: str | None = None,
73
- model_provider: str | None = None,
77
+ base_url: 'str',
78
+ api_key_env: 'typing.Union[str, None]' = None,
79
+ model_provider: 'typing.Union[str, None]' = None,
74
80
  ):
75
81
  config = CompatServerConfig.from_base_url(
76
82
  base_url,
@@ -85,10 +91,10 @@ def launch_chat_completion_compat_server(
85
91
  class ManagedResponseServer:
86
92
  @staticmethod
87
93
  def build_app(
88
- config: CompatServerConfig,
94
+ config: 'CompatServerConfig',
89
95
  session_store=None,
90
96
  stream_router=None,
91
- ) -> FastAPI:
97
+ ) -> 'FastAPI':
92
98
  response_server = ResponseServer(
93
99
  config,
94
100
  session_store=session_store,
@@ -99,7 +105,7 @@ class ManagedResponseServer:
99
105
 
100
106
  @app.get("/health")
101
107
  @app.get("/healthz")
102
- async def health() -> dict[str, bool]:
108
+ async def health() -> 'typing.Dict[str, bool]':
103
109
  return {"ok": True}
104
110
 
105
111
  @app.get("/models")
@@ -115,7 +121,7 @@ class ManagedResponseServer:
115
121
 
116
122
  @app.post("/responses")
117
123
  @app.post("/v1/responses")
118
- async def responses(request: Request):
124
+ async def responses(request: 'Request'):
119
125
  try:
120
126
  request_body = await request.json()
121
127
  except Exception as exc:
@@ -152,7 +158,7 @@ class ManagedResponseServer:
152
158
 
153
159
  return app
154
160
 
155
- def __init__(self, config: CompatServerConfig) -> None:
161
+ def __init__(self, config: 'CompatServerConfig') -> 'None':
156
162
  port = config.port or _reserve_free_port()
157
163
  self._config = replace(config, port=port)
158
164
  self._app = self.build_app(self._config)
@@ -164,13 +170,17 @@ class ManagedResponseServer:
164
170
  access_log=False,
165
171
  )
166
172
  self._server = uvicorn.Server(self._uvicorn_config)
167
- self._thread = threading.Thread(target=self._server.run, daemon=True)
173
+ self._thread = threading.Thread(
174
+ target=_run_uvicorn_server,
175
+ args=(self._server,),
176
+ daemon=True,
177
+ )
168
178
 
169
179
  @property
170
- def base_url(self) -> str:
180
+ def base_url(self) -> 'str':
171
181
  return f"http://{self._config.host}:{self._config.port}/v1"
172
182
 
173
- def start(self, timeout_seconds: float = 10.0) -> None:
183
+ def start(self, timeout_seconds: 'float' = 10.0) -> 'None':
174
184
  self._thread.start()
175
185
  deadline = time.time() + timeout_seconds
176
186
  while not self._server.started:
@@ -180,7 +190,7 @@ class ManagedResponseServer:
180
190
  )
181
191
  time.sleep(0.01)
182
192
 
183
- def stop(self, timeout_seconds: float = 5.0) -> None:
193
+ def stop(self, timeout_seconds: 'float' = 5.0) -> 'None':
184
194
  self._server.should_exit = True
185
195
  self._thread.join(timeout=timeout_seconds)
186
196
  if self._thread.is_alive():
@@ -189,7 +199,7 @@ class ManagedResponseServer:
189
199
  )
190
200
 
191
201
 
192
- def main() -> None:
202
+ def main() -> 'None':
193
203
  args = build_parser().parse_args()
194
204
  run_server(
195
205
  CompatServerConfig(
@@ -207,7 +217,7 @@ if __name__ == "__main__":
207
217
  main()
208
218
 
209
219
 
210
- def _reserve_free_port() -> int:
220
+ def _reserve_free_port() -> 'int':
211
221
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
212
222
  try:
213
223
  sock.bind(("127.0.0.1", 0))
@@ -1,34 +1,34 @@
1
- from __future__ import annotations
2
1
 
3
2
  import os
4
3
  from dataclasses import dataclass
5
4
  import urllib.parse
5
+ import typing
6
6
 
7
7
 
8
- @dataclass(frozen=True, slots=True)
8
+ @dataclass(frozen=True, )
9
9
  class CompatServerConfig:
10
- host: str = "127.0.0.1"
11
- port: int = 0
12
- outcomming_base_url: str = "http://127.0.0.1:8000/v1"
13
- outcomming_api_key_env: str | None = None
14
- model_provider: str | None = None
15
- timeout_seconds: float = 120.0
16
-
17
- def outcomming_api_key(self) -> str | None:
10
+ host: 'str' = "127.0.0.1"
11
+ port: 'int' = 0
12
+ outcomming_base_url: 'str' = "http://127.0.0.1:8000/v1"
13
+ outcomming_api_key_env: 'typing.Union[str, None]' = None
14
+ model_provider: 'typing.Union[str, None]' = None
15
+ timeout_seconds: 'float' = 120.0
16
+
17
+ def outcomming_api_key(self) -> 'typing.Union[str, None]':
18
18
  if self.outcomming_api_key_env is None:
19
19
  return None
20
20
  value = os.environ.get(self.outcomming_api_key_env, "").strip()
21
21
  return value or None
22
22
 
23
- def outcomming_chat_completions_url(self) -> str:
23
+ def outcomming_chat_completions_url(self) -> 'str':
24
24
  base = self.outcomming_base_url.rstrip("/")
25
25
  return f"{base}/chat/completions"
26
26
 
27
- def outcomming_models_url(self) -> str:
27
+ def outcomming_models_url(self) -> 'str':
28
28
  base = self.outcomming_base_url.rstrip("/")
29
29
  return f"{base}/models"
30
30
 
31
- def with_ephemeral_port(self) -> CompatServerConfig:
31
+ def with_ephemeral_port(self) -> 'CompatServerConfig':
32
32
  return CompatServerConfig(
33
33
  host=self.host,
34
34
  port=0,
@@ -41,10 +41,10 @@ class CompatServerConfig:
41
41
  @classmethod
42
42
  def from_base_url(
43
43
  cls,
44
- outcomming_base_url: str,
45
- api_key_env: str | None = None,
46
- model_provider: str | None = None,
47
- ) -> CompatServerConfig:
44
+ outcomming_base_url: 'str',
45
+ api_key_env: 'typing.Union[str, None]' = None,
46
+ model_provider: 'typing.Union[str, None]' = None,
47
+ ) -> 'CompatServerConfig':
48
48
  parsed = urllib.parse.urlparse(outcomming_base_url)
49
49
  if not parsed.scheme or not parsed.netloc:
50
50
  raise ValueError(f"invalid outcomming base url: {outcomming_base_url}")
@@ -1,4 +1,3 @@
1
- from __future__ import annotations
2
1
 
3
2
  """Provider-specific post-process hooks for canonical outgoing chat requests.
4
3
 
@@ -9,11 +8,12 @@ building one canonical `outcomming_request`, while `server.py` selects the
9
8
  appropriate hook from `CompatServerConfig.model_provider`.
10
9
  """
11
10
 
12
- from collections.abc import Callable
13
11
  from copy import deepcopy
14
- from typing import Optional, TypedDict
12
+ from typing import Callable, Optional
13
+ import typing
14
+ from typing_extensions import TypedDict
15
15
 
16
- ChatMessage = dict[str, object]
16
+ ChatMessage = typing.Dict[str, object]
17
17
 
18
18
 
19
19
  class OutgoingRequest(TypedDict):
@@ -25,24 +25,24 @@ class OutgoingRequest(TypedDict):
25
25
  not rely on TypedDict inheritance.
26
26
  """
27
27
 
28
- model: str
29
- messages: list[ChatMessage]
30
- stream: bool
31
- tools: Optional[list[dict[str, object]]]
32
- tool_choice: Optional[object]
33
- parallel_tool_calls: Optional[bool]
28
+ model: 'str'
29
+ messages: 'typing.List[ChatMessage]'
30
+ stream: 'bool'
31
+ tools: 'Optional[typing.List[typing.Dict[str, object]]]'
32
+ tool_choice: 'Optional[object]'
33
+ parallel_tool_calls: 'Optional[bool]'
34
34
 
35
35
 
36
36
  PayloadPostProcessor = Callable[[OutgoingRequest], OutgoingRequest]
37
37
 
38
38
 
39
- def _identity(outcomming_request: OutgoingRequest) -> OutgoingRequest:
39
+ def _identity(outcomming_request: 'OutgoingRequest') -> 'OutgoingRequest':
40
40
  """Keep the canonical request unchanged."""
41
41
 
42
42
  return outcomming_request
43
43
 
44
44
 
45
- def _drop_developer_messages(outcomming_request: OutgoingRequest) -> OutgoingRequest:
45
+ def _drop_developer_messages(outcomming_request: 'OutgoingRequest') -> 'OutgoingRequest':
46
46
  """Remove all developer-role messages for providers that reject them."""
47
47
 
48
48
  outcomming_request["messages"] = [
@@ -53,7 +53,7 @@ def _drop_developer_messages(outcomming_request: OutgoingRequest) -> OutgoingReq
53
53
  return outcomming_request
54
54
 
55
55
 
56
- PAYLOAD_POST_PROCESSORS: dict[str, PayloadPostProcessor] = {
56
+ PAYLOAD_POST_PROCESSORS: 'typing.Dict[str, PayloadPostProcessor]' = {
57
57
  "stepfun": _drop_developer_messages,
58
58
  "vllm": _identity,
59
59
  }
@@ -61,9 +61,9 @@ PAYLOAD_POST_PROCESSORS: dict[str, PayloadPostProcessor] = {
61
61
 
62
62
 
63
63
  def post_process_outcomming_request(
64
- outcomming_request: OutgoingRequest,
65
- model_provider: str | None,
66
- ) -> OutgoingRequest:
64
+ outcomming_request: 'OutgoingRequest',
65
+ model_provider: 'typing.Union[str, None]',
66
+ ) -> 'OutgoingRequest':
67
67
  """Apply the provider-specific payload hook to one outgoing request.
68
68
 
69
69
  This is the single wrapper around `PAYLOAD_POST_PROCESSORS`: it normalizes
@@ -1,41 +1,41 @@
1
- from __future__ import annotations
2
1
 
3
2
  from .config import CompatServerConfig
4
3
  from .payload_processors import post_process_outcomming_request
5
4
  from .session_store import SessionStore
6
5
  from .stream_router import StreamRouter
6
+ import typing
7
7
 
8
8
 
9
9
  class ResponseServer:
10
10
  def __init__(
11
11
  self,
12
- config: CompatServerConfig,
13
- session_store: SessionStore | None = None,
14
- stream_router: StreamRouter | None = None,
15
- ) -> None:
12
+ config: 'CompatServerConfig',
13
+ session_store: 'typing.Union[SessionStore, None]' = None,
14
+ stream_router: 'typing.Union[StreamRouter, None]' = None,
15
+ ) -> 'None':
16
16
  self._config = config
17
17
  self._session_store = session_store or SessionStore()
18
18
  self._stream_router = stream_router or StreamRouter(config)
19
19
 
20
20
  @property
21
- def config(self) -> CompatServerConfig:
21
+ def config(self) -> 'CompatServerConfig':
22
22
  return self._config
23
23
 
24
24
  @property
25
- def session_store(self) -> SessionStore:
25
+ def session_store(self) -> 'SessionStore':
26
26
  return self._session_store
27
27
 
28
28
  @property
29
- def stream_router(self) -> StreamRouter:
29
+ def stream_router(self) -> 'StreamRouter':
30
30
  return self._stream_router
31
31
 
32
- def list_models(self) -> dict[str, object]:
32
+ def list_models(self) -> 'typing.Dict[str, object]':
33
33
  return self._stream_router.list_models()
34
34
 
35
35
  def start_response_stream(
36
36
  self,
37
- request_body: dict[str, object],
38
- request_headers: dict[str, str],
37
+ request_body: 'typing.Dict[str, object]',
38
+ request_headers: 'typing.Dict[str, str]',
39
39
  ):
40
40
  outcomming_request = self._stream_router.build_outcomming_request(request_body)
41
41
  outcomming_request = post_process_outcomming_request(
@@ -1,25 +1,25 @@
1
- from __future__ import annotations
2
1
 
3
2
  from dataclasses import dataclass
4
3
  import threading
5
4
  import time
5
+ import typing
6
6
 
7
7
 
8
- @dataclass(frozen=True, slots=True)
8
+ @dataclass(frozen=True, )
9
9
  class StoredResponse:
10
- response_id: str
11
- session_id: str | None
12
- model: str
13
- created_at: float
10
+ response_id: 'str'
11
+ session_id: 'typing.Union[str, None]'
12
+ model: 'str'
13
+ created_at: 'float'
14
14
 
15
15
 
16
16
  class SessionStore:
17
- def __init__(self) -> None:
17
+ def __init__(self) -> 'None':
18
18
  self._lock = threading.Lock()
19
19
  self._next_response_number = 1
20
- self._responses: dict[str, StoredResponse] = {}
20
+ self._responses: 'typing.Dict[str, StoredResponse]' = {}
21
21
 
22
- def create_response(self, session_id: str | None, model: str) -> StoredResponse:
22
+ def create_response(self, session_id: 'typing.Union[str, None]', model: 'str') -> 'StoredResponse':
23
23
  with self._lock:
24
24
  response_id = f"resp_{self._next_response_number:08d}"
25
25
  self._next_response_number += 1
@@ -32,6 +32,6 @@ class SessionStore:
32
32
  self._responses[response_id] = stored
33
33
  return stored
34
34
 
35
- def get_response(self, response_id: str) -> StoredResponse | None:
35
+ def get_response(self, response_id: 'str') -> 'typing.Union[StoredResponse, None]':
36
36
  with self._lock:
37
37
  return self._responses.get(response_id)