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.
- pycodex/__init__.py +139 -2
- pycodex/agent.py +290 -0
- pycodex/cli.py +641 -0
- pycodex/collaboration.py +21 -0
- pycodex/context.py +580 -0
- pycodex/doctor.py +360 -0
- pycodex/model.py +533 -0
- pycodex/prompts/collaboration_default.md +11 -0
- pycodex/prompts/collaboration_plan.md +128 -0
- pycodex/prompts/default_base_instructions.md +275 -0
- pycodex/prompts/exec_tools.json +411 -0
- pycodex/prompts/models.json +847 -0
- pycodex/prompts/permissions/approval_policy/never.md +1 -0
- pycodex/prompts/permissions/approval_policy/on_failure.md +1 -0
- pycodex/prompts/permissions/approval_policy/on_request.md +57 -0
- pycodex/prompts/permissions/approval_policy/on_request_rule_request_permission.md +33 -0
- pycodex/prompts/permissions/approval_policy/unless_trusted.md +1 -0
- pycodex/prompts/permissions/sandbox_mode/danger_full_access.md +1 -0
- pycodex/prompts/permissions/sandbox_mode/read_only.md +1 -0
- pycodex/prompts/permissions/sandbox_mode/workspace_write.md +1 -0
- pycodex/prompts/subagent_tools.json +163 -0
- pycodex/protocol.py +347 -0
- pycodex/runtime.py +200 -0
- pycodex/runtime_services.py +408 -0
- pycodex/tools/__init__.py +58 -0
- pycodex/tools/agent_tool_schemas.py +70 -0
- pycodex/tools/apply_patch_tool.py +363 -0
- pycodex/tools/base_tool.py +168 -0
- pycodex/tools/close_agent_tool.py +55 -0
- pycodex/tools/code_mode_manager.py +519 -0
- pycodex/tools/exec_command_tool.py +96 -0
- pycodex/tools/exec_runtime.js +161 -0
- pycodex/tools/exec_tool.py +48 -0
- pycodex/tools/grep_files_tool.py +150 -0
- pycodex/tools/list_dir_tool.py +135 -0
- pycodex/tools/read_file_tool.py +217 -0
- pycodex/tools/request_permissions_tool.py +95 -0
- pycodex/tools/request_user_input_tool.py +167 -0
- pycodex/tools/resume_agent_tool.py +56 -0
- pycodex/tools/send_input_tool.py +106 -0
- pycodex/tools/shell_command_tool.py +107 -0
- pycodex/tools/shell_tool.py +112 -0
- pycodex/tools/spawn_agent_tool.py +97 -0
- pycodex/tools/unified_exec_manager.py +380 -0
- pycodex/tools/update_plan_tool.py +79 -0
- pycodex/tools/view_image_tool.py +111 -0
- pycodex/tools/wait_agent_tool.py +75 -0
- pycodex/tools/wait_tool.py +68 -0
- pycodex/tools/web_search_tool.py +30 -0
- pycodex/tools/write_stdin_tool.py +75 -0
- pycodex/utils/__init__.py +40 -0
- pycodex/utils/dotenv.py +64 -0
- pycodex/utils/get_env.py +218 -0
- pycodex/utils/random_ids.py +19 -0
- pycodex/utils/visualize.py +978 -0
- python_codex-0.1.0.dist-info/METADATA +267 -0
- python_codex-0.1.0.dist-info/RECORD +60 -0
- python_codex-0.1.0.dist-info/entry_points.txt +2 -0
- python_codex-0.1.0.dist-info/licenses/LICENSE +201 -0
- python_codex-0.0.1.dist-info/METADATA +0 -30
- python_codex-0.0.1.dist-info/RECORD +0 -4
- {python_codex-0.0.1.dist-info → python_codex-0.1.0.dist-info}/WHEEL +0 -0
pycodex/utils/get_env.py
ADDED
|
@@ -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))
|