python-codex 0.2.6__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 +18 -14
  2. pycodex/agent.py +468 -462
  3. pycodex/bootstrap.py +417 -0
  4. pycodex/cli.py +236 -436
  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 +329 -252
  12. pycodex/model_metadata.py +19 -7
  13. pycodex/portable.py +90 -52
  14. pycodex/portable_server.py +32 -24
  15. pycodex/prompts/models.json +235 -803
  16. pycodex/protocol.py +177 -137
  17. pycodex/runtime.py +579 -174
  18. pycodex/runtime_services.py +204 -157
  19. pycodex/tools/__init__.py +4 -1
  20. pycodex/tools/apply_patch_tool.py +69 -48
  21. pycodex/tools/base_tool.py +89 -42
  22. pycodex/tools/clock_tool.py +201 -0
  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 +13 -13
  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 +50 -66
  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/utils/image_utils.py +76 -0
  52. pycodex/utils/random_ids.py +1 -2
  53. pycodex/utils/session_persist.py +263 -161
  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 +25 -22
  61. responses_server/messages_api.py +96 -49
  62. responses_server/payload_processors.py +25 -19
  63. responses_server/server.py +11 -11
  64. responses_server/session_store.py +14 -11
  65. responses_server/stream_router.py +196 -107
  66. responses_server/tools/custom_adapter.py +17 -16
  67. responses_server/tools/web_search.py +39 -36
  68. responses_server/trajectory_dump.py +51 -13
  69. workspace_server/__main__.py +0 -1
  70. workspace_server/app.py +470 -384
  71. workspace_server/workspace.html +859 -232
  72. workspace_server/workspaces.html +94 -95
  73. workspace_server/workspaces.py +168 -100
  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 -553
  80. python_codex-0.2.6.dist-info/METADATA +0 -441
  81. python_codex-0.2.6.dist-info/RECORD +0 -91
  82. {python_codex-0.2.6.dist-info → python_codex-0.3.0.dist-info}/WHEEL +0 -0
  83. {python_codex-0.2.6.dist-info → python_codex-0.3.0.dist-info}/entry_points.txt +0 -0
  84. {python_codex-0.2.6.dist-info → python_codex-0.3.0.dist-info}/licenses/LICENSE +0 -0
pycodex/doctor.py CHANGED
@@ -1,9 +1,9 @@
1
-
2
1
  import asyncio
3
2
  import json
4
3
  import socket
5
4
  import ssl
6
5
  import time
6
+ import typing
7
7
  import urllib.parse
8
8
  from dataclasses import asdict, dataclass, field
9
9
  from pathlib import Path
@@ -13,32 +13,31 @@ import requests
13
13
  from .model import ResponsesModelClient, ResponsesProviderConfig
14
14
  from .protocol import AssistantMessage, Prompt, UserMessage
15
15
  from .utils.dotenv import DOTENV_FILENAME, load_codex_dotenv
16
- import typing
17
16
 
18
17
 
19
18
  @dataclass
20
19
  class DoctorCheck:
21
- name: 'str'
22
- ok: 'bool'
23
- detail: 'str'
20
+ name: "str"
21
+ ok: "bool"
22
+ detail: "str"
24
23
 
25
24
 
26
25
  @dataclass
27
26
  class DoctorReport:
