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.
Files changed (60) hide show
  1. pycodex/__init__.py +5 -1
  2. pycodex/agent.py +89 -51
  3. pycodex/cli.py +152 -45
  4. pycodex/collaboration.py +6 -7
  5. pycodex/compat.py +99 -0
  6. pycodex/context.py +110 -87
  7. pycodex/doctor.py +40 -40
  8. pycodex/model.py +429 -90
  9. pycodex/portable.py +33 -33
  10. pycodex/portable_server.py +22 -21
  11. pycodex/prompts/models.json +30 -0
  12. pycodex/protocol.py +84 -86
  13. pycodex/runtime.py +36 -35
  14. pycodex/runtime_services.py +69 -69
  15. pycodex/tools/agent_tool_schemas.py +0 -2
  16. pycodex/tools/apply_patch_tool.py +45 -46
  17. pycodex/tools/base_tool.py +35 -36
  18. pycodex/tools/close_agent_tool.py +2 -4
  19. pycodex/tools/code_mode_manager.py +61 -61
  20. pycodex/tools/exec_command_tool.py +5 -6
  21. pycodex/tools/exec_runtime.js +3 -3
  22. pycodex/tools/exec_tool.py +2 -4
  23. pycodex/tools/grep_files_tool.py +10 -11
  24. pycodex/tools/list_dir_tool.py +8 -9
  25. pycodex/tools/read_file_tool.py +13 -14
  26. pycodex/tools/request_permissions_tool.py +2 -4
  27. pycodex/tools/request_user_input_tool.py +13 -14
  28. pycodex/tools/resume_agent_tool.py +2 -4
  29. pycodex/tools/send_input_tool.py +8 -9
  30. pycodex/tools/shell_command_tool.py +5 -6
  31. pycodex/tools/shell_tool.py +5 -6
  32. pycodex/tools/spawn_agent_tool.py +4 -5
  33. pycodex/tools/unified_exec_manager.py +62 -61
  34. pycodex/tools/update_plan_tool.py +4 -5
  35. pycodex/tools/view_image_tool.py +4 -5
  36. pycodex/tools/wait_agent_tool.py +2 -4
  37. pycodex/tools/wait_tool.py +4 -5
  38. pycodex/tools/web_search_tool.py +1 -3
  39. pycodex/tools/write_stdin_tool.py +4 -5
  40. pycodex/utils/__init__.py +4 -0
  41. pycodex/utils/compactor.py +189 -0
  42. pycodex/utils/dotenv.py +6 -6
  43. pycodex/utils/get_env.py +37 -33
  44. pycodex/utils/random_ids.py +1 -2
  45. pycodex/utils/session_persist.py +483 -0
  46. pycodex/utils/visualize.py +197 -83
  47. {python_codex-0.1.2.dist-info → python_codex-0.1.4.dist-info}/METADATA +32 -11
  48. python_codex-0.1.4.dist-info/RECORD +76 -0
  49. {python_codex-0.1.2.dist-info → python_codex-0.1.4.dist-info}/WHEEL +1 -1
  50. responses_server/app.py +32 -20
  51. responses_server/config.py +17 -17
  52. responses_server/payload_processors.py +26 -17
  53. responses_server/server.py +11 -11
  54. responses_server/session_store.py +10 -10
  55. responses_server/stream_router.py +83 -64
  56. responses_server/tools/custom_adapter.py +12 -12
  57. responses_server/tools/web_search.py +33 -33
  58. python_codex-0.1.2.dist-info/RECORD +0 -73
  59. {python_codex-0.1.2.dist-info → python_codex-0.1.4.dist-info}/entry_points.txt +0 -0
  60. {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: dict[str, bool],
42
- default: bool | None = None,
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: dict[str, object],
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: dict[str, object],
94
- ) -> set[str]:
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) -> dict[str, object]:
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: dict[str, object],
108
- ) -> dict[str, object]:
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: dict[str, object] = {
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: dict[str, object]):
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
- for _event_name, data in self._iter_sse_events(response):
165
- if not data:
166
- continue
167
- if data == "[DONE]":
168
- break
169
- yield json.loads(data)
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: dict[str, object],
185
- custom_tool_names: set[str] | None = None,
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: list[str] = []
201
- reasoning_parts: list[str] = []
202
- latest_usage: dict[str, object] = {}
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: dict[int, dict[str, object]] = {}
208
- current_usage: dict[str, object] = {}
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: list[object],
295
- ) -> list[dict[str, object]]:
296
- messages: list[dict[str, object]] = []
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: dict[str, object] | None = None
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: list[str] = []
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: dict[str, object]) -> str:
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: list[str] = []
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: list[object]) -> list[dict[str, object]]:
486
- translated: list[dict[str, object]] = []
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: dict[str, object],
545
- reasoning_parts: list[str],
546
- text_parts: list[str],
547
- tool_calls: dict[int, dict[str, object]],
548
- current_usage: dict[str, object],
549
- ) -> list[tuple[str, dict[str, object]]]:
550
- events: list[tuple[str, dict[str, object]]] = []
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: dict[str, object],
631
- usage: dict[str, object],
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: list[str],
665
- text_parts: list[str],
666
- tool_calls: dict[int, dict[str, object]],
667
- custom_tool_names: set[str],
668
- ) -> list[dict[str, object]]:
669
- items: list[dict[str, object]] = []
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) -> dict[str, object]:
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) -> dict[str, 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 | None = None
764
- data_lines: list[str] = []
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) -> set[str]:
86
- names: set[str] = set()
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: dict[str, object]) -> dict[str, object]:
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: dict[str, object]) -> dict[str, object]:
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: dict[str, object], index: int) -> dict[str, object]:
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: dict[str, object]) -> str:
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: list[str] = []
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: dict[str, object]) -> str:
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: dict[str, object]) -> str:
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: dict[str, object]) -> str:
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: dict[str, object] = {
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) -> dict[str, object]:
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: dict[int, dict[str, object]],
62
- outcomming_request: dict[str, object],
63
- ) -> tuple[list[dict[str, object]], dict[int, dict[str, object]]]:
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: list[dict[str, object]] = []
66
- ordinary_tool_calls: dict[int, dict[str, object]] = {}
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: dict[int, dict[str, object]],
82
- outcomming_request: dict[str, object],
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: list[dict[str, object]],
108
- ) -> list[dict[str, object]]:
109
- items: list[dict[str, object]] = []
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: dict[str, object] = {"type": "search"}
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: dict[str, object],
133
- mock_search_calls: list[dict[str, object]],
134
- reasoning_text: str | None = None,
135
- ) -> dict[str, object]:
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: list[dict[str, object]] = []
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: dict[str, object] = {
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) -> tuple[str, list[str]]:
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: list[str] = []
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: dict[str, object],
242
- ) -> set[str]:
243
- names: set[str] = set()
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) -> dict[str, object]:
253
+ def _build_mock_output(raw_arguments: 'JSONValue') -> 'typing.Dict[str, object]':
254
254
  query, queries = extract_queries(raw_arguments)
255
- output_payload: dict[str, object] = {
255
+ output_payload: 'typing.Dict[str, object]' = {
256
256
  "results": [],
257
257
  "mock": True,
258
258
  }