python-codex 0.2.7__py3-none-any.whl → 0.3.0__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 +14 -14
- pycodex/agent.py +465 -499
- pycodex/bootstrap.py +417 -0
- pycodex/cli.py +236 -510
- pycodex/compat.py +19 -5
- pycodex/context.py +222 -212
- pycodex/doctor.py +52 -48
- pycodex/events.py +857 -0
- pycodex/feishu_card.py +217 -163
- pycodex/feishu_link.py +43 -83
- pycodex/model.py +324 -253
- pycodex/model_metadata.py +19 -7
- pycodex/portable.py +76 -45
- pycodex/portable_server.py +32 -24
- pycodex/prompts/models.json +245 -983
- pycodex/protocol.py +177 -137
- pycodex/runtime.py +579 -176
- pycodex/runtime_services.py +204 -157
- pycodex/tools/__init__.py +1 -1
- pycodex/tools/apply_patch_tool.py +69 -48
- pycodex/tools/base_tool.py +89 -42
- pycodex/tools/clock_tool.py +58 -25
- pycodex/tools/close_agent_tool.py +2 -2
- pycodex/tools/code_mode_manager.py +77 -64
- pycodex/tools/exec_command_tool.py +26 -11
- pycodex/tools/exec_tool.py +4 -4
- pycodex/tools/grep_files_tool.py +12 -10
- pycodex/tools/ipython_tool.py +10 -13
- pycodex/tools/list_dir_tool.py +13 -9
- pycodex/tools/read_file_tool.py +29 -17
- pycodex/tools/request_permissions_tool.py +15 -5
- pycodex/tools/request_user_input_tool.py +13 -104
- pycodex/tools/resume_agent_tool.py +2 -2
- pycodex/tools/send_input_tool.py +11 -8
- pycodex/tools/shell_command_tool.py +7 -5
- pycodex/tools/shell_tool.py +7 -5
- pycodex/tools/spawn_agent_tool.py +7 -4
- pycodex/tools/unified_exec_manager.py +102 -69
- pycodex/tools/update_plan_tool.py +8 -5
- pycodex/tools/view_image_tool.py +7 -5
- pycodex/tools/wait_agent_tool.py +27 -4
- pycodex/tools/wait_tool.py +5 -4
- pycodex/tools/web_search_tool.py +4 -2
- pycodex/tools/write_stdin_tool.py +12 -11
- pycodex/utils/__init__.py +2 -17
- pycodex/utils/compactor.py +41 -72
- pycodex/utils/debug.py +2 -2
- pycodex/utils/dotenv.py +6 -7
- pycodex/utils/event_helpers.py +190 -0
- pycodex/utils/get_env.py +27 -70
- pycodex/{image_utils.py → utils/image_utils.py} +8 -11
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/session_persist.py +217 -163
- pycodex/utils/truncation.py +21 -45
- python_codex-0.3.0.dist-info/METADATA +704 -0
- python_codex-0.3.0.dist-info/RECORD +90 -0
- responses_server/__init__.py +1 -5
- responses_server/__main__.py +0 -1
- responses_server/app.py +36 -31
- responses_server/config.py +23 -23
- responses_server/messages_api.py +51 -53
- responses_server/payload_processors.py +25 -20
- responses_server/server.py +11 -11
- responses_server/session_store.py +14 -11
- responses_server/stream_router.py +101 -98
- responses_server/tools/custom_adapter.py +17 -16
- responses_server/tools/web_search.py +39 -36
- responses_server/trajectory_dump.py +36 -14
- workspace_server/__main__.py +0 -1
- workspace_server/app.py +461 -375
- workspace_server/workspace.html +852 -228
- workspace_server/workspaces.html +94 -95
- workspace_server/workspaces.py +137 -79
- pycodex/collaboration.py +0 -20
- pycodex/interactive_session.py +0 -415
- pycodex/prompts/collaboration_default.md +0 -11
- pycodex/prompts/collaboration_plan.md +0 -128
- pycodex/utils/toolcall_visualize.py +0 -713
- pycodex/utils/visualize.py +0 -560
- python_codex-0.2.7.dist-info/METADATA +0 -455
- python_codex-0.2.7.dist-info/RECORD +0 -93
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/WHEEL +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/entry_points.txt +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
"""Provider-specific post-process hooks for canonical outgoing chat requests.
|
|
3
2
|
|
|
4
3
|
Each downstream chat-completions provider may have its own payload quirks:
|
|
@@ -8,9 +7,10 @@ building one canonical `outcomming_request`, while `server.py` selects the
|
|
|
8
7
|
appropriate hook from `CompatServerConfig.model_provider`.
|
|
9
8
|
"""
|
|
10
9
|
|
|
10
|
+
import typing
|
|
11
11
|
from copy import deepcopy
|
|
12
12
|
from typing import Callable, Optional
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
from typing_extensions import TypedDict
|
|
15
15
|
|
|
16
16
|
ChatMessage = typing.Dict[str, object]
|
|
@@ -25,27 +25,29 @@ class OutgoingRequest(TypedDict):
|
|
|
25
25
|
not rely on TypedDict inheritance.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
-
model:
|
|
29
|
-
messages:
|
|
30
|
-
stream:
|
|
31
|
-
chat_template_kwargs:
|
|
32
|
-
max_tokens:
|
|
33
|
-
tools:
|
|
34
|
-
tool_choice:
|
|
35
|
-
parallel_tool_calls:
|
|
36
|
-
return_token_ids:
|
|
28
|
+
model: "str"
|
|
29
|
+
messages: "typing.List[ChatMessage]"
|
|
30
|
+
stream: "bool"
|
|
31
|
+
chat_template_kwargs: "Optional[typing.Dict[str, object]]"
|
|
32
|
+
max_tokens: "Optional[int]"
|
|
33
|
+
tools: "Optional[typing.List[typing.Dict[str, object]]]"
|
|
34
|
+
tool_choice: "Optional[object]"
|
|
35
|
+
parallel_tool_calls: "Optional[bool]"
|
|
36
|
+
return_token_ids: "Optional[bool]"
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
PayloadPostProcessor = Callable[[OutgoingRequest], OutgoingRequest]
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
def _identity(outcomming_request:
|
|
42
|
+
def _identity(outcomming_request: "OutgoingRequest") -> "OutgoingRequest":
|
|
43
43
|
"""Keep the canonical request unchanged."""
|
|
44
44
|
|
|
45
45
|
return outcomming_request
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def _drop_developer_messages(
|
|
48
|
+
def _drop_developer_messages(
|
|
49
|
+
outcomming_request: "OutgoingRequest",
|
|
50
|
+
) -> "OutgoingRequest":
|
|
49
51
|
"""Remove all developer-role messages for providers that reject them."""
|
|
50
52
|
|
|
51
53
|
outcomming_request["messages"] = [
|
|
@@ -55,17 +57,20 @@ def _drop_developer_messages(outcomming_request: 'OutgoingRequest') -> 'Outgoing
|
|
|
55
57
|
]
|
|
56
58
|
return outcomming_request
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
|
|
61
|
+
def _replace_developer_messages(
|
|
62
|
+
outcomming_request: "OutgoingRequest",
|
|
63
|
+
) -> "OutgoingRequest":
|
|
59
64
|
"""Replace all developer-role messages to system-role messages"""
|
|
60
65
|
|
|
61
|
-
for message in outcomming_request[
|
|
66
|
+
for message in outcomming_request["messages"]:
|
|
62
67
|
if message.get("role") == "developer":
|
|
63
|
-
message[
|
|
68
|
+
message["role"] = "system"
|
|
64
69
|
|
|
65
70
|
return outcomming_request
|
|
66
71
|
|
|
67
72
|
|
|
68
|
-
PAYLOAD_POST_PROCESSORS:
|
|
73
|
+
PAYLOAD_POST_PROCESSORS: "typing.Dict[str, PayloadPostProcessor]" = {
|
|
69
74
|
"stepfun": _replace_developer_messages,
|
|
70
75
|
"vllm": _identity,
|
|
71
76
|
}
|
|
@@ -73,9 +78,9 @@ PAYLOAD_POST_PROCESSORS: 'typing.Dict[str, PayloadPostProcessor]' = {
|
|
|
73
78
|
|
|
74
79
|
|
|
75
80
|
def post_process_outcomming_request(
|
|
76
|
-
outcomming_request:
|
|
77
|
-
model_provider:
|
|
78
|
-
) ->
|
|
81
|
+
outcomming_request: "OutgoingRequest",
|
|
82
|
+
model_provider: "typing.Union[str, None]",
|
|
83
|
+
) -> "OutgoingRequest":
|
|
79
84
|
"""Apply the provider-specific payload hook to one outgoing request.
|
|
80
85
|
|
|
81
86
|
This is the single wrapper around `PAYLOAD_POST_PROCESSORS`: it normalizes
|
responses_server/server.py
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
+
import typing
|
|
1
2
|
|
|
2
3
|
from .config import CompatServerConfig
|
|
3
4
|
from .payload_processors import post_process_outcomming_request
|
|
4
5
|
from .session_store import SessionStore
|
|
5
6
|
from .stream_router import StreamRouter
|
|
6
7
|
from .trajectory_dump import TrajectoryDumpWriter
|
|
7
|
-
import typing
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class ResponseServer:
|
|
11
11
|
def __init__(
|
|
12
12
|
self,
|
|
13
|
-
config:
|
|
14
|
-
session_store:
|
|
15
|
-
stream_router:
|
|
16
|
-
) ->
|
|
13
|
+
config: "CompatServerConfig",
|
|
14
|
+
session_store: "typing.Union[SessionStore, None]" = None,
|
|
15
|
+
stream_router: "typing.Union[StreamRouter, None]" = None,
|
|
16
|
+
) -> "None":
|
|
17
17
|
self._config = config
|
|
18
18
|
self._session_store = session_store or SessionStore()
|
|
19
19
|
self._stream_router = stream_router or StreamRouter(config)
|
|
20
20
|
self._trajectory_dump = TrajectoryDumpWriter.from_env()
|
|
21
21
|
|
|
22
22
|
@property
|
|
23
|
-
def config(self) ->
|
|
23
|
+
def config(self) -> "CompatServerConfig":
|
|
24
24
|
return self._config
|
|
25
25
|
|
|
26
26
|
@property
|
|
27
|
-
def session_store(self) ->
|
|
27
|
+
def session_store(self) -> "SessionStore":
|
|
28
28
|
return self._session_store
|
|
29
29
|
|
|
30
30
|
@property
|
|
31
|
-
def stream_router(self) ->
|
|
31
|
+
def stream_router(self) -> "StreamRouter":
|
|
32
32
|
return self._stream_router
|
|
33
33
|
|
|
34
|
-
def list_models(self) ->
|
|
34
|
+
def list_models(self) -> "typing.Dict[str, object]":
|
|
35
35
|
return self._stream_router.list_models()
|
|
36
36
|
|
|
37
37
|
def start_response_stream(
|
|
38
38
|
self,
|
|
39
|
-
request_body:
|
|
40
|
-
request_headers:
|
|
39
|
+
request_body: "typing.Dict[str, object]",
|
|
40
|
+
request_headers: "typing.Dict[str, str]",
|
|
41
41
|
):
|
|
42
42
|
outcomming_request = self._stream_router.build_outcomming_request(request_body)
|
|
43
43
|
if self._trajectory_dump is not None:
|
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
from dataclasses import dataclass
|
|
3
1
|
import threading
|
|
4
2
|
import time
|
|
5
3
|
import typing
|
|
4
|
+
from dataclasses import dataclass
|
|
6
5
|
|
|
7
6
|
|
|
8
|
-
@dataclass(
|
|
7
|
+
@dataclass(
|
|
8
|
+
frozen=True,
|
|
9
|
+
)
|
|
9
10
|
class StoredResponse:
|
|
10
|
-
response_id:
|
|
11
|
-
session_id:
|
|
12
|
-
model:
|
|
13
|
-
created_at:
|
|
11
|
+
response_id: "str"
|
|
12
|
+
session_id: "typing.Union[str, None]"
|
|
13
|
+
model: "str"
|
|
14
|
+
created_at: "float"
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class SessionStore:
|
|
17
|
-
def __init__(self) ->
|
|
18
|
+
def __init__(self) -> "None":
|
|
18
19
|
self._lock = threading.Lock()
|
|
19
20
|
self._next_response_number = 1
|
|
20
|
-
self._responses:
|
|
21
|
+
self._responses: "typing.Dict[str, StoredResponse]" = {}
|
|
21
22
|
|
|
22
|
-
def create_response(
|
|
23
|
+
def create_response(
|
|
24
|
+
self, session_id: "typing.Union[str, None]", model: "str"
|
|
25
|
+
) -> "StoredResponse":
|
|
23
26
|
with self._lock:
|
|
24
27
|
response_id = f"resp_{self._next_response_number:08d}"
|
|
25
28
|
self._next_response_number += 1
|
|
@@ -32,6 +35,6 @@ class SessionStore:
|
|
|
32
35
|
self._responses[response_id] = stored
|
|
33
36
|
return stored
|
|
34
37
|
|
|
35
|
-
def get_response(self, response_id:
|
|
38
|
+
def get_response(self, response_id: "str") -> "typing.Union[StoredResponse, None]":
|
|
36
39
|
with self._lock:
|
|
37
40
|
return self._responses.get(response_id)
|
|
@@ -1,25 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
import json
|
|
3
1
|
import http.client
|
|
2
|
+
import json
|
|
4
3
|
import ssl
|
|
4
|
+
import typing
|
|
5
5
|
import urllib.error
|
|
6
6
|
import urllib.request
|
|
7
7
|
|
|
8
8
|
from .config import CompatServerConfig
|
|
9
|
-
from .messages_api import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
iter_chat_chunks as iter_chat_chunks_from_messages,
|
|
13
|
-
saw_message_stop as messages_saw_message_stop,
|
|
14
|
-
)
|
|
9
|
+
from .messages_api import MessagesAPIAdapterError, build_messages_request
|
|
10
|
+
from .messages_api import iter_chat_chunks as iter_chat_chunks_from_messages
|
|
11
|
+
from .messages_api import saw_message_stop as messages_saw_message_stop
|
|
15
12
|
from .session_store import StoredResponse
|
|
16
13
|
from .tools import WebSearchTool, collect_custom_tool_names
|
|
17
|
-
from .tools.custom_adapter import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
build_tool_definition as build_custom_tool_definition,
|
|
22
|
-
)
|
|
14
|
+
from .tools.custom_adapter import CustomToolAdapterError
|
|
15
|
+
from .tools.custom_adapter import build_output_item as build_custom_output_item
|
|
16
|
+
from .tools.custom_adapter import build_tool_call as build_custom_tool_call
|
|
17
|
+
from .tools.custom_adapter import build_tool_definition as build_custom_tool_definition
|
|
23
18
|
from .tools.web_search import (
|
|
24
19
|
build_followup_request,
|
|
25
20
|
build_output_items,
|
|
@@ -28,7 +23,6 @@ from .tools.web_search import (
|
|
|
28
23
|
partition_tool_calls,
|
|
29
24
|
)
|
|
30
25
|
from .trajectory_dump import TrajectoryDumpWriter
|
|
31
|
-
import typing
|
|
32
26
|
|
|
33
27
|
|
|
34
28
|
class UnsupportedIncommingFeature(ValueError):
|
|
@@ -38,23 +32,23 @@ class UnsupportedIncommingFeature(ValueError):
|
|
|
38
32
|
class OutcommingChatError(RuntimeError):
|
|
39
33
|
def __init__(
|
|
40
34
|
self,
|
|
41
|
-
message:
|
|
42
|
-
error_type:
|
|
43
|
-
) ->
|
|
35
|
+
message: "str",
|
|
36
|
+
error_type: "typing.Union[str, None]" = None,
|
|
37
|
+
) -> "None":
|
|
44
38
|
super().__init__(message)
|
|
45
39
|
self.error_type = error_type
|
|
46
40
|
|
|
47
41
|
|
|
48
42
|
class StreamRouter:
|
|
49
|
-
def __init__(self, config:
|
|
43
|
+
def __init__(self, config: "CompatServerConfig") -> "None":
|
|
50
44
|
self._config = config
|
|
51
45
|
self._mock_web_search = WebSearchTool()
|
|
52
46
|
|
|
53
47
|
def _provider_capability(
|
|
54
48
|
self,
|
|
55
|
-
explicit_support:
|
|
56
|
-
default:
|
|
57
|
-
) ->
|
|
49
|
+
explicit_support: "typing.Dict[str, bool]",
|
|
50
|
+
default: "typing.Union[bool, None]" = None,
|
|
51
|
+
) -> "bool":
|
|
58
52
|
provider_name = str(self._config.model_provider or "").strip().lower()
|
|
59
53
|
if provider_name in explicit_support:
|
|
60
54
|
return explicit_support[provider_name]
|
|
@@ -64,7 +58,7 @@ class StreamRouter:
|
|
|
64
58
|
return default
|
|
65
59
|
raise KeyError("provider capability map is missing `vllm` fallback")
|
|
66
60
|
|
|
67
|
-
def _supports_chat_reasoning(self) ->
|
|
61
|
+
def _supports_chat_reasoning(self) -> "bool":
|
|
68
62
|
# Unknown providers inherit the vLLM compatibility behavior unless a
|
|
69
63
|
# provider is explicitly declared otherwise.
|
|
70
64
|
return self._provider_capability(
|
|
@@ -74,7 +68,7 @@ class StreamRouter:
|
|
|
74
68
|
}
|
|
75
69
|
)
|
|
76
70
|
|
|
77
|
-
def _supports_stream_usage(self) ->
|
|
71
|
+
def _supports_stream_usage(self) -> "bool":
|
|
78
72
|
return self._provider_capability(
|
|
79
73
|
{
|
|
80
74
|
"vllm": True,
|
|
@@ -84,8 +78,8 @@ class StreamRouter:
|
|
|
84
78
|
|
|
85
79
|
def validate_incomming_request(
|
|
86
80
|
self,
|
|
87
|
-
incomming_request:
|
|
88
|
-
) ->
|
|
81
|
+
incomming_request: "typing.Dict[str, object]",
|
|
82
|
+
) -> "None":
|
|
89
83
|
model = str(incomming_request.get("model", "")).strip()
|
|
90
84
|
if not model:
|
|
91
85
|
raise UnsupportedIncommingFeature("incomming request is missing `model`")
|
|
@@ -104,11 +98,11 @@ class StreamRouter:
|
|
|
104
98
|
|
|
105
99
|
def collect_custom_tool_names(
|
|
106
100
|
self,
|
|
107
|
-
incomming_request:
|
|
108
|
-
) ->
|
|
101
|
+
incomming_request: "typing.Dict[str, object]",
|
|
102
|
+
) -> "typing.Set[str]":
|
|
109
103
|
return collect_custom_tool_names(incomming_request.get("tools") or [])
|
|
110
104
|
|
|
111
|
-
def list_models(self) ->
|
|
105
|
+
def list_models(self) -> "typing.Dict[str, object]":
|
|
112
106
|
request = urllib.request.Request(
|
|
113
107
|
self._config.outcomming_models_url(),
|
|
114
108
|
headers=self._build_headers(accept="application/json"),
|
|
@@ -118,8 +112,8 @@ class StreamRouter:
|
|
|
118
112
|
|
|
119
113
|
def build_outcomming_request(
|
|
120
114
|
self,
|
|
121
|
-
incomming_request:
|
|
122
|
-
) ->
|
|
115
|
+
incomming_request: "typing.Dict[str, object]",
|
|
116
|
+
) -> "typing.Dict[str, object]":
|
|
123
117
|
model = str(incomming_request.get("model", "")).strip()
|
|
124
118
|
if not model:
|
|
125
119
|
raise UnsupportedIncommingFeature("incomming request is missing `model`")
|
|
@@ -135,7 +129,7 @@ class StreamRouter:
|
|
|
135
129
|
if not isinstance(input_items, list):
|
|
136
130
|
raise UnsupportedIncommingFeature("incomming `input` must be a list")
|
|
137
131
|
|
|
138
|
-
payload:
|
|
132
|
+
payload: "typing.Dict[str, object]" = {
|
|
139
133
|
"model": model,
|
|
140
134
|
"messages": self._responses_input_to_chat_messages(
|
|
141
135
|
instructions,
|
|
@@ -180,7 +174,7 @@ class StreamRouter:
|
|
|
180
174
|
|
|
181
175
|
return payload
|
|
182
176
|
|
|
183
|
-
def open_outcomming_stream(self, outcomming_request:
|
|
177
|
+
def open_outcomming_stream(self, outcomming_request: "typing.Dict[str, object]"):
|
|
184
178
|
outcomming_api = self._config.normalized_outcomming_api()
|
|
185
179
|
if outcomming_api == "messages":
|
|
186
180
|
return self._open_outcomming_messages_stream(outcomming_request)
|
|
@@ -192,7 +186,7 @@ class StreamRouter:
|
|
|
192
186
|
|
|
193
187
|
def _open_outcomming_chat_stream(
|
|
194
188
|
self,
|
|
195
|
-
outcomming_request:
|
|
189
|
+
outcomming_request: "typing.Dict[str, object]",
|
|
196
190
|
):
|
|
197
191
|
request = urllib.request.Request(
|
|
198
192
|
self._config.outcomming_chat_completions_url(),
|
|
@@ -214,7 +208,13 @@ class StreamRouter:
|
|
|
214
208
|
if data == "[DONE]":
|
|
215
209
|
saw_done = True
|
|
216
210
|
break
|
|
217
|
-
|
|
211
|
+
payload = json.loads(data)
|
|
212
|
+
if isinstance(payload, dict) and payload.get("error") is not None:
|
|
213
|
+
raise OutcommingChatError(
|
|
214
|
+
"outcomming chat stream returned an error: "
|
|
215
|
+
+ json.dumps(payload["error"], ensure_ascii=False)[:500]
|
|
216
|
+
)
|
|
217
|
+
yield payload
|
|
218
218
|
if not saw_done:
|
|
219
219
|
raise OutcommingChatError(
|
|
220
220
|
"outcomming chat stream ended before [DONE]"
|
|
@@ -247,7 +247,7 @@ class StreamRouter:
|
|
|
247
247
|
|
|
248
248
|
def _open_outcomming_messages_stream(
|
|
249
249
|
self,
|
|
250
|
-
outcomming_request:
|
|
250
|
+
outcomming_request: "typing.Dict[str, object]",
|
|
251
251
|
):
|
|
252
252
|
try:
|
|
253
253
|
messages_request = build_messages_request(outcomming_request)
|
|
@@ -267,7 +267,7 @@ class StreamRouter:
|
|
|
267
267
|
timeout=self._config.timeout_seconds,
|
|
268
268
|
) as response:
|
|
269
269
|
try:
|
|
270
|
-
stream_state:
|
|
270
|
+
stream_state: "typing.Dict[str, object]" = {}
|
|
271
271
|
for event_name, data in self._iter_sse_events(response):
|
|
272
272
|
if not data:
|
|
273
273
|
continue
|
|
@@ -313,10 +313,10 @@ class StreamRouter:
|
|
|
313
313
|
|
|
314
314
|
def route_stream(
|
|
315
315
|
self,
|
|
316
|
-
stored_response:
|
|
317
|
-
outcomming_request:
|
|
318
|
-
custom_tool_names:
|
|
319
|
-
trajectory_dump:
|
|
316
|
+
stored_response: "StoredResponse",
|
|
317
|
+
outcomming_request: "typing.Dict[str, object]",
|
|
318
|
+
custom_tool_names: "typing.Union[typing.Set[str], None]" = None,
|
|
319
|
+
trajectory_dump: "typing.Union[TrajectoryDumpWriter, None]" = None,
|
|
320
320
|
):
|
|
321
321
|
yield (
|
|
322
322
|
"response.created",
|
|
@@ -331,9 +331,9 @@ class StreamRouter:
|
|
|
331
331
|
},
|
|
332
332
|
)
|
|
333
333
|
|
|
334
|
-
text_parts:
|
|
335
|
-
reasoning_parts:
|
|
336
|
-
latest_usage:
|
|
334
|
+
text_parts: "typing.List[str]" = []
|
|
335
|
+
reasoning_parts: "typing.List[str]" = []
|
|
336
|
+
latest_usage: "typing.Dict[str, object]" = {}
|
|
337
337
|
current_request = json.loads(json.dumps(outcomming_request))
|
|
338
338
|
current_stream = self._open_tracked_outcomming_stream(
|
|
339
339
|
current_request,
|
|
@@ -342,9 +342,9 @@ class StreamRouter:
|
|
|
342
342
|
retried_reasoning_only_output = False
|
|
343
343
|
|
|
344
344
|
while True:
|
|
345
|
-
tool_calls:
|
|
346
|
-
finish_reasons:
|
|
347
|
-
current_usage:
|
|
345
|
+
tool_calls: "typing.Dict[int, typing.Dict[str, object]]" = {}
|
|
346
|
+
finish_reasons: "typing.List[str]" = []
|
|
347
|
+
current_usage: "typing.Dict[str, object]" = {}
|
|
348
348
|
reasoning_start = len(reasoning_parts)
|
|
349
349
|
text_start = len(text_parts)
|
|
350
350
|
for chunk in current_stream:
|
|
@@ -458,8 +458,8 @@ class StreamRouter:
|
|
|
458
458
|
|
|
459
459
|
def _open_tracked_outcomming_stream(
|
|
460
460
|
self,
|
|
461
|
-
outcomming_request:
|
|
462
|
-
trajectory_dump:
|
|
461
|
+
outcomming_request: "typing.Dict[str, object]",
|
|
462
|
+
trajectory_dump: "typing.Union[TrajectoryDumpWriter, None]" = None,
|
|
463
463
|
):
|
|
464
464
|
outcomming_stream = self.open_outcomming_stream(outcomming_request)
|
|
465
465
|
if trajectory_dump is None:
|
|
@@ -471,17 +471,17 @@ class StreamRouter:
|
|
|
471
471
|
|
|
472
472
|
def _responses_input_to_chat_messages(
|
|
473
473
|
self,
|
|
474
|
-
instructions:
|
|
475
|
-
input_items:
|
|
476
|
-
) ->
|
|
477
|
-
messages:
|
|
474
|
+
instructions: "str",
|
|
475
|
+
input_items: "typing.List[object]",
|
|
476
|
+
) -> "typing.List[typing.Dict[str, object]]":
|
|
477
|
+
messages: "typing.List[typing.Dict[str, object]]" = []
|
|
478
478
|
if instructions:
|
|
479
479
|
messages.append({"role": "developer", "content": instructions})
|
|
480
480
|
|
|
481
|
-
pending_assistant:
|
|
482
|
-
pending_tool_images:
|
|
481
|
+
pending_assistant: "typing.Union[typing.Dict[str, object], None]" = None
|
|
482
|
+
pending_tool_images: "typing.List[typing.Dict[str, object]]" = []
|
|
483
483
|
|
|
484
|
-
def flush_pending_assistant() ->
|
|
484
|
+
def flush_pending_assistant() -> "None":
|
|
485
485
|
nonlocal pending_assistant
|
|
486
486
|
if pending_assistant is None:
|
|
487
487
|
return
|
|
@@ -495,7 +495,7 @@ class StreamRouter:
|
|
|
495
495
|
messages.append(pending_assistant)
|
|
496
496
|
pending_assistant = None
|
|
497
497
|
|
|
498
|
-
def flush_pending_tool_images() ->
|
|
498
|
+
def flush_pending_tool_images() -> "None":
|
|
499
499
|
if not pending_tool_images:
|
|
500
500
|
return
|
|
501
501
|
messages.append({"role": "user", "content": list(pending_tool_images)})
|
|
@@ -531,9 +531,9 @@ class StreamRouter:
|
|
|
531
531
|
continue
|
|
532
532
|
flush_pending_assistant()
|
|
533
533
|
if image_parts:
|
|
534
|
-
content:
|
|
535
|
-
|
|
536
|
-
)
|
|
534
|
+
content: "object" = (
|
|
535
|
+
[{"type": "text", "text": text}] if text else []
|
|
536
|
+
) + image_parts
|
|
537
537
|
else:
|
|
538
538
|
content = text
|
|
539
539
|
messages.append({"role": role, "content": content})
|
|
@@ -625,7 +625,7 @@ class StreamRouter:
|
|
|
625
625
|
flush_pending_tool_images()
|
|
626
626
|
return messages
|
|
627
627
|
|
|
628
|
-
def _coerce_positive_int(self, raw_value:
|
|
628
|
+
def _coerce_positive_int(self, raw_value: "object") -> "typing.Union[int, None]":
|
|
629
629
|
if isinstance(raw_value, bool):
|
|
630
630
|
return None
|
|
631
631
|
if isinstance(raw_value, int) and raw_value > 0:
|
|
@@ -634,8 +634,8 @@ class StreamRouter:
|
|
|
634
634
|
|
|
635
635
|
def _split_content_parts(
|
|
636
636
|
self,
|
|
637
|
-
raw_content:
|
|
638
|
-
) ->
|
|
637
|
+
raw_content: "object",
|
|
638
|
+
) -> "typing.Tuple[str, typing.List[typing.Dict[str, object]]]":
|
|
639
639
|
if raw_content is None:
|
|
640
640
|
return "", []
|
|
641
641
|
if isinstance(raw_content, str):
|
|
@@ -645,8 +645,8 @@ class StreamRouter:
|
|
|
645
645
|
"message `content` must be a list or string"
|
|
646
646
|
)
|
|
647
647
|
|
|
648
|
-
text_parts:
|
|
649
|
-
image_parts:
|
|
648
|
+
text_parts: "typing.List[str]" = []
|
|
649
|
+
image_parts: "typing.List[typing.Dict[str, object]]" = []
|
|
650
650
|
for part in raw_content:
|
|
651
651
|
if not isinstance(part, dict):
|
|
652
652
|
raise UnsupportedIncommingFeature(
|
|
@@ -666,14 +666,14 @@ class StreamRouter:
|
|
|
666
666
|
|
|
667
667
|
def _build_chat_image_part(
|
|
668
668
|
self,
|
|
669
|
-
part:
|
|
670
|
-
) ->
|
|
669
|
+
part: "typing.Dict[str, object]",
|
|
670
|
+
) -> "typing.Dict[str, object]":
|
|
671
671
|
image_url = str(part.get("image_url", "") or "").strip()
|
|
672
672
|
if not image_url:
|
|
673
673
|
raise UnsupportedIncommingFeature(
|
|
674
674
|
"`input_image` content parts must carry a non-empty `image_url`"
|
|
675
675
|
)
|
|
676
|
-
image_payload:
|
|
676
|
+
image_payload: "typing.Dict[str, object]" = {"url": image_url}
|
|
677
677
|
detail = part.get("detail")
|
|
678
678
|
if isinstance(detail, str) and detail in {"auto", "low", "high"}:
|
|
679
679
|
image_payload["detail"] = detail
|
|
@@ -681,18 +681,18 @@ class StreamRouter:
|
|
|
681
681
|
|
|
682
682
|
def _split_tool_output_parts(
|
|
683
683
|
self,
|
|
684
|
-
raw_output:
|
|
685
|
-
) ->
|
|
684
|
+
raw_output: "object",
|
|
685
|
+
) -> "typing.Tuple[str, typing.List[typing.Dict[str, object]]]":
|
|
686
686
|
if isinstance(raw_output, str):
|
|
687
687
|
return raw_output, []
|
|
688
688
|
if isinstance(raw_output, list):
|
|
689
689
|
return self._split_content_parts(raw_output)
|
|
690
690
|
return json.dumps(raw_output, ensure_ascii=False), []
|
|
691
691
|
|
|
692
|
-
def _coalesce_reasoning_text(self, raw_item:
|
|
692
|
+
def _coalesce_reasoning_text(self, raw_item: "typing.Dict[str, object]") -> "str":
|
|
693
693
|
content = raw_item.get("content")
|
|
694
694
|
if isinstance(content, list):
|
|
695
|
-
text_parts:
|
|
695
|
+
text_parts: "typing.List[str]" = []
|
|
696
696
|
for part in content:
|
|
697
697
|
if not isinstance(part, dict):
|
|
698
698
|
continue
|
|
@@ -719,8 +719,10 @@ class StreamRouter:
|
|
|
719
719
|
return value
|
|
720
720
|
return ""
|
|
721
721
|
|
|
722
|
-
def _translate_tools(
|
|
723
|
-
|
|
722
|
+
def _translate_tools(
|
|
723
|
+
self, incomming_tools: "typing.List[object]"
|
|
724
|
+
) -> "typing.List[typing.Dict[str, object]]":
|
|
725
|
+
translated: "typing.List[typing.Dict[str, object]]" = []
|
|
724
726
|
for raw_tool in incomming_tools:
|
|
725
727
|
if not isinstance(raw_tool, dict):
|
|
726
728
|
raise UnsupportedIncommingFeature("tool definitions must be objects")
|
|
@@ -755,7 +757,7 @@ class StreamRouter:
|
|
|
755
757
|
)
|
|
756
758
|
return translated
|
|
757
759
|
|
|
758
|
-
def _translate_tool_choice(self, raw_tool_choice:
|
|
760
|
+
def _translate_tool_choice(self, raw_tool_choice: "object") -> "object":
|
|
759
761
|
if isinstance(raw_tool_choice, str):
|
|
760
762
|
return raw_tool_choice
|
|
761
763
|
if not isinstance(raw_tool_choice, dict):
|
|
@@ -778,14 +780,14 @@ class StreamRouter:
|
|
|
778
780
|
|
|
779
781
|
def _consume_chat_chunk(
|
|
780
782
|
self,
|
|
781
|
-
payload:
|
|
782
|
-
reasoning_parts:
|
|
783
|
-
text_parts:
|
|
784
|
-
tool_calls:
|
|
785
|
-
current_usage:
|
|
786
|
-
finish_reasons:
|
|
787
|
-
) ->
|
|
788
|
-
events:
|
|
783
|
+
payload: "typing.Dict[str, object]",
|
|
784
|
+
reasoning_parts: "typing.List[str]",
|
|
785
|
+
text_parts: "typing.List[str]",
|
|
786
|
+
tool_calls: "typing.Dict[int, typing.Dict[str, object]]",
|
|
787
|
+
current_usage: "typing.Dict[str, object]",
|
|
788
|
+
finish_reasons: "typing.List[str]",
|
|
789
|
+
) -> "typing.List[typing.Tuple[str, typing.Dict[str, object]]]":
|
|
790
|
+
events: "typing.List[typing.Tuple[str, typing.Dict[str, object]]]" = []
|
|
789
791
|
usage = payload.get("usage")
|
|
790
792
|
if isinstance(usage, dict):
|
|
791
793
|
self._capture_usage_snapshot(current_usage, usage)
|
|
@@ -876,9 +878,9 @@ class StreamRouter:
|
|
|
876
878
|
|
|
877
879
|
def _capture_usage_snapshot(
|
|
878
880
|
self,
|
|
879
|
-
current_usage:
|
|
880
|
-
usage:
|
|
881
|
-
) ->
|
|
881
|
+
current_usage: "typing.Dict[str, object]",
|
|
882
|
+
usage: "typing.Dict[str, object]",
|
|
883
|
+
) -> "None":
|
|
882
884
|
scalar_mappings = (
|
|
883
885
|
("input_tokens", usage.get("input_tokens", usage.get("prompt_tokens"))),
|
|
884
886
|
(
|
|
@@ -910,12 +912,12 @@ class StreamRouter:
|
|
|
910
912
|
|
|
911
913
|
def _build_output_items(
|
|
912
914
|
self,
|
|
913
|
-
reasoning_parts:
|
|
914
|
-
text_parts:
|
|
915
|
-
tool_calls:
|
|
916
|
-
custom_tool_names:
|
|
917
|
-
) ->
|
|
918
|
-
items:
|
|
915
|
+
reasoning_parts: "typing.List[str]",
|
|
916
|
+
text_parts: "typing.List[str]",
|
|
917
|
+
tool_calls: "typing.Dict[int, typing.Dict[str, object]]",
|
|
918
|
+
custom_tool_names: "typing.Set[str]",
|
|
919
|
+
) -> "typing.List[typing.Dict[str, object]]":
|
|
920
|
+
items: "typing.List[typing.Dict[str, object]]" = []
|
|
919
921
|
reasoning_text = "".join(reasoning_parts)
|
|
920
922
|
if reasoning_text:
|
|
921
923
|
items.append(
|
|
@@ -971,8 +973,7 @@ class StreamRouter:
|
|
|
971
973
|
items.append(
|
|
972
974
|
{
|
|
973
975
|
"type": "function_call",
|
|
974
|
-
"call_id": str(tool_call.get("id", "")).strip()
|
|
975
|
-
or f"call_{index}",
|
|
976
|
+
"call_id": str(tool_call.get("id", "")).strip() or f"call_{index}",
|
|
976
977
|
"name": name,
|
|
977
978
|
"arguments": arguments,
|
|
978
979
|
}
|
|
@@ -980,7 +981,9 @@ class StreamRouter:
|
|
|
980
981
|
|
|
981
982
|
return items
|
|
982
983
|
|
|
983
|
-
def _request_json(
|
|
984
|
+
def _request_json(
|
|
985
|
+
self, request: "urllib.request.Request"
|
|
986
|
+
) -> "typing.Dict[str, object]":
|
|
984
987
|
try:
|
|
985
988
|
with urllib.request.urlopen(
|
|
986
989
|
request,
|
|
@@ -1003,7 +1006,7 @@ class StreamRouter:
|
|
|
1003
1006
|
f"outcomming request failed: {exc.reason}"
|
|
1004
1007
|
) from exc
|
|
1005
1008
|
|
|
1006
|
-
def _build_headers(self, accept:
|
|
1009
|
+
def _build_headers(self, accept: "str") -> "typing.Dict[str, str]":
|
|
1007
1010
|
headers = {
|
|
1008
1011
|
"Accept": accept,
|
|
1009
1012
|
"Content-Type": "application/json",
|
|
@@ -1014,8 +1017,8 @@ class StreamRouter:
|
|
|
1014
1017
|
return headers
|
|
1015
1018
|
|
|
1016
1019
|
def _iter_sse_events(self, response):
|
|
1017
|
-
event_name:
|
|
1018
|
-
data_lines:
|
|
1020
|
+
event_name: "typing.Union[str, None]" = None
|
|
1021
|
+
data_lines: "typing.List[str]" = []
|
|
1019
1022
|
|
|
1020
1023
|
for raw_line in response:
|
|
1021
1024
|
line = raw_line.decode("utf-8", errors="replace").rstrip("\r\n")
|