python-codex 0.0.1__py3-none-any.whl → 0.1.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 (62) hide show
  1. pycodex/__init__.py +139 -2
  2. pycodex/agent.py +290 -0
  3. pycodex/cli.py +641 -0
  4. pycodex/collaboration.py +21 -0
  5. pycodex/context.py +580 -0
  6. pycodex/doctor.py +360 -0
  7. pycodex/model.py +533 -0
  8. pycodex/prompts/collaboration_default.md +11 -0
  9. pycodex/prompts/collaboration_plan.md +128 -0
  10. pycodex/prompts/default_base_instructions.md +275 -0
  11. pycodex/prompts/exec_tools.json +411 -0
  12. pycodex/prompts/models.json +847 -0
  13. pycodex/prompts/permissions/approval_policy/never.md +1 -0
  14. pycodex/prompts/permissions/approval_policy/on_failure.md +1 -0
  15. pycodex/prompts/permissions/approval_policy/on_request.md +57 -0
  16. pycodex/prompts/permissions/approval_policy/on_request_rule_request_permission.md +33 -0
  17. pycodex/prompts/permissions/approval_policy/unless_trusted.md +1 -0
  18. pycodex/prompts/permissions/sandbox_mode/danger_full_access.md +1 -0
  19. pycodex/prompts/permissions/sandbox_mode/read_only.md +1 -0
  20. pycodex/prompts/permissions/sandbox_mode/workspace_write.md +1 -0
  21. pycodex/prompts/subagent_tools.json +163 -0
  22. pycodex/protocol.py +347 -0
  23. pycodex/runtime.py +200 -0
  24. pycodex/runtime_services.py +408 -0
  25. pycodex/tools/__init__.py +58 -0
  26. pycodex/tools/agent_tool_schemas.py +70 -0
  27. pycodex/tools/apply_patch_tool.py +363 -0
  28. pycodex/tools/base_tool.py +168 -0
  29. pycodex/tools/close_agent_tool.py +55 -0
  30. pycodex/tools/code_mode_manager.py +519 -0
  31. pycodex/tools/exec_command_tool.py +96 -0
  32. pycodex/tools/exec_runtime.js +161 -0
  33. pycodex/tools/exec_tool.py +48 -0
  34. pycodex/tools/grep_files_tool.py +150 -0
  35. pycodex/tools/list_dir_tool.py +135 -0
  36. pycodex/tools/read_file_tool.py +217 -0
  37. pycodex/tools/request_permissions_tool.py +95 -0
  38. pycodex/tools/request_user_input_tool.py +167 -0
  39. pycodex/tools/resume_agent_tool.py +56 -0
  40. pycodex/tools/send_input_tool.py +106 -0
  41. pycodex/tools/shell_command_tool.py +107 -0
  42. pycodex/tools/shell_tool.py +112 -0
  43. pycodex/tools/spawn_agent_tool.py +97 -0
  44. pycodex/tools/unified_exec_manager.py +380 -0
  45. pycodex/tools/update_plan_tool.py +79 -0
  46. pycodex/tools/view_image_tool.py +111 -0
  47. pycodex/tools/wait_agent_tool.py +75 -0
  48. pycodex/tools/wait_tool.py +68 -0
  49. pycodex/tools/web_search_tool.py +30 -0
  50. pycodex/tools/write_stdin_tool.py +75 -0
  51. pycodex/utils/__init__.py +40 -0
  52. pycodex/utils/dotenv.py +64 -0
  53. pycodex/utils/get_env.py +218 -0
  54. pycodex/utils/random_ids.py +19 -0
  55. pycodex/utils/visualize.py +978 -0
  56. python_codex-0.1.0.dist-info/METADATA +267 -0
  57. python_codex-0.1.0.dist-info/RECORD +60 -0
  58. python_codex-0.1.0.dist-info/entry_points.txt +2 -0
  59. python_codex-0.1.0.dist-info/licenses/LICENSE +201 -0
  60. python_codex-0.0.1.dist-info/METADATA +0 -30
  61. python_codex-0.0.1.dist-info/RECORD +0 -4
  62. {python_codex-0.0.1.dist-info → python_codex-0.1.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,218 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib.metadata
4
+ import os
5
+ import platform
6
+ import re
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+ import subprocess
10
+
11
+
12
+ def get_shell_name() -> str:
13
+ shell_path = os.environ.get("SHELL")
14
+ if shell_path:
15
+ return Path(shell_path).name or shell_path
16
+ return "bash"
17
+
18
+
19
+ def get_timezone_name() -> str:
20
+ timezone_env = os.environ.get("TZ")
21
+ if timezone_env:
22
+ return timezone_env
23
+ zoneinfo_root = Path("/usr/share/zoneinfo")
24
+ localtime = Path("/etc/localtime")
25
+ try:
26
+ resolved = localtime.resolve()
27
+ except OSError:
28
+ resolved = None
29
+ if resolved is not None and zoneinfo_root in resolved.parents:
30
+ return str(resolved.relative_to(zoneinfo_root))
31
+ timezone = datetime.now().astimezone().tzinfo
32
+ if timezone is None:
33
+ return "Etc/UTC"
34
+ name = str(timezone)
35
+ return name or "Etc/UTC"
36
+ def get_sandbox_tag(sandbox_mode: str | None) -> str:
37
+ if sandbox_mode == "danger-full-access":
38
+ return "none"
39
+ if sandbox_mode == "read-only":
40
+ return "read-only"
41
+ if sandbox_mode == "workspace-write":
42
+ return "workspace-write"
43
+ return "none"
44
+
45
+
46
+ def get_workspace_turn_metadata(cwd: str | Path) -> dict[str, object] | None:
47
+ resolved_cwd = Path(cwd).resolve()
48
+ repo_root = _git_output(
49
+ resolved_cwd,
50
+ ["rev-parse", "--show-toplevel"],
51
+ )
52
+ if repo_root is None:
53
+ return None
54
+
55
+ workspace: dict[str, object] = {}
56
+ head = _git_output(resolved_cwd, ["rev-parse", "HEAD"])
57
+ if head is not None:
58
+ workspace["latest_git_commit_hash"] = head
59
+
60
+ remotes = _git_remote_urls(resolved_cwd)
61
+ if remotes:
62
+ workspace["associated_remote_urls"] = remotes
63
+
64
+ has_changes = _git_has_changes(resolved_cwd)
65
+ if has_changes is not None:
66
+ workspace["has_changes"] = has_changes
67
+
68
+ if not workspace:
69
+ return None
70
+ return {"workspaces": {repo_root: workspace}}
71
+
72
+
73
+ def build_user_agent(originator: str) -> str:
74
+ version = get_package_version()
75
+ terminal = get_terminal_user_agent_token()
76
+ os_name, os_version = get_os_info()
77
+ arch = platform.machine() or "unknown"
78
+ suffix = _user_agent_suffix(originator, version)
79
+ return f"{originator}/{version} ({os_name} {os_version}; {arch}) {terminal}{suffix}"
80
+
81
+
82
+ def get_package_version() -> str:
83
+ detected = _detect_upstream_codex_version()
84
+ if detected is not None:
85
+ return detected
86
+ try:
87
+ return importlib.metadata.version("pycodex")
88
+ except importlib.metadata.PackageNotFoundError:
89
+ return "0.1.0"
90
+
91
+
92
+ def get_os_info() -> tuple[str, str]:
93
+ os_release = Path("/etc/os-release")
94
+ if os_release.is_file():
95
+ values: dict[str, str] = {}
96
+ for line in os_release.read_text().splitlines():
97
+ if "=" not in line:
98
+ continue
99
+ key, value = line.split("=", 1)
100
+ values[key] = value.strip().strip('"')
101
+ name = values.get("NAME")
102
+ version = values.get("VERSION_ID")
103
+ if name and version:
104
+ return name, _normalize_os_version(version)
105
+ return platform.system(), platform.release()
106
+
107
+
108
+ def get_terminal_user_agent_token() -> str:
109
+ term_program = os.environ.get("TERM_PROGRAM", "")
110
+ if term_program.lower() == "tmux":
111
+ client_termname = _tmux_display_message("#{client_termname}")
112
+ if client_termname:
113
+ return _sanitize_header_token(client_termname)
114
+
115
+ term = os.environ.get("TERM")
116
+ if term:
117
+ return _sanitize_header_token(term)
118
+ return "unknown"
119
+
120
+
121
+ def _git_output(cwd: Path, args: list[str]) -> str | None:
122
+ try:
123
+ completed = subprocess.run(
124
+ ["git", *args],
125
+ cwd=str(cwd),
126
+ check=True,
127
+ capture_output=True,
128
+ text=True,
129
+ )
130
+ except (OSError, subprocess.CalledProcessError):
131
+ return None
132
+ value = completed.stdout.strip()
133
+ return value or None
134
+
135
+
136
+ def _git_remote_urls(cwd: Path) -> dict[str, str]:
137
+ remote_names = _git_output(cwd, ["remote"])
138
+ if remote_names is None:
139
+ return {}
140
+ remotes: dict[str, str] = {}
141
+ for name in remote_names.splitlines():
142
+ remote_name = name.strip()
143
+ if not remote_name:
144
+ continue
145
+ url = _git_output(cwd, ["remote", "get-url", remote_name])
146
+ if url is not None:
147
+ remotes[remote_name] = url
148
+ return remotes
149
+
150
+
151
+ def _git_has_changes(cwd: Path) -> bool | None:
152
+ try:
153
+ completed = subprocess.run(
154
+ ["git", "status", "--porcelain"],
155
+ cwd=str(cwd),
156
+ check=True,
157
+ capture_output=True,
158
+ text=True,
159
+ )
160
+ except (OSError, subprocess.CalledProcessError):
161
+ return None
162
+ return bool(completed.stdout.strip())
163
+
164
+
165
+ def _user_agent_suffix(originator: str, version: str) -> str:
166
+ if originator == "codex_exec":
167
+ return f" (codex-exec; {version})"
168
+ if originator == "codex-tui":
169
+ return f" (codex-tui; {version})"
170
+ return ""
171
+
172
+
173
+ def _normalize_os_version(version: str) -> str:
174
+ parts = version.split(".")
175
+ if len(parts) == 2 and all(part.isdigit() for part in parts):
176
+ major, minor = parts
177
+ return f"{int(major)}.{int(minor)}.0"
178
+ return version
179
+
180
+
181
+ def _tmux_display_message(fmt: str) -> str | None:
182
+ try:
183
+ output = subprocess.run(
184
+ ["tmux", "display-message", "-p", fmt],
185
+ check=True,
186
+ capture_output=True,
187
+ text=True,
188
+ )
189
+ except (OSError, subprocess.CalledProcessError):
190
+ return None
191
+ value = output.stdout.strip()
192
+ return value or None
193
+
194
+
195
+ def _sanitize_header_token(value: str) -> str:
196
+ return "".join(
197
+ character
198
+ if (character.isalnum() or character in {"-", "_", ".", "/"})
199
+ else "_"
200
+ for character in value
201
+ )
202
+
203
+
204
+ def _detect_upstream_codex_version() -> str | None:
205
+ try:
206
+ output = subprocess.run(
207
+ ["codex", "--version"],
208
+ check=True,
209
+ capture_output=True,
210
+ text=True,
211
+ )
212
+ except (OSError, subprocess.CalledProcessError):
213
+ return None
214
+
215
+ match = re.search(r"\b(\d+\.\d+\.\d+)\b", output.stdout)
216
+ if match is None:
217
+ return None
218
+ return match.group(1)
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ import time
5
+ import uuid
6
+
7
+
8
+ def uuid7_string() -> str:
9
+ timestamp_ms = int(time.time() * 1000) & ((1 << 48) - 1)
10
+ rand_a = random.getrandbits(12)
11
+ rand_b = random.getrandbits(62)
12
+
13
+ value = 0
14
+ value |= timestamp_ms << 80
15
+ value |= 0x7 << 76
16
+ value |= rand_a << 64
17
+ value |= 0b10 << 62
18
+ value |= rand_b
19
+ return str(uuid.UUID(int=value))