python-codex 0.1.1__py3-none-any.whl → 0.1.3__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 (59) hide show
  1. pycodex/__init__.py +5 -1
  2. pycodex/agent.py +39 -41
  3. pycodex/cli.py +51 -43
  4. pycodex/collaboration.py +6 -7
  5. pycodex/compat.py +99 -0
  6. pycodex/context.py +87 -87
  7. pycodex/doctor.py +40 -40
  8. pycodex/model.py +69 -69
  9. pycodex/portable.py +33 -33
  10. pycodex/portable_server.py +22 -21
  11. pycodex/protocol.py +84 -86
  12. pycodex/runtime.py +36 -35
  13. pycodex/runtime_services.py +72 -69
  14. pycodex/tools/agent_tool_schemas.py +0 -2
  15. pycodex/tools/apply_patch_tool.py +43 -44
  16. pycodex/tools/base_tool.py +35 -36
  17. pycodex/tools/close_agent_tool.py +2 -4
  18. pycodex/tools/code_mode_manager.py +61 -61
  19. pycodex/tools/exec_command_tool.py +5 -6
  20. pycodex/tools/exec_runtime.js +3 -3
  21. pycodex/tools/exec_tool.py +3 -5
  22. pycodex/tools/grep_files_tool.py +10 -11
  23. pycodex/tools/list_dir_tool.py +8 -9
  24. pycodex/tools/read_file_tool.py +13 -14
  25. pycodex/tools/request_permissions_tool.py +2 -4
  26. pycodex/tools/request_user_input_tool.py +13 -14
  27. pycodex/tools/resume_agent_tool.py +2 -4
  28. pycodex/tools/send_input_tool.py +8 -9
  29. pycodex/tools/shell_command_tool.py +5 -6
  30. pycodex/tools/shell_tool.py +5 -6
  31. pycodex/tools/spawn_agent_tool.py +4 -5
  32. pycodex/tools/unified_exec_manager.py +79 -61
  33. pycodex/tools/update_plan_tool.py +4 -5
  34. pycodex/tools/view_image_tool.py +4 -5
  35. pycodex/tools/wait_agent_tool.py +2 -4
  36. pycodex/tools/wait_tool.py +4 -5
  37. pycodex/tools/web_search_tool.py +1 -3
  38. pycodex/tools/write_stdin_tool.py +4 -5
  39. pycodex/utils/dotenv.py +6 -6
  40. pycodex/utils/get_env.py +57 -34
  41. pycodex/utils/random_ids.py +1 -2
  42. pycodex/utils/visualize.py +79 -79
  43. {python_codex-0.1.1.dist-info → python_codex-0.1.3.dist-info}/METADATA +15 -9
  44. python_codex-0.1.3.dist-info/RECORD +74 -0
  45. {python_codex-0.1.1.dist-info → python_codex-0.1.3.dist-info}/WHEEL +1 -1
  46. responses_server/__init__.py +17 -0
  47. responses_server/__main__.py +5 -0
  48. responses_server/app.py +227 -0
  49. responses_server/config.py +63 -0
  50. responses_server/payload_processors.py +86 -0
  51. responses_server/server.py +63 -0
  52. responses_server/session_store.py +37 -0
  53. responses_server/stream_router.py +784 -0
  54. responses_server/tools/__init__.py +4 -0
  55. responses_server/tools/custom_adapter.py +235 -0
  56. responses_server/tools/web_search.py +263 -0
  57. python_codex-0.1.1.dist-info/RECORD +0 -62
  58. {python_codex-0.1.1.dist-info → python_codex-0.1.3.dist-info}/entry_points.txt +0 -0
  59. {python_codex-0.1.1.dist-info → python_codex-0.1.3.dist-info}/licenses/LICENSE +0 -0
@@ -9,13 +9,12 @@ Expected behavior:
9
9
  mode used to inspect code structure without shelling out to `sed` or `cat`.
