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.
- pycodex/__init__.py +5 -1
- pycodex/agent.py +39 -41
- pycodex/cli.py +51 -43
- pycodex/collaboration.py +6 -7
- pycodex/compat.py +99 -0
- pycodex/context.py +87 -87
- pycodex/doctor.py +40 -40
- pycodex/model.py +69 -69
- pycodex/portable.py +33 -33
- pycodex/portable_server.py +22 -21
- pycodex/protocol.py +84 -86
- pycodex/runtime.py +36 -35
- pycodex/runtime_services.py +72 -69
- pycodex/tools/agent_tool_schemas.py +0 -2
- pycodex/tools/apply_patch_tool.py +43 -44
- pycodex/tools/base_tool.py +35 -36
- pycodex/tools/close_agent_tool.py +2 -4
- pycodex/tools/code_mode_manager.py +61 -61
- pycodex/tools/exec_command_tool.py +5 -6
- pycodex/tools/exec_runtime.js +3 -3
- pycodex/tools/exec_tool.py +3 -5
- pycodex/tools/grep_files_tool.py +10 -11
- pycodex/tools/list_dir_tool.py +8 -9
- pycodex/tools/read_file_tool.py +13 -14
- pycodex/tools/request_permissions_tool.py +2 -4
- pycodex/tools/request_user_input_tool.py +13 -14
- pycodex/tools/resume_agent_tool.py +2 -4
- pycodex/tools/send_input_tool.py +8 -9
- pycodex/tools/shell_command_tool.py +5 -6
- pycodex/tools/shell_tool.py +5 -6
- pycodex/tools/spawn_agent_tool.py +4 -5
- pycodex/tools/unified_exec_manager.py +79 -61
- pycodex/tools/update_plan_tool.py +4 -5
- pycodex/tools/view_image_tool.py +4 -5
- pycodex/tools/wait_agent_tool.py +2 -4
- pycodex/tools/wait_tool.py +4 -5
- pycodex/tools/web_search_tool.py +1 -3
- pycodex/tools/write_stdin_tool.py +4 -5
- pycodex/utils/dotenv.py +6 -6
- pycodex/utils/get_env.py +57 -34
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/visualize.py +79 -79
- {python_codex-0.1.1.dist-info → python_codex-0.1.3.dist-info}/METADATA +15 -9
- python_codex-0.1.3.dist-info/RECORD +74 -0
- {python_codex-0.1.1.dist-info → python_codex-0.1.3.dist-info}/WHEEL +1 -1
- responses_server/__init__.py +17 -0
- responses_server/__main__.py +5 -0
- responses_server/app.py +227 -0
- responses_server/config.py +63 -0
- responses_server/payload_processors.py +86 -0
- responses_server/server.py +63 -0
- responses_server/session_store.py +37 -0
- responses_server/stream_router.py +784 -0
- responses_server/tools/__init__.py +4 -0
- responses_server/tools/custom_adapter.py +235 -0
- responses_server/tools/web_search.py +263 -0
- python_codex-0.1.1.dist-info/RECORD +0 -62
- {python_codex-0.1.1.dist-info → python_codex-0.1.3.dist-info}/entry_points.txt +0 -0
- {python_codex-0.1.1.dist-info → python_codex-0.1.3.dist-info}/licenses/LICENSE +0 -0
pycodex/context.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
1
|
|
|
3
2
|
from dataclasses import dataclass
|
|
4
3
|
from datetime import datetime
|
|
5
4
|
from functools import lru_cache
|
|
6
5
|
import json
|
|
7
6
|
from pathlib import Path
|
|
7
|
+
import typing
|
|
8
8
|
|
|
9
9
|
try:
|
|
10
10
|
import tomllib
|
|
@@ -66,26 +66,26 @@ SKILLS_GUIDANCE = """- Discovery: The list above is the skills available in this
|
|
|
66
66
|
- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue."""
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
@dataclass(frozen=True,
|
|
69
|
+
@dataclass(frozen=True, )
|
|
70
70
|
class ContextConfig:
|
|
71
|
-
base_instructions: str
|
|
72
|
-
developer_instructions: str
|
|
73
|
-
user_instructions: str
|
|
74
|
-
codex_home_instructions: str
|
|
75
|
-
model_instructions_file: Path
|
|
76
|
-
codex_home: Path
|
|
77
|
-
project_doc_max_bytes: int
|
|
78
|
-
model: str
|
|
79
|
-
personality: str
|
|
80
|
-
approval_policy: str
|
|
81
|
-
sandbox_mode: str
|
|
71
|
+
base_instructions: 'typing.Union[str, None]' = None
|
|
72
|
+
developer_instructions: 'typing.Union[str, None]' = None
|
|
73
|
+
user_instructions: 'typing.Union[str, None]' = None
|
|
74
|
+
codex_home_instructions: 'typing.Union[str, None]' = None
|
|
75
|
+
model_instructions_file: 'typing.Union[Path, None]' = None
|
|
76
|
+
codex_home: 'typing.Union[Path, None]' = None
|
|
77
|
+
project_doc_max_bytes: 'typing.Union[int, None]' = None
|
|
78
|
+
model: 'typing.Union[str, None]' = None
|
|
79
|
+
personality: 'typing.Union[str, None]' = None
|
|
80
|
+
approval_policy: 'typing.Union[str, None]' = None
|
|
81
|
+
sandbox_mode: 'typing.Union[str, None]' = None
|
|
82
82
|
|
|
83
83
|
@classmethod
|
|
84
84
|
def from_codex_config(
|
|
85
85
|
cls,
|
|
86
|
-
config_path: str
|
|
87
|
-
profile: str
|
|
88
|
-
) -> ContextConfig:
|
|
86
|
+
config_path: 'typing.Union[str, Path]',
|
|
87
|
+
profile: 'typing.Union[str, None]' = None,
|
|
88
|
+
) -> 'ContextConfig':
|
|
89
89
|
path = Path(config_path)
|
|
90
90
|
data = tomllib.loads(path.read_text())
|
|
91
91
|
selected = dict(data)
|
|
@@ -123,26 +123,26 @@ class ContextConfig:
|
|
|
123
123
|
)
|
|
124
124
|
|
|
125
125
|
|
|
126
|
-
@dataclass(frozen=True,
|
|
126
|
+
@dataclass(frozen=True, )
|
|
127
127
|
class SkillDescriptor:
|
|
128
|
-
name: str
|
|
129
|
-
description: str
|
|
130
|
-
path_to_skill_md: Path
|
|
131
|
-
scope_rank: int
|
|
128
|
+
name: 'str'
|
|
129
|
+
description: 'str'
|
|
130
|
+
path_to_skill_md: 'Path'
|
|
131
|
+
scope_rank: 'int'
|
|
132
132
|
|
|
133
133
|
|
|
134
134
|
class ContextManager:
|
|
135
135
|
def __init__(
|
|
136
136
|
self,
|
|
137
|
-
base_instructions_override: str
|
|
138
|
-
config: ContextConfig
|
|
139
|
-
collaboration_mode: CollaborationMode = DEFAULT_COLLABORATION_MODE,
|
|
140
|
-
collaboration_instructions: str
|
|
141
|
-
include_collaboration_instructions: bool = False,
|
|
142
|
-
include_permissions_instructions: bool = True,
|
|
143
|
-
include_skills_instructions: bool = True,
|
|
144
|
-
network_access: str = "enabled",
|
|
145
|
-
) -> None:
|
|
137
|
+
base_instructions_override: 'typing.Union[str, None]' = None,
|
|
138
|
+
config: 'typing.Union[ContextConfig, None]' = None,
|
|
139
|
+
collaboration_mode: 'CollaborationMode' = DEFAULT_COLLABORATION_MODE,
|
|
140
|
+
collaboration_instructions: 'typing.Union[str, None]' = None,
|
|
141
|
+
include_collaboration_instructions: 'bool' = False,
|
|
142
|
+
include_permissions_instructions: 'bool' = True,
|
|
143
|
+
include_skills_instructions: 'bool' = True,
|
|
144
|
+
network_access: 'str' = "enabled",
|
|
145
|
+
) -> 'None':
|
|
146
146
|
self.cwd = Path.cwd().resolve()
|
|
147
147
|
self._shell = get_shell_name()
|
|
148
148
|
self._current_date = datetime.now().date().isoformat()
|
|
@@ -160,21 +160,21 @@ class ContextManager:
|
|
|
160
160
|
self._include_skills_instructions = include_skills_instructions
|
|
161
161
|
self._network_access = network_access
|
|
162
162
|
self._default_base_instructions = DEFAULT_BASE_INSTRUCTIONS_PATH.read_text()
|
|
163
|
-
self._workspace_metadata_turn_id: str
|
|
164
|
-
self._workspace_metadata_cache: JSONDict
|
|
163
|
+
self._workspace_metadata_turn_id: 'typing.Union[str, None]' = None
|
|
164
|
+
self._workspace_metadata_cache: 'typing.Union[JSONDict, None]' = None
|
|
165
165
|
|
|
166
166
|
@classmethod
|
|
167
167
|
def from_codex_config(
|
|
168
168
|
cls,
|
|
169
|
-
config_path: str
|
|
170
|
-
profile: str
|
|
171
|
-
base_instructions_override: str
|
|
172
|
-
collaboration_mode: CollaborationMode = DEFAULT_COLLABORATION_MODE,
|
|
173
|
-
include_collaboration_instructions: bool = False,
|
|
174
|
-
include_permissions_instructions: bool = True,
|
|
175
|
-
include_skills_instructions: bool = True,
|
|
176
|
-
network_access: str = "enabled",
|
|
177
|
-
) -> ContextManager:
|
|
169
|
+
config_path: 'typing.Union[str, Path]',
|
|
170
|
+
profile: 'typing.Union[str, None]' = None,
|
|
171
|
+
base_instructions_override: 'typing.Union[str, None]' = None,
|
|
172
|
+
collaboration_mode: 'CollaborationMode' = DEFAULT_COLLABORATION_MODE,
|
|
173
|
+
include_collaboration_instructions: 'bool' = False,
|
|
174
|
+
include_permissions_instructions: 'bool' = True,
|
|
175
|
+
include_skills_instructions: 'bool' = True,
|
|
176
|
+
network_access: 'str' = "enabled",
|
|
177
|
+
) -> 'ContextManager':
|
|
178
178
|
config = ContextConfig.from_codex_config(config_path, profile)
|
|
179
179
|
return cls(
|
|
180
180
|
base_instructions_override=base_instructions_override,
|
|
@@ -187,11 +187,11 @@ class ContextManager:
|
|
|
187
187
|
)
|
|
188
188
|
|
|
189
189
|
@property
|
|
190
|
-
def collaboration_mode(self) -> CollaborationMode:
|
|
190
|
+
def collaboration_mode(self) -> 'CollaborationMode':
|
|
191
191
|
return self._collaboration_mode
|
|
192
192
|
|
|
193
|
-
def get_turn_metadata(self, turn_id: str) -> JSONDict:
|
|
194
|
-
metadata: JSONDict = {"turn_id": turn_id}
|
|
193
|
+
def get_turn_metadata(self, turn_id: 'str') -> 'JSONDict':
|
|
194
|
+
metadata: 'JSONDict' = {"turn_id": turn_id}
|
|
195
195
|
if self._workspace_metadata_turn_id is None:
|
|
196
196
|
self._workspace_metadata_turn_id = turn_id
|
|
197
197
|
self._workspace_metadata_cache = get_workspace_turn_metadata(self.cwd)
|
|
@@ -205,12 +205,12 @@ class ContextManager:
|
|
|
205
205
|
|
|
206
206
|
def build_prompt(
|
|
207
207
|
self,
|
|
208
|
-
history:
|
|
209
|
-
tools:
|
|
210
|
-
parallel_tool_calls: bool,
|
|
211
|
-
turn_id: str
|
|
212
|
-
) -> Prompt:
|
|
213
|
-
input_items:
|
|
208
|
+
history: 'typing.Union[typing.Tuple[ConversationItem, ...], typing.List[ConversationItem]]',
|
|
209
|
+
tools: 'typing.List[ToolSpec]',
|
|
210
|
+
parallel_tool_calls: 'bool',
|
|
211
|
+
turn_id: 'typing.Union[str, None]' = None,
|
|
212
|
+
) -> 'Prompt':
|
|
213
|
+
input_items: 'typing.List[ConversationItem]' = []
|
|
214
214
|
turn_metadata = self.get_turn_metadata(turn_id) if turn_id is not None else None
|
|
215
215
|
|
|
216
216
|
developer_message = self._build_developer_message()
|
|
@@ -228,7 +228,7 @@ class ContextManager:
|
|
|
228
228
|
turn_metadata=turn_metadata,
|
|
229
229
|
)
|
|
230
230
|
|
|
231
|
-
def resolve_base_instructions(self) -> str:
|
|
231
|
+
def resolve_base_instructions(self) -> 'str':
|
|
232
232
|
if self._base_instructions_override is not None:
|
|
233
233
|
return self._base_instructions_override
|
|
234
234
|
if self._config.base_instructions is not None:
|
|
@@ -240,7 +240,7 @@ class ContextManager:
|
|
|
240
240
|
return resolved
|
|
241
241
|
return self._default_base_instructions
|
|
242
242
|
|
|
243
|
-
def _resolve_model_instructions(self) -> str
|
|
243
|
+
def _resolve_model_instructions(self) -> 'typing.Union[str, None]':
|
|
244
244
|
model_slug = self._config.model
|
|
245
245
|
if model_slug is None:
|
|
246
246
|
return None
|
|
@@ -264,8 +264,8 @@ class ContextManager:
|
|
|
264
264
|
return base_instructions
|
|
265
265
|
return None
|
|
266
266
|
|
|
267
|
-
def _build_developer_message(self) -> ContextMessage
|
|
268
|
-
sections:
|
|
267
|
+
def _build_developer_message(self) -> 'typing.Union[ContextMessage, None]':
|
|
268
|
+
sections: 'typing.List[str]' = []
|
|
269
269
|
if self._include_permissions_instructions:
|
|
270
270
|
permissions = self._build_permissions_instructions()
|
|
271
271
|
if permissions is not None:
|
|
@@ -290,7 +290,7 @@ class ContextManager:
|
|
|
290
290
|
content_items=tuple(_input_text_item(section) for section in sections),
|
|
291
291
|
)
|
|
292
292
|
|
|
293
|
-
def _build_permissions_instructions(self) -> str
|
|
293
|
+
def _build_permissions_instructions(self) -> 'typing.Union[str, None]':
|
|
294
294
|
sandbox_mode = self._config.sandbox_mode or "danger-full-access"
|
|
295
295
|
approval_policy = self._config.approval_policy or "never"
|
|
296
296
|
sandbox_prompt_name = sandbox_mode.replace("-", "_")
|
|
@@ -318,7 +318,7 @@ class ContextManager:
|
|
|
318
318
|
]
|
|
319
319
|
)
|
|
320
320
|
|
|
321
|
-
def _build_skills_instructions(self) -> str
|
|
321
|
+
def _build_skills_instructions(self) -> 'typing.Union[str, None]':
|
|
322
322
|
skills = self._discover_skills()
|
|
323
323
|
if not skills:
|
|
324
324
|
return None
|
|
@@ -338,15 +338,15 @@ class ContextManager:
|
|
|
338
338
|
body = "\n".join(lines)
|
|
339
339
|
return f"{SKILLS_OPEN_TAG}\n{body}\n{SKILLS_CLOSE_TAG}"
|
|
340
340
|
|
|
341
|
-
def _discover_skills(self) ->
|
|
341
|
+
def _discover_skills(self) -> 'typing.List[SkillDescriptor]':
|
|
342
342
|
codex_home = self._config.codex_home
|
|
343
343
|
if codex_home is None:
|
|
344
344
|
return []
|
|
345
345
|
|
|
346
346
|
user_root = codex_home / "skills"
|
|
347
347
|
system_root = user_root / ".system"
|
|
348
|
-
discovered:
|
|
349
|
-
seen:
|
|
348
|
+
discovered: 'typing.List[SkillDescriptor]' = []
|
|
349
|
+
seen: 'typing.Set[Path]' = set()
|
|
350
350
|
|
|
351
351
|
user_paths = _discover_skill_files(user_root, excluded_root=system_root)
|
|
352
352
|
system_paths = _discover_skill_files(system_root)
|
|
@@ -366,8 +366,8 @@ class ContextManager:
|
|
|
366
366
|
key=lambda skill: (skill.scope_rank, skill.name, skill.path_to_skill_md),
|
|
367
367
|
)
|
|
368
368
|
|
|
369
|
-
def _build_contextual_user_messages(self) ->
|
|
370
|
-
sections:
|
|
369
|
+
def _build_contextual_user_messages(self) -> 'typing.List[ContextMessage]':
|
|
370
|
+
sections: 'typing.List[str]' = []
|
|
371
371
|
user_instructions = self._merged_user_instructions()
|
|
372
372
|
if user_instructions is not None:
|
|
373
373
|
sections.append(
|
|
@@ -386,8 +386,8 @@ class ContextManager:
|
|
|
386
386
|
)
|
|
387
387
|
]
|
|
388
388
|
|
|
389
|
-
def _merged_user_instructions(self) -> str
|
|
390
|
-
parts:
|
|
389
|
+
def _merged_user_instructions(self) -> 'typing.Union[str, None]':
|
|
390
|
+
parts: 'typing.List[str]' = []
|
|
391
391
|
if self._config.user_instructions is not None:
|
|
392
392
|
parts.append(self._config.user_instructions)
|
|
393
393
|
if self._config.codex_home_instructions is not None:
|
|
@@ -402,8 +402,8 @@ class ContextManager:
|
|
|
402
402
|
|
|
403
403
|
return "\n\n".join(parts) or None
|
|
404
404
|
|
|
405
|
-
def _read_project_docs(self) -> str
|
|
406
|
-
docs:
|
|
405
|
+
def _read_project_docs(self) -> 'typing.Union[str, None]':
|
|
406
|
+
docs: 'typing.List[str]' = []
|
|
407
407
|
remaining = self._config.project_doc_max_bytes
|
|
408
408
|
for path in self._discover_project_doc_paths():
|
|
409
409
|
text = path.read_text()
|
|
@@ -421,9 +421,9 @@ class ContextManager:
|
|
|
421
421
|
return None
|
|
422
422
|
return "\n\n".join(docs)
|
|
423
423
|
|
|
424
|
-
def _discover_project_doc_paths(self) ->
|
|
425
|
-
seen:
|
|
426
|
-
discovered:
|
|
424
|
+
def _discover_project_doc_paths(self) -> 'typing.List[Path]':
|
|
425
|
+
seen: 'typing.Set[Path]' = set()
|
|
426
|
+
discovered: 'typing.List[Path]' = []
|
|
427
427
|
|
|
428
428
|
search_dirs = self._project_search_dirs()
|
|
429
429
|
for directory in search_dirs:
|
|
@@ -435,9 +435,9 @@ class ContextManager:
|
|
|
435
435
|
break
|
|
436
436
|
return discovered
|
|
437
437
|
|
|
438
|
-
def _project_search_dirs(self) ->
|
|
438
|
+
def _project_search_dirs(self) -> 'typing.List[Path]':
|
|
439
439
|
project_root = self._find_project_root()
|
|
440
|
-
directories:
|
|
440
|
+
directories: 'typing.List[Path]' = []
|
|
441
441
|
current = self.cwd
|
|
442
442
|
chain = [current]
|
|
443
443
|
while current != project_root and current.parent != current:
|
|
@@ -447,13 +447,13 @@ class ContextManager:
|
|
|
447
447
|
directories.extend(chain)
|
|
448
448
|
return directories
|
|
449
449
|
|
|
450
|
-
def _find_project_root(self) -> Path:
|
|
450
|
+
def _find_project_root(self) -> 'Path':
|
|
451
451
|
for ancestor in [self.cwd, *self.cwd.parents]:
|
|
452
452
|
if (ancestor / ".git").exists():
|
|
453
453
|
return ancestor
|
|
454
454
|
return self.cwd
|
|
455
455
|
|
|
456
|
-
def _serialize_environment_context(self) -> str:
|
|
456
|
+
def _serialize_environment_context(self) -> 'str':
|
|
457
457
|
lines = [
|
|
458
458
|
"<environment_context>",
|
|
459
459
|
f" <cwd>{self.cwd}</cwd>",
|
|
@@ -465,30 +465,30 @@ class ContextManager:
|
|
|
465
465
|
return "\n".join(lines)
|
|
466
466
|
|
|
467
467
|
|
|
468
|
-
def _input_text_item(text: str) -> JSONDict:
|
|
468
|
+
def _input_text_item(text: 'str') -> 'JSONDict':
|
|
469
469
|
return {"type": "input_text", "text": text}
|
|
470
470
|
|
|
471
471
|
|
|
472
|
-
def _normalize_text(value) -> str
|
|
472
|
+
def _normalize_text(value) -> 'typing.Union[str, None]':
|
|
473
473
|
if value is None:
|
|
474
474
|
return None
|
|
475
475
|
text = str(value).strip()
|
|
476
476
|
return text or None
|
|
477
477
|
|
|
478
478
|
|
|
479
|
-
def _normalize_int(value) -> int
|
|
479
|
+
def _normalize_int(value) -> 'typing.Union[int, None]':
|
|
480
480
|
if value is None:
|
|
481
481
|
return None
|
|
482
482
|
return int(value)
|
|
483
483
|
|
|
484
484
|
|
|
485
|
-
def _default_collaboration_instructions(mode: CollaborationMode) -> str:
|
|
485
|
+
def _default_collaboration_instructions(mode: 'CollaborationMode') -> 'str':
|
|
486
486
|
if mode == "plan":
|
|
487
487
|
return PLAN_COLLABORATION_INSTRUCTIONS_PATH.read_text()
|
|
488
488
|
return DEFAULT_COLLABORATION_INSTRUCTIONS_PATH.read_text()
|
|
489
489
|
|
|
490
490
|
|
|
491
|
-
def _read_first_instruction_file(base: Path) -> str
|
|
491
|
+
def _read_first_instruction_file(base: 'Path') -> 'typing.Union[str, None]':
|
|
492
492
|
for candidate_name in (LOCAL_PROJECT_DOC_FILENAME, DEFAULT_PROJECT_DOC_FILENAME):
|
|
493
493
|
candidate = base / candidate_name
|
|
494
494
|
try:
|
|
@@ -502,10 +502,10 @@ def _read_first_instruction_file(base: Path) -> str | None:
|
|
|
502
502
|
|
|
503
503
|
|
|
504
504
|
@lru_cache(maxsize=1)
|
|
505
|
-
def _load_models_by_slug() ->
|
|
505
|
+
def _load_models_by_slug() -> 'typing.Dict[str, JSONDict]':
|
|
506
506
|
payload = json.loads(DEFAULT_MODELS_PATH.read_text())
|
|
507
507
|
models = payload.get("models", [])
|
|
508
|
-
by_slug:
|
|
508
|
+
by_slug: 'typing.Dict[str, JSONDict]' = {}
|
|
509
509
|
for model in models:
|
|
510
510
|
slug = model.get("slug")
|
|
511
511
|
if isinstance(slug, str):
|
|
@@ -513,7 +513,7 @@ def _load_models_by_slug() -> dict[str, JSONDict]:
|
|
|
513
513
|
return by_slug
|
|
514
514
|
|
|
515
515
|
|
|
516
|
-
def _resolve_personality_message(variables, personality: str
|
|
516
|
+
def _resolve_personality_message(variables, personality: 'typing.Union[str, None]') -> 'str':
|
|
517
517
|
if not isinstance(variables, dict):
|
|
518
518
|
return ""
|
|
519
519
|
normalized = (personality or "").strip().lower()
|
|
@@ -532,13 +532,13 @@ def _resolve_personality_message(variables, personality: str | None) -> str:
|
|
|
532
532
|
|
|
533
533
|
|
|
534
534
|
def _discover_skill_files(
|
|
535
|
-
root: Path,
|
|
536
|
-
excluded_root: Path
|
|
537
|
-
) ->
|
|
535
|
+
root: 'Path',
|
|
536
|
+
excluded_root: 'typing.Union[Path, None]' = None,
|
|
537
|
+
) -> 'typing.List[Path]':
|
|
538
538
|
if not root.exists() or not root.is_dir():
|
|
539
539
|
return []
|
|
540
540
|
excluded = excluded_root.resolve() if excluded_root is not None and excluded_root.exists() else None
|
|
541
|
-
paths:
|
|
541
|
+
paths: 'typing.List[Path]' = []
|
|
542
542
|
for path in root.glob("**/SKILL.md"):
|
|
543
543
|
resolved = path.resolve()
|
|
544
544
|
if excluded is not None and (resolved == excluded or excluded in resolved.parents):
|
|
@@ -547,7 +547,7 @@ def _discover_skill_files(
|
|
|
547
547
|
return sorted(paths)
|
|
548
548
|
|
|
549
549
|
|
|
550
|
-
def _parse_skill_descriptor(path: Path, scope_rank: int) -> SkillDescriptor
|
|
550
|
+
def _parse_skill_descriptor(path: 'Path', scope_rank: 'int') -> 'typing.Union[SkillDescriptor, None]':
|
|
551
551
|
text = path.read_text()
|
|
552
552
|
if not text.startswith("---\n"):
|
|
553
553
|
return None
|
|
@@ -556,7 +556,7 @@ def _parse_skill_descriptor(path: Path, scope_rank: int) -> SkillDescriptor | No
|
|
|
556
556
|
if end_index == -1:
|
|
557
557
|
return None
|
|
558
558
|
frontmatter = text[4:end_index]
|
|
559
|
-
fields:
|
|
559
|
+
fields: 'typing.Dict[str, str]' = {}
|
|
560
560
|
for line in frontmatter.splitlines():
|
|
561
561
|
if ":" not in line:
|
|
562
562
|
continue
|
|
@@ -574,7 +574,7 @@ def _parse_skill_descriptor(path: Path, scope_rank: int) -> SkillDescriptor | No
|
|
|
574
574
|
)
|
|
575
575
|
|
|
576
576
|
|
|
577
|
-
def _strip_yaml_string(value: str) -> str:
|
|
577
|
+
def _strip_yaml_string(value: 'str') -> 'str':
|
|
578
578
|
if len(value) >= 2 and value[0] == value[-1] and value[0] in {'"', "'"}:
|
|
579
579
|
return value[1:-1]
|
|
580
580
|
return value
|
pycodex/doctor.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
1
|
|
|
3
2
|
import asyncio
|
|
4
3
|
import json
|
|
@@ -14,31 +13,32 @@ import requests
|
|
|
14
13
|
from .model import ResponsesModelClient, ResponsesProviderConfig
|
|
15
14
|
from .protocol import AssistantMessage, Prompt, UserMessage
|
|
16
15
|
from .utils.dotenv import DOTENV_FILENAME, load_codex_dotenv
|
|
16
|
+
import typing
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
@dataclass
|
|
19
|
+
@dataclass
|
|
20
20
|
class DoctorCheck:
|
|
21
|
-
name: str
|
|
22
|
-
ok: bool
|
|
23
|
-
detail: str
|
|
21
|
+
name: 'str'
|
|
22
|
+
ok: 'bool'
|
|
23
|
+
detail: 'str'
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
@dataclass
|
|
26
|
+
@dataclass
|
|
27
27
|
class DoctorReport:
|
|
28
|
-
ok: bool
|
|
29
|
-
config_path: str
|
|
30
|
-
dotenv_path: str
|
|
31
|
-
profile: str
|
|
32
|
-
provider_name: str
|
|
33
|
-
model: str
|
|
34
|
-
base_url: str
|
|
35
|
-
responses_url: str
|
|
36
|
-
api_key_env: str
|
|
37
|
-
api_key_loaded: bool = False
|
|
38
|
-
checks:
|
|
39
|
-
live_output_text: str
|
|
40
|
-
|
|
41
|
-
def to_dict(self) ->
|
|
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]':
|
|
42
42
|
return asdict(self)
|
|
43
43
|
|
|
44
44
|
|
|
@@ -79,11 +79,11 @@ def build_doctor_parser():
|
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
async def collect_doctor_report(
|
|
82
|
-
config_path: str
|
|
83
|
-
profile: str
|
|
84
|
-
timeout_seconds: float = 120.0,
|
|
85
|
-
skip_live: bool = False,
|
|
86
|
-
) -> DoctorReport:
|
|
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':
|
|
87
87
|
config_file = Path(config_path).expanduser().resolve()
|
|
88
88
|
dotenv_file = config_file.parent / DOTENV_FILENAME
|
|
89
89
|
report = DoctorReport(
|
|
@@ -227,7 +227,7 @@ async def collect_doctor_report(
|
|
|
227
227
|
return _finalize_report(report)
|
|
228
228
|
|
|
229
229
|
|
|
230
|
-
async def run_doctor_cli(args) -> int:
|
|
230
|
+
async def run_doctor_cli(args) -> 'int':
|
|
231
231
|
report = await collect_doctor_report(
|
|
232
232
|
args.config,
|
|
233
233
|
args.profile,
|
|
@@ -243,7 +243,7 @@ async def run_doctor_cli(args) -> int:
|
|
|
243
243
|
return 0 if report.ok else 1
|
|
244
244
|
|
|
245
245
|
|
|
246
|
-
def format_doctor_report(report: DoctorReport) -> str:
|
|
246
|
+
def format_doctor_report(report: 'DoctorReport') -> 'str':
|
|
247
247
|
lines = [
|
|
248
248
|
f"config: {report.config_path}",
|
|
249
249
|
f"dotenv: {report.dotenv_path}",
|
|
@@ -267,7 +267,7 @@ def format_doctor_report(report: DoctorReport) -> str:
|
|
|
267
267
|
return "\n".join(lines)
|
|
268
268
|
|
|
269
269
|
|
|
270
|
-
def _loaded_api_key(provider_config: ResponsesProviderConfig) -> str:
|
|
270
|
+
def _loaded_api_key(provider_config: 'ResponsesProviderConfig') -> 'str':
|
|
271
271
|
try:
|
|
272
272
|
return provider_config.api_key()
|
|
273
273
|
except Exception:
|
|
@@ -275,11 +275,11 @@ def _loaded_api_key(provider_config: ResponsesProviderConfig) -> str:
|
|
|
275
275
|
|
|
276
276
|
|
|
277
277
|
def _probe_transport(
|
|
278
|
-
scheme: str,
|
|
279
|
-
host: str,
|
|
280
|
-
port: int,
|
|
281
|
-
timeout_seconds: float,
|
|
282
|
-
) ->
|
|
278
|
+
scheme: 'str',
|
|
279
|
+
host: 'str',
|
|
280
|
+
port: 'int',
|
|
281
|
+
timeout_seconds: 'float',
|
|
282
|
+
) -> 'typing.Tuple[bool, str]':
|
|
283
283
|
started = time.perf_counter()
|
|
284
284
|
try:
|
|
285
285
|
with socket.create_connection((host, port), timeout=timeout_seconds) as sock:
|
|
@@ -298,7 +298,7 @@ def _probe_transport(
|
|
|
298
298
|
return True, f"{label} {host}:{port} connected in {elapsed:.2f}s"
|
|
299
299
|
|
|
300
300
|
|
|
301
|
-
def _proxy_detail(proxies:
|
|
301
|
+
def _proxy_detail(proxies: 'typing.Dict[str, str]') -> 'str':
|
|
302
302
|
if not proxies:
|
|
303
303
|
return "not configured"
|
|
304
304
|
return ", ".join(
|
|
@@ -306,7 +306,7 @@ def _proxy_detail(proxies: dict[str, str]) -> str:
|
|
|
306
306
|
)
|
|
307
307
|
|
|
308
308
|
|
|
309
|
-
def _redact_proxy_url(value: str) -> str:
|
|
309
|
+
def _redact_proxy_url(value: 'str') -> 'str':
|
|
310
310
|
parsed = urllib.parse.urlsplit(value)
|
|
311
311
|
if not parsed.scheme or not parsed.netloc:
|
|
312
312
|
return value
|
|
@@ -319,10 +319,10 @@ def _redact_proxy_url(value: str) -> str:
|
|
|
319
319
|
|
|
320
320
|
|
|
321
321
|
async def _run_live_check(
|
|
322
|
-
config_path: Path,
|
|
323
|
-
profile: str
|
|
324
|
-
timeout_seconds: float,
|
|
325
|
-
) ->
|
|
322
|
+
config_path: 'Path',
|
|
323
|
+
profile: 'typing.Union[str, None]',
|
|
324
|
+
timeout_seconds: 'float',
|
|
325
|
+
) -> 'typing.Tuple[bool, str, typing.Union[str, None]]':
|
|
326
326
|
client = ResponsesModelClient.from_codex_config(
|
|
327
327
|
config_path,
|
|
328
328
|
profile,
|
|
@@ -355,6 +355,6 @@ async def _run_live_check(
|
|
|
355
355
|
return True, f"completed in {elapsed:.2f}s", output_text
|
|
356
356
|
|
|
357
357
|
|
|
358
|
-
def _finalize_report(report: DoctorReport) -> DoctorReport:
|
|
358
|
+
def _finalize_report(report: 'DoctorReport') -> 'DoctorReport':
|
|
359
359
|
report.ok = all(check.ok for check in report.checks)
|
|
360
360
|
return report
|