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.
Files changed (84) hide show
  1. pycodex/__init__.py +14 -14
  2. pycodex/agent.py +465 -499
  3. pycodex/bootstrap.py +417 -0
  4. pycodex/cli.py +236 -510
  5. pycodex/compat.py +19 -5
  6. pycodex/context.py +222 -212
  7. pycodex/doctor.py +52 -48
  8. pycodex/events.py +857 -0
  9. pycodex/feishu_card.py +217 -163
  10. pycodex/feishu_link.py +43 -83
  11. pycodex/model.py +324 -253
  12. pycodex/model_metadata.py +19 -7
  13. pycodex/portable.py +76 -45
  14. pycodex/portable_server.py +32 -24
  15. pycodex/prompts/models.json +245 -983
  16. pycodex/protocol.py +177 -137
  17. pycodex/runtime.py +579 -176
  18. pycodex/runtime_services.py +204 -157
  19. pycodex/tools/__init__.py +1 -1
  20. pycodex/tools/apply_patch_tool.py +69 -48
  21. pycodex/tools/base_tool.py +89 -42
  22. pycodex/tools/clock_tool.py +58 -25
  23. pycodex/tools/close_agent_tool.py +2 -2
  24. pycodex/tools/code_mode_manager.py +77 -64
  25. pycodex/tools/exec_command_tool.py +26 -11
  26. pycodex/tools/exec_tool.py +4 -4
  27. pycodex/tools/grep_files_tool.py +12 -10
  28. pycodex/tools/ipython_tool.py +10 -13
  29. pycodex/tools/list_dir_tool.py +13 -9
  30. pycodex/tools/read_file_tool.py +29 -17
  31. pycodex/tools/request_permissions_tool.py +15 -5
  32. pycodex/tools/request_user_input_tool.py +13 -104
  33. pycodex/tools/resume_agent_tool.py +2 -2
  34. pycodex/tools/send_input_tool.py +11 -8
  35. pycodex/tools/shell_command_tool.py +7 -5
  36. pycodex/tools/shell_tool.py +7 -5
  37. pycodex/tools/spawn_agent_tool.py +7 -4
  38. pycodex/tools/unified_exec_manager.py +102 -69
  39. pycodex/tools/update_plan_tool.py +8 -5
  40. pycodex/tools/view_image_tool.py +7 -5
  41. pycodex/tools/wait_agent_tool.py +27 -4
  42. pycodex/tools/wait_tool.py +5 -4
  43. pycodex/tools/web_search_tool.py +4 -2
  44. pycodex/tools/write_stdin_tool.py +12 -11
  45. pycodex/utils/__init__.py +2 -17
  46. pycodex/utils/compactor.py +41 -72
  47. pycodex/utils/debug.py +2 -2
  48. pycodex/utils/dotenv.py +6 -7
  49. pycodex/utils/event_helpers.py +190 -0
  50. pycodex/utils/get_env.py +27 -70
  51. pycodex/{image_utils.py → utils/image_utils.py} +8 -11
  52. pycodex/utils/random_ids.py +1 -2
  53. pycodex/utils/session_persist.py +217 -163
  54. pycodex/utils/truncation.py +21 -45
  55. python_codex-0.3.0.dist-info/METADATA +704 -0
  56. python_codex-0.3.0.dist-info/RECORD +90 -0
  57. responses_server/__init__.py +1 -5
  58. responses_server/__main__.py +0 -1
  59. responses_server/app.py +36 -31
  60. responses_server/config.py +23 -23
  61. responses_server/messages_api.py +51 -53
  62. responses_server/payload_processors.py +25 -20
  63. responses_server/server.py +11 -11
  64. responses_server/session_store.py +14 -11
  65. responses_server/stream_router.py +101 -98
  66. responses_server/tools/custom_adapter.py +17 -16
  67. responses_server/tools/web_search.py +39 -36
  68. responses_server/trajectory_dump.py +36 -14
  69. workspace_server/__main__.py +0 -1
  70. workspace_server/app.py +461 -375
  71. workspace_server/workspace.html +852 -228
  72. workspace_server/workspaces.html +94 -95
  73. workspace_server/workspaces.py +137 -79
  74. pycodex/collaboration.py +0 -20
  75. pycodex/interactive_session.py +0 -415
  76. pycodex/prompts/collaboration_default.md +0 -11
  77. pycodex/prompts/collaboration_plan.md +0 -128
  78. pycodex/utils/toolcall_visualize.py +0 -713
  79. pycodex/utils/visualize.py +0 -560
  80. python_codex-0.2.7.dist-info/METADATA +0 -455
  81. python_codex-0.2.7.dist-info/RECORD +0 -93
  82. {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/WHEEL +0 -0
  83. {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/entry_points.txt +0 -0
  84. {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,6 @@
1
-
2
- from copy import deepcopy
3
1
  import json
4
2
  import typing
3
+ from copy import deepcopy
5
4
 
6
5
 
7
6
  class CustomToolAdapterError(ValueError):
@@ -82,8 +81,8 @@ It is important to remember:
82
81
  """
83
82
 
84
83
 
85
- def collect_custom_tool_names(raw_tools: 'object') -> 'typing.Set[str]':
86
- names: 'typing.Set[str]' = set()
84
+ def collect_custom_tool_names(raw_tools: "object") -> "typing.Set[str]":
85
+ names: "typing.Set[str]" = set()
87
86
  if not isinstance(raw_tools, list):
88
87
  return names
89
88
  for raw_tool in raw_tools:
@@ -95,7 +94,9 @@ def collect_custom_tool_names(raw_tools: 'object') -> 'typing.Set[str]':
95
94
  return names
96
95
 
97
96
 
98
- def build_tool_definition(raw_tool: 'typing.Dict[str, object]') -> 'typing.Dict[str, object]':
97
+ def build_tool_definition(
98
+ raw_tool: "typing.Dict[str, object]",
99
+ ) -> "typing.Dict[str, object]":
99
100
  name = _required_tool_name(raw_tool)
100
101
  description = _build_description(raw_tool)
101
102
  input_description = (
@@ -125,7 +126,7 @@ def build_tool_definition(raw_tool: 'typing.Dict[str, object]') -> 'typing.Dict[
125
126
  }
126
127
 
127
128
 
128
- def build_tool_call(raw_item: 'typing.Dict[str, object]') -> 'typing.Dict[str, object]':
129
+ def build_tool_call(raw_item: "typing.Dict[str, object]") -> "typing.Dict[str, object]":
129
130
  name = _required_item_name(raw_item)
130
131
  return {
131
132
  "id": str(raw_item.get("call_id", "")).strip() or name,
@@ -141,7 +142,9 @@ def build_tool_call(raw_item: 'typing.Dict[str, object]') -> 'typing.Dict[str, o
141
142
  }
142
143
 
143
144
 
144
- def build_output_item(tool_call: 'typing.Dict[str, object]', index: 'int') -> 'typing.Dict[str, object]':
145
+ def build_output_item(
146
+ tool_call: "typing.Dict[str, object]", index: "int"
147
+ ) -> "typing.Dict[str, object]":
145
148
  function = tool_call.get("function") or {}
146
149
  if not isinstance(function, dict):
147
150
  raise CustomToolAdapterError(
@@ -149,9 +152,7 @@ def build_output_item(tool_call: 'typing.Dict[str, object]', index: 'int') -> 't
149
152
  )
150
153
  name = str(function.get("name", "")).strip()
151
154
  if not name:
152
- raise CustomToolAdapterError(
153
- "outcomming custom tool call is missing `name`"
154
- )
155
+ raise CustomToolAdapterError("outcomming custom tool call is missing `name`")
155
156
  return {
156
157
  "type": "custom_tool_call",
157
158
  "call_id": str(tool_call.get("id", "")).strip() or f"call_{index}",
@@ -160,7 +161,7 @@ def build_output_item(tool_call: 'typing.Dict[str, object]', index: 'int') -> 't
160
161
  }
161
162
 
162
163
 
163
- def extract_input_text(raw_arguments: 'object') -> 'str':
164
+ def extract_input_text(raw_arguments: "object") -> "str":
164
165
  if isinstance(raw_arguments, dict):
165
166
  parsed = deepcopy(raw_arguments)
166
167
  else:
@@ -186,7 +187,7 @@ def extract_input_text(raw_arguments: 'object') -> 'str':
186
187
  return str(raw_arguments or "")
187
188
 
188
189
 
189
- def _build_description(raw_tool: 'typing.Dict[str, object]') -> 'str':
190
+ def _build_description(raw_tool: "typing.Dict[str, object]") -> "str":
190
191
  name = _tool_name(raw_tool)
191
192
  if name == APPLY_PATCH_NAME:
192
193
  return APPLY_PATCH_CHAT_DESCRIPTION
@@ -200,7 +201,7 @@ def _build_description(raw_tool: 'typing.Dict[str, object]') -> 'str':
200
201
 
201
202
  raw_format = raw_tool.get("format")
202
203
  if isinstance(raw_format, dict):
203
- format_lines: 'typing.List[str]' = []
204
+ format_lines: "typing.List[str]" = []
204
205
  format_type = str(raw_format.get("type", "")).strip()
205
206
  syntax = str(raw_format.get("syntax", "")).strip()
206
207
  definition = str(raw_format.get("definition", "") or "").strip()
@@ -217,18 +218,18 @@ def _build_description(raw_tool: 'typing.Dict[str, object]') -> 'str':
217
218
  return "\n\n".join(parts)
218
219
 
219
220
 
220
- def _tool_name(raw_tool: 'typing.Dict[str, object]') -> 'str':
221
+ def _tool_name(raw_tool: "typing.Dict[str, object]") -> "str":
221
222
  return str(raw_tool.get("name", "")).strip()
222
223
 
223
224
 
224
- def _required_tool_name(raw_tool: 'typing.Dict[str, object]') -> 'str':
225
+ def _required_tool_name(raw_tool: "typing.Dict[str, object]") -> "str":
225
226
  name = _tool_name(raw_tool)
226
227
  if not name:
227
228
  raise CustomToolAdapterError("custom tool definition is missing `name`")
228
229
  return name
229
230
 
230
231
 
231
- def _required_item_name(raw_item: 'typing.Dict[str, object]') -> 'str':
232
+ def _required_item_name(raw_item: "typing.Dict[str, object]") -> "str":
232
233
  name = str(raw_item.get("name", "")).strip()
233
234
  if not name:
234
235
  raise CustomToolAdapterError("custom tool call is missing `name`")
@@ -1,10 +1,9 @@
1
-
2
- from copy import deepcopy
3
1
  import json
2
+ import typing
3
+ from copy import deepcopy
4
4
 
5
5
  from pycodex.protocol import JSONValue
6
6
  from pycodex.tools.base_tool import BaseTool, ToolContext
7
- import typing
8
7
 
9
8
 
10
9
  class WebSearchTool(BaseTool):
@@ -29,10 +28,10 @@ class WebSearchTool(BaseTool):
29
28
  }
30
29
  supports_parallel = False
31
30
 
32
- async def run(self, context: 'ToolContext', args: 'JSONValue') -> 'JSONValue':
31
+ async def run(self, context: "ToolContext", args: "JSONValue") -> "JSONValue":
33
32
  del context
34
33
  query, queries = extract_queries(args)
35
- output_payload: 'typing.Dict[str, object]' = {
34
+ output_payload: "typing.Dict[str, object]" = {
36
35
  "results": [],
37
36
  "mock": True,
38
37
  }
@@ -43,7 +42,7 @@ class WebSearchTool(BaseTool):
43
42
  return output_payload
44
43
 
45
44
 
46
- def build_tool_definition(tool: 'WebSearchTool') -> 'typing.Dict[str, object]':
45
+ def build_tool_definition(tool: "WebSearchTool") -> "typing.Dict[str, object]":
47
46
  return {
48
47
  "type": "function",
49
48
  "name": tool.name,
@@ -57,13 +56,13 @@ def build_tool_definition(tool: 'WebSearchTool') -> 'typing.Dict[str, object]':
57
56
 
58
57
 
59
58
  def partition_tool_calls(
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]]]':
59
+ tool: "WebSearchTool",
60
+ tool_calls: "typing.Dict[int, typing.Dict[str, object]]",
61
+ outcomming_request: "typing.Dict[str, object]",
62
+ ) -> "typing.Tuple[typing.List[typing.Dict[str, object]], typing.Dict[int, typing.Dict[str, object]]]":
64
63
  mock_tool_names = _collect_mock_tool_names(tool, outcomming_request)
65
- mock_calls: 'typing.List[typing.Dict[str, object]]' = []
66
- ordinary_tool_calls: 'typing.Dict[int, typing.Dict[str, object]]' = {}
64
+ mock_calls: "typing.List[typing.Dict[str, object]]" = []
65
+ ordinary_tool_calls: "typing.Dict[int, typing.Dict[str, object]]" = {}
67
66
  for index in sorted(tool_calls):
68
67
  tool_call = tool_calls[index]
69
68
  function = tool_call.get("function") or {}
@@ -78,9 +77,9 @@ def partition_tool_calls(
78
77
 
79
78
 
80
79
  def hydrate_tool_call_names(
81
- tool_calls: 'typing.Dict[int, typing.Dict[str, object]]',
82
- outcomming_request: 'typing.Dict[str, object]',
83
- ) -> 'None':
80
+ tool_calls: "typing.Dict[int, typing.Dict[str, object]]",
81
+ outcomming_request: "typing.Dict[str, object]",
82
+ ) -> "None":
84
83
  raw_tools = outcomming_request.get("tools") or []
85
84
  if not isinstance(raw_tools, list):
86
85
  return
@@ -104,15 +103,15 @@ def hydrate_tool_call_names(
104
103
 
105
104
 
106
105
  def build_output_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]]' = []
106
+ mock_search_calls: "typing.List[typing.Dict[str, object]]",
107
+ ) -> "typing.List[typing.Dict[str, object]]":
108
+ items: "typing.List[typing.Dict[str, object]]" = []
110
109
  for tool_call in mock_search_calls:
111
110
  function = tool_call.get("function") or {}
112
111
  if not isinstance(function, dict):
113
112
  continue
114
113
  query, queries = extract_queries(function.get("arguments"))
115
- action: 'typing.Dict[str, object]' = {"type": "search"}
114
+ action: "typing.Dict[str, object]" = {"type": "search"}
116
115
  if query:
117
116
  action["query"] = query
118
117
  if queries:
@@ -128,17 +127,17 @@ def build_output_items(
128
127
 
129
128
 
130
129
  def build_followup_request(
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]':
130
+ tool: "WebSearchTool",
131
+ outcomming_request: "typing.Dict[str, object]",
132
+ mock_search_calls: "typing.List[typing.Dict[str, object]]",
133
+ reasoning_text: "typing.Union[str, None]" = None,
134
+ ) -> "typing.Dict[str, object]":
136
135
  followup_request = deepcopy(outcomming_request)
137
136
  messages = followup_request.get("messages") or []
138
137
  if not isinstance(messages, list):
139
138
  raise ValueError("outcomming request messages must be a list")
140
139
 
141
- assistant_tool_calls: 'typing.List[typing.Dict[str, object]]' = []
140
+ assistant_tool_calls: "typing.List[typing.Dict[str, object]]" = []
142
141
  for tool_call in mock_search_calls:
143
142
  function = tool_call.get("function") or {}
144
143
  if not isinstance(function, dict):
@@ -154,7 +153,7 @@ def build_followup_request(
154
153
  }
155
154
  )
156
155
  if assistant_tool_calls:
157
- assistant_message: 'typing.Dict[str, object]' = {
156
+ assistant_message: "typing.Dict[str, object]" = {
158
157
  "role": "assistant",
159
158
  "tool_calls": assistant_tool_calls,
160
159
  }
@@ -163,7 +162,9 @@ def build_followup_request(
163
162
  messages.append(assistant_message)
164
163
 
165
164
  for tool_call in mock_search_calls:
166
- tool_output = _build_mock_output((tool_call.get("function") or {}).get("arguments"))
165
+ tool_output = _build_mock_output(
166
+ (tool_call.get("function") or {}).get("arguments")
167
+ )
167
168
  messages.append(
168
169
  {
169
170
  "role": "tool",
@@ -187,7 +188,9 @@ def build_followup_request(
187
188
  return followup_request
188
189
 
189
190
 
190
- def extract_queries(raw_arguments: 'JSONValue') -> 'typing.Tuple[str, typing.List[str]]':
191
+ def extract_queries(
192
+ raw_arguments: "JSONValue",
193
+ ) -> "typing.Tuple[str, typing.List[str]]":
191
194
  if isinstance(raw_arguments, dict):
192
195
  parsed = raw_arguments
193
196
  else:
@@ -211,7 +214,7 @@ def extract_queries(raw_arguments: 'JSONValue') -> 'typing.Tuple[str, typing.Lis
211
214
 
212
215
  query = str(parsed.get("query", "")).strip()
213
216
  queries_value = parsed.get("queries") or []
214
- queries: 'typing.List[str]' = []
217
+ queries: "typing.List[str]" = []
215
218
  if isinstance(queries_value, list):
216
219
  for value in queries_value:
217
220
  normalized = str(value).strip()
@@ -224,7 +227,7 @@ def extract_queries(raw_arguments: 'JSONValue') -> 'typing.Tuple[str, typing.Lis
224
227
  return query, queries
225
228
 
226
229
 
227
- def is_mock_tool(tool: 'WebSearchTool', raw_tool: 'object') -> 'bool':
230
+ def is_mock_tool(tool: "WebSearchTool", raw_tool: "object") -> "bool":
228
231
  if not isinstance(raw_tool, dict) or raw_tool.get("type") != "function":
229
232
  return False
230
233
  function = raw_tool.get("function") or {}
@@ -237,10 +240,10 @@ def is_mock_tool(tool: 'WebSearchTool', raw_tool: 'object') -> 'bool':
237
240
 
238
241
 
239
242
  def _collect_mock_tool_names(
240
- tool: 'WebSearchTool',
241
- outcomming_request: 'typing.Dict[str, object]',
242
- ) -> 'typing.Set[str]':
243
- names: 'typing.Set[str]' = set()
243
+ tool: "WebSearchTool",
244
+ outcomming_request: "typing.Dict[str, object]",
245
+ ) -> "typing.Set[str]":
246
+ names: "typing.Set[str]" = set()
244
247
  raw_tools = outcomming_request.get("tools") or []
245
248
  if not isinstance(raw_tools, list):
246
249
  return names
@@ -250,9 +253,9 @@ def _collect_mock_tool_names(
250
253
  return names
251
254
 
252
255
 
253
- def _build_mock_output(raw_arguments: 'JSONValue') -> 'typing.Dict[str, object]':
256
+ def _build_mock_output(raw_arguments: "JSONValue") -> "typing.Dict[str, object]":
254
257
  query, queries = extract_queries(raw_arguments)
255
- output_payload: 'typing.Dict[str, object]' = {
258
+ output_payload: "typing.Dict[str, object]" = {
256
259
  "results": [],
257
260
  "mock": True,
258
261
  }
@@ -4,19 +4,20 @@ import sys
4
4
  import threading
5
5
  import time
6
6
  import typing
7
+ import urllib.error
7
8
 
8
9
 
9
10
  class TrajectoryDumpWriter:
10
11
  ENV_VAR = "PYCODEX_DUMP"
11
12
 
12
- def __init__(self, root_dir: 'str') -> 'None':
13
+ def __init__(self, root_dir: "str") -> "None":
13
14
  self._root_dir = os.path.abspath(root_dir)
14
15
  self._dump_path = os.path.join(self._root_dir, "dump.jsonl")
15
16
  self._lock = threading.Lock()
16
17
  os.makedirs(self._root_dir, exist_ok=True)
17
18
 
18
19
  @classmethod
19
- def from_env(cls) -> 'typing.Union[TrajectoryDumpWriter, None]':
20
+ def from_env(cls) -> "typing.Union[TrajectoryDumpWriter, None]":
20
21
  root_dir = str(os.environ.get(cls.ENV_VAR, "") or "").strip()
21
22
  if not root_dir:
22
23
  return None
@@ -25,7 +26,7 @@ class TrajectoryDumpWriter:
25
26
  def wrap_stream(
26
27
  self,
27
28
  outcomming_stream,
28
- outcomming_request: 'typing.Dict[str, object]',
29
+ outcomming_request: "typing.Dict[str, object]",
29
30
  ):
30
31
  def iter_stream():
31
32
  capture = _TrajectoryCapture(
@@ -37,12 +38,22 @@ class TrajectoryDumpWriter:
37
38
  for chunk in outcomming_stream:
38
39
  capture.observe_chunk(chunk)
39
40
  yield chunk
41
+ except Exception as error:
42
+ capture.stream_error_type = type(error).__name__
43
+ cause = error.__cause__
44
+ if cause is not None:
45
+ capture.stream_error_cause_type = type(cause).__name__
46
+ if isinstance(cause, urllib.error.HTTPError):
47
+ capture.stream_error_http_status = cause.code
48
+ raise
49
+ else:
50
+ capture.stream_completed = True
40
51
  finally:
41
52
  capture.flush()
42
53
 
43
54
  return iter_stream()
44
55
 
45
- def _append_record(self, record: 'typing.Dict[str, object]') -> 'None':
56
+ def _append_record(self, record: "typing.Dict[str, object]") -> "None":
46
57
  serialized = json.dumps(record, ensure_ascii=False)
47
58
  with self._lock:
48
59
  os.makedirs(self._root_dir, exist_ok=True)
@@ -54,19 +65,24 @@ class TrajectoryDumpWriter:
54
65
  class _TrajectoryCapture:
55
66
  def __init__(
56
67
  self,
57
- writer: 'TrajectoryDumpWriter',
58
- send_timestamp: 'float',
59
- outcomming_request: 'typing.Dict[str, object]',
60
- ) -> 'None':
68
+ writer: "TrajectoryDumpWriter",
69
+ send_timestamp: "float",
70
+ outcomming_request: "typing.Dict[str, object]",
71
+ ) -> "None":
61
72
  self._writer = writer
62
73
  self._send_timestamp = float(send_timestamp)
63
74
  self._outcomming_request = json.loads(json.dumps(outcomming_request))
64
75
  self._prefill_token_ids = None
65
76
  self._decode_token_ids = []
66
- self._usage: 'typing.Dict[str, object]' = {}
77
+ self._usage: "typing.Dict[str, object]" = {}
78
+ self._finish_reason = None
67
79
  self._closed = False
80
+ self.stream_completed = False
81
+ self.stream_error_type = None
82
+ self.stream_error_cause_type = None
83
+ self.stream_error_http_status = None
68
84
 
69
- def observe_chunk(self, payload: 'object') -> 'None':
85
+ def observe_chunk(self, payload: "object") -> "None":
70
86
  if not isinstance(payload, dict):
71
87
  return
72
88
  usage = payload.get("usage")
@@ -83,34 +99,40 @@ class _TrajectoryCapture:
83
99
  for raw_choice in choices:
84
100
  if not isinstance(raw_choice, dict):
85
101
  continue
102
+ if raw_choice.get("finish_reason") is not None:
103
+ self._finish_reason = raw_choice["finish_reason"]
86
104
  normalized_decode = _normalize_token_ids(raw_choice.get("token_ids"))
87
105
  if normalized_decode:
88
106
  self._decode_token_ids.extend(normalized_decode)
89
107
 
90
- def flush(self) -> 'None':
108
+ def flush(self) -> "None":
91
109
  if self._closed:
92
110
  return
93
111
  self._closed = True
94
112
  record = {
95
113
  "request": self._outcomming_request,
96
114
  "usage": self._usage,
115
+ "finish_reason": self._finish_reason,
97
116
  "tokens": {
98
117
  "prefill": list(self._prefill_token_ids or []),
99
118
  "decode": list(self._decode_token_ids),
100
119
  },
101
120
  "send_timestamp": self._send_timestamp,
121
+ "stream_completed": self.stream_completed,
122
+ "stream_error_type": self.stream_error_type,
123
+ "stream_error_cause_type": self.stream_error_cause_type,
124
+ "stream_error_http_status": self.stream_error_http_status,
102
125
  }
103
126
  try:
104
127
  self._writer._append_record(record)
105
128
  except Exception as exc:
106
129
  print(
107
- "responses_server: failed to append PYCODEX_DUMP trajectory: %s"
108
- % exc,
130
+ "responses_server: failed to append PYCODEX_DUMP trajectory: %s" % exc,
109
131
  file=sys.stderr,
110
132
  )
111
133
 
112
134
 
113
- def _normalize_token_ids(raw_value: 'object') -> 'typing.Union[typing.List[int], None]':
135
+ def _normalize_token_ids(raw_value: "object") -> "typing.Union[typing.List[int], None]":
114
136
  if not isinstance(raw_value, list):
115
137
  return None
116
138
  token_ids = []
@@ -1,5 +1,4 @@
1
1
  from .app import main
2
2
 
3
-
4
3
  if __name__ == "__main__":
5
4
  main()