28
- ok: 'bool'
29
- config_path: 'str'
30
- dotenv_path: 'str'
31
- profile: 'typing.Union[str, None]'
32
- provider_name: 'typing.Union[str, None]' = None
33
- model: 'typing.Union[str, None]' = None
34
- base_url: 'typing.Union[str, None]' = None
35
- responses_url: 'typing.Union[str, None]' = None
36
- api_key_env: 'typing.Union[str, None]' = None
37
- api_key_loaded: 'bool' = False
38
- checks: 'typing.List[DoctorCheck]' = field(default_factory=list)
39
- live_output_text: 'typing.Union[str, None]' = None
40
-
41
- def to_dict(self) -> 'typing.Dict[str, object]':
27
+ ok: "bool"
28
+ config_path: "str"
29
+ dotenv_path: "str"
30
+ profile: "typing.Union[str, None]"
31
+ provider_name: "typing.Union[str, None]" = None
32
+ model: "typing.Union[str, None]" = None
33
+ base_url: "typing.Union[str, None]" = None
34
+ responses_url: "typing.Union[str, None]" = None
35
+ api_key_env: "typing.Union[str, None]" = None
36
+ api_key_loaded: "bool" = False
37
+ checks: "typing.List[DoctorCheck]" = field(default_factory=list)
38
+ live_output_text: "typing.Union[str, None]" = None
39
+
40
+ def to_dict(self) -> "typing.Dict[str, object]":
42
41
  return asdict(self)
43
42
 
44
43
 
@@ -79,11 +78,11 @@ def build_doctor_parser():
79
78
 
80
79
 
81
80
  async def collect_doctor_report(
82
- config_path: 'typing.Union[str, Path]',
83
- profile: 'typing.Union[str, None]' = None,
84
- timeout_seconds: 'float' = 120.0,
85
- skip_live: 'bool' = False,
86
- ) -> 'DoctorReport':
81
+ config_path: "typing.Union[str, Path]",
82
+ profile: "typing.Union[str, None]" = None,
83
+ timeout_seconds: "float" = 120.0,
84
+ skip_live: "bool" = False,
85
+ ) -> "DoctorReport":
87
86
  config_file = Path(config_path).expanduser().resolve()
88
87
  dotenv_file = config_file.parent / DOTENV_FILENAME
