python-codex 0.1.2__py3-none-any.whl → 0.1.4__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 +89 -51
- pycodex/cli.py +152 -45
- pycodex/collaboration.py +6 -7
- pycodex/compat.py +99 -0
- pycodex/context.py +110 -87
- pycodex/doctor.py +40 -40
- pycodex/model.py +429 -90
- pycodex/portable.py +33 -33
- pycodex/portable_server.py +22 -21
- pycodex/prompts/models.json +30 -0
- 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 +45 -46
- 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/__init__.py +4 -0
- pycodex/utils/compactor.py +189 -0
- pycodex/utils/dotenv.py +6 -6
- pycodex/utils/get_env.py +37 -33
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/session_persist.py +483 -0
- pycodex/utils/visualize.py +197 -83
- {python_codex-0.1.2.dist-info → python_codex-0.1.4.dist-info}/METADATA +32 -11
- python_codex-0.1.4.dist-info/RECORD +76 -0
- {python_codex-0.1.2.dist-info → python_codex-0.1.4.dist-info}/WHEEL +1 -1
- responses_server/app.py +32 -20
- responses_server/config.py +17 -17
- responses_server/payload_processors.py +26 -17
- responses_server/server.py +11 -11
- responses_server/session_store.py +10 -10
- responses_server/stream_router.py +83 -64
- 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.4.dist-info}/entry_points.txt +0 -0
- {python_codex-0.1.2.dist-info → python_codex-0.1.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
1
|
|
|
3
2
|
import json
|
|
3
|
+
import http.client
|
|
4
4
|
import ssl
|
|
5
5
|
import urllib.error
|
|
6
6
|
import urllib.request
|
|
@@ -21,6 +21,7 @@ from .tools.web_search import (
|
|
|
21
21
|
hydrate_tool_call_names,
|
|
22
22
|
partition_tool_calls,
|
|
23
23
|
)
|
|
24
|
+
import typing
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class UnsupportedIncommingFeature(ValueError):
|
|
@@ -32,15 +33,15 @@ class OutcommingChatError(RuntimeError):
|
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
class StreamRouter:
|
|
35
|
-
def __init__(self, config: CompatServerConfig) -> None:
|
|
36
|
+
def __init__(self, config: 'CompatServerConfig') -> 'None':
|
|
36
37
|
self._config = config
|
|
37
38
|
self._mock_web_search = WebSearchTool()
|
|
38
39
|
|
|
39
40
|
def _provider_capability(
|
|
40
41
|
self,
|
|
41
|
-
explicit_support:
|
|
42
|
-
default: bool
|
|
43
|
-
) -> bool:
|
|
42
|
+
explicit_support: 'typing.Dict[str, bool]',
|
|
43
|
+
default: 'typing.Union[bool, None]' = None,
|
|
44
|
+
) -> 'bool':
|
|
44
45
|
provider_name = str(self._config.model_provider or "").strip().lower()
|
|
45
46
|
if provider_name in explicit_support:
|
|
46
47
|
return explicit_support[provider_name]
|
|
@@ -50,7 +51,7 @@ class StreamRouter:
|
|
|
50
51
|
return default
|
|
51
52
|
raise KeyError("provider capability map is missing `vllm` fallback")
|
|
52
53
|
|
|
53
|
-
def _supports_chat_reasoning(self) -> bool:
|
|
54
|
+
def _supports_chat_reasoning(self) -> 'bool':
|
|
54
55
|
# Unknown providers inherit the vLLM compatibility behavior unless a
|
|
55
56
|
# provider is explicitly declared otherwise.
|
|
56
57
|
return self._provider_capability(
|
|
@@ -60,7 +61,7 @@ class StreamRouter:
|
|
|
60
61
|
}
|
|
61
62
|
)
|
|
62
63
|
|
|
63
|
-
def _supports_stream_usage(self) -> bool:
|
|
64
|
+
def _supports_stream_usage(self) -> 'bool':
|
|
64
65
|
return self._provider_capability(
|
|
65
66
|
{
|
|
66
67
|
"vllm": True,
|
|
@@ -70,8 +71,8 @@ class StreamRouter:
|
|
|
70
71
|
|
|
71
72
|
def validate_incomming_request(
|
|
72
73
|
self,
|
|
73
|
-
incomming_request:
|
|
74
|
-
) -> None:
|
|
74
|
+
incomming_request: 'typing.Dict[str, object]',
|
|
75
|
+
) -> 'None':
|
|
75
76
|
model = str(incomming_request.get("model", "")).strip()
|
|
76
77
|
if not model:
|
|
77
78
|
raise UnsupportedIncommingFeature("incomming request is missing `model`")
|
|
@@ -90,11 +91,11 @@ class StreamRouter:
|
|
|
90
91
|
|
|
91
92
|
def collect_custom_tool_names(
|
|
92
93
|
self,
|
|
93
|
-
incomming_request:
|
|
94
|
-
) ->
|
|
94
|
+
incomming_request: 'typing.Dict[str, object]',
|
|
95
|
+
) -> 'typing.Set[str]':
|
|
95
96
|
return collect_custom_tool_names(incomming_request.get("tools") or [])
|
|
96
97
|
|
|
97
|
-
def list_models(self) ->
|
|
98
|
+
def list_models(self) -> 'typing.Dict[str, object]':
|
|
98
99
|
request = urllib.request.Request(
|
|
99
100
|
self._config.outcomming_models_url(),
|
|
100
101
|
headers=self._build_headers(accept="application/json"),
|
|
@@ -104,8 +105,8 @@ class StreamRouter:
|
|
|
104
105
|
|
|
105
106
|
def build_outcomming_request(
|
|
106
107
|
self,
|
|
107
|
-
incomming_request:
|
|
108
|
-
) ->
|
|
108
|
+
incomming_request: 'typing.Dict[str, object]',
|
|
109
|
+
) -> 'typing.Dict[str, object]':
|
|
109
110
|
model = str(incomming_request.get("model", "")).strip()
|
|
110
111
|
if not model:
|
|
111
112
|
raise UnsupportedIncommingFeature("incomming request is missing `model`")
|
|
@@ -121,7 +122,7 @@ class StreamRouter:
|
|
|
121
122
|
if not isinstance(input_items, list):
|
|
122
123
|
raise UnsupportedIncommingFeature("incomming `input` must be a list")
|
|
123
124
|
|
|
124
|
-
payload:
|
|
125
|
+
payload: 'typing.Dict[str, object]' = {
|
|
125
126
|
"model": model,
|
|
126
127
|
"messages": self._responses_input_to_chat_messages(
|
|
127
128
|
instructions,
|
|
@@ -148,7 +149,7 @@ class StreamRouter:
|
|
|
148
149
|
|
|
149
150
|
return payload
|
|
150
151
|
|
|
151
|
-
def open_outcomming_stream(self, outcomming_request:
|
|
152
|
+
def open_outcomming_stream(self, outcomming_request: 'typing.Dict[str, object]'):
|
|
152
153
|
request = urllib.request.Request(
|
|
153
154
|
self._config.outcomming_chat_completions_url(),
|
|
154
155
|
data=json.dumps(outcomming_request).encode("utf-8"),
|
|
@@ -161,12 +162,30 @@ class StreamRouter:
|
|
|
161
162
|
context=ssl.create_default_context(),
|
|
162
163
|
timeout=self._config.timeout_seconds,
|
|
163
164
|
) as response:
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
try:
|
|
166
|
+
saw_done = False
|
|
167
|
+
for _event_name, data in self._iter_sse_events(response):
|
|
168
|
+
if not data:
|
|
169
|
+
continue
|
|
170
|
+
if data == "[DONE]":
|
|
171
|
+
saw_done = True
|
|
172
|
+
break
|
|
173
|
+
yield json.loads(data)
|
|
174
|
+
if not saw_done:
|
|
175
|
+
raise OutcommingChatError(
|
|
176
|
+
"outcomming chat stream ended before [DONE]"
|
|
177
|
+
)
|
|
178
|
+
except (
|
|
179
|
+
ConnectionError,
|
|
180
|
+
EOFError,
|
|
181
|
+
OSError,
|
|
182
|
+
http.client.HTTPException,
|
|
183
|
+
json.JSONDecodeError,
|
|
184
|
+
) as exc:
|
|
185
|
+
raise OutcommingChatError(
|
|
186
|
+
"outcomming chat stream failed while reading response body: "
|
|
187
|
+
f"{exc}"
|
|
188
|
+
) from exc
|
|
170
189
|
except urllib.error.HTTPError as exc:
|
|
171
190
|
body = exc.read().decode("utf-8", errors="replace")
|
|
172
191
|
raise OutcommingChatError(
|
|
@@ -180,9 +199,9 @@ class StreamRouter:
|
|
|
180
199
|
def route_stream(
|
|
181
200
|
self,
|
|
182
201
|
incomming_stream,
|
|
183
|
-
stored_response: StoredResponse,
|
|
184
|
-
outcomming_request:
|
|
185
|
-
custom_tool_names:
|
|
202
|
+
stored_response: 'StoredResponse',
|
|
203
|
+
outcomming_request: 'typing.Dict[str, object]',
|
|
204
|
+
custom_tool_names: 'typing.Union[typing.Set[str], None]' = None,
|
|
186
205
|
):
|
|
187
206
|
yield (
|
|
188
207
|
"response.created",
|
|
@@ -197,15 +216,15 @@ class StreamRouter:
|
|
|
197
216
|
},
|
|
198
217
|
)
|
|
199
218
|
|
|
200
|
-
text_parts:
|
|
201
|
-
reasoning_parts:
|
|
202
|
-
latest_usage:
|
|
219
|
+
text_parts: 'typing.List[str]' = []
|
|
220
|
+
reasoning_parts: 'typing.List[str]' = []
|
|
221
|
+
latest_usage: 'typing.Dict[str, object]' = {}
|
|
203
222
|
current_request = json.loads(json.dumps(outcomming_request))
|
|
204
223
|
current_stream = incomming_stream
|
|
205
224
|
|
|
206
225
|
while True:
|
|
207
|
-
tool_calls:
|
|
208
|
-
current_usage:
|
|
226
|
+
tool_calls: 'typing.Dict[int, typing.Dict[str, object]]' = {}
|
|
227
|
+
current_usage: 'typing.Dict[str, object]' = {}
|
|
209
228
|
for chunk in current_stream:
|
|
210
229
|
for event_name, payload in self._consume_chat_chunk(
|
|
211
230
|
chunk,
|
|
@@ -290,16 +309,16 @@ class StreamRouter:
|
|
|
290
309
|
|
|
291
310
|
def _responses_input_to_chat_messages(
|
|
292
311
|
self,
|
|
293
|
-
instructions: str,
|
|
294
|
-
input_items:
|
|
295
|
-
) ->
|
|
296
|
-
messages:
|
|
312
|
+
instructions: 'str',
|
|
313
|
+
input_items: 'typing.List[object]',
|
|
314
|
+
) -> 'typing.List[typing.Dict[str, object]]':
|
|
315
|
+
messages: 'typing.List[typing.Dict[str, object]]' = []
|
|
297
316
|
if instructions:
|
|
298
317
|
messages.append({"role": "developer", "content": instructions})
|
|
299
318
|
|
|
300
|
-
pending_assistant:
|
|
319
|
+
pending_assistant: 'typing.Union[typing.Dict[str, object], None]' = None
|
|
301
320
|
|
|
302
|
-
def flush_pending_assistant() -> None:
|
|
321
|
+
def flush_pending_assistant() -> 'None':
|
|
303
322
|
nonlocal pending_assistant
|
|
304
323
|
if pending_assistant is None:
|
|
305
324
|
return
|
|
@@ -420,7 +439,7 @@ class StreamRouter:
|
|
|
420
439
|
flush_pending_assistant()
|
|
421
440
|
return messages
|
|
422
441
|
|
|
423
|
-
def _coalesce_content_text(self, raw_content: object) -> str:
|
|
442
|
+
def _coalesce_content_text(self, raw_content: 'object') -> 'str':
|
|
424
443
|
if raw_content is None:
|
|
425
444
|
return ""
|
|
426
445
|
if isinstance(raw_content, str):
|
|
@@ -430,7 +449,7 @@ class StreamRouter:
|
|
|
430
449
|
"message `content` must be a list or string"
|
|
431
450
|
)
|
|
432
451
|
|
|
433
|
-
text_parts:
|
|
452
|
+
text_parts: 'typing.List[str]' = []
|
|
434
453
|
for part in raw_content:
|
|
435
454
|
if not isinstance(part, dict):
|
|
436
455
|
raise UnsupportedIncommingFeature(
|
|
@@ -445,17 +464,17 @@ class StreamRouter:
|
|
|
445
464
|
)
|
|
446
465
|
return "".join(text_parts)
|
|
447
466
|
|
|
448
|
-
def _coalesce_tool_output_text(self, raw_output: object) -> str:
|
|
467
|
+
def _coalesce_tool_output_text(self, raw_output: 'object') -> 'str':
|
|
449
468
|
if isinstance(raw_output, str):
|
|
450
469
|
return raw_output
|
|
451
470
|
if isinstance(raw_output, list):
|
|
452
471
|
return self._coalesce_content_text(raw_output)
|
|
453
472
|
return json.dumps(raw_output, ensure_ascii=False)
|
|
454
473
|
|
|
455
|
-
def _coalesce_reasoning_text(self, raw_item:
|
|
474
|
+
def _coalesce_reasoning_text(self, raw_item: 'typing.Dict[str, object]') -> 'str':
|
|
456
475
|
content = raw_item.get("content")
|
|
457
476
|
if isinstance(content, list):
|
|
458
|
-
text_parts:
|
|
477
|
+
text_parts: 'typing.List[str]' = []
|
|
459
478
|
for part in content:
|
|
460
479
|
if not isinstance(part, dict):
|
|
461
480
|
continue
|
|
@@ -482,8 +501,8 @@ class StreamRouter:
|
|
|
482
501
|
return value
|
|
483
502
|
return ""
|
|
484
503
|
|
|
485
|
-
def _translate_tools(self, incomming_tools:
|
|
486
|
-
translated:
|
|
504
|
+
def _translate_tools(self, incomming_tools: 'typing.List[object]') -> 'typing.List[typing.Dict[str, object]]':
|
|
505
|
+
translated: 'typing.List[typing.Dict[str, object]]' = []
|
|
487
506
|
for raw_tool in incomming_tools:
|
|
488
507
|
if not isinstance(raw_tool, dict):
|
|
489
508
|
raise UnsupportedIncommingFeature("tool definitions must be objects")
|
|
@@ -518,7 +537,7 @@ class StreamRouter:
|
|
|
518
537
|
)
|
|
519
538
|
return translated
|
|
520
539
|
|
|
521
|
-
def _translate_tool_choice(self, raw_tool_choice: object) -> object:
|
|
540
|
+
def _translate_tool_choice(self, raw_tool_choice: 'object') -> 'object':
|
|
522
541
|
if isinstance(raw_tool_choice, str):
|
|
523
542
|
return raw_tool_choice
|
|
524
543
|
if not isinstance(raw_tool_choice, dict):
|
|
@@ -541,13 +560,13 @@ class StreamRouter:
|
|
|
541
560
|
|
|
542
561
|
def _consume_chat_chunk(
|
|
543
562
|
self,
|
|
544
|
-
payload:
|
|
545
|
-
reasoning_parts:
|
|
546
|
-
text_parts:
|
|
547
|
-
tool_calls:
|
|
548
|
-
current_usage:
|
|
549
|
-
) ->
|
|
550
|
-
events:
|
|
563
|
+
payload: 'typing.Dict[str, object]',
|
|
564
|
+
reasoning_parts: 'typing.List[str]',
|
|
565
|
+
text_parts: 'typing.List[str]',
|
|
566
|
+
tool_calls: 'typing.Dict[int, typing.Dict[str, object]]',
|
|
567
|
+
current_usage: 'typing.Dict[str, object]',
|
|
568
|
+
) -> 'typing.List[typing.Tuple[str, typing.Dict[str, object]]]':
|
|
569
|
+
events: 'typing.List[typing.Tuple[str, typing.Dict[str, object]]]' = []
|
|
551
570
|
usage = payload.get("usage")
|
|
552
571
|
if isinstance(usage, dict):
|
|
553
572
|
self._capture_usage_snapshot(current_usage, usage)
|
|
@@ -627,9 +646,9 @@ class StreamRouter:
|
|
|
627
646
|
|
|
628
647
|
def _capture_usage_snapshot(
|
|
629
648
|
self,
|
|
630
|
-
current_usage:
|
|
631
|
-
usage:
|
|
632
|
-
) -> None:
|
|
649
|
+
current_usage: 'typing.Dict[str, object]',
|
|
650
|
+
usage: 'typing.Dict[str, object]',
|
|
651
|
+
) -> 'None':
|
|
633
652
|
scalar_mappings = (
|
|
634
653
|
("input_tokens", usage.get("input_tokens", usage.get("prompt_tokens"))),
|
|
635
654
|
(
|
|
@@ -661,12 +680,12 @@ class StreamRouter:
|
|
|
661
680
|
|
|
662
681
|
def _build_output_items(
|
|
663
682
|
self,
|
|
664
|
-
reasoning_parts:
|
|
665
|
-
text_parts:
|
|
666
|
-
tool_calls:
|
|
667
|
-
custom_tool_names:
|
|
668
|
-
) ->
|
|
669
|
-
items:
|
|
683
|
+
reasoning_parts: 'typing.List[str]',
|
|
684
|
+
text_parts: 'typing.List[str]',
|
|
685
|
+
tool_calls: 'typing.Dict[int, typing.Dict[str, object]]',
|
|
686
|
+
custom_tool_names: 'typing.Set[str]',
|
|
687
|
+
) -> 'typing.List[typing.Dict[str, object]]':
|
|
688
|
+
items: 'typing.List[typing.Dict[str, object]]' = []
|
|
670
689
|
reasoning_text = "".join(reasoning_parts)
|
|
671
690
|
if reasoning_text:
|
|
672
691
|
items.append(
|
|
@@ -731,7 +750,7 @@ class StreamRouter:
|
|
|
731
750
|
|
|
732
751
|
return items
|
|
733
752
|
|
|
734
|
-
def _request_json(self, request: urllib.request.Request) ->
|
|
753
|
+
def _request_json(self, request: 'urllib.request.Request') -> 'typing.Dict[str, object]':
|
|
735
754
|
try:
|
|
736
755
|
with urllib.request.urlopen(
|
|
737
756
|
request,
|
|
@@ -749,7 +768,7 @@ class StreamRouter:
|
|
|
749
768
|
f"outcomming request failed: {exc.reason}"
|
|
750
769
|
) from exc
|
|
751
770
|
|
|
752
|
-
def _build_headers(self, accept: str) ->
|
|
771
|
+
def _build_headers(self, accept: 'str') -> 'typing.Dict[str, str]':
|
|
753
772
|
headers = {
|
|
754
773
|
"Accept": accept,
|
|
755
774
|
"Content-Type": "application/json",
|
|
@@ -760,8 +779,8 @@ class StreamRouter:
|
|
|
760
779
|
return headers
|
|
761
780
|
|
|
762
781
|
def _iter_sse_events(self, response):
|
|
763
|
-
event_name: str
|
|
764
|
-
data_lines:
|
|
782
|
+
event_name: 'typing.Union[str, None]' = None
|
|
783
|
+
data_lines: 'typing.List[str]' = []
|
|
765
784
|
|
|
766
785
|
for raw_line in response:
|
|
767
786
|
line = raw_line.decode("utf-8", errors="replace").rstrip("\r\n")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
1
|
|
|
3
2
|
from copy import deepcopy
|
|
4
3
|
import json
|
|
4
|
+
import typing
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class CustomToolAdapterError(ValueError):
|
|
@@ -82,8 +82,8 @@ It is important to remember:
|
|
|
82
82
|
"""
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
def collect_custom_tool_names(raw_tools: object) ->
|
|
86
|
-
names:
|
|
85
|
+
def collect_custom_tool_names(raw_tools: 'object') -> 'typing.Set[str]':
|
|
86
|
+
names: 'typing.Set[str]' = set()
|
|
87
87
|
if not isinstance(raw_tools, list):
|
|
88
88
|
return names
|
|
89
89
|
for raw_tool in raw_tools:
|
|
@@ -95,7 +95,7 @@ def collect_custom_tool_names(raw_tools: object) -> set[str]:
|
|
|
95
95
|
return names
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
def build_tool_definition(raw_tool:
|
|
98
|
+
def build_tool_definition(raw_tool: 'typing.Dict[str, object]') -> 'typing.Dict[str, object]':
|
|
99
99
|
name = _required_tool_name(raw_tool)
|
|
100
100
|
description = _build_description(raw_tool)
|
|
101
101
|
input_description = (
|
|
@@ -125,7 +125,7 @@ def build_tool_definition(raw_tool: dict[str, object]) -> dict[str, object]:
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
|
|
128
|
-
def build_tool_call(raw_item:
|
|
128
|
+
def build_tool_call(raw_item: 'typing.Dict[str, object]') -> 'typing.Dict[str, object]':
|
|
129
129
|
name = _required_item_name(raw_item)
|
|
130
130
|
return {
|
|
131
131
|
"id": str(raw_item.get("call_id", "")).strip() or name,
|
|
@@ -141,7 +141,7 @@ def build_tool_call(raw_item: dict[str, object]) -> dict[str, object]:
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
|
|
144
|
-
def build_output_item(tool_call:
|
|
144
|
+
def build_output_item(tool_call: 'typing.Dict[str, object]', index: 'int') -> 'typing.Dict[str, object]':
|
|
145
145
|
function = tool_call.get("function") or {}
|
|
146
146
|
if not isinstance(function, dict):
|
|
147
147
|
raise CustomToolAdapterError(
|
|
@@ -160,7 +160,7 @@ def build_output_item(tool_call: dict[str, object], index: int) -> dict[str, obj
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
|
|
163
|
-
def extract_input_text(raw_arguments: object) -> str:
|
|
163
|
+
def extract_input_text(raw_arguments: 'object') -> 'str':
|
|
164
164
|
if isinstance(raw_arguments, dict):
|
|
165
165
|
parsed = deepcopy(raw_arguments)
|
|
166
166
|
else:
|
|
@@ -186,7 +186,7 @@ def extract_input_text(raw_arguments: object) -> str:
|
|
|
186
186
|
return str(raw_arguments or "")
|
|
187
187
|
|
|
188
188
|
|
|
189
|
-
def _build_description(raw_tool:
|
|
189
|
+
def _build_description(raw_tool: 'typing.Dict[str, object]') -> 'str':
|
|
190
190
|
name = _tool_name(raw_tool)
|
|
191
191
|
if name == APPLY_PATCH_NAME:
|
|
192
192
|
return APPLY_PATCH_CHAT_DESCRIPTION
|
|
@@ -200,7 +200,7 @@ def _build_description(raw_tool: dict[str, object]) -> str:
|
|
|
200
200
|
|
|
201
201
|
raw_format = raw_tool.get("format")
|
|
202
202
|
if isinstance(raw_format, dict):
|
|
203
|
-
format_lines:
|
|
203
|
+
format_lines: 'typing.List[str]' = []
|
|
204
204
|
format_type = str(raw_format.get("type", "")).strip()
|
|
205
205
|
syntax = str(raw_format.get("syntax", "")).strip()
|
|
206
206
|
definition = str(raw_format.get("definition", "") or "").strip()
|
|
@@ -217,18 +217,18 @@ def _build_description(raw_tool: dict[str, object]) -> str:
|
|
|
217
217
|
return "\n\n".join(parts)
|
|
218
218
|
|
|
219
219
|
|
|
220
|
-
def _tool_name(raw_tool:
|
|
220
|
+
def _tool_name(raw_tool: 'typing.Dict[str, object]') -> 'str':
|
|
221
221
|
return str(raw_tool.get("name", "")).strip()
|
|
222
222
|
|
|
223
223
|
|
|
224
|
-
def _required_tool_name(raw_tool:
|
|
224
|
+
def _required_tool_name(raw_tool: 'typing.Dict[str, object]') -> 'str':
|
|
225
225
|
name = _tool_name(raw_tool)
|
|
226
226
|
if not name:
|
|
227
227
|
raise CustomToolAdapterError("custom tool definition is missing `name`")
|
|
228
228
|
return name
|
|
229
229
|
|
|
230
230
|
|
|
231
|
-
def _required_item_name(raw_item:
|
|
231
|
+
def _required_item_name(raw_item: 'typing.Dict[str, object]') -> 'str':
|
|
232
232
|
name = str(raw_item.get("name", "")).strip()
|
|
233
233
|
if not name:
|
|
234
234
|
raise CustomToolAdapterError("custom tool call is missing `name`")
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
1
|
|
|
3
2
|
from copy import deepcopy
|
|
4
3
|
import json
|
|
5
4
|
|
|
6
5
|
from pycodex.protocol import JSONValue
|
|
7
6
|
from pycodex.tools.base_tool import BaseTool, ToolContext
|
|
7
|
+
import typing
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class WebSearchTool(BaseTool):
|
|
@@ -29,10 +29,10 @@ class WebSearchTool(BaseTool):
|
|
|
29
29
|
}
|
|
30
30
|
supports_parallel = False
|
|
31
31
|
|
|
32
|
-
async def run(self, context: ToolContext, args: JSONValue) -> JSONValue:
|
|
32
|
+
async def run(self, context: 'ToolContext', args: 'JSONValue') -> 'JSONValue':
|
|
33
33
|
del context
|
|
34
34
|
query, queries = extract_queries(args)
|
|
35
|
-
output_payload:
|
|
35
|
+
output_payload: 'typing.Dict[str, object]' = {
|
|
36
36
|
"results": [],
|
|
37
37
|
"mock": True,
|
|
38
38
|
}
|
|
@@ -43,7 +43,7 @@ class WebSearchTool(BaseTool):
|
|
|
43
43
|
return output_payload
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
def build_tool_definition(tool: WebSearchTool) ->
|
|
46
|
+
def build_tool_definition(tool: 'WebSearchTool') -> 'typing.Dict[str, object]':
|
|
47
47
|
return {
|
|
48
48
|
"type": "function",
|
|
49
49
|
"name": tool.name,
|
|
@@ -57,13 +57,13 @@ def build_tool_definition(tool: WebSearchTool) -> dict[str, object]:
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def partition_tool_calls(
|
|
60
|
-
tool: WebSearchTool,
|
|
61
|
-
tool_calls:
|
|
62
|
-
outcomming_request:
|
|
63
|
-
) ->
|
|
60
|
+
tool: 'WebSearchTool',
|
|
61
|
+
tool_calls: 'typing.Dict[int, typing.Dict[str, object]]',
|
|
62
|
+
outcomming_request: 'typing.Dict[str, object]',
|
|
63
|
+
) -> 'typing.Tuple[typing.List[typing.Dict[str, object]], typing.Dict[int, typing.Dict[str, object]]]':
|
|
64
64
|
mock_tool_names = _collect_mock_tool_names(tool, outcomming_request)
|
|
65
|
-
mock_calls:
|
|
66
|
-
ordinary_tool_calls:
|
|
65
|
+
mock_calls: 'typing.List[typing.Dict[str, object]]' = []
|
|
66
|
+
ordinary_tool_calls: 'typing.Dict[int, typing.Dict[str, object]]' = {}
|
|
67
67
|
for index in sorted(tool_calls):
|
|
68
68
|
tool_call = tool_calls[index]
|
|
69
69
|
function = tool_call.get("function") or {}
|
|
@@ -78,9 +78,9 @@ def partition_tool_calls(
|
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
def hydrate_tool_call_names(
|
|
81
|
-
tool_calls:
|
|
82
|
-
outcomming_request:
|
|
83
|
-
) -> None:
|
|
81
|
+
tool_calls: 'typing.Dict[int, typing.Dict[str, object]]',
|
|
82
|
+
outcomming_request: 'typing.Dict[str, object]',
|
|
83
|
+
) -> 'None':
|
|
84
84
|
raw_tools = outcomming_request.get("tools") or []
|
|
85
85
|
if not isinstance(raw_tools, list):
|
|
86
86
|
return
|
|
@@ -104,15 +104,15 @@ def hydrate_tool_call_names(
|
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
def build_output_items(
|
|
107
|
-
mock_search_calls:
|
|
108
|
-
) ->
|
|
109
|
-
items:
|
|
107
|
+
mock_search_calls: 'typing.List[typing.Dict[str, object]]',
|
|
108
|
+
) -> 'typing.List[typing.Dict[str, object]]':
|
|
109
|
+
items: 'typing.List[typing.Dict[str, object]]' = []
|
|
110
110
|
for tool_call in mock_search_calls:
|
|
111
111
|
function = tool_call.get("function") or {}
|
|
112
112
|
if not isinstance(function, dict):
|
|
113
113
|
continue
|
|
114
114
|
query, queries = extract_queries(function.get("arguments"))
|
|
115
|
-
action:
|
|
115
|
+
action: 'typing.Dict[str, object]' = {"type": "search"}
|
|
116
116
|
if query:
|
|
117
117
|
action["query"] = query
|
|
118
118
|
if queries:
|
|
@@ -128,17 +128,17 @@ def build_output_items(
|
|
|
128
128
|
|
|
129
129
|
|
|
130
130
|
def build_followup_request(
|
|
131
|
-
tool: WebSearchTool,
|
|
132
|
-
outcomming_request:
|
|
133
|
-
mock_search_calls:
|
|
134
|
-
reasoning_text: str
|
|
135
|
-
) ->
|
|
131
|
+
tool: 'WebSearchTool',
|
|
132
|
+
outcomming_request: 'typing.Dict[str, object]',
|
|
133
|
+
mock_search_calls: 'typing.List[typing.Dict[str, object]]',
|
|
134
|
+
reasoning_text: 'typing.Union[str, None]' = None,
|
|
135
|
+
) -> 'typing.Dict[str, object]':
|
|
136
136
|
followup_request = deepcopy(outcomming_request)
|
|
137
137
|
messages = followup_request.get("messages") or []
|
|
138
138
|
if not isinstance(messages, list):
|
|
139
139
|
raise ValueError("outcomming request messages must be a list")
|
|
140
140
|
|
|
141
|
-
assistant_tool_calls:
|
|
141
|
+
assistant_tool_calls: 'typing.List[typing.Dict[str, object]]' = []
|
|
142
142
|
for tool_call in mock_search_calls:
|
|
143
143
|
function = tool_call.get("function") or {}
|
|
144
144
|
if not isinstance(function, dict):
|
|
@@ -154,7 +154,7 @@ def build_followup_request(
|
|
|
154
154
|
}
|
|
155
155
|
)
|
|
156
156
|
if assistant_tool_calls:
|
|
157
|
-
assistant_message:
|
|
157
|
+
assistant_message: 'typing.Dict[str, object]' = {
|
|
158
158
|
"role": "assistant",
|
|
159
159
|
"tool_calls": assistant_tool_calls,
|
|
160
160
|
}
|
|
@@ -187,7 +187,7 @@ def build_followup_request(
|
|
|
187
187
|
return followup_request
|
|
188
188
|
|
|
189
189
|
|
|
190
|
-
def extract_queries(raw_arguments: JSONValue) ->
|
|
190
|
+
def extract_queries(raw_arguments: 'JSONValue') -> 'typing.Tuple[str, typing.List[str]]':
|
|
191
191
|
if isinstance(raw_arguments, dict):
|
|
192
192
|
parsed = raw_arguments
|
|
193
193
|
else:
|
|
@@ -211,7 +211,7 @@ def extract_queries(raw_arguments: JSONValue) -> tuple[str, list[str]]:
|
|
|
211
211
|
|
|
212
212
|
query = str(parsed.get("query", "")).strip()
|
|
213
213
|
queries_value = parsed.get("queries") or []
|
|
214
|
-
queries:
|
|
214
|
+
queries: 'typing.List[str]' = []
|
|
215
215
|
if isinstance(queries_value, list):
|
|
216
216
|
for value in queries_value:
|
|
217
217
|
normalized = str(value).strip()
|
|
@@ -224,7 +224,7 @@ def extract_queries(raw_arguments: JSONValue) -> tuple[str, list[str]]:
|
|
|
224
224
|
return query, queries
|
|
225
225
|
|
|
226
226
|
|
|
227
|
-
def is_mock_tool(tool: WebSearchTool, raw_tool: object) -> bool:
|
|
227
|
+
def is_mock_tool(tool: 'WebSearchTool', raw_tool: 'object') -> 'bool':
|
|
228
228
|
if not isinstance(raw_tool, dict) or raw_tool.get("type") != "function":
|
|
229
229
|
return False
|
|
230
230
|
function = raw_tool.get("function") or {}
|
|
@@ -237,10 +237,10 @@ def is_mock_tool(tool: WebSearchTool, raw_tool: object) -> bool:
|
|
|
237
237
|
|
|
238
238
|
|
|
239
239
|
def _collect_mock_tool_names(
|
|
240
|
-
tool: WebSearchTool,
|
|
241
|
-
outcomming_request:
|
|
242
|
-
) ->
|
|
243
|
-
names:
|
|
240
|
+
tool: 'WebSearchTool',
|
|
241
|
+
outcomming_request: 'typing.Dict[str, object]',
|
|
242
|
+
) -> 'typing.Set[str]':
|
|
243
|
+
names: 'typing.Set[str]' = set()
|
|
244
244
|
raw_tools = outcomming_request.get("tools") or []
|
|
245
245
|
if not isinstance(raw_tools, list):
|
|
246
246
|
return names
|
|
@@ -250,9 +250,9 @@ def _collect_mock_tool_names(
|
|
|
250
250
|
return names
|
|
251
251
|
|
|
252
252
|
|
|
253
|
-
def _build_mock_output(raw_arguments: JSONValue) ->
|
|
253
|
+
def _build_mock_output(raw_arguments: 'JSONValue') -> 'typing.Dict[str, object]':
|
|
254
254
|
query, queries = extract_queries(raw_arguments)
|
|
255
|
-
output_payload:
|
|
255
|
+
output_payload: 'typing.Dict[str, object]' = {
|
|
256
256
|
"results": [],
|
|
257
257
|
"mock": True,
|
|
258
258
|
}
|