python-codex 0.1.13__py3-none-any.whl → 0.2.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 (50) hide show
  1. pycodex/agent.py +71 -11
  2. pycodex/cli.py +16 -356
  3. pycodex/context.py +12 -0
  4. pycodex/feishu_card.py +76 -30
  5. pycodex/feishu_link.py +131 -11
  6. pycodex/interactive_session.py +397 -0
  7. pycodex/model.py +11 -22
  8. pycodex/protocol.py +0 -5
  9. pycodex/runtime.py +23 -0
  10. pycodex/runtime_services.py +2 -2
  11. pycodex/tools/agent_tool_schemas.py +1 -1
  12. pycodex/tools/apply_patch_tool.py +1 -1
  13. pycodex/tools/base_tool.py +1 -27
  14. pycodex/tools/close_agent_tool.py +11 -4
  15. pycodex/tools/code_mode_manager.py +1 -1
  16. pycodex/tools/exec_command_tool.py +40 -16
  17. pycodex/tools/exec_tool.py +18 -2
  18. pycodex/tools/grep_files_tool.py +19 -6
  19. pycodex/tools/ipython_tool.py +3 -2
  20. pycodex/tools/list_dir_tool.py +19 -6
  21. pycodex/tools/read_file_tool.py +39 -9
  22. pycodex/tools/request_permissions_tool.py +12 -1
  23. pycodex/tools/request_user_input_tool.py +28 -1
  24. pycodex/tools/send_input_tool.py +4 -2
  25. pycodex/tools/shell_command_tool.py +23 -6
  26. pycodex/tools/shell_tool.py +13 -4
  27. pycodex/tools/spawn_agent_tool.py +31 -8
  28. pycodex/tools/unified_exec_manager.py +49 -93
  29. pycodex/tools/update_plan_tool.py +14 -6
  30. pycodex/tools/view_image_tool.py +17 -16
  31. pycodex/tools/wait_agent_tool.py +15 -3
  32. pycodex/tools/wait_tool.py +18 -4
  33. pycodex/tools/web_search_tool.py +2 -1
  34. pycodex/tools/write_stdin_tool.py +42 -10
  35. pycodex/utils/compactor.py +7 -1
  36. pycodex/utils/session_persist.py +42 -1
  37. pycodex/utils/truncation.py +206 -0
  38. pycodex/utils/visualize.py +34 -15
  39. {python_codex-0.1.13.dist-info → python_codex-0.2.0.dist-info}/METADATA +4 -1
  40. python_codex-0.2.0.dist-info/RECORD +88 -0
  41. {python_codex-0.1.13.dist-info → python_codex-0.2.0.dist-info}/entry_points.txt +1 -0
  42. workspace_server/__init__.py +23 -0
  43. workspace_server/__main__.py +5 -0
  44. workspace_server/app.py +1347 -0
  45. workspace_server/workspace.html +866 -0
  46. pycodex/prompts/exec_tools.json +0 -411
  47. pycodex/prompts/subagent_tools.json +0 -163
  48. python_codex-0.1.13.dist-info/RECORD +0 -84
  49. {python_codex-0.1.13.dist-info → python_codex-0.2.0.dist-info}/WHEEL +0 -0
  50. {python_codex-0.1.13.dist-info → python_codex-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,206 @@
1
+ """Shared truncation helpers for model-visible tool output."""
2
+
3
+ import math
4
+
5
+ from ..protocol import JSONValue, ToolResult
6
+ import typing
7
+
8
+ DEFAULT_MAX_OUTPUT_TOKENS = 10_000
9
+ TRUNCATION_SERIALIZATION_BUDGET_MULTIPLIER = 1.2
10
+ HISTORY_TOOL_OUTPUT_TOKENS = int(
11
+ math.ceil(DEFAULT_MAX_OUTPUT_TOKENS * TRUNCATION_SERIALIZATION_BUDGET_MULTIPLIER)
12
+ )
13
+ APPROX_BYTES_PER_TOKEN = 4
14
+
15
+
16
+ def approx_token_count(text: 'str') -> 'int':
17
+ """Estimate token count using the upstream Codex 4-bytes-per-token rule."""
18
+ if not text:
19
+ return 0
20
+ byte_length = len(text.encode("utf-8"))
21
+ return max(
22
+ 1,
23
+ (byte_length + APPROX_BYTES_PER_TOKEN - 1) // APPROX_BYTES_PER_TOKEN,
24
+ )
25
+
26
+
27
+ def formatted_truncate_text(text: 'str', max_tokens: 'int') -> 'str':
28
+ """Format a direct tool response with line count plus middle truncation."""
29
+ byte_budget = _approx_bytes_for_tokens(max_tokens)
30
+ if len(text.encode("utf-8")) <= byte_budget:
31
+ return text
32
+
33
+ total_lines = len(text.splitlines())
34
+ return f"Total output lines: {total_lines}\n\n{_truncate_text(text, max_tokens)}"
35
+
36
+
37
+ def truncate_tool_result_for_history(
38
+ result: 'ToolResult',
39
+ ) -> 'ToolResult':
40
+ """Truncate model-visible ToolResult content before storing it in history."""
41
+ if result.content_items is not None:
42
+ truncated_content_items = _truncate_content_items(
43
+ result.content_items,
44
+ HISTORY_TOOL_OUTPUT_TOKENS,
45
+ )
46
+ if truncated_content_items == result.content_items:
47
+ return result
48
+ return ToolResult(
49
+ call_id=result.call_id,
50
+ name=result.name,
51
+ output=result.output,
52
+ content_items=truncated_content_items,
53
+ success=result.success,
54
+ is_error=result.is_error,
55
+ tool_type=result.tool_type,
56
+ )
57
+
58
+ output_text = _tool_output_text(result.output)
59
+ truncated_output = _truncate_text(output_text, HISTORY_TOOL_OUTPUT_TOKENS)
60
+ if truncated_output == output_text:
61
+ return result
62
+ return ToolResult(
63
+ call_id=result.call_id,
64
+ name=result.name,
65
+ output=truncated_output,
66
+ success=result.success,
67
+ is_error=result.is_error,
68
+ tool_type=result.tool_type,
69
+ )
70
+
71
+
72
+ def truncate_tool_results_for_history(
73
+ results: 'typing.Iterable[ToolResult]',
74
+ ) -> 'typing.List[ToolResult]':
75
+ """Apply history-layer truncation to a batch of completed tool results."""
76
+ return [
77
+ truncate_tool_result_for_history(result)
78
+ for result in results
79
+ ]
80
+
81
+
82
+ def _tool_output_text(output: 'JSONValue') -> 'str':
83
+ if isinstance(output, str):
84
+ return output
85
+
86
+ import json
87
+
88
+ return json.dumps(
89
+ output,
90
+ ensure_ascii=False,
91
+ separators=(",", ":"),
92
+ )
93
+
94
+
95
+ def _truncate_content_items(
96
+ content_items: 'typing.Tuple[typing.Dict[str, typing.Any], ...]',
97
+ token_limit: 'int',
98
+ ) -> 'typing.Tuple[typing.Dict[str, typing.Any], ...]':
99
+ output: 'typing.List[typing.Dict[str, typing.Any]]' = []
100
+ remaining_budget = token_limit
101
+ omitted_text_items = 0
102
+
103
+ for item in content_items:
104
+ if item.get("type") != "input_text":
105
+ output.append(dict(item))
106
+ continue
107
+
108
+ text = str(item.get("text", ""))
109
+ if remaining_budget <= 0:
110
+ omitted_text_items += 1
111
+ continue
112
+
113
+ cost = approx_token_count(text)
114
+ if cost <= remaining_budget:
115
+ output.append(dict(item))
116
+ remaining_budget -= cost
117
+ continue
118
+
119
+ truncated_text = _truncate_text(text, remaining_budget)
120
+ if truncated_text:
121
+ next_item = dict(item)
122
+ next_item["text"] = truncated_text
123
+ output.append(next_item)
124
+ else:
125
+ omitted_text_items += 1
126
+ remaining_budget = 0
127
+
128
+ if omitted_text_items > 0:
129
+ output.append(
130
+ {
131
+ "type": "input_text",
132
+ "text": f"[omitted {omitted_text_items} text items ...]",
133
+ }
134
+ )
135
+ return tuple(output)
136
+
137
+
138
+ def _approx_tokens_from_byte_count(byte_count: 'int') -> 'int':
139
+ if byte_count <= 0:
140
+ return 0
141
+ return (byte_count + APPROX_BYTES_PER_TOKEN - 1) // APPROX_BYTES_PER_TOKEN
142
+
143
+
144
+ def _approx_bytes_for_tokens(token_count: 'int') -> 'int':
145
+ return max(token_count, 0) * APPROX_BYTES_PER_TOKEN
146
+
147
+
148
+ def _split_budget(byte_budget: 'int') -> 'typing.Tuple[int, int]':
149
+ left_budget = byte_budget // 2
150
+ return left_budget, byte_budget - left_budget
151
+
152
+
153
+ def _split_string(
154
+ text: 'str',
155
+ beginning_bytes: 'int',
156
+ end_bytes: 'int',
157
+ ) -> 'typing.Tuple[str, str]':
158
+ if not text:
159
+ return "", ""
160
+
161
+ total_bytes = len(text.encode("utf-8"))
162
+ tail_start_target = max(total_bytes - end_bytes, 0)
163
+ prefix_end = 0
164
+ suffix_start = len(text)
165
+ suffix_started = False
166
+ current_byte = 0
167
+
168
+ for index, char in enumerate(text):
169
+ char_bytes = len(char.encode("utf-8"))
170
+ char_start = current_byte
171
+ char_end = current_byte + char_bytes
172
+ if char_end <= beginning_bytes:
173
+ prefix_end = index + 1
174
+ current_byte = char_end
175
+ continue
176
+ if char_start >= tail_start_target:
177
+ if not suffix_started:
178
+ suffix_start = index
179
+ suffix_started = True
180
+ current_byte = char_end
181
+ continue
182
+ current_byte = char_end
183
+
184
+ if suffix_start < prefix_end:
185
+ suffix_start = prefix_end
186
+
187
+ return text[:prefix_end], text[suffix_start:]
188
+
189
+
190
+ def _truncate_text(text: 'str', max_tokens: 'int') -> 'str':
191
+ if not text:
192
+ return ""
193
+
194
+ max_bytes = _approx_bytes_for_tokens(max_tokens)
195
+ total_bytes = len(text.encode("utf-8"))
196
+ if total_bytes <= max_bytes:
197
+ return text
198
+
199
+ removed_tokens = _approx_tokens_from_byte_count(total_bytes - max_bytes)
200
+ marker = f"\u2026{removed_tokens} tokens truncated\u2026"
201
+ if max_bytes == 0:
202
+ return marker
203
+
204
+ left_budget, right_budget = _split_budget(max_bytes)
205
+ prefix, suffix = _split_string(text, left_budget, right_budget)
206
+ return f"{prefix}{marker}{suffix}"
@@ -17,6 +17,7 @@ import typing
17
17
  STATUS_FRAMES = ("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏")
18
18
  PROMPT_CONTEXT_BASELINE_TOKENS = 12_000
19
19
  DEFAULT_MAIN_PROMPT = "pycodex> "
20
+ IDLE_LISTENING_STATUS = "idle: listening"
20
21
 
21
22
 
22
23
  def shorten_title(text: "str", limit: "int" = 48) -> "str":
@@ -235,8 +236,22 @@ class CliSessionView:
235
236
  pending_prompt = self._pending_user_prompts.pop(submission_id, None)
236
237
  if pending_prompt is not None:
237
238
  self._history.append((pending_prompt, final_text))
238
- self.prompter.set_status(active=False)
239
+ self._set_idle_status(event)
240
+ return
241
+
242
+ if event.kind == "stream_error":
243
+ self._stream_buffer = ""
244
+ message = str(event.payload.get("message", "")).strip() or "Reconnecting..."
245
+ self._print_line(
246
+ colorize_cli_message(
247
+ f"[status] {message}",
248
+ "status",
249
+ self._color_enabled,
250
+ )
251
+ )
252
+ self.prompter.set_status("reconnecting")
239
253
  return
254
+
240
255
  self.finish_stream()
241
256
 
242
257
  if event.kind == "turn_started":
@@ -285,18 +300,6 @@ class CliSessionView:
285
300
  # self.prompter.set_status("thinking")
286
301
  return
287
302
 
288
- if event.kind == "stream_error":
289
- message = str(event.payload.get("message", "")).strip() or "Reconnecting..."
290
- self._print_line(
291
- colorize_cli_message(
292
- f"[status] {message}",
293
- "status",
294
- self._color_enabled,
295
- )
296
- )
297
- self.prompter.set_status("reconnecting")
298
- return
299
-
300
303
  if event.kind == "auto_compact_started":
301
304
  total_tokens = event.payload.get("total_tokens")
302
305
  token_limit = event.payload.get("token_limit")
@@ -377,7 +380,7 @@ class CliSessionView:
377
380
  event.payload.get("submission_id", event.turn_id)
378
381
  ).strip()