89
88
  report = DoctorReport(
@@ -118,9 +117,13 @@ async def collect_doctor_report(
118
117
  load_codex_dotenv(config_file)
119
118
 
120
119
  try:
121
- provider_config = ResponsesProviderConfig.from_codex_config(config_file, profile)
120
+ provider_config = ResponsesProviderConfig.from_codex_config(
121
+ config_file, profile
122
+ )
122
123
  except Exception as exc:
123
- checks.append(DoctorCheck("config_parse", False, f"{type(exc).__name__}: {exc}"))
124
+ checks.append(
125
+ DoctorCheck("config_parse", False, f"{type(exc).__name__}: {exc}")
126
+ )
124
127
  return report
125
128
 
126
129
  report.provider_name = provider_config.provider_name
@@ -129,7 +132,9 @@ async def collect_doctor_report(
129
132
  client = ResponsesModelClient(provider_config)
130
133
  report.responses_url = client.responses_url()
131
134
  report.api_key_env = provider_config.api_key_env
132
- report.api_key_loaded = bool(provider_config.api_key_env and _loaded_api_key(provider_config))
135
+ report.api_key_loaded = bool(
136
+ provider_config.api_key_env and _loaded_api_key(provider_config)
137
+ )
133
138
 
134
139
  checks.append(
135
140
  DoctorCheck(
@@ -227,7 +232,7 @@ async def collect_doctor_report(
227
232
  return _finalize_report(report)
228
233
 
229
234
 
230
- async def run_doctor_cli(args) -> 'int':
235
+ async def run_doctor_cli(args) -> "int":
231
236
  report = await collect_doctor_report(
232
237
  args.config,
233
238
  args.profile,
@@ -243,7 +248,7 @@ async def run_doctor_cli(args) -> 'int':
243
248
  return 0 if report.ok else 1
244
249
 
245
250
 
246
- def format_doctor_report(report: 'DoctorReport') -> 'str':
251
+ def format_doctor_report(report: "DoctorReport") -> "str":
247
252
  lines = [
248
253
  f"config: {report.config_path}",
249
254
  f"dotenv: {report.dotenv_path}",
@@ -267,7 +272,7 @@ def format_doctor_report(report: 'DoctorReport') -> 'str':
267
272
  return "\n".join(lines)
268
273
 
269
274
 
270
- def _loaded_api_key(provider_config: 'ResponsesProviderConfig') -> 'str':
275
+ def _loaded_api_key(provider_config: "ResponsesProviderConfig") -> "str":
271
276
  try:
272
277
  return provider_config.api_key()
273
278
  except Exception:
@@ -275,11 +280,11 @@ def _loaded_api_key(provider_config: 'ResponsesProviderConfig') -> 'str':
275
280
 
276
281
 
277
282
  def _probe_transport(
278
- scheme: 'str',
279
- host: 'str',
280
- port: 'int',
281
- timeout_seconds: 'float',
282
- ) -> 'typing.Tuple[bool, str]':
283
+ scheme: "str",
284
+ host: "str",
285
+ port: "int",
286
+ timeout_seconds: "float",
287
+ ) -> "typing.Tuple[bool, str]":
283
288
  started = time.perf_counter()
284
289
  try:
285
290
  with socket.create_connection((host, port), timeout=timeout_seconds) as sock:
@@ -291,14 +296,17 @@ def _probe_transport(
291
296
  pass
292
297
  except OSError as exc:
293
298
  elapsed = time.perf_counter() - started
294
- return False, f"{scheme.upper()} {host}:{port} failed after {elapsed:.2f}s: {exc}"
299
+ return (
300
+ False,
301
+ f"{scheme.upper()} {host}:{port} failed after {elapsed:.2f}s: {exc}",
302
+ )
295
303
 
296
304
  elapsed = time.perf_counter() - started
297
305
  label = "tls" if scheme == "https" else "tcp"
298
306
  return True, f"{label} {host}:{port} connected in {elapsed:.2f}s"
299
307
 
300
308
 
301
- def _proxy_detail(proxies: 'typing.Dict[str, str]') -> 'str':
309
+ def _proxy_detail(proxies: "typing.Dict[str, str]") -> "str":
302
310
  if not proxies:
303
311
  return "not configured"
304
312
  return ", ".join(
@@ -306,7 +314,7 @@ def _proxy_detail(proxies: 'typing.Dict[str, str]') -> 'str':
306
314
  )
307
315
 
308
316
 
309
- def _redact_proxy_url(value: 'str') -> 'str':
317
+ def _redact_proxy_url(value: "str") -> "str":
310
318
  parsed = urllib.parse.urlsplit(value)
311
319
  if not parsed.scheme or not parsed.netloc:
312
320
  return value
@@ -319,10 +327,10 @@ def _redact_proxy_url(value: 'str') -> 'str':
319
327
 
320
328
 
321
329
  async def _run_live_check(
322
- config_path: 'Path',
323
- profile: 'typing.Union[str, None]',
324
- timeout_seconds: 'float',
325
- ) -> 'typing.Tuple[bool, str, typing.Union[str, None]]':
330
+ config_path: "Path",
331
+ profile: "typing.Union[str, None]",
332
+ timeout_seconds: "float",
333
+ ) -> "typing.Tuple[bool, str, typing.Union[str, None]]":
326
334
  client = ResponsesModelClient.from_codex_config(
327
335
  config_path,
328
336
  profile,
@@ -345,16 +353,12 @@ async def _run_live_check(
345
353
 
346
354
  elapsed = time.perf_counter() - started
347
355
  output_text = next(
348
- (
349
- item.text
350
- for item in response.items
351
- if isinstance(item, AssistantMessage)
352
- ),
356
+ (item.text for item in response.items if isinstance(item, AssistantMessage)),
353
357
  None,
354
358
  )
355
359
  return True, f"completed in {elapsed:.2f}s", output_text
356
360
 
357
361
 
358
- def _finalize_report(report: 'DoctorReport') -> 'DoctorReport':
362
+ def _finalize_report(report: "DoctorReport") -> "DoctorReport":
359
363
  report.ok = all(check.ok for check in report.checks)
360
364
  return report