10
10
  """
11
11
 
12
- from __future__ import annotations
13
-
14
12
  from collections import deque
15
13
  from pathlib import Path
16
14
 
17
15
  from ..protocol import JSONDict, JSONValue
18
16
  from .base_tool import BaseTool, ToolContext
17
+ import typing
19
18
 
20
19
  MAX_LINE_LENGTH = 500
21
20
  TAB_WIDTH = 4
@@ -49,7 +48,7 @@ class ReadFileTool(BaseTool):
49
48
  "required": ["file_path"],
50
49
  }
51
50
 
52
- async def run(self, context: ToolContext, args: JSONDict) -> JSONValue:
51
+ async def run(self, context: 'ToolContext', args: 'JSONDict') -> 'JSONValue':
53
52
  del context
54
53
  file_path = Path(str(args.get("file_path", "")))
55
54
  offset = int(args.get("offset", 1))
@@ -72,7 +71,7 @@ class ReadFileTool(BaseTool):
72
71
  return self._read_indentation(file_path, offset, limit, indentation)
73
72
  return self._read_slice(file_path, offset, limit)
74
73
 
75
- def _read_slice(self, file_path: Path, offset: int, limit: int) -> str:
74
+ def _read_slice(self, file_path: 'Path', offset: 'int', limit: 'int') -> 'str':
76
75
  lines = file_path.read_text(errors="replace").splitlines()
77
76
  if offset > len(lines):
78
77
  return "Error: `offset` exceeds file length."
@@ -85,11 +84,11 @@ class ReadFileTool(BaseTool):
85
84
 
86
85
  def _read_indentation(
87
86
  self,
88
- file_path: Path,
89
- offset: int,
90
- limit: int,
91
- indentation: JSONDict,
92
- ) -> str:
87
+ file_path: 'Path',
88
+ offset: 'int',
89
+ limit: 'int',
90
+ indentation: 'JSONDict',
91
+ ) -> 'str':
93
92
  lines = self._collect_line_records(file_path)
94
93
  anchor_line = int(indentation.get("anchor_line", offset))
95
94
  max_levels = int(indentation.get("max_levels", 0))
@@ -171,7 +170,7 @@ class ReadFileTool(BaseTool):
171
170
  f"L{record['number']}: {record['display']}" for record in selected
172
171
  )
173
172
 
174
- def _collect_line_records(self, file_path: Path) -> list[dict[str, object]]:
173
+ def _collect_line_records(self, file_path: 'Path') -> 'typing.List[typing.Dict[str, object]]':
175
174
  records = []
176
175
  for number, raw in enumerate(file_path.read_text(errors="replace").splitlines(), start=1):
177
176
  records.append(
@@ -185,7 +184,7 @@ class ReadFileTool(BaseTool):
185
184
  )
186
185
  return records
187
186
 
188
- def _compute_effective_indents(self, records: list[dict[str, object]]) -> list[int]:
187
+ def _compute_effective_indents(self, records: 'typing.List[typing.Dict[str, object]]') -> 'typing.List[int]':
189
188
  effective = []
190
189
  previous_indent = 0
191
190
  for record in records:
@@ -196,7 +195,7 @@ class ReadFileTool(BaseTool):
196
195
  effective.append(previous_indent)
197
196
  return effective
198
197
 
199
- def _measure_indent(self, line: str) -> int:
198
+ def _measure_indent(self, line: 'str') -> 'int':
200
199
  total = 0
201
200
  for character in line:
202
201
  if character == " ":
@@ -207,10 +206,10 @@ class ReadFileTool(BaseTool):
207
206
  break
208
207
  return total
209
208
 
210
- def _format_line(self, text: str) -> str:
209
+ def _format_line(self, text: 'str') -> 'str':
211
210
  return text[:MAX_LINE_LENGTH]
212
211
 
213
- def _trim_empty_lines(self, records: deque[dict[str, object]]) -> None:
212
+ def _trim_empty_lines(self, records: 'deque[typing.Dict[str, object]]') -> 'None':
214
213
  while records and not str(records[0]["raw"]).strip():
215
214
  records.popleft()
216
215
  while records and not str(records[-1]["raw"]).strip():
@@ -9,8 +9,6 @@ Expected behavior:
9
9
  - Return the granted permission profile and scope so later tool calls can use it.
10
10
  """
11
11
 
12
- from __future__ import annotations
13
-
14
12
  from ..protocol import JSONDict, JSONValue
15
13
  from ..runtime_services import RequestPermissionsManager
16
14
  from .base_tool import BaseTool, ToolContext
@@ -73,10 +71,10 @@ class RequestPermissionsTool(BaseTool):
73
71
  }
74
72
  supports_parallel = False
75
73
 
76
- def __init__(self, request_manager: RequestPermissionsManager) -> None:
74
+ def __init__(self, request_manager: 'RequestPermissionsManager') -> 'None':
77
75
  self._request_manager = request_manager
78
76
 
