python-codex 0.1.0__py3-none-any.whl → 0.1.2__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 +2 -0
- pycodex/cli.py +101 -30
- pycodex/portable.py +390 -0
- pycodex/portable_server.py +205 -0
- pycodex/runtime.py +6 -2
- pycodex/runtime_services.py +7 -3
- pycodex/tools/exec_tool.py +1 -1
- pycodex/tools/unified_exec_manager.py +19 -2
- pycodex/utils/get_env.py +23 -4
- python_codex-0.1.2.dist-info/METADATA +355 -0
- {python_codex-0.1.0.dist-info → python_codex-0.1.2.dist-info}/RECORD +25 -12
- responses_server/__init__.py +17 -0
- responses_server/__main__.py +5 -0
- responses_server/app.py +217 -0
- responses_server/config.py +63 -0
- responses_server/payload_processors.py +86 -0
- responses_server/server.py +63 -0
- responses_server/session_store.py +37 -0
- responses_server/stream_router.py +784 -0
- responses_server/tools/__init__.py +4 -0
- responses_server/tools/custom_adapter.py +235 -0
- responses_server/tools/web_search.py +263 -0
- python_codex-0.1.0.dist-info/METADATA +0 -267
- {python_codex-0.1.0.dist-info → python_codex-0.1.2.dist-info}/WHEEL +0 -0
- {python_codex-0.1.0.dist-info → python_codex-0.1.2.dist-info}/entry_points.txt +0 -0
- {python_codex-0.1.0.dist-info → python_codex-0.1.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
import urllib.parse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, slots=True)
|
|
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:
|
|
18
|
+
if self.outcomming_api_key_env is None:
|
|
19
|
+
return None
|
|
20
|
+
value = os.environ.get(self.outcomming_api_key_env, "").strip()
|
|
21
|
+
return value or None
|
|
22
|
+
|
|
23
|
+
def outcomming_chat_completions_url(self) -> str:
|
|
24
|
+
base = self.outcomming_base_url.rstrip("/")
|
|
25
|
+
return f"{base}/chat/completions"
|
|
26
|
+
|
|
27
|
+
def outcomming_models_url(self) -> str:
|
|
28
|
+
base = self.outcomming_base_url.rstrip("/")
|
|
29
|
+
return f"{base}/models"
|
|
30
|
+
|
|
31
|
+
def with_ephemeral_port(self) -> CompatServerConfig:
|
|
32
|
+
return CompatServerConfig(
|
|
33
|
+
host=self.host,
|
|
34
|
+
port=0,
|
|
35
|
+
outcomming_base_url=self.outcomming_base_url,
|
|
36
|
+
outcomming_api_key_env=self.outcomming_api_key_env,
|
|
37
|
+
model_provider=self.model_provider,
|
|
38
|
+
timeout_seconds=self.timeout_seconds,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def from_base_url(
|
|
43
|
+
cls,
|
|
44
|
+
outcomming_base_url: str,
|
|
45
|
+
api_key_env: str | None = None,
|
|
46
|
+
model_provider: str | None = None,
|
|
47
|
+
) -> CompatServerConfig:
|
|
48
|
+
parsed = urllib.parse.urlparse(outcomming_base_url)
|
|
49
|
+
if not parsed.scheme or not parsed.netloc:
|
|
50
|
+
raise ValueError(f"invalid outcomming base url: {outcomming_base_url}")
|
|
51
|
+
normalized_path = parsed.path.rstrip("/")
|
|
52
|
+
if normalized_path in {"", "/"}:
|
|
53
|
+
parsed = parsed._replace(path="/v1")
|
|
54
|
+
outcomming_base_url = urllib.parse.urlunparse(parsed)
|
|
55
|
+
else:
|
|
56
|
+
outcomming_base_url = urllib.parse.urlunparse(
|
|
57
|
+
parsed._replace(path=normalized_path)
|
|
58
|
+
)
|
|
59
|
+
return cls(
|
|
60
|
+
outcomming_base_url=outcomming_base_url,
|
|
61
|
+
outcomming_api_key_env=api_key_env,
|
|
62
|
+
model_provider=model_provider,
|
|
63
|
+
)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Provider-specific post-process hooks for canonical outgoing chat requests.
|
|
4
|
+
|
|
5
|
+
Each downstream chat-completions provider may have its own payload quirks:
|
|
6
|
+
extra fields, removed fields, role normalization, tool-shape tweaks, etc.
|
|
7
|
+
Keep all of those provider-specific rewrites here so `StreamRouter` can keep
|
|
8
|
+
building one canonical `outcomming_request`, while `server.py` selects the
|
|
9
|
+
appropriate hook from `CompatServerConfig.model_provider`.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from collections.abc import Callable
|
|
13
|
+
from copy import deepcopy
|
|
14
|
+
from typing import Optional, TypedDict
|
|
15
|
+
|
|
16
|
+
ChatMessage = dict[str, object]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class OutgoingRequest(TypedDict):
|
|
20
|
+
"""Canonical downstream `/v1/chat/completions` request shape.
|
|
21
|
+
|
|
22
|
+
`model`, `messages`, and `stream` are always populated by
|
|
23
|
+
`StreamRouter.build_outcomming_request(...)`. Provider-specific fields that
|
|
24
|
+
may be omitted use `Optional[...]` here so the schema stays simple and does
|
|
25
|
+
not rely on TypedDict inheritance.
|
|
26
|
+
"""
|
|
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]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
PayloadPostProcessor = Callable[[OutgoingRequest], OutgoingRequest]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _identity(outcomming_request: OutgoingRequest) -> OutgoingRequest:
|
|
40
|
+
"""Keep the canonical request unchanged."""
|
|
41
|
+
|
|
42
|
+
return outcomming_request
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _drop_developer_messages(outcomming_request: OutgoingRequest) -> OutgoingRequest:
|
|
46
|
+
"""Remove all developer-role messages for providers that reject them."""
|
|
47
|
+
|
|
48
|
+
outcomming_request["messages"] = [
|
|
49
|
+
message
|
|
50
|
+
for message in outcomming_request["messages"]
|
|
51
|
+
if message.get("role") != "developer"
|
|
52
|
+
]
|
|
53
|
+
return outcomming_request
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
PAYLOAD_POST_PROCESSORS: dict[str, PayloadPostProcessor] = {
|
|
57
|
+
"stepfun": _drop_developer_messages,
|
|
58
|
+
"vllm": _identity,
|
|
59
|
+
}
|
|
60
|
+
"""Mapping from normalized `model_provider` name to payload rewrite hook."""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def post_process_outcomming_request(
|
|
64
|
+
outcomming_request: OutgoingRequest,
|
|
65
|
+
model_provider: str | None,
|
|
66
|
+
) -> OutgoingRequest:
|
|
67
|
+
"""Apply the provider-specific payload hook to one outgoing request.
|
|
68
|
+
|
|
69
|
+
This is the single wrapper around `PAYLOAD_POST_PROCESSORS`: it normalizes
|
|
70
|
+
the provider name, falls back to the default `vllm` behavior when the
|
|
71
|
+
provider is missing or unknown, deep-copies the canonical request, applies
|
|
72
|
+
the selected hook, and validates that the hook returns another request dict.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
processed_request = deepcopy(outcomming_request)
|
|
76
|
+
provider_name = str(model_provider or "").strip().lower()
|
|
77
|
+
provider_processor = PAYLOAD_POST_PROCESSORS.get(
|
|
78
|
+
provider_name,
|
|
79
|
+
PAYLOAD_POST_PROCESSORS.get("vllm"),
|
|
80
|
+
)
|
|
81
|
+
if provider_processor is None:
|
|
82
|
+
return processed_request
|
|
83
|
+
processed_request = provider_processor(processed_request)
|
|
84
|
+
if not isinstance(processed_request, dict):
|
|
85
|
+
raise TypeError("payload processor must return a dict outcomming_request")
|
|
86
|
+
return processed_request
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .config import CompatServerConfig
|
|
4
|
+
from .payload_processors import post_process_outcomming_request
|
|
5
|
+
from .session_store import SessionStore
|
|
6
|
+
from .stream_router import StreamRouter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ResponseServer:
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
config: CompatServerConfig,
|
|
13
|
+
session_store: SessionStore | None = None,
|
|
14
|
+
stream_router: StreamRouter | None = None,
|
|
15
|
+
) -> None:
|
|
16
|
+
self._config = config
|
|
17
|
+
self._session_store = session_store or SessionStore()
|
|
18
|
+
self._stream_router = stream_router or StreamRouter(config)
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def config(self) -> CompatServerConfig:
|
|
22
|
+
return self._config
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def session_store(self) -> SessionStore:
|
|
26
|
+
return self._session_store
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def stream_router(self) -> StreamRouter:
|
|
30
|
+
return self._stream_router
|
|
31
|
+
|
|
32
|
+
def list_models(self) -> dict[str, object]:
|
|
33
|
+
return self._stream_router.list_models()
|
|
34
|
+
|
|
35
|
+
def start_response_stream(
|
|
36
|
+
self,
|
|
37
|
+
request_body: dict[str, object],
|
|
38
|
+
request_headers: dict[str, str],
|
|
39
|
+
):
|
|
40
|
+
outcomming_request = self._stream_router.build_outcomming_request(request_body)
|
|
41
|
+
outcomming_request = post_process_outcomming_request(
|
|
42
|
+
outcomming_request,
|
|
43
|
+
self._config.model_provider,
|
|
44
|
+
)
|
|
45
|
+
custom_tool_names = self._stream_router.collect_custom_tool_names(request_body)
|
|
46
|
+
session_id = (
|
|
47
|
+
request_headers.get("x-client-request-id")
|
|
48
|
+
or str(request_body.get("prompt_cache_key", "")).strip()
|
|
49
|
+
or None
|
|
50
|
+
)
|
|
51
|
+
stored_response = self._session_store.create_response(
|
|
52
|
+
session_id=session_id,
|
|
53
|
+
model=str(outcomming_request["model"]),
|
|
54
|
+
)
|
|
55
|
+
incomming_stream = self._stream_router.open_outcomming_stream(
|
|
56
|
+
outcomming_request
|
|
57
|
+
)
|
|
58
|
+
return self._stream_router.route_stream(
|
|
59
|
+
incomming_stream,
|
|
60
|
+
stored_response,
|
|
61
|
+
outcomming_request,
|
|
62
|
+
custom_tool_names,
|
|
63
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, slots=True)
|
|
9
|
+
class StoredResponse:
|
|
10
|
+
response_id: str
|
|
11
|
+
session_id: str | None
|
|
12
|
+
model: str
|
|
13
|
+
created_at: float
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SessionStore:
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
self._lock = threading.Lock()
|
|
19
|
+
self._next_response_number = 1
|
|
20
|
+
self._responses: dict[str, StoredResponse] = {}
|
|
21
|
+
|
|
22
|
+
def create_response(self, session_id: str | None, model: str) -> StoredResponse:
|
|
23
|
+
with self._lock:
|
|
24
|
+
response_id = f"resp_{self._next_response_number:08d}"
|
|
25
|
+
self._next_response_number += 1
|
|
26
|
+
stored = StoredResponse(
|
|
27
|
+
response_id=response_id,
|
|
28
|
+
session_id=session_id,
|
|
29
|
+
model=model,
|
|
30
|
+
created_at=time.time(),
|
|
31
|
+
)
|
|
32
|
+
self._responses[response_id] = stored
|
|
33
|
+
return stored
|
|
34
|
+
|
|
35
|
+
def get_response(self, response_id: str) -> StoredResponse | None:
|
|
36
|
+
with self._lock:
|
|
37
|
+
return self._responses.get(response_id)
|