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.
- pycodex/__init__.py +5 -1
- pycodex/agent.py +39 -41
- pycodex/cli.py +43 -42
- pycodex/collaboration.py +6 -7
- pycodex/compat.py +99 -0
- pycodex/context.py +87 -87
- pycodex/doctor.py +40 -40
- pycodex/model.py +69 -69
- pycodex/portable.py +33 -33
- pycodex/portable_server.py +22 -21
- pycodex/protocol.py +84 -86
- pycodex/runtime.py +36 -35
- pycodex/runtime_services.py +69 -69
- pycodex/tools/agent_tool_schemas.py +0 -2
- pycodex/tools/apply_patch_tool.py +43 -44
- pycodex/tools/base_tool.py +35 -36
- pycodex/tools/close_agent_tool.py +2 -4
- pycodex/tools/code_mode_manager.py +61 -61
- pycodex/tools/exec_command_tool.py +5 -6
- pycodex/tools/exec_runtime.js +3 -3
- pycodex/tools/exec_tool.py +2 -4
- pycodex/tools/grep_files_tool.py +10 -11
- pycodex/tools/list_dir_tool.py +8 -9
- pycodex/tools/read_file_tool.py +13 -14
- pycodex/tools/request_permissions_tool.py +2 -4
- pycodex/tools/request_user_input_tool.py +13 -14
- pycodex/tools/resume_agent_tool.py +2 -4
- pycodex/tools/send_input_tool.py +8 -9
- pycodex/tools/shell_command_tool.py +5 -6
- pycodex/tools/shell_tool.py +5 -6
- pycodex/tools/spawn_agent_tool.py +4 -5
- pycodex/tools/unified_exec_manager.py +62 -61
- pycodex/tools/update_plan_tool.py +4 -5
- pycodex/tools/view_image_tool.py +4 -5
- pycodex/tools/wait_agent_tool.py +2 -4
- pycodex/tools/wait_tool.py +4 -5
- pycodex/tools/web_search_tool.py +1 -3
- pycodex/tools/write_stdin_tool.py +4 -5
- pycodex/utils/dotenv.py +6 -6
- pycodex/utils/get_env.py +37 -33
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/visualize.py +79 -79
- {python_codex-0.1.2.dist-info → python_codex-0.1.3.dist-info}/METADATA +15 -9
- python_codex-0.1.3.dist-info/RECORD +74 -0
- {python_codex-0.1.2.dist-info → python_codex-0.1.3.dist-info}/WHEEL +1 -1
- responses_server/app.py +29 -19
- responses_server/config.py +17 -17
- responses_server/payload_processors.py +16 -16
- responses_server/server.py +11 -11
- responses_server/session_store.py +10 -10
- responses_server/stream_router.py +58 -58
- responses_server/tools/custom_adapter.py +12 -12
- responses_server/tools/web_search.py +33 -33
- python_codex-0.1.2.dist-info/RECORD +0 -73
- {python_codex-0.1.2.dist-info → python_codex-0.1.3.dist-info}/entry_points.txt +0 -0
- {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
|
|
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:
|
|
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
|
|
73
|
-
model_provider: str
|
|
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() ->
|
|
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(
|
|
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))
|
responses_server/config.py
CHANGED
|
@@ -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,
|
|
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
|
|
14
|
-
model_provider: str
|
|
15
|
-
timeout_seconds: float = 120.0
|
|
16
|
-
|
|
17
|
-
def outcomming_api_key(self) -> str
|
|
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
|
|
46
|
-
model_provider: str
|
|
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
|
|
12
|
+
from typing import Callable, Optional
|
|
13
|
+
import typing
|
|
14
|
+
from typing_extensions import TypedDict
|
|
15
15
|
|
|
16
|
-
ChatMessage =
|
|
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:
|
|
30
|
-
stream: bool
|
|
31
|
-
tools: Optional[
|
|
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:
|
|
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
|
|
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
|
responses_server/server.py
CHANGED
|
@@ -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
|
|
14
|
-
stream_router: StreamRouter
|
|
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) ->
|
|
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:
|
|
38
|
-
request_headers:
|
|
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,
|
|
8
|
+
@dataclass(frozen=True, )
|
|
9
9
|
class StoredResponse:
|
|
10
|
-
response_id: str
|
|
11
|
-
session_id: str
|
|
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:
|
|
20
|
+
self._responses: 'typing.Dict[str, StoredResponse]' = {}
|
|
21
21
|
|
|
22
|
-
def create_response(self, session_id: str
|
|
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
|
|
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)
|