79
- async def run(self, context: ToolContext, args: JSONDict) -> JSONValue:
77
+ async def run(self, context: 'ToolContext', args: 'JSONDict') -> 'JSONValue':
80
78
  del context
81
79
  permissions = args.get("permissions")
82
80
  if not isinstance(permissions, dict):
@@ -12,14 +12,13 @@ Expected behavior:
12
12
  `success=true`.
13
13
  """
14
14
 
15
- from __future__ import annotations
16
-
17
15
  import json
18
16
 
19
17
  from ..collaboration import collaboration_mode_display_name
20
18
  from ..protocol import JSONDict, JSONValue
21
19
  from ..runtime_services import RequestUserInputManager
22
20
  from .base_tool import BaseTool, StructuredToolOutput, ToolContext
21
+ import typing
23
22
 
24
23
  REQUEST_USER_INPUT_QUESTION_SCHEMA = {
25
24
  "type": "object",
@@ -67,9 +66,9 @@ REQUEST_USER_INPUT_QUESTION_SCHEMA = {
67
66
 
68
67
 
69
68
  def request_user_input_is_available(
70
- mode: str,
71
- default_mode_request_user_input: bool = False,
72
- ) -> bool:
69
+ mode: 'str',
70
+ default_mode_request_user_input: 'bool' = False,
71
+ ) -> 'bool':
73
72
  normalized = mode.strip().lower()
74
73
  return normalized == "plan" or (
75
74
  default_mode_request_user_input and normalized == "default"
@@ -77,9 +76,9 @@ def request_user_input_is_available(
77
76
 
78
77
 
79
78
  def request_user_input_unavailable_message(
80
- mode: str,
81
- default_mode_request_user_input: bool = False,
82
- ) -> str | None:
79
+ mode: 'str',
80
+ default_mode_request_user_input: 'bool' = False,
81
+ ) -> 'typing.Union[str, None]':
83
82
  if request_user_input_is_available(mode, default_mode_request_user_input):
84
83
  return None
85
84
  return (
@@ -89,8 +88,8 @@ def request_user_input_unavailable_message(
89
88
 
90
89
 
91
90
  def request_user_input_tool_description(
92
- default_mode_request_user_input: bool = False,
93
- ) -> str:
91
+ default_mode_request_user_input: 'bool' = False,
92
+ ) -> 'str':
94
93
  if default_mode_request_user_input:
95
94
  allowed_modes = "Default or Plan mode"
96
95
  else:
@@ -120,16 +119,16 @@ class RequestUserInputTool(BaseTool):
120
119
 
121
120
  def __init__(
122
121
  self,
123
- request_manager: RequestUserInputManager,
124
- default_mode_request_user_input: bool = False,
125
- ) -> None:
122
+ request_manager: 'RequestUserInputManager',
123
+ default_mode_request_user_input: 'bool' = False,
124
+ ) -> 'None':
126
125
  self._request_manager = request_manager
127
126
  self._default_mode_request_user_input = default_mode_request_user_input
128
127
  self.description = request_user_input_tool_description(
129
128
  default_mode_request_user_input
130
129
  )
131
130
 
132
- async def run(self, context: ToolContext, args: JSONDict) -> JSONValue:
131
+ async def run(self, context: 'ToolContext', args: 'JSONDict') -> 'JSONValue':
133
132
  unavailable = request_user_input_unavailable_message(
134
133
  context.collaboration_mode,
135
134
  self._default_mode_request_user_input,
@@ -8,8 +8,6 @@ Expected behavior:
8
8
  - Return the agent's current status payload.
9
9
  """
10
10
 
11
- from __future__ import annotations
12
-
13
11
  from ..protocol import JSONDict, JSONValue
14
12
  from ..runtime_services import SubAgentManager
15
13
  from .agent_tool_schemas import AGENT_STATUS_SCHEMA
@@ -45,10 +43,10 @@ class ResumeAgentTool(BaseTool):
45
43
  output_schema = RESUME_AGENT_OUTPUT_SCHEMA
46
44
  supports_parallel = False
47
45
 
48
- def __init__(self, subagent_manager: SubAgentManager) -> None:
46
+ def __init__(self, subagent_manager: 'SubAgentManager') -> 'None':
49
47
  self._subagent_manager = subagent_manager
50
48
 
51
- async def run(self, context: ToolContext, args: JSONDict) -> JSONValue:
49
+ async def run(self, context: 'ToolContext', args: 'JSONDict') -> 'JSONValue':
52
50
  del context
53
51
  agent_id = str(args.get("id", "")).strip()