379
382
  self._pending_user_prompts.pop(submission_id, None)
380
- self.prompter.set_status(active=False)
383
+ self._set_idle_status(event)
381
384
  return
382
385
 
383
386
  if event.kind == "turn_interrupted":
@@ -388,7 +391,7 @@ class CliSessionView:
388
391
  pending_prompt = self._pending_user_prompts.pop(submission_id, None)
389
392
  if pending_prompt is not None and final_text:
390
393
  self._history.append((pending_prompt, final_text))
391
- self.prompter.set_status(active=False)
394
+ self._set_idle_status(event)
392
395
  return
393
396
 
394
397
  def show_history(self) -> "None":
@@ -406,6 +409,15 @@ class CliSessionView:
406
409
  self.finish_stream()
407
410
  self._print_line(f"Session: {self._title or 'untitled'}")
408
411
 
412
+ def set_session_title(self, title: "str") -> "None":
413
+ self.finish_stream()
414
+ self._title = title or None
415
+ self._print_line(f"Session: {self._title or 'untitled'}")
416
+
417
+ def show_resumed_session(self, title: "str") -> "None":
418
+ self.write_line(f"Resumed session: {title}")
419
+ self.show_history()
420
+
409
421
  def load_session_history(
410
422
  self,
411
423
  title: "typing.Union[str, None]",
@@ -454,6 +466,13 @@ class CliSessionView:
454
466
  return prompt
455
467
  return f"pyco({self._context_remaining_percent}%)> "
456
468
 
469
+ def _set_idle_status(self, event: "AgentEvent") -> "None":
470
+ background_work_count = event.payload.get("background_exec_count", 0)
471
+ if background_work_count > 0:
472
+ self.prompter.set_status(IDLE_LISTENING_STATUS)
473
+ else:
474
+ self.prompter.set_status(active=False)
475
+
457
476
  def show_steer_queued(self, turn_id: "str", prompt: "str") -> "None":
458
477
  preview = shorten_title(prompt, limit=72)
459
478
  self._queued_steer_prompts.setdefault(turn_id, []).append(preview)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-codex
3
- Version: 0.1.13
3
+ Version: 0.2.0
4
4
  Summary: A minimal Python extraction of Codex's main agent loop
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.6.2
@@ -165,12 +165,15 @@ pycodex --put @127.0.0.1:5577
165
165
  pycodex --put /data/.codex/@127.0.0.1:5577
166
166
  pycodex --call SECRET-CALLID@127.0.0.1:5577 "Reply with exactly OK."
167
167
  pycodex doctor
168
+ pycodex-ws --listen 0.0.0.0:6007 --board ./board.html
168
169
  ```
169
170
 
170
171
  Current behavior:
171
172
 
172
173
  - with no argv prompt and a TTY stdin, enter interactive mode
173
174
  - with an argv prompt or piped stdin, run a single turn
175
+ - `pycodex-ws` starts the standalone browser workspace with a board pane and a
176
+ pycodex session pane
174
177
  - interactive mode supports `/exit` and `/quit`
175
178
  - interactive mode shows a compact event stream for user-visible phases such as
176
179
  tool execution and model follow-up after tool results
@@ -0,0 +1,88 @@
1
+ pycodex/__init__.py,sha256=I1P7OHkabjHP7g32A31o-L9NMd3lKYJ2xhV8AHLPYXA,3196
2
+ pycodex/agent.py,sha256=HKzWVFRag_Le8LVR1qqfdfVzk23bRsXkykZV8Zq2hLE,21659
3
+ pycodex/cli.py,sha256=6URw4uSV-GPevu8NiqoraWIa180QmSUFOHScCZHO2Mc,20616
4
+ pycodex/collaboration.py,sha256=yQ6pBD-R3ZWR4_FAYQFoS7KF0m4LLD42otXIbPqw2ys,641
5
+ pycodex/compat.py,sha256=l35JE0vGAOCn9NWbWbqwGURGk83HXddXQ5wJIfcG41o,3254
6
+ pycodex/context.py,sha256=Hwh3vU-qSvaleUDdOWuWiGyNmbULVAZmboxiu9iiHt8,26549
7
+ pycodex/doctor.py,sha256=De3M4hRBJq8ZeqsUJgHz0vitqrH18YugrEnz7oHhTdQ,10572
8
+ pycodex/feishu_card.py,sha256=De6pM--3MfhgGo-WcWfhm-fop5UtzjwKrZ4gI2Lls3w,26198
9
+ pycodex/feishu_link.py,sha256=XGV93CIorcc03pNgBnXay9SkivnDL4i2GkLR5NjBjq0,15588
10
+ pycodex/interactive_session.py,sha256=VmQ4xLk5b9QrI4LvgrdyhXTvIk0K65loKxh2XbfV2FA,15617
11
+ pycodex/model.py,sha256=O5Gf35qCMAGrqvr_5d1ZQNk3vHmNRD5Uv-8k7-zbpXg,36679
12
+ pycodex/portable.py,sha256=gxl2E2h5uZJbasMEPPs-nyALFPIvX79T2ZYsu6vXZrg,15656
13
+ pycodex/portable_server.py,sha256=6I3pQkWj3e_SFlDXY2mGdCPns1w_3PSxByBV9wv5epI,7331
14
+ pycodex/protocol.py,sha256=4qiEcBQc3d3RZqsIKBjwORsHsQa78cqwvNljtnIuNbM,10795
15
+ pycodex/runtime.py,sha256=dXxLkcsjufBZ8F7o4QKFXF--mkgq3cpxhyUbPPaBYy4,8684
16
+ pycodex/runtime_services.py,sha256=6PQMI4MM8F9imijaVKBIz_0ADtoKDcdYs6vQ-c4frtQ,13931
17
+ pycodex/prompts/collaboration_default.md,sha256=MBTmPuMubeWfZgIeFVj49wwnwD4n_o3fVYAbgWKwu6Q,955
18
+ pycodex/prompts/collaboration_plan.md,sha256=IzjQAA5oHJz-3FmJdOjsJ4LHq6LW1tlEYMoy09n0HKk,8777
19
+ pycodex/prompts/default_base_instructions.md,sha256=D65mcj6bo4CDvVom-D9cbJRJVNquo0NghKt164_fRsg,20923
20
+ pycodex/prompts/models.json,sha256=D9AdqWpvxkhN6y4k3G6tJsjQFj99ATb4mjwGRcggWa8,349617
21
+ pycodex/prompts/permissions/approval_policy/never.md,sha256=QceTG6wjkaJARjYr0HYV1aPnPcpGcrkRUW-smWRr6MQ,120
22
+ pycodex/prompts/permissions/approval_policy/on_failure.md,sha256=dfJjpXkpO6_ANdCKxbVJ8o4vyLxevrJWfKsGHTqtbkc,289
23
+ pycodex/prompts/permissions/approval_policy/on_request.md,sha256=hVQalzh0FAdkKzw5u-N4H7-LtC9ijVDlYsh3OKsZKzo,3661
24
+ pycodex/prompts/permissions/approval_policy/on_request_rule_request_permission.md,sha256=mOinishp1k-wlPsaEuIOMn5GoVm_dAIsWIuEMmv2r7o,1725
25
+ pycodex/prompts/permissions/approval_policy/unless_trusted.md,sha256=XHpi1Lfx1iIXFbbQ_ho_kGstA3JN-RLho291HM30UNw,247
26
+ pycodex/prompts/permissions/sandbox_mode/danger_full_access.md,sha256=nZ7YHacBd3cAHKRZc9XClOOOnXJPXPh0WFBueh5C2D0,197
27
+ pycodex/prompts/permissions/sandbox_mode/read_only.md,sha256=2rAPEXsBYCcuttI5j3euS-3uv_v97catIsnhxlSQSIM,173
28
+ pycodex/prompts/permissions/sandbox_mode/workspace_write.md,sha256=lVN-LwrBbHqlv5yVjcd_mU8tzZW8jfKpTatJKIZu9HI,277
29
+ pycodex/tools/__init__.py,sha256=aSLXrr_31KGQgDfRow5zVIc-2-KdXlHaCE6qUnE4HWI,1772
30
+ pycodex/tools/agent_tool_schemas.py,sha256=8cL9ml4So0H6IIeK0ZzjAo9bk3VeprDOSYPpIamOqEU,2048
31
+ pycodex/tools/apply_patch_tool.py,sha256=wLHk5sl_UkR7iv0yqMJBMLHbGkZlMmCuX9a0yTevzps,13802
32
+ pycodex/tools/base_tool.py,sha256=0aQ69ygvSYKtDq1wHXr_j7EF3NtKIfmj0A9AbB-pD4Q,5431
33
+ pycodex/tools/close_agent_tool.py,sha256=79qr4ljPTvjnpH-Oe83yFTUGZdgNNnGoLZbpdqp4eOY,2031
34
+ pycodex/tools/code_mode_manager.py,sha256=T8RHM8tyJvHWwS6AD2g9hsaOmvApMmlWuSCRfmDVMDE,19053
35
+ pycodex/tools/exec_command_tool.py,sha256=KIt-pni7b2I41EMXGa1NW9YWJ1AoiWlPW_Ljlu7kLpk,4082
36
+ pycodex/tools/exec_runtime.js,sha256=DR1uocKailTqNWAcJNFJuQgFFMSUzTpT_uQsRaneg2s,3643
37
+ pycodex/tools/exec_tool.py,sha256=6uACLanhMxa5_5dZGO8CPztnM20O2ACANbaf9AUBS8M,2278
38
+ pycodex/tools/grep_files_tool.py,sha256=7M5xLQX6RJ_45korFmwBic97zDToiuojcmi1t9s-cko,5279
39
+ pycodex/tools/ipython_tool.py,sha256=7kn04RmR7GxSX3vDvFPhniEsChsoWl7_lVHyUCxouO4,4576
40
+ pycodex/tools/list_dir_tool.py,sha256=XMo1_hqDKX1004OfkyTcHz_8Shz9-4ikXd05z71U2gA,5283
41
+ pycodex/tools/read_file_tool.py,sha256=csClKG5Ldc38NKa2Enc3thBGxS8CU9lLAuMjmRKPgNw,9549
42
+ pycodex/tools/request_permissions_tool.py,sha256=Px0gvuYp5nPMCK-NotVmURRcwHG_0C0d44_s5tEaKPo,3787
43
+ pycodex/tools/request_user_input_tool.py,sha256=AF_EYdIrm-z8irHiJJzh7ubhEnDxaBdGtCprmvoh9m4,7201
44
+ pycodex/tools/resume_agent_tool.py,sha256=o62xdrsRxGFdRLxEhKEny-YEcaBOeqIneImrkME35II,1614
45
+ pycodex/tools/send_input_tool.py,sha256=vRg-f7LI2Sr01j1LeCPKeXto_ROQZ7bcUC5GIjx9NcI,3747
46
+ pycodex/tools/shell_command_tool.py,sha256=wUw4lw8VLGIQ-7BIgyEsI0oqjj-Yr-MZn-_VDrnimAw,4202
47
+ pycodex/tools/shell_tool.py,sha256=1m-Tcbn3His4ggyK5ec8Mkg6ihopqTvBd9F6fJlm6m4,4054
48
+ pycodex/tools/spawn_agent_tool.py,sha256=7Me4Frjz7_fTpjMMFCvNmDHPOK2G1qTfXUr2Ugs61bU,6194
49
+ pycodex/tools/unified_exec_manager.py,sha256=cKW_YNjSweVlGt8xNdeK_7gVwsGXxumCqjr-OlW5Xe0,12868
50
+ pycodex/tools/update_plan_tool.py,sha256=UsChtCBqI1RnVnPQbByPmD2LMZXMoCwfe6NpGqP8qu0,3174
51
+ pycodex/tools/view_image_tool.py,sha256=2Xu5Vx7djVpz7-IV-LKrDKJjvVJc9AND-MFXp3CWplg,3892
52
+ pycodex/tools/wait_agent_tool.py,sha256=0Uj9-IrXe2dSvOtOMq-RAc0XzaidwFH5q7Mri3BXWyM,3135
53
+ pycodex/tools/wait_tool.py,sha256=N6IrwzMp-fr1EohF-bxSwUlVs7KvZO2vV2csm3xCmS0,3205
54
+ pycodex/tools/web_search_tool.py,sha256=mhiK_G6VC6K6-01KctmIpsr-BURDF8L4ZJyY6IFGlNA,964
55
+ pycodex/tools/write_stdin_tool.py,sha256=K--XMp-AyEdp8yQ9EseYHsK3QrGrK4AgzOyvUk4swmw,3608
56
+ pycodex/utils/__init__.py,sha256=p3jaERPxkimNDhmMIsm4glvKiazf3UMv1X-qoH5Zl3U,963
57
+ pycodex/utils/async_bridge.py,sha256=d21Pjim-nsQbSG5pJddd0WaQ03CzA3w3TINWDmmjWbg,1815
58
+ pycodex/utils/compactor.py,sha256=zny3xhbc68FzpIlUJ1o0eEw0Cr9_rjBO_CyWDuoL74E,9013
59
+ pycodex/utils/debug.py,sha256=JeEB5JfzYfbdG0fXlrWFmXyR1ts86fKsI_97IqgF6R0,296
60
+ pycodex/utils/dotenv.py,sha256=rGKmurHjm7GdP4giyjHBPpSPv2Oi45qBqDB6HG3CnfA,1866
61
+ pycodex/utils/get_env.py,sha256=5fNhcNhujOakWV6AS66rGW3jEA68WGpuE4YVXJZFE6U,7427
62
+ pycodex/utils/random_ids.py,sha256=zBphjVGc7OXk9ZNExAbxRi_bk7ipyLG491qTv7hi8jM,380
63
+ pycodex/utils/session_persist.py,sha256=LnyKbQQ2SyONXhZ_XF_5pUUz_ZqhWsA7dOL5pCcKsCg,18491
64
+ pycodex/utils/toolcall_visualize.py,sha256=zIqmdsOfyYaLy_P4jpKnRxDsfTgYLRBx55R8m1P_lBE,24708
65
+ pycodex/utils/truncation.py,sha256=B_RvfXC2-M1oKz--eQIqDLqMD0g7_J-MSQd3WD6Rh08,6110
66
+ pycodex/utils/visualize.py,sha256=oTl1vqYW3nFBrdLcDyzwYfkNaPEW4OhVahJMHFSVkEg,20591
67
+ responses_server/__init__.py,sha256=3yPv_zeGT7P11tTnmj5kXktISLNsNW-02MUnnbiZcb0,394
68
+ responses_server/__main__.py,sha256=9SRp-Yw7ShGxc6DhSIXcDLKgGEdAVm3oBZ59rBOPjT0,62
69
+ responses_server/app.py,sha256=ack2a0otiBwq_DpsFURqLMlQzcf9oJPwo8o6iJ1fuig,7885
70
+ responses_server/config.py,sha256=leb3_uPrCyYdUIkyRyVPX4luGF88dQ62OkhRLPe7uxw,2718
71
+ responses_server/messages_api.py,sha256=WgO6J1jz2pOJkI79rLXp-pS1yxtLARcwX8T6JX5Vkcc,16971
72
+ responses_server/payload_processors.py,sha256=cbXGW8Xi-mliaWRg0_Af41X0vXV2W6R9VBzTE6DXfe4,3483
73
+ responses_server/server.py,sha256=Ko-Cqz_kW-uve091itucMklsPhEei77v-YcTjtjEdqU,2286
74
+ responses_server/session_store.py,sha256=ZD3cH2aEOkWaQsu5qTzcal2mThTSFQPAhAhPUN9srgI,1115
75
+ responses_server/stream_router.py,sha256=UiP-T4IKgJubD1L0AY93N3DqUh4K41fNcdONmC3Z-0A,37161
76
+ responses_server/trajectory_dump.py,sha256=XCwYaZZmlAxSsSXOfhk3zRvyfDpOHX5R8KzspScNFUM,3435
77
+ responses_server/tools/__init__.py,sha256=ivsBSEy0SBUhY-Uea5v1XMLXShkwHdCVl0id-1FwdZg,150
78
+ responses_server/tools/custom_adapter.py,sha256=LxO7ldydvR-GWachDz8GKC0Q8KGGFoFPbZxM0QvxuZ0,8350
79
+ responses_server/tools/web_search.py,sha256=pm4ZUiHUfxc0bGY1kEvt-BCzDrZIyP24xzPUcga2ul0,8908
80
+ workspace_server/__init__.py,sha256=XbSU6aAeYBMsBTVi9Ug6ZW9sxcwefqPS7T_7wpt_VMo,462
81
+ workspace_server/__main__.py,sha256=9SRp-Yw7ShGxc6DhSIXcDLKgGEdAVm3oBZ59rBOPjT0,62
82
+ workspace_server/app.py,sha256=aDfUqIwyMsndavSTP47rfcBKIwgo37F8U_lkCgUfUc0,49260
83
+ workspace_server/workspace.html,sha256=7d1eTxQ_4Hl-8YbMFHMTmRPpMRXE-a0KSJn1224JpXw,25563
84
+ python_codex-0.2.0.dist-info/METADATA,sha256=oCzNpTEbt1eODGxsJeHJE-gjTf53He7UDvcs-bovqAc,16812
85
+ python_codex-0.2.0.dist-info/WHEEL,sha256=KGYbc1zXlYddvwxnNty23BeaKzh7YuoSIvIMO4jEhvw,87
86
+ python_codex-0.2.0.dist-info/entry_points.txt,sha256=vkV2UWCtEKvQNMJuPNjt8HyBKiwp83JyqBatrBNGDp8,80
87
+ python_codex-0.2.0.dist-info/licenses/LICENSE,sha256=0X8ifk312hYAORM4hlzg8wVSEXYKNmiPgWlB1YIy2Nw,10926
88
+ python_codex-0.2.0.dist-info/RECORD,,
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  pycodex = pycodex.cli:main
3
+ pycodex-ws = workspace_server:main
@@ -0,0 +1,23 @@
1
+ from .app import (
2
+ ThreadedWorkspaceInteractiveSession,
3
+ WebSessionView,
4
+ WorkspaceInteractiveSession,
5
+ WorkspaceSessionManager,
6
+ build_parser,
7
+ create_app,
8
+ main,
9
+ parse_target,
10
+ run_serve_cli,
11
+ )
12
+
13
+ __all__ = [
14
+ "ThreadedWorkspaceInteractiveSession",
15
+ "WebSessionView",
16
+ "WorkspaceInteractiveSession",
17
+ "WorkspaceSessionManager",
18
+ "build_parser",
19
+ "create_app",
20
+ "main",
21
+ "parse_target",
22
+ "run_serve_cli",
23
+ ]
@@ -0,0 +1,5 @@
1
+ from .app import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ main()