python-codex 0.2.7__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.
- pycodex/__init__.py +14 -14
- pycodex/agent.py +465 -499
- pycodex/bootstrap.py +417 -0
- pycodex/cli.py +236 -510
- pycodex/compat.py +19 -5
- pycodex/context.py +222 -212
- pycodex/doctor.py +52 -48
- pycodex/events.py +857 -0
- pycodex/feishu_card.py +217 -163
- pycodex/feishu_link.py +43 -83
- pycodex/model.py +324 -253
- pycodex/model_metadata.py +19 -7
- pycodex/portable.py +76 -45
- pycodex/portable_server.py +32 -24
- pycodex/prompts/models.json +245 -983
- pycodex/protocol.py +177 -137
- pycodex/runtime.py +579 -176
- pycodex/runtime_services.py +204 -157
- pycodex/tools/__init__.py +1 -1
- pycodex/tools/apply_patch_tool.py +69 -48
- pycodex/tools/base_tool.py +89 -42
- pycodex/tools/clock_tool.py +58 -25
- pycodex/tools/close_agent_tool.py +2 -2
- pycodex/tools/code_mode_manager.py +77 -64
- pycodex/tools/exec_command_tool.py +26 -11
- pycodex/tools/exec_tool.py +4 -4
- pycodex/tools/grep_files_tool.py +12 -10
- pycodex/tools/ipython_tool.py +10 -13
- pycodex/tools/list_dir_tool.py +13 -9
- pycodex/tools/read_file_tool.py +29 -17
- pycodex/tools/request_permissions_tool.py +15 -5
- pycodex/tools/request_user_input_tool.py +13 -104
- pycodex/tools/resume_agent_tool.py +2 -2
- pycodex/tools/send_input_tool.py +11 -8
- pycodex/tools/shell_command_tool.py +7 -5
- pycodex/tools/shell_tool.py +7 -5
- pycodex/tools/spawn_agent_tool.py +7 -4
- pycodex/tools/unified_exec_manager.py +102 -69
- pycodex/tools/update_plan_tool.py +8 -5
- pycodex/tools/view_image_tool.py +7 -5
- pycodex/tools/wait_agent_tool.py +27 -4
- pycodex/tools/wait_tool.py +5 -4
- pycodex/tools/web_search_tool.py +4 -2
- pycodex/tools/write_stdin_tool.py +12 -11
- pycodex/utils/__init__.py +2 -17
- pycodex/utils/compactor.py +41 -72
- pycodex/utils/debug.py +2 -2
- pycodex/utils/dotenv.py +6 -7
- pycodex/utils/event_helpers.py +190 -0
- pycodex/utils/get_env.py +27 -70
- pycodex/{image_utils.py → utils/image_utils.py} +8 -11
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/session_persist.py +217 -163
- pycodex/utils/truncation.py +21 -45
- python_codex-0.3.0.dist-info/METADATA +704 -0
- python_codex-0.3.0.dist-info/RECORD +90 -0
- responses_server/__init__.py +1 -5
- responses_server/__main__.py +0 -1
- responses_server/app.py +36 -31
- responses_server/config.py +23 -23
- responses_server/messages_api.py +51 -53
- responses_server/payload_processors.py +25 -20
- responses_server/server.py +11 -11
- responses_server/session_store.py +14 -11
- responses_server/stream_router.py +101 -98
- responses_server/tools/custom_adapter.py +17 -16
- responses_server/tools/web_search.py +39 -36
- responses_server/trajectory_dump.py +36 -14
- workspace_server/__main__.py +0 -1
- workspace_server/app.py +461 -375
- workspace_server/workspace.html +852 -228
- workspace_server/workspaces.html +94 -95
- workspace_server/workspaces.py +137 -79
- pycodex/collaboration.py +0 -20
- pycodex/interactive_session.py +0 -415
- pycodex/prompts/collaboration_default.md +0 -11
- pycodex/prompts/collaboration_plan.md +0 -128
- pycodex/utils/toolcall_visualize.py +0 -713
- pycodex/utils/visualize.py +0 -560
- python_codex-0.2.7.dist-info/METADATA +0 -455
- python_codex-0.2.7.dist-info/RECORD +0 -93
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/WHEEL +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/entry_points.txt +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/licenses/LICENSE +0 -0
pycodex/cli.py
CHANGED
|
@@ -1,103 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
import atexit
|
|
3
1
|
import argparse
|
|
4
2
|
import asyncio
|
|
3
|
+
import inspect
|
|
5
4
|
import os
|
|
6
5
|
import shlex
|
|
6
|
+
import signal
|
|
7
7
|
import sys
|
|
8
8
|
import tempfile
|
|
9
|
+
import threading
|
|
9
10
|
import traceback
|
|
10
|
-
from dataclasses import replace
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Sequence
|
|
13
|
-
|
|
14
|
-
from .agent import Agent
|
|
15
|
-
from .collaboration import DEFAULT_COLLABORATION_MODE, CollaborationMode
|
|
16
|
-
from .compat import Literal
|
|
17
|
-
from .context import ContextManager
|
|
18
|
-
from .model import DEFAULT_CODEX_CONFIG_PATH, ResponsesModelClient, ResponsesProviderConfig
|
|
19
|
-
from .portable import bootstrap_called_home, upload_codex_home
|
|
20
|
-
from .runtime import CliSubmissionQueue
|
|
21
|
-
from .runtime_services import AgentRuntimeEnvironment, create_agent_runtime_environment
|
|
22
|
-
from .utils import CliSessionView, get_debug_dir, load_codex_dotenv, uuid7_string
|
|
23
|
-
from .interactive_session import (
|
|
24
|
-
EXTRA_COMMANDS_LINE,
|
|
25
|
-
format_turn_output,
|
|
26
|
-
run_interactive_session as _run_interactive_session,
|
|
27
|
-
prompt_request_permissions,
|
|
28
|
-
prompt_request_user_input,
|
|
29
|
-
)
|
|
30
|
-
from .utils.session_persist import (
|
|
31
|
-
SessionRolloutRecorder,
|
|
32
|
-
resolve_codex_home,
|
|
33
|
-
)
|
|
34
11
|
import typing
|
|
12
|
+
from contextlib import contextmanager
|
|
35
13
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def launch_chat_completion_compat_server(*args, **kwargs):
|
|
42
|
-
from responses_server import (
|
|
43
|
-
launch_chat_completion_compat_server as launch_compat_server,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
return launch_compat_server(*args, **kwargs)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _resolve_vllm_model(
|
|
50
|
-
endpoint: 'str',
|
|
51
|
-
provider_config: 'ResponsesProviderConfig',
|
|
52
|
-
timeout_seconds: 'float',
|
|
53
|
-
) -> 'str':
|
|
54
|
-
from responses_server import CompatServerConfig
|
|
55
|
-
|
|
56
|
-
normalized = CompatServerConfig.from_base_url(endpoint)
|
|
57
|
-
probe_config = replace(
|
|
58
|
-
provider_config,
|
|
59
|
-
provider_name="vllm",
|
|
60
|
-
base_url=normalized.outcomming_base_url,
|
|
61
|
-
api_key_env=None,
|
|
62
|
-
query_params={},
|
|
63
|
-
responses_lite_override=False,
|
|
64
|
-
)
|
|
65
|
-
probe_client = ResponsesModelClient(
|
|
66
|
-
probe_config,
|
|
67
|
-
timeout_seconds,
|
|
68
|
-
originator=CLI_ORIGINATOR,
|
|
69
|
-
)
|
|
70
|
-
models = probe_client.list_models_sync()
|
|
71
|
-
if not models:
|
|
72
|
-
raise RuntimeError(
|
|
73
|
-
"vLLM endpoint returned no models from "
|
|
74
|
-
f"{normalized.outcomming_models_url()}"
|
|
75
|
-
)
|
|
76
|
-
return models[-1]
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def configure_loguru() -> 'None':
|
|
80
|
-
try:
|
|
81
|
-
from loguru import logger
|
|
82
|
-
except ImportError: # pragma: no cover - dependency may be absent in minimal envs
|
|
83
|
-
return
|
|
84
|
-
|
|
85
|
-
logger.remove()
|
|
86
|
-
debug_dir = get_debug_dir()
|
|
87
|
-
if debug_dir is not None:
|
|
88
|
-
logger.add(str(debug_dir / "loguru.log"), level="DEBUG")
|
|
89
|
-
return
|
|
14
|
+
from prompt_toolkit import PromptSession
|
|
15
|
+
from prompt_toolkit.enums import DEFAULT_BUFFER
|
|
16
|
+
from prompt_toolkit.filters import has_focus
|
|
17
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
18
|
+
from prompt_toolkit.patch_stdout import patch_stdout
|
|
90
19
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
logger.add(sys.stderr, level="DEBUG")
|
|
20
|
+
from .bootstrap import build_agent, build_model, build_runtime, configure_loguru
|
|
21
|
+
from .events import DEFAULT_MAIN_PROMPT, EventDisplay
|
|
22
|
+
from .model import DEFAULT_CODEX_CONFIG_PATH
|
|
23
|
+
from .portable import bootstrap_called_home, upload_codex_home
|
|
24
|
+
from .utils import get_debug_dir
|
|
25
|
+
from .utils.event_helpers import format_error, render_result
|
|
98
26
|
|
|
99
27
|
|
|
100
|
-
def build_parser()
|
|
28
|
+
def build_parser():
|
|
101
29
|
parser = argparse.ArgumentParser(
|
|
102
30
|
prog="pycodex",
|
|
103
31
|
description="Minimal Codex-style local CLI backed by ~/.codex/config.toml.",
|
|
@@ -109,17 +37,12 @@ def build_parser() -> 'argparse.ArgumentParser':
|
|
|
109
37
|
"--put",
|
|
110
38
|
default=None,
|
|
111
39
|
metavar="PATH@SERVER",
|
|
112
|
-
help=
|
|
113
|
-
"Upload a Codex home using `--put @host:port` or "
|
|
114
|
-
"`--put /path/.codex@host:port`."
|
|
115
|
-
),
|
|
40
|
+
help="Upload a Codex home using `--put @host:port` or `--put /path/.codex@host:port`.",
|
|
116
41
|
)
|
|
117
42
|
parser.add_argument(
|
|
118
43
|
"--call",
|
|
119
44
|
default=None,
|
|
120
|
-
help=
|
|
121
|
-
"Download and use a stored Codex home via <secret>-<call_id>@<host:port>."
|
|
122
|
-
),
|
|
45
|
+
help="Download and use a stored Codex home via <secret>-<call_id>@<host:port>.",
|
|
123
46
|
)
|
|
124
47
|
parser.add_argument(
|
|
125
48
|
"--config",
|
|
@@ -127,35 +50,24 @@ def build_parser() -> 'argparse.ArgumentParser':
|
|
|
127
50
|
help="Path to Codex config.toml.",
|
|
128
51
|
)
|
|
129
52
|
parser.add_argument(
|
|
130
|
-
"--profile",
|
|
131
|
-
default=None,
|
|
132
|
-
help="Optional profile name from config.toml.",
|
|
53
|
+
"--profile", default=None, help="Optional profile name from config.toml."
|
|
133
54
|
)
|
|
134
55
|
parser.add_argument(
|
|
135
56
|
"--vllm-endpoint",
|
|
136
57
|
default=None,
|
|
137
|
-
help=
|
|
138
|
-
"Optional base URL for a chat-completions-backed vLLM server. "
|
|
139
|
-
"When set, pycodex starts a local responses compat server for this "
|
|
140
|
-
"session and appends /v1 if the path is empty."
|
|
141
|
-
),
|
|
58
|
+
help="Start a local responses compat server for a chat-completions-backed vLLM endpoint.",
|
|
142
59
|
)
|
|
143
60
|
parser.add_argument(
|
|
144
61
|
"--use-chat-completion",
|
|
145
62
|
default=False,
|
|
146
63
|
action="store_true",
|
|
147
|
-
help=
|
|
148
|
-
"When set, pycodex starts a local responses compat server for this session."
|
|
149
|
-
),
|
|
64
|
+
help="Start a local responses compat server for this session.",
|
|
150
65
|
)
|
|
151
66
|
parser.add_argument(
|
|
152
67
|
"--use-messages",
|
|
153
68
|
default=False,
|
|
154
69
|
action="store_true",
|
|
155
|
-
help=
|
|
156
|
-
"When set, pycodex starts a local responses compat server and routes "
|
|
157
|
-
"to a downstream /v1/messages backend for this session."
|
|
158
|
-
),
|
|
70
|
+
help="Route the local responses compat server to a downstream /v1/messages backend.",
|
|
159
71
|
)
|
|
160
72
|
parser.add_argument(
|
|
161
73
|
"--system-prompt",
|
|
@@ -169,366 +81,39 @@ def build_parser() -> 'argparse.ArgumentParser':
|
|
|
169
81
|
help="HTTP timeout for one model call.",
|
|
170
82
|
)
|
|
171
83
|
parser.add_argument(
|
|
172
|
-
"--json",
|
|
173
|
-
action="store_true",
|
|
174
|
-
help="Print the full TurnResult as JSON.",
|
|
84
|
+
"--json", action="store_true", help="Print the full TurnResult as JSON."
|
|
175
85
|
)
|
|
176
86
|
return parser
|
|
177
87
|
|
|
178
88
|
|
|
179
|
-
def should_run_interactive(prompt_parts
|
|
89
|
+
def should_run_interactive(prompt_parts, stdin_is_tty):
|
|
180
90
|
return not prompt_parts and stdin_is_tty
|
|
181
91
|
|
|
182
92
|
|
|
183
|
-
def resolve_prompt_text(prompt_parts
|
|
93
|
+
def resolve_prompt_text(prompt_parts):
|
|
184
94
|
if prompt_parts:
|
|
185
95
|
return " ".join(prompt_parts).strip()
|
|
186
|
-
|
|
187
96
|
if not sys.stdin.isatty():
|
|
188
97
|
prompt_text = sys.stdin.read().strip()
|
|
189
98
|
if prompt_text:
|
|
190
99
|
return prompt_text
|
|
191
|
-
|
|
192
100
|
raise ValueError("prompt is required either as argv text or stdin")
|
|
193
101
|
|
|
194
102
|
|
|
195
|
-
def
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
ApplyPatchTool,
|
|
203
|
-
ClockManager,
|
|
204
|
-
ClockTool,
|
|
205
|
-
CloseAgentTool,
|
|
206
|
-
CodeModeManager,
|
|
207
|
-
ExecTool,
|
|
208
|
-
ExecCommandTool,
|
|
209
|
-
GrepFilesTool,
|
|
210
|
-
ListDirTool,
|
|
211
|
-
ReadFileTool,
|
|
212
|
-
RequestPermissionsTool,
|
|
213
|
-
RequestUserInputTool,
|
|
214
|
-
ResumeAgentTool,
|
|
215
|
-
Registry,
|
|
216
|
-
SendInputTool,
|
|
217
|
-
ShellCommandTool,
|
|
218
|
-
ShellTool,
|
|
219
|
-
SpawnAgentTool,
|
|
220
|
-
UnifiedExecManager,
|
|
221
|
-
UpdatePlanTool,
|
|
222
|
-
ViewImageTool,
|
|
223
|
-
WaitAgentTool,
|
|
224
|
-
WaitTool,
|
|
225
|
-
WebSearchTool,
|
|
226
|
-
WriteStdinTool,
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
runtime_environment = runtime_environment or create_agent_runtime_environment()
|
|
230
|
-
registry = Registry()
|
|
231
|
-
code_mode_manager = CodeModeManager(registry, cwd=cwd)
|
|
232
|
-
unified_exec_manager = UnifiedExecManager(cwd=cwd)
|
|
233
|
-
clock_manager = ClockManager()
|
|
234
|
-
exec_tool = ExecTool(code_mode_manager)
|
|
235
|
-
wait_tool = WaitTool(code_mode_manager)
|
|
236
|
-
web_search_tool = WebSearchTool()
|
|
237
|
-
update_plan_tool = UpdatePlanTool(runtime_environment.plan_store)
|
|
238
|
-
request_user_input_tool = RequestUserInputTool(
|
|
239
|
-
runtime_environment.request_user_input_manager
|
|
240
|
-
)
|
|
241
|
-
request_permissions_tool = RequestPermissionsTool(
|
|
242
|
-
runtime_environment.request_permissions_manager
|
|
243
|
-
)
|
|
244
|
-
spawn_agent_tool = SpawnAgentTool(runtime_environment.subagent_manager)
|
|
245
|
-
send_input_tool = SendInputTool(runtime_environment.subagent_manager)
|
|
246
|
-
resume_agent_tool = ResumeAgentTool(runtime_environment.subagent_manager)
|
|
247
|
-
wait_agent_tool = WaitAgentTool(runtime_environment.subagent_manager)
|
|
248
|
-
close_agent_tool = CloseAgentTool(runtime_environment.subagent_manager)
|
|
249
|
-
apply_patch_tool = ApplyPatchTool(cwd=cwd)
|
|
250
|
-
shell_tool = ShellTool(cwd=cwd)
|
|
251
|
-
shell_command_tool = ShellCommandTool(cwd=cwd)
|
|
252
|
-
exec_command_tool = ExecCommandTool(unified_exec_manager)
|
|
253
|
-
write_stdin_tool = WriteStdinTool(unified_exec_manager)
|
|
254
|
-
clock_tool = ClockTool(clock_manager)
|
|
255
|
-
grep_files_tool = GrepFilesTool(cwd=cwd)
|
|
256
|
-
read_file_tool = ReadFileTool()
|
|
257
|
-
list_dir_tool = ListDirTool()
|
|
258
|
-
view_image_tool = ViewImageTool(cwd=cwd)
|
|
259
|
-
tools = (
|
|
260
|
-
shell_tool,
|
|
261
|
-
shell_command_tool,
|
|
262
|
-
exec_command_tool,
|
|
263
|
-
write_stdin_tool,
|
|
264
|
-
clock_tool,
|
|
265
|
-
exec_tool,
|
|
266
|
-
wait_tool,
|
|
267
|
-
web_search_tool,
|
|
268
|
-
update_plan_tool,
|
|
269
|
-
request_user_input_tool,
|
|
270
|
-
request_permissions_tool,
|
|
271
|
-
spawn_agent_tool,
|
|
272
|
-
send_input_tool,
|
|
273
|
-
resume_agent_tool,
|
|
274
|
-
wait_agent_tool,
|
|
275
|
-
close_agent_tool,
|
|
276
|
-
apply_patch_tool,
|
|
277
|
-
grep_files_tool,
|
|
278
|
-
read_file_tool,
|
|
279
|
-
list_dir_tool,
|
|
280
|
-
view_image_tool,
|
|
281
|
-
)
|
|
282
|
-
if toolset is not None:
|
|
283
|
-
available_tools = {tool.name: tool for tool in tools}
|
|
284
|
-
toolset = tuple(toolset)
|
|
285
|
-
unknown_tools = set(toolset) - set(available_tools)
|
|
286
|
-
if unknown_tools:
|
|
287
|
-
raise ValueError(
|
|
288
|
-
"unknown toolset entries: {0}".format(", ".join(sorted(unknown_tools)))
|
|
289
|
-
)
|
|
290
|
-
tools = tuple(available_tools[name] for name in toolset)
|
|
291
|
-
elif exec_mode:
|
|
292
|
-
tools = (
|
|
293
|
-
exec_command_tool,
|
|
294
|
-
write_stdin_tool,
|
|
295
|
-
clock_tool,
|
|
296
|
-
update_plan_tool,
|
|
297
|
-
request_user_input_tool,
|
|
298
|
-
apply_patch_tool,
|
|
299
|
-
web_search_tool,
|
|
300
|
-
view_image_tool,
|
|
301
|
-
spawn_agent_tool,
|
|
302
|
-
send_input_tool,
|
|
303
|
-
resume_agent_tool,
|
|
304
|
-
wait_agent_tool,
|
|
305
|
-
close_agent_tool,
|
|
306
|
-
)
|
|
307
|
-
for tool in tools:
|
|
308
|
-
registry.register(tool)
|
|
309
|
-
return registry
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
def get_subagent_tools(
|
|
313
|
-
runtime_environment: 'typing.Union[AgentRuntimeEnvironment, None]' = None,
|
|
314
|
-
cwd: 'typing.Union[str, Path, None]' = None,
|
|
315
|
-
):
|
|
316
|
-
from .tools import (
|
|
317
|
-
ApplyPatchTool,
|
|
318
|
-
ExecCommandTool,
|
|
319
|
-
Registry,
|
|
320
|
-
UnifiedExecManager,
|
|
321
|
-
UpdatePlanTool,
|
|
322
|
-
ViewImageTool,
|
|
323
|
-
WebSearchTool,
|
|
324
|
-
WriteStdinTool,
|
|
325
|
-
)
|
|
326
|
-
|
|
327
|
-
runtime_environment = runtime_environment or create_agent_runtime_environment()
|
|
328
|
-
registry = Registry()
|
|
329
|
-
unified_exec_manager = UnifiedExecManager(cwd=cwd)
|
|
330
|
-
registry.register(ExecCommandTool(unified_exec_manager))
|
|
331
|
-
registry.register(WriteStdinTool(unified_exec_manager))
|
|
332
|
-
registry.register(UpdatePlanTool(runtime_environment.plan_store))
|
|
333
|
-
registry.register(ApplyPatchTool(cwd=cwd))
|
|
334
|
-
registry.register(WebSearchTool())
|
|
335
|
-
registry.register(ViewImageTool(cwd=cwd))
|
|
336
|
-
return registry
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
def build_agent(
|
|
340
|
-
client,
|
|
341
|
-
config_path: 'typing.Union[str, Path]' = DEFAULT_CODEX_CONFIG_PATH,
|
|
342
|
-
profile: 'typing.Union[str, None]' = None,
|
|
343
|
-
system_prompt: 'typing.Union[str, None]' = None,
|
|
344
|
-
session_mode: 'CliSessionMode' = "exec",
|
|
345
|
-
collaboration_mode: 'CollaborationMode' = DEFAULT_COLLABORATION_MODE,
|
|
346
|
-
extra_contextual_user_messages: 'typing.Iterable[str]' = (),
|
|
347
|
-
cwd: 'typing.Union[str, Path, None]' = None,
|
|
348
|
-
toolset: 'typing.Union[typing.Iterable[str], None]' = None,
|
|
349
|
-
) -> 'Agent':
|
|
350
|
-
config_path = str(config_path)
|
|
351
|
-
resolved_cwd = Path(cwd or Path.cwd()).resolve()
|
|
352
|
-
context_manager = ContextManager.from_codex_config(
|
|
353
|
-
config_path,
|
|
354
|
-
profile,
|
|
355
|
-
base_instructions_override=system_prompt,
|
|
356
|
-
collaboration_mode=collaboration_mode,
|
|
357
|
-
include_collaboration_instructions=session_mode == "tui",
|
|
358
|
-
extra_contextual_user_messages=extra_contextual_user_messages,
|
|
359
|
-
cwd=resolved_cwd,
|
|
360
|
-
)
|
|
361
|
-
session_id = getattr(client, "_session_id", None) or uuid7_string()
|
|
362
|
-
if hasattr(client, "_session_id"):
|
|
363
|
-
client._session_id = session_id
|
|
364
|
-
subagent_context_manager = ContextManager.from_codex_config(
|
|
365
|
-
config_path,
|
|
366
|
-
profile,
|
|
367
|
-
base_instructions_override=system_prompt,
|
|
368
|
-
include_collaboration_instructions=False,
|
|
369
|
-
extra_contextual_user_messages=extra_contextual_user_messages,
|
|
370
|
-
cwd=resolved_cwd,
|
|
371
|
-
)
|
|
372
|
-
runtime_environment = create_agent_runtime_environment()
|
|
373
|
-
runtime_environment.request_user_input_manager.set_handler(None)
|
|
374
|
-
runtime_environment.request_permissions_manager.set_handler(None)
|
|
375
|
-
rollout_recorder = SessionRolloutRecorder.create(
|
|
376
|
-
resolve_codex_home(config_path),
|
|
377
|
-
session_id,
|
|
378
|
-
context_manager.cwd,
|
|
379
|
-
getattr(client, "_originator", CLI_ORIGINATOR),
|
|
380
|
-
getattr(getattr(client, "_config", None), "provider_name", None),
|
|
381
|
-
context_manager.resolve_base_instructions(),
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
def make_subagent_queue_builder(base_client):
|
|
385
|
-
def build_subagent_queue(
|
|
386
|
-
model_override: 'typing.Union[str, None]',
|
|
387
|
-
reasoning_effort_override: 'typing.Union[str, None]',
|
|
388
|
-
initial_history=(),
|
|
389
|
-
session_id: 'typing.Union[str, None]' = None,
|
|
390
|
-
) -> 'CliSubmissionQueue':
|
|
391
|
-
nested_client = base_client.with_overrides(
|
|
392
|
-
model_override,
|
|
393
|
-
reasoning_effort_override,
|
|
394
|
-
session_id=session_id,
|
|
395
|
-
openai_subagent="collab_spawn",
|
|
396
|
-
)
|
|
397
|
-
subagent_agent_runtime_environment = create_agent_runtime_environment()
|
|
398
|
-
subagent_agent_runtime_environment.request_user_input_manager.set_handler(None)
|
|
399
|
-
subagent_agent_runtime_environment.request_permissions_manager.set_handler(None)
|
|
400
|
-
subagent_agent_runtime_environment.subagent_manager.set_queue_builder(
|
|
401
|
-
make_subagent_queue_builder(nested_client)
|
|
402
|
-
)
|
|
403
|
-
sub_agent = Agent(
|
|
404
|
-
nested_client,
|
|
405
|
-
get_subagent_tools(subagent_agent_runtime_environment, cwd=resolved_cwd),
|
|
406
|
-
subagent_context_manager,
|
|
407
|
-
initial_history=tuple(initial_history),
|
|
408
|
-
runtime_environment=subagent_agent_runtime_environment,
|
|
409
|
-
)
|
|
410
|
-
return CliSubmissionQueue(sub_agent)
|
|
411
|
-
|
|
412
|
-
return build_subagent_queue
|
|
413
|
-
|
|
414
|
-
runtime_environment.subagent_manager.set_queue_builder(
|
|
415
|
-
make_subagent_queue_builder(client)
|
|
416
|
-
)
|
|
417
|
-
return Agent(
|
|
418
|
-
client,
|
|
419
|
-
get_tools(
|
|
420
|
-
runtime_environment,
|
|
421
|
-
exec_mode=True,
|
|
422
|
-
cwd=resolved_cwd,
|
|
423
|
-
toolset=toolset,
|
|
424
|
-
),
|
|
425
|
-
context_manager,
|
|
426
|
-
rollout_recorder=rollout_recorder,
|
|
427
|
-
runtime_environment=runtime_environment,
|
|
428
|
-
)
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
def build_model(
|
|
432
|
-
config_path: 'typing.Union[str, Path]' = DEFAULT_CODEX_CONFIG_PATH,
|
|
433
|
-
profile: 'typing.Union[str, None]' = None,
|
|
434
|
-
timeout_seconds: 'float' = 120.0,
|
|
435
|
-
managed_responses_base_url: 'typing.Union[str, None]' = None,
|
|
436
|
-
vllm_endpoint: 'typing.Union[str, None]' = None,
|
|
437
|
-
use_chat_completion: 'typing.Union[bool, None]' = None,
|
|
438
|
-
use_messages: 'bool' = False,
|
|
439
|
-
):
|
|
440
|
-
load_codex_dotenv(config_path)
|
|
441
|
-
provider_config = ResponsesProviderConfig.from_codex_config(
|
|
442
|
-
config_path,
|
|
443
|
-
profile,
|
|
444
|
-
)
|
|
445
|
-
if use_chat_completion is None:
|
|
446
|
-
use_chat_completion = bool(provider_config.use_chat_completion)
|
|
447
|
-
if use_chat_completion and use_messages:
|
|
448
|
-
raise ValueError("--use-chat-completion and --use-messages cannot be combined")
|
|
449
|
-
if vllm_endpoint and use_messages:
|
|
450
|
-
raise ValueError("--vllm-endpoint and --use-messages cannot be combined")
|
|
451
|
-
uses_local_responses_compat = (
|
|
452
|
-
managed_responses_base_url is not None
|
|
453
|
-
or vllm_endpoint is not None
|
|
454
|
-
or bool(use_chat_completion)
|
|
455
|
-
or use_messages
|
|
456
|
-
)
|
|
457
|
-
if vllm_endpoint is not None:
|
|
458
|
-
provider_config = replace(
|
|
459
|
-
provider_config,
|
|
460
|
-
model=_resolve_vllm_model(
|
|
461
|
-
vllm_endpoint,
|
|
462
|
-
provider_config,
|
|
463
|
-
timeout_seconds,
|
|
464
|
-
),
|
|
465
|
-
)
|
|
466
|
-
url, key_env = provider_config.base_url, provider_config.api_key_env
|
|
467
|
-
if managed_responses_base_url is not None:
|
|
468
|
-
url, key_env = (
|
|
469
|
-
managed_responses_base_url,
|
|
470
|
-
LOCAL_RESPONSES_SERVER_API_KEY_ENV,
|
|
471
|
-
)
|
|
472
|
-
os.environ.setdefault(LOCAL_RESPONSES_SERVER_API_KEY_ENV, "dummy")
|
|
473
|
-
elif vllm_endpoint or use_chat_completion or use_messages:
|
|
474
|
-
if vllm_endpoint:
|
|
475
|
-
managed_server = launch_chat_completion_compat_server(
|
|
476
|
-
vllm_endpoint,
|
|
477
|
-
model_provider="vllm",
|
|
478
|
-
)
|
|
479
|
-
else:
|
|
480
|
-
managed_server = launch_chat_completion_compat_server(
|
|
481
|
-
provider_config.base_url,
|
|
482
|
-
provider_config.api_key_env,
|
|
483
|
-
model_provider=provider_config.provider_name,
|
|
484
|
-
outcomming_api=(
|
|
485
|
-
"messages" if use_messages else "chat_completions"
|
|
486
|
-
),
|
|
487
|
-
)
|
|
488
|
-
atexit.register(managed_server.stop)
|
|
489
|
-
url, key_env = (
|
|
490
|
-
managed_server.base_url,
|
|
491
|
-
LOCAL_RESPONSES_SERVER_API_KEY_ENV,
|
|
492
|
-
)
|
|
493
|
-
os.environ.setdefault(LOCAL_RESPONSES_SERVER_API_KEY_ENV, "dummy")
|
|
494
|
-
|
|
495
|
-
provider_config = replace(
|
|
496
|
-
provider_config,
|
|
497
|
-
base_url=url,
|
|
498
|
-
api_key_env=key_env,
|
|
499
|
-
responses_lite_override=(
|
|
500
|
-
False if uses_local_responses_compat else provider_config.responses_lite_override
|
|
501
|
-
),
|
|
502
|
-
)
|
|
503
|
-
return ResponsesModelClient(
|
|
504
|
-
provider_config,
|
|
505
|
-
timeout_seconds,
|
|
506
|
-
originator=CLI_ORIGINATOR,
|
|
507
|
-
)
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
def build_cli_queue(agent: 'Agent') -> 'CliSubmissionQueue':
|
|
511
|
-
return CliSubmissionQueue(agent)
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
async def run_interactive_session(
|
|
515
|
-
queue: 'CliSubmissionQueue',
|
|
516
|
-
json_mode: 'bool',
|
|
517
|
-
config_path: 'typing.Union[str, None]' = None,
|
|
518
|
-
) -> 'int':
|
|
519
|
-
return await _run_interactive_session(
|
|
520
|
-
queue,
|
|
521
|
-
json_mode,
|
|
522
|
-
config_path,
|
|
523
|
-
view_factory=CliSessionView,
|
|
103
|
+
async def run_cli(args):
|
|
104
|
+
runtime = None
|
|
105
|
+
debug_dir = get_debug_dir()
|
|
106
|
+
phase_handle = (
|
|
107
|
+
None
|
|
108
|
+
if debug_dir is None
|
|
109
|
+
else (debug_dir / "phase.log").open("a", encoding="utf-8")
|
|
524
110
|
)
|
|
525
111
|
|
|
112
|
+
def phase(message):
|
|
113
|
+
if phase_handle is not None:
|
|
114
|
+
phase_handle.write(message + "\n")
|
|
115
|
+
phase_handle.flush()
|
|
526
116
|
|
|
527
|
-
async def run_cli(args: 'argparse.Namespace') -> 'int':
|
|
528
|
-
queued_agent = None
|
|
529
|
-
worker = None
|
|
530
|
-
debug_dir = get_debug_dir()
|
|
531
|
-
phase_handle = None if debug_dir is None else (debug_dir / "phase.log").open("a", encoding="utf-8")
|
|
532
117
|
try:
|
|
533
118
|
if args.put is not None and args.call:
|
|
534
119
|
raise ValueError("--put and --call cannot be combined")
|
|
@@ -537,7 +122,8 @@ async def run_cli(args: 'argparse.Namespace') -> 'int':
|
|
|
537
122
|
configure_loguru()
|
|
538
123
|
config_path = args.config
|
|
539
124
|
if args.put is not None:
|
|
540
|
-
|
|
125
|
+
|
|
126
|
+
def emit_put_log(message):
|
|
541
127
|
print(message, flush=True)
|
|
542
128
|
|
|
543
129
|
call_spec = upload_codex_home(args.put, event_handler=emit_put_log)
|
|
@@ -549,17 +135,11 @@ async def run_cli(args: 'argparse.Namespace') -> 'int':
|
|
|
549
135
|
print(f"pycodex --call {shlex.quote(call_spec)}", flush=True)
|
|
550
136
|
return 0
|
|
551
137
|
if args.call:
|
|
552
|
-
|
|
553
|
-
phase_handle.write("bootstrap_called_home:start\n")
|
|
554
|
-
phase_handle.flush()
|
|
138
|
+
phase("bootstrap_called_home:start")
|
|
555
139
|
config_path = bootstrap_called_home(args.call)
|
|
556
|
-
|
|
557
|
-
phase_handle.write("bootstrap_called_home:done\n")
|
|
558
|
-
phase_handle.flush()
|
|
140
|
+
phase("bootstrap_called_home:done")
|
|
559
141
|
os.environ["CODEX_HOME"] = str(config_path.parent)
|
|
560
|
-
|
|
561
|
-
phase_handle.write("build_model:start\n")
|
|
562
|
-
phase_handle.flush()
|
|
142
|
+
phase("build_model:start")
|
|
563
143
|
model = build_model(
|
|
564
144
|
config_path=str(config_path),
|
|
565
145
|
profile=args.profile,
|
|
@@ -568,79 +148,227 @@ async def run_cli(args: 'argparse.Namespace') -> 'int':
|
|
|
568
148
|
use_chat_completion=args.use_chat_completion or None,
|
|
569
149
|
use_messages=args.use_messages,
|
|
570
150
|
)
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
phase_handle.write("build_agent:start\n")
|
|
574
|
-
phase_handle.flush()
|
|
151
|
+
phase("build_model:done")
|
|
152
|
+
phase("build_agent:start")
|
|
575
153
|
agent = build_agent(
|
|
576
154
|
model,
|
|
577
155
|
config_path=str(config_path),
|
|
578
156
|
profile=args.profile,
|
|
579
157
|
system_prompt=args.system_prompt,
|
|
580
|
-
session_mode="tui",
|
|
581
158
|
)
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
phase_handle.write("build_cli_queue:start\n")
|
|
585
|
-
phase_handle.flush()
|
|
586
|
-
queued_agent = build_cli_queue(agent)
|
|
587
|
-
if phase_handle is not None:
|
|
588
|
-
phase_handle.write("build_cli_queue:done\n")
|
|
589
|
-
phase_handle.flush()
|
|
159
|
+
phase("build_agent:done")
|
|
160
|
+
runtime = build_runtime(agent)
|
|
590
161
|
if should_run_interactive(args.prompt, sys.stdin.isatty()):
|
|
591
|
-
return await run_interactive_session(
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
phase_handle.write("submit_user_turn:start\n")
|
|
601
|
-
phase_handle.flush()
|
|
602
|
-
result = await queued_agent.submit_user_turn(prompt_text)
|
|
603
|
-
if phase_handle is not None:
|
|
604
|
-
phase_handle.write("submit_user_turn:done\n")
|
|
605
|
-
phase_handle.flush()
|
|
606
|
-
print(format_turn_output(result, args.json))
|
|
607
|
-
return 0
|
|
162
|
+
return await run_interactive_session(runtime, args.json, str(config_path))
|
|
163
|
+
prompt_text = resolve_prompt_text(args.prompt)
|
|
164
|
+
await runtime.start(str(config_path))
|
|
165
|
+
phase("submit_input:start")
|
|
166
|
+
receipt = await runtime.submit_input(prompt_text, "cli")
|
|
167
|
+
result = await receipt.future
|
|
168
|
+
phase("submit_input:done")
|
|
169
|
+
render_result(receipt.kind, result, args.json, print)
|
|
170
|
+
return 0
|
|
608
171
|
except Exception as exc:
|
|
609
|
-
|
|
610
|
-
phase_handle.write("fatal_exception\n")
|
|
611
|
-
phase_handle.flush()
|
|
172
|
+
phase("fatal_exception")
|
|
612
173
|
if debug_dir is not None:
|
|
613
174
|
(debug_dir / "fatal_error.txt").write_text(
|
|
614
175
|
traceback.format_exc(), encoding="utf-8"
|
|
615
176
|
)
|
|
616
|
-
print(
|
|
177
|
+
print(format_error(exc, indent=False), file=sys.stderr)
|
|
617
178
|
return 1
|
|
618
179
|
finally:
|
|
619
180
|
if phase_handle is not None:
|
|
620
181
|
phase_handle.close()
|
|
621
|
-
if
|
|
622
|
-
await
|
|
623
|
-
|
|
182
|
+
if runtime is not None:
|
|
183
|
+
await runtime.close()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@contextmanager
|
|
187
|
+
def _cli_sigint_handler(runtime):
|
|
188
|
+
if threading.current_thread() is not threading.main_thread():
|
|
189
|
+
yield lambda: None
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
def handle_sigint(signum, frame):
|
|
193
|
+
if not runtime.accepts_input:
|
|
194
|
+
# A second interrupt must not re-enter asyncio's shutdown waits.
|
|
195
|
+
os._exit(130)
|
|
196
|
+
signal.default_int_handler(signum, frame)
|
|
197
|
+
|
|
198
|
+
def install_handler():
|
|
199
|
+
signal.signal(signal.SIGINT, handle_sigint)
|
|
200
|
+
|
|
201
|
+
previous_handler = signal.getsignal(signal.SIGINT)
|
|
202
|
+
install_handler()
|
|
203
|
+
try:
|
|
204
|
+
yield install_handler
|
|
205
|
+
finally:
|
|
206
|
+
signal.signal(signal.SIGINT, previous_handler)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
async def run_interactive_session(runtime, json_mode, config_path=None, view=None):
|
|
210
|
+
if view is None:
|
|
211
|
+
view = CliSessionView()
|
|
212
|
+
await runtime.start(config_path)
|
|
213
|
+
frontend_id = runtime.attach(view.handle_event)
|
|
214
|
+
|
|
215
|
+
def show_result(future):
|
|
216
|
+
if not future.cancelled() and future.exception() is None:
|
|
217
|
+
render_result("turn", future.result(), True, view.write_line)
|
|
624
218
|
|
|
625
|
-
|
|
219
|
+
view.display.start(runtime.commands())
|
|
220
|
+
with _cli_sigint_handler(runtime) as install_sigint_handler:
|
|
221
|
+
try:
|
|
222
|
+
while not view.display.closed:
|
|
223
|
+
try:
|
|
224
|
+
raw_line = await view.poll_prompt()
|
|
225
|
+
except EOFError:
|
|
226
|
+
break
|
|
227
|
+
if raw_line is None:
|
|
228
|
+
await asyncio.sleep(0.05)
|
|
229
|
+
continue
|
|
230
|
+
install_sigint_handler()
|
|
231
|
+
try:
|
|
232
|
+
receipt = await runtime.submit_input(raw_line, sender="cli")
|
|
233
|
+
except Exception as exc:
|
|
234
|
+
view.display.show_error(str(exc))
|
|
235
|
+
continue
|
|
236
|
+
if json_mode and receipt.kind == "turn":
|
|
237
|
+
receipt.future.add_done_callback(show_result)
|
|
238
|
+
finally:
|
|
239
|
+
# Older prompt_toolkit versions reset SIGINT when the prompt exits.
|
|
240
|
+
install_sigint_handler()
|
|
241
|
+
try:
|
|
242
|
+
await runtime.close()
|
|
243
|
+
finally:
|
|
244
|
+
runtime.detach(frontend_id)
|
|
245
|
+
view.close()
|
|
246
|
+
return 0
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class Prompter:
|
|
250
|
+
def __init__(self, prompt: str = DEFAULT_MAIN_PROMPT, lock=None):
|
|
251
|
+
self.lock = lock or threading.Lock()
|
|
252
|
+
self._prompt_session = PromptSession(**prompt_session_kwargs())
|
|
253
|
+
self.prompt = prompt
|
|
254
|
+
self._status = None
|
|
255
|
+
self._status_frame_index = 0
|
|
256
|
+
self._prompt_task = None
|
|
257
|
+
|
|
258
|
+
def set_prompt(self, prompt):
|
|
259
|
+
self.prompt = prompt
|
|
260
|
+
|
|
261
|
+
def set_status(self, text):
|
|
262
|
+
self._status = text
|
|
263
|
+
|
|
264
|
+
async def poll_input(self) -> "typing.Union[str, None]":
|
|
265
|
+
if self._prompt_task is None:
|
|
266
|
+
self._prompt_task = asyncio.create_task(self._block_prompt())
|
|
267
|
+
done, _pending = await asyncio.wait(
|
|
268
|
+
{self._prompt_task},
|
|
269
|
+
timeout=0.05,
|
|
270
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
271
|
+
)
|
|
272
|
+
if not done:
|
|
273
|
+
return None
|
|
274
|
+
prompt_task, self._prompt_task = self._prompt_task, None
|
|
275
|
+
try:
|
|
276
|
+
return prompt_task.result()
|
|
277
|
+
except asyncio.CancelledError:
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
async def _block_prompt(self):
|
|
281
|
+
with patch_stdout(raw=True):
|
|
282
|
+
return await self._prompt_session.prompt_async(
|
|
283
|
+
lambda: self.prompt,
|
|
284
|
+
refresh_interval=0.12,
|
|
285
|
+
bottom_toolbar=self._get_status,
|
|
286
|
+
set_exception_handler=False,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
def _get_status(self):
|
|
290
|
+
self._status_frame_index += 1
|
|
291
|
+
return EventDisplay.status_frame(self._status, self._status_frame_index)
|
|
292
|
+
|
|
293
|
+
def close(self) -> "None":
|
|
294
|
+
if self._prompt_task is not None and not self._prompt_task.done():
|
|
295
|
+
self._prompt_task.cancel()
|
|
296
|
+
self._prompt_task = None
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def prompt_session_kwargs() -> "typing.Dict[str, object]":
|
|
300
|
+
key_bindings = KeyBindings()
|
|
301
|
+
|
|
302
|
+
@key_bindings.add("c-c", filter=has_focus(DEFAULT_BUFFER))
|
|
303
|
+
@key_bindings.add("<sigint>")
|
|
304
|
+
def exit_prompt(event):
|
|
305
|
+
event.app.exit(exception=EOFError, style="class:aborting")
|
|
306
|
+
|
|
307
|
+
kwargs = {
|
|
308
|
+
"erase_when_done": True,
|
|
309
|
+
"enable_system_prompt": True,
|
|
310
|
+
"key_bindings": key_bindings,
|
|
311
|
+
}
|
|
312
|
+
try:
|
|
313
|
+
parameters = inspect.signature(PromptSession.__init__).parameters
|
|
314
|
+
except (TypeError, ValueError):
|
|
315
|
+
return kwargs
|
|
316
|
+
if "show_frame" in parameters:
|
|
317
|
+
kwargs["show_frame"] = True
|
|
318
|
+
return kwargs
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class CliSessionView:
|
|
322
|
+
"""Execute terminal I/O; events own presentation decisions and state."""
|
|
323
|
+
|
|
324
|
+
def __init__(self, context_window_tokens=None):
|
|
325
|
+
self._line_output = print
|
|
326
|
+
self._terminal_lock = threading.RLock()
|
|
327
|
+
self.prompter = Prompter(lock=self._terminal_lock)
|
|
328
|
+
color_enabled = sys.stdout.isatty() and os.environ.get(
|
|
329
|
+
"PYCODEX_NO_COLOR",
|
|
330
|
+
"",
|
|
331
|
+
).strip().lower() not in {"1", "true", "yes", "on"}
|
|
332
|
+
self.display = EventDisplay(
|
|
333
|
+
self.write_line,
|
|
334
|
+
self.prompter.set_status,
|
|
335
|
+
self.prompter.set_prompt,
|
|
336
|
+
color_enabled,
|
|
337
|
+
context_window_tokens,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
def handle_event(self, event):
|
|
341
|
+
with self._terminal_lock:
|
|
342
|
+
event.render(self.display)
|
|
343
|
+
|
|
344
|
+
def write_line(self, text):
|
|
345
|
+
with self._terminal_lock:
|
|
346
|
+
self._line_output(text)
|
|
347
|
+
|
|
348
|
+
async def poll_prompt(self, prompt=None):
|
|
349
|
+
if prompt:
|
|
350
|
+
self.prompter.set_prompt(prompt)
|
|
351
|
+
return await self.prompter.poll_input()
|
|
352
|
+
|
|
353
|
+
def close(self):
|
|
354
|
+
self.prompter.close()
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def ipython_agent(config_path=DEFAULT_CODEX_CONFIG_PATH):
|
|
626
358
|
from loguru import logger
|
|
359
|
+
|
|
360
|
+
from .tools.ipython_tool import attach_ipython_tool
|
|
361
|
+
|
|
627
362
|
logger.remove()
|
|
628
363
|
logger.add(sys.stderr, level="INFO")
|
|
629
|
-
|
|
630
364
|
model = build_model(config_path)
|
|
631
365
|
agent = build_agent(client=model, config_path=config_path)
|
|
632
|
-
|
|
633
|
-
from pycodex.tools.ipython_tool import attach_ipython_tool
|
|
634
|
-
|
|
635
366
|
attach_ipython_tool(agent)
|
|
636
|
-
|
|
637
367
|
return agent
|
|
638
368
|
|
|
639
|
-
def main(argv: 'typing.Union[Sequence[str], None]' = None) -> 'int':
|
|
640
|
-
raw_args = list(argv) if argv is not None else None
|
|
641
|
-
if raw_args is None:
|
|
642
|
-
raw_args = sys.argv[1:]
|
|
643
369
|
|
|
370
|
+
def main(argv=None):
|
|
371
|
+
raw_args = list(argv) if argv is not None else sys.argv[1:]
|
|
644
372
|
if raw_args and raw_args[0] == "doctor":
|
|
645
373
|
from .doctor import build_doctor_parser, run_doctor_cli
|
|
646
374
|
|
|
@@ -653,10 +381,8 @@ def main(argv: 'typing.Union[Sequence[str], None]' = None) -> 'int':
|
|
|
653
381
|
except KeyboardInterrupt:
|
|
654
382
|
return 130
|
|
655
383
|
return 0
|
|
656
|
-
|
|
657
384
|
parser = build_parser()
|
|
658
385
|
args = parser.parse_args(raw_args)
|
|
659
|
-
|
|
660
386
|
try:
|
|
661
387
|
return asyncio.run(run_cli(args))
|
|
662
388
|
except ValueError as exc:
|