54
52
  if not agent_id:
@@ -9,12 +9,11 @@ Expected behavior:
9
9
  - Return the submission id for the queued input.
10
10
  """
11
11
 
12
- from __future__ import annotations
13
-
14
12
  from ..protocol import JSONDict, JSONValue
15
13
  from ..runtime_services import SubAgentManager
16
14
  from .agent_tool_schemas import COLLAB_INPUT_ITEMS_SCHEMA
17
15
  from .base_tool import BaseTool, ToolContext
16
+ import typing
18
17
 
19
18
  SEND_INPUT_OUTPUT_SCHEMA = {
20
19
  "type": "object",
@@ -58,10 +57,10 @@ class SendInputTool(BaseTool):
58
57
  output_schema = SEND_INPUT_OUTPUT_SCHEMA
59
58
  supports_parallel = False
60
59
 
61
- def __init__(self, subagent_manager: SubAgentManager) -> None:
60
+ def __init__(self, subagent_manager: 'SubAgentManager') -> 'None':
62
61
  self._subagent_manager = subagent_manager
63
62
 
64
- async def run(self, context: ToolContext, args: JSONDict) -> JSONValue:
63
+ async def run(self, context: 'ToolContext', args: 'JSONDict') -> 'JSONValue':
65
64
  del context
66
65
  agent_id = str(args.get("id", "")).strip()
67
66
  if not agent_id:
@@ -83,10 +82,10 @@ class SendInputTool(BaseTool):
83
82
 
84
83
  def _compose_prompt(
85
84
  self,
86
- message: str | None,
87
- items: list[dict[str, object]] | None,
88
- ) -> str:
89
- parts: list[str] = []
85
+ message: 'typing.Union[str, None]',
86
+ items: 'typing.Union[typing.List[typing.Dict[str, object]], None]',
87
+ ) -> 'str':
88
+ parts: 'typing.List[str]' = []
90
89
  if message:
91
90
  parts.append(message.strip())
92
91
  for item in items or []:
@@ -99,7 +98,7 @@ class SendInputTool(BaseTool):
99
98
  parts.append(str(item))
100
99
  return "\n\n".join(part for part in parts if part)
101
100
 
102
- def _optional_string(self, args: JSONDict, key: str) -> str | None:
101
+ def _optional_string(self, args: 'JSONDict', key: 'str') -> 'typing.Union[str, None]':
103
102
  value = args.get(key)
104
103
  if value in (None, ""):
105
104
  return None
@@ -11,13 +11,12 @@ Expected behavior:
11
11
  stdout, and stderr.
12
12
  """
13
13
 
14
- from __future__ import annotations
15
-
16
14
  import asyncio
17
15
  from pathlib import Path
18
16
 
19
17
  from ..protocol import JSONDict, JSONValue
20
18
  from .base_tool import BaseTool, ToolContext
19
+ import typing
21
20
 
22
21
  DEFAULT_SHELL_TIMEOUT_MS = 30_000
23
22
  MAX_OUTPUT_CHARS = 12_000
@@ -38,10 +37,10 @@ class ShellCommandTool(BaseTool):
38
37
  }
39
38
  supports_parallel = False
40
39
 
41
- def __init__(self, cwd: str | Path | None = None) -> None:
40
+ def __init__(self, cwd: 'typing.Union[typing.Union[str, Path], None]' = None) -> 'None':
42
41
  self._working_directory = Path(cwd or Path.cwd()).resolve()
43
42
 
44
- async def run(self, context: ToolContext, args: JSONDict) -> JSONValue:
43
+ async def run(self, context: 'ToolContext', args: 'JSONDict') -> 'JSONValue':
45
44
  del context
46
45
  command = str(args.get("command", "")).strip()
47
46
  timeout_ms = int(args.get("timeout_ms", DEFAULT_SHELL_TIMEOUT_MS))
@@ -93,7 +92,7 @@ class ShellCommandTool(BaseTool):
93
92
 
94
93
  return "\n".join(pieces)
95
94
 
96
- def _resolve_workdir(self, workdir_arg) -> Path:
95
+ def _resolve_workdir(self, workdir_arg) -> 'Path':
97
96
  if workdir_arg in (None, ""):
98
97
  return self._working_directory
99
98
  workdir = Path(str(workdir_arg))
@@ -101,7 +100,7 @@ class ShellCommandTool(BaseTool):
101
100
  workdir = self._working_directory / workdir
102
101
  return workdir.resolve()
103
102
 
104
- def _clip_output(self, text: str) -> str:
103
+ def _clip_output(self, text: 'str') -> 'str':
105
104
  if len(text) <= MAX_OUTPUT_CHARS:
106
105
  return text
107
106
  return text[:MAX_OUTPUT_CHARS] + "\n...[truncated]..."
@@ -11,13 +11,12 @@ Expected behavior:
11
11
  stdout, and stderr.
12
12
  """
13
13
 
14
- from __future__ import annotations
15
-
16
14
  import asyncio
17
15
  from pathlib import Path
18
16
 
19
17
  from ..protocol import JSONDict, JSONValue
20
18
  from .base_tool import BaseTool, ToolContext
19
+ import typing
21
20
 
22
21
  DEFAULT_SHELL_TIMEOUT_MS = 30_000
23
22
  MAX_OUTPUT_CHARS = 12_000
@@ -43,10 +42,10 @@ class ShellTool(BaseTool):
43
42
  }
44
43
  supports_parallel = False
45
44
 
46
- def __init__(self, cwd: str | Path | None = None) -> None:
45
+ def __init__(self, cwd: 'typing.Union[typing.Union[str, Path], None]' = None) -> 'None':
47
46
  self._working_directory = Path(cwd or Path.cwd()).resolve()
48
47
 
49
- async def run(self, context: ToolContext, args: JSONDict) -> JSONValue:
48
+ async def run(self, context: 'ToolContext', args: 'JSONDict') -> 'JSONValue':
50
49
  del context
51
50
  command = args.get("command")
52
51
  timeout_ms = int(args.get("timeout_ms", DEFAULT_SHELL_TIMEOUT_MS))
@@ -98,7 +97,7 @@ class ShellTool(BaseTool):
98
97
 
99
98
  return "\n".join(pieces)
100
99
 
101
- def _resolve_workdir(self, workdir_arg) -> Path:
100
+ def _resolve_workdir(self, workdir_arg) -> 'Path':
102
101
  if workdir_arg in (None, ""):
103
102
  return self._working_directory
104
103
  workdir = Path(str(workdir_arg))
@@ -106,7 +105,7 @@ class ShellTool(BaseTool):
106
105
  workdir = self._working_directory / workdir
107
106
  return workdir.resolve()
108
107
 
109
- def _clip_output(self, text: str) -> str:
108
+ def _clip_output(self, text: 'str') -> 'str':
110
109
  if len(text) <= MAX_OUTPUT_CHARS:
111
110
  return text
112
111
  return text[:MAX_OUTPUT_CHARS] + "\n...[truncated]..."
@@ -9,12 +9,11 @@ Expected behavior:
9
9
  - Return the new agent identifier plus any user-facing nickname.
10
10
  """
11
11
 
12
- from __future__ import annotations
13
-
14
12
  from ..protocol import JSONDict, JSONValue
15
13
  from ..runtime_services import SubAgentManager
16
14
  from .agent_tool_schemas import COLLAB_INPUT_ITEMS_SCHEMA
17
15
  from .base_tool import BaseTool, ToolContext
16
+ import typing
18
17
 
19
18
  SPAWN_AGENT_OUTPUT_SCHEMA = {
20
19
  "type": "object",
@@ -70,10 +69,10 @@ class SpawnAgentTool(BaseTool):
70
69
  output_schema = SPAWN_AGENT_OUTPUT_SCHEMA
71
70
  supports_parallel = False
72
71
 
73
- def __init__(self, subagent_manager: SubAgentManager) -> None:
72
+ def __init__(self, subagent_manager: 'SubAgentManager') -> 'None':
74
73
  self._subagent_manager = subagent_manager
75
74
 
76
- async def run(self, context: ToolContext, args: JSONDict) -> JSONValue:
75
+ async def run(self, context: 'ToolContext', args: 'JSONDict') -> 'JSONValue':
77
76
  message = self._optional_string(args, "message")
78
77
  items = args.get("items")
79
78
  if items is not None and not isinstance(items, list):
@@ -90,7 +89,7 @@ class SpawnAgentTool(BaseTool):
90
89
  history=context.history,
91
90
  )
92
91
 
93
- def _optional_string(self, args: JSONDict, key: str) -> str | None:
92
+ def _optional_string(self, args: 'JSONDict', key: 'str') -> 'typing.Union[str, None]':
94
93
  value = args.get(key)
95
94
  if value in (None, ""):
96
95
  return None