python-codex 0.2.6__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 +18 -14
- pycodex/agent.py +468 -462
- pycodex/bootstrap.py +417 -0
- pycodex/cli.py +236 -436
- 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 +329 -252
- pycodex/model_metadata.py +19 -7
- pycodex/portable.py +90 -52
- pycodex/portable_server.py +32 -24
- pycodex/prompts/models.json +235 -803
- pycodex/protocol.py +177 -137
- pycodex/runtime.py +579 -174
- pycodex/runtime_services.py +204 -157
- pycodex/tools/__init__.py +4 -1
- pycodex/tools/apply_patch_tool.py +69 -48
- pycodex/tools/base_tool.py +89 -42
- pycodex/tools/clock_tool.py +201 -0
- 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 +13 -13
- 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 +50 -66
- 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/utils/image_utils.py +76 -0
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/session_persist.py +263 -161
- 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 +25 -22
- responses_server/messages_api.py +96 -49
- responses_server/payload_processors.py +25 -19
- responses_server/server.py +11 -11
- responses_server/session_store.py +14 -11
- responses_server/stream_router.py +196 -107
- responses_server/tools/custom_adapter.py +17 -16
- responses_server/tools/web_search.py +39 -36
- responses_server/trajectory_dump.py +51 -13
- workspace_server/__main__.py +0 -1
- workspace_server/app.py +470 -384
- workspace_server/workspace.html +859 -232
- workspace_server/workspaces.html +94 -95
- workspace_server/workspaces.py +168 -100
- 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 -553
- python_codex-0.2.6.dist-info/METADATA +0 -441
- python_codex-0.2.6.dist-info/RECORD +0 -91
- {python_codex-0.2.6.dist-info → python_codex-0.3.0.dist-info}/WHEEL +0 -0
- {python_codex-0.2.6.dist-info → python_codex-0.3.0.dist-info}/entry_points.txt +0 -0
- {python_codex-0.2.6.dist-info → python_codex-0.3.0.dist-info}/licenses/LICENSE +0 -0
pycodex/cli.py
CHANGED
|
@@ -1,73 +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 configure_loguru() -> 'None':
|
|
50
|
-
try:
|
|
51
|
-
from loguru import logger
|
|
52
|
-
except ImportError: # pragma: no cover - dependency may be absent in minimal envs
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
logger.remove()
|
|
56
|
-
debug_dir = get_debug_dir()
|
|
57
|
-
if debug_dir is not None:
|
|
58
|
-
logger.add(str(debug_dir / "loguru.log"), level="DEBUG")
|
|
59
|
-
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
|
|
60
19
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
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
|
|
68
26
|
|
|
69
27
|
|
|
70
|
-
def build_parser()
|
|
28
|
+
def build_parser():
|
|
71
29
|
parser = argparse.ArgumentParser(
|
|
72
30
|
prog="pycodex",
|
|
73
31
|
description="Minimal Codex-style local CLI backed by ~/.codex/config.toml.",
|
|
@@ -79,17 +37,12 @@ def build_parser() -> 'argparse.ArgumentParser':
|
|
|
79
37
|
"--put",
|
|
80
38
|
default=None,
|
|
81
39
|
metavar="PATH@SERVER",
|
|
82
|
-
help=
|
|
83
|
-
"Upload a Codex home using `--put @host:port` or "
|
|
84
|
-
"`--put /path/.codex@host:port`."
|
|
85
|
-
),
|
|
40
|
+
help="Upload a Codex home using `--put @host:port` or `--put /path/.codex@host:port`.",
|
|
86
41
|
)
|
|
87
42
|
parser.add_argument(
|
|
88
43
|
"--call",
|
|
89
44
|
default=None,
|
|
90
|
-
help=
|
|
91
|
-
"Download and use a stored Codex home via <secret>-<call_id>@<host:port>."
|
|
92
|
-
),
|
|
45
|
+
help="Download and use a stored Codex home via <secret>-<call_id>@<host:port>.",
|
|
93
46
|
)
|
|
94
47
|
parser.add_argument(
|
|
95
48
|
"--config",
|
|
@@ -97,35 +50,24 @@ def build_parser() -> 'argparse.ArgumentParser':
|
|
|
97
50
|
help="Path to Codex config.toml.",
|
|
98
51
|
)
|
|
99
52
|
parser.add_argument(
|
|
100
|
-
"--profile",
|
|
101
|
-
default=None,
|
|
102
|
-
help="Optional profile name from config.toml.",
|
|
53
|
+
"--profile", default=None, help="Optional profile name from config.toml."
|
|
103
54
|
)
|
|
104
55
|
parser.add_argument(
|
|
105
56
|
"--vllm-endpoint",
|
|
106
57
|
default=None,
|
|
107
|
-
help=
|
|
108
|
-
"Optional base URL for a chat-completions-backed vLLM server. "
|
|
109
|
-
"When set, pycodex starts a local responses compat server for this "
|
|
110
|
-
"session and appends /v1 if the path is empty."
|
|
111
|
-
),
|
|
58
|
+
help="Start a local responses compat server for a chat-completions-backed vLLM endpoint.",
|
|
112
59
|
)
|
|
113
60
|
parser.add_argument(
|
|
114
61
|
"--use-chat-completion",
|
|
115
62
|
default=False,
|
|
116
63
|
action="store_true",
|
|
117
|
-
help=
|
|
118
|
-
"When set, pycodex starts a local responses compat server for this session."
|
|
119
|
-
),
|
|
64
|
+
help="Start a local responses compat server for this session.",
|
|
120
65
|
)
|
|
121
66
|
parser.add_argument(
|
|
122
67
|
"--use-messages",
|
|
123
68
|
default=False,
|
|
124
69
|
action="store_true",
|
|
125
|
-
help=
|
|
126
|
-
"When set, pycodex starts a local responses compat server and routes "
|
|
127
|
-
"to a downstream /v1/messages backend for this session."
|
|
128
|
-
),
|
|
70
|
+
help="Route the local responses compat server to a downstream /v1/messages backend.",
|
|
129
71
|
)
|
|
130
72
|
parser.add_argument(
|
|
131
73
|
"--system-prompt",
|
|
@@ -139,322 +81,39 @@ def build_parser() -> 'argparse.ArgumentParser':
|
|
|
139
81
|
help="HTTP timeout for one model call.",
|
|
140
82
|
)
|
|
141
83
|
parser.add_argument(
|
|
142
|
-
"--json",
|
|
143
|
-
action="store_true",
|
|
144
|
-
help="Print the full TurnResult as JSON.",
|
|
84
|
+
"--json", action="store_true", help="Print the full TurnResult as JSON."
|
|
145
85
|
)
|
|
146
86
|
return parser
|
|
147
87
|
|
|
148
88
|
|
|
149
|
-
def should_run_interactive(prompt_parts
|
|
89
|
+
def should_run_interactive(prompt_parts, stdin_is_tty):
|
|
150
90
|
return not prompt_parts and stdin_is_tty
|
|
151
91
|
|
|
152
92
|
|
|
153
|
-
def resolve_prompt_text(prompt_parts
|
|
93
|
+
def resolve_prompt_text(prompt_parts):
|
|
154
94
|
if prompt_parts:
|
|
155
95
|
return " ".join(prompt_parts).strip()
|
|
156
|
-
|
|
157
96
|
if not sys.stdin.isatty():
|
|
158
97
|
prompt_text = sys.stdin.read().strip()
|
|
159
98
|
if prompt_text:
|
|
160
99
|
return prompt_text
|
|
161
|
-
|
|
162
100
|
raise ValueError("prompt is required either as argv text or stdin")
|
|
163
101
|
|
|
164
102
|
|
|
165
|
-
def
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
CloseAgentTool,
|
|
173
|
-
CodeModeManager,
|
|
174
|
-
ExecTool,
|
|
175
|
-
ExecCommandTool,
|
|
176
|
-
GrepFilesTool,
|
|
177
|
-
ListDirTool,
|
|
178
|
-
ReadFileTool,
|
|
179
|
-
RequestPermissionsTool,
|
|
180
|
-
RequestUserInputTool,
|
|
181
|
-
ResumeAgentTool,
|
|
182
|
-
Registry,
|
|
183
|
-
SendInputTool,
|
|
184
|
-
ShellCommandTool,
|
|
185
|
-
ShellTool,
|
|
186
|
-
SpawnAgentTool,
|
|
187
|
-
UnifiedExecManager,
|
|
188
|
-
UpdatePlanTool,
|
|
189
|
-
ViewImageTool,
|
|
190
|
-
WaitAgentTool,
|
|
191
|
-
WaitTool,
|
|
192
|
-
WebSearchTool,
|
|
193
|
-
WriteStdinTool,
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
runtime_environment = runtime_environment or create_agent_runtime_environment()
|
|
197
|
-
registry = Registry()
|
|
198
|
-
code_mode_manager = CodeModeManager(registry, cwd=cwd)
|
|
199
|
-
unified_exec_manager = UnifiedExecManager(cwd=cwd)
|
|
200
|
-
exec_tool = ExecTool(code_mode_manager)
|
|
201
|
-
wait_tool = WaitTool(code_mode_manager)
|
|
202
|
-
web_search_tool = WebSearchTool()
|
|
203
|
-
update_plan_tool = UpdatePlanTool(runtime_environment.plan_store)
|
|
204
|
-
request_user_input_tool = RequestUserInputTool(
|
|
205
|
-
runtime_environment.request_user_input_manager
|
|
206
|
-
)
|
|
207
|
-
request_permissions_tool = RequestPermissionsTool(
|
|
208
|
-
runtime_environment.request_permissions_manager
|
|
209
|
-
)
|
|
210
|
-
spawn_agent_tool = SpawnAgentTool(runtime_environment.subagent_manager)
|
|
211
|
-
send_input_tool = SendInputTool(runtime_environment.subagent_manager)
|
|
212
|
-
resume_agent_tool = ResumeAgentTool(runtime_environment.subagent_manager)
|
|
213
|
-
wait_agent_tool = WaitAgentTool(runtime_environment.subagent_manager)
|
|
214
|
-
close_agent_tool = CloseAgentTool(runtime_environment.subagent_manager)
|
|
215
|
-
apply_patch_tool = ApplyPatchTool(cwd=cwd)
|
|
216
|
-
shell_tool = ShellTool(cwd=cwd)
|
|
217
|
-
shell_command_tool = ShellCommandTool(cwd=cwd)
|
|
218
|
-
exec_command_tool = ExecCommandTool(unified_exec_manager)
|
|
219
|
-
write_stdin_tool = WriteStdinTool(unified_exec_manager)
|
|
220
|
-
grep_files_tool = GrepFilesTool(cwd=cwd)
|
|
221
|
-
read_file_tool = ReadFileTool()
|
|
222
|
-
list_dir_tool = ListDirTool()
|
|
223
|
-
view_image_tool = ViewImageTool(cwd=cwd)
|
|
224
|
-
if exec_mode:
|
|
225
|
-
registry.register(exec_command_tool)
|
|
226
|
-
registry.register(write_stdin_tool)
|
|
227
|
-
registry.register(update_plan_tool)
|
|
228
|
-
registry.register(request_user_input_tool)
|
|
229
|
-
registry.register(apply_patch_tool)
|
|
230
|
-
registry.register(web_search_tool)
|
|
231
|
-
registry.register(view_image_tool)
|
|
232
|
-
registry.register(spawn_agent_tool)
|
|
233
|
-
registry.register(send_input_tool)
|
|
234
|
-
registry.register(resume_agent_tool)
|
|
235
|
-
registry.register(wait_agent_tool)
|
|
236
|
-
registry.register(close_agent_tool)
|
|
237
|
-
return registry
|
|
238
|
-
|
|
239
|
-
registry.register(shell_tool)
|
|
240
|
-
registry.register(shell_command_tool)
|
|
241
|
-
registry.register(exec_command_tool)
|
|
242
|
-
registry.register(write_stdin_tool)
|
|
243
|
-
registry.register(exec_tool)
|
|
244
|
-
registry.register(wait_tool)
|
|
245
|
-
registry.register(web_search_tool)
|
|
246
|
-
registry.register(update_plan_tool)
|
|
247
|
-
registry.register(request_user_input_tool)
|
|
248
|
-
registry.register(request_permissions_tool)
|
|
249
|
-
registry.register(spawn_agent_tool)
|
|
250
|
-
registry.register(send_input_tool)
|
|
251
|
-
registry.register(resume_agent_tool)
|
|
252
|
-
registry.register(wait_agent_tool)
|
|
253
|
-
registry.register(close_agent_tool)
|
|
254
|
-
registry.register(apply_patch_tool)
|
|
255
|
-
registry.register(grep_files_tool)
|
|
256
|
-
registry.register(read_file_tool)
|
|
257
|
-
registry.register(list_dir_tool)
|
|
258
|
-
registry.register(view_image_tool)
|
|
259
|
-
return registry
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
def get_subagent_tools(
|
|
263
|
-
runtime_environment: 'typing.Union[AgentRuntimeEnvironment, None]' = None,
|
|
264
|
-
cwd: 'typing.Union[str, Path, None]' = None,
|
|
265
|
-
):
|
|
266
|
-
from .tools import (
|
|
267
|
-
ApplyPatchTool,
|
|
268
|
-
ExecCommandTool,
|
|
269
|
-
Registry,
|
|
270
|
-
UnifiedExecManager,
|
|
271
|
-
UpdatePlanTool,
|
|
272
|
-
ViewImageTool,
|
|
273
|
-
WebSearchTool,
|
|
274
|
-
WriteStdinTool,
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
runtime_environment = runtime_environment or create_agent_runtime_environment()
|
|
278
|
-
registry = Registry()
|
|
279
|
-
unified_exec_manager = UnifiedExecManager(cwd=cwd)
|
|
280
|
-
registry.register(ExecCommandTool(unified_exec_manager))
|
|
281
|
-
registry.register(WriteStdinTool(unified_exec_manager))
|
|
282
|
-
registry.register(UpdatePlanTool(runtime_environment.plan_store))
|
|
283
|
-
registry.register(ApplyPatchTool(cwd=cwd))
|
|
284
|
-
registry.register(WebSearchTool())
|
|
285
|
-
registry.register(ViewImageTool(cwd=cwd))
|
|
286
|
-
return registry
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
def build_agent(
|
|
290
|
-
client,
|
|
291
|
-
config_path: 'typing.Union[str, Path]' = DEFAULT_CODEX_CONFIG_PATH,
|
|
292
|
-
profile: 'typing.Union[str, None]' = None,
|
|
293
|
-
system_prompt: 'typing.Union[str, None]' = None,
|
|
294
|
-
session_mode: 'CliSessionMode' = "exec",
|
|
295
|
-
collaboration_mode: 'CollaborationMode' = DEFAULT_COLLABORATION_MODE,
|
|
296
|
-
extra_contextual_user_messages: 'typing.Iterable[str]' = (),
|
|
297
|
-
cwd: 'typing.Union[str, Path, None]' = None,
|
|
298
|
-
) -> 'Agent':
|
|
299
|
-
config_path = str(config_path)
|
|
300
|
-
resolved_cwd = Path(cwd or Path.cwd()).resolve()
|
|
301
|
-
context_manager = ContextManager.from_codex_config(
|
|
302
|
-
config_path,
|
|
303
|
-
profile,
|
|
304
|
-
base_instructions_override=system_prompt,
|
|
305
|
-
collaboration_mode=collaboration_mode,
|
|
306
|
-
include_collaboration_instructions=session_mode == "tui",
|
|
307
|
-
extra_contextual_user_messages=extra_contextual_user_messages,
|
|
308
|
-
cwd=resolved_cwd,
|
|
309
|
-
)
|
|
310
|
-
session_id = getattr(client, "_session_id", None) or uuid7_string()
|
|
311
|
-
if hasattr(client, "_session_id"):
|
|
312
|
-
client._session_id = session_id
|
|
313
|
-
subagent_context_manager = ContextManager.from_codex_config(
|
|
314
|
-
config_path,
|
|
315
|
-
profile,
|
|
316
|
-
base_instructions_override=system_prompt,
|
|
317
|
-
include_collaboration_instructions=False,
|
|
318
|
-
extra_contextual_user_messages=extra_contextual_user_messages,
|
|
319
|
-
cwd=resolved_cwd,
|
|
320
|
-
)
|
|
321
|
-
runtime_environment = create_agent_runtime_environment()
|
|
322
|
-
runtime_environment.request_user_input_manager.set_handler(None)
|
|
323
|
-
runtime_environment.request_permissions_manager.set_handler(None)
|
|
324
|
-
rollout_recorder = SessionRolloutRecorder.create(
|
|
325
|
-
resolve_codex_home(config_path),
|
|
326
|
-
session_id,
|
|
327
|
-
context_manager.cwd,
|
|
328
|
-
getattr(client, "_originator", CLI_ORIGINATOR),
|
|
329
|
-
getattr(getattr(client, "_config", None), "provider_name", None),
|
|
330
|
-
context_manager.resolve_base_instructions(),
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
def make_subagent_queue_builder(base_client):
|
|
334
|
-
def build_subagent_queue(
|
|
335
|
-
model_override: 'typing.Union[str, None]',
|
|
336
|
-
reasoning_effort_override: 'typing.Union[str, None]',
|
|
337
|
-
initial_history=(),
|
|
338
|
-
session_id: 'typing.Union[str, None]' = None,
|
|
339
|
-
) -> 'CliSubmissionQueue':
|
|
340
|
-
nested_client = base_client.with_overrides(
|
|
341
|
-
model_override,
|
|
342
|
-
reasoning_effort_override,
|
|
343
|
-
session_id=session_id,
|
|
344
|
-
openai_subagent="collab_spawn",
|
|
345
|
-
)
|
|
346
|
-
subagent_agent_runtime_environment = create_agent_runtime_environment()
|
|
347
|
-
subagent_agent_runtime_environment.request_user_input_manager.set_handler(None)
|
|
348
|
-
subagent_agent_runtime_environment.request_permissions_manager.set_handler(None)
|
|
349
|
-
subagent_agent_runtime_environment.subagent_manager.set_queue_builder(
|
|
350
|
-
make_subagent_queue_builder(nested_client)
|
|
351
|
-
)
|
|
352
|
-
sub_agent = Agent(
|
|
353
|
-
nested_client,
|
|
354
|
-
get_subagent_tools(subagent_agent_runtime_environment, cwd=resolved_cwd),
|
|
355
|
-
subagent_context_manager,
|
|
356
|
-
initial_history=tuple(initial_history),
|
|
357
|
-
runtime_environment=subagent_agent_runtime_environment,
|
|
358
|
-
)
|
|
359
|
-
return CliSubmissionQueue(sub_agent)
|
|
360
|
-
|
|
361
|
-
return build_subagent_queue
|
|
362
|
-
|
|
363
|
-
runtime_environment.subagent_manager.set_queue_builder(
|
|
364
|
-
make_subagent_queue_builder(client)
|
|
365
|
-
)
|
|
366
|
-
return Agent(
|
|
367
|
-
client,
|
|
368
|
-
get_tools(runtime_environment, exec_mode=True, cwd=resolved_cwd),
|
|
369
|
-
context_manager,
|
|
370
|
-
rollout_recorder=rollout_recorder,
|
|
371
|
-
runtime_environment=runtime_environment,
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
def build_model(
|
|
376
|
-
config_path: 'typing.Union[str, Path]' = DEFAULT_CODEX_CONFIG_PATH,
|
|
377
|
-
profile: 'typing.Union[str, None]' = None,
|
|
378
|
-
timeout_seconds: 'float' = 120.0,
|
|
379
|
-
managed_responses_base_url: 'typing.Union[str, None]' = None,
|
|
380
|
-
vllm_endpoint: 'typing.Union[str, None]' = None,
|
|
381
|
-
use_chat_completion: 'typing.Union[bool, None]' = None,
|
|
382
|
-
use_messages: 'bool' = False,
|
|
383
|
-
):
|
|
384
|
-
load_codex_dotenv(config_path)
|
|
385
|
-
provider_config = ResponsesProviderConfig.from_codex_config(
|
|
386
|
-
config_path,
|
|
387
|
-
profile,
|
|
388
|
-
)
|
|
389
|
-
if use_chat_completion is None:
|
|
390
|
-
use_chat_completion = bool(provider_config.use_chat_completion)
|
|
391
|
-
if use_chat_completion and use_messages:
|
|
392
|
-
raise ValueError("--use-chat-completion and --use-messages cannot be combined")
|
|
393
|
-
if vllm_endpoint and use_messages:
|
|
394
|
-
raise ValueError("--vllm-endpoint and --use-messages cannot be combined")
|
|
395
|
-
url, key_env = provider_config.base_url, provider_config.api_key_env
|
|
396
|
-
if managed_responses_base_url is not None:
|
|
397
|
-
url, key_env = (
|
|
398
|
-
managed_responses_base_url,
|
|
399
|
-
LOCAL_RESPONSES_SERVER_API_KEY_ENV,
|
|
400
|
-
)
|
|
401
|
-
os.environ.setdefault(LOCAL_RESPONSES_SERVER_API_KEY_ENV, "dummy")
|
|
402
|
-
elif vllm_endpoint or use_chat_completion or use_messages:
|
|
403
|
-
if vllm_endpoint:
|
|
404
|
-
managed_server = launch_chat_completion_compat_server(
|
|
405
|
-
vllm_endpoint,
|
|
406
|
-
model_provider="vllm",
|
|
407
|
-
)
|
|
408
|
-
else:
|
|
409
|
-
managed_server = launch_chat_completion_compat_server(
|
|
410
|
-
provider_config.base_url,
|
|
411
|
-
provider_config.api_key_env,
|
|
412
|
-
model_provider=provider_config.provider_name,
|
|
413
|
-
outcomming_api=(
|
|
414
|
-
"messages" if use_messages else "chat_completions"
|
|
415
|
-
),
|
|
416
|
-
)
|
|
417
|
-
atexit.register(managed_server.stop)
|
|
418
|
-
url, key_env = (
|
|
419
|
-
managed_server.base_url,
|
|
420
|
-
LOCAL_RESPONSES_SERVER_API_KEY_ENV,
|
|
421
|
-
)
|
|
422
|
-
os.environ.setdefault(LOCAL_RESPONSES_SERVER_API_KEY_ENV, "dummy")
|
|
423
|
-
|
|
424
|
-
provider_config = replace(
|
|
425
|
-
provider_config,
|
|
426
|
-
base_url=url,
|
|
427
|
-
api_key_env=key_env,
|
|
428
|
-
)
|
|
429
|
-
return ResponsesModelClient(
|
|
430
|
-
provider_config,
|
|
431
|
-
timeout_seconds,
|
|
432
|
-
originator=CLI_ORIGINATOR,
|
|
433
|
-
)
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
def build_cli_queue(agent: 'Agent') -> 'CliSubmissionQueue':
|
|
437
|
-
return CliSubmissionQueue(agent)
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
async def run_interactive_session(
|
|
441
|
-
queue: 'CliSubmissionQueue',
|
|
442
|
-
json_mode: 'bool',
|
|
443
|
-
config_path: 'typing.Union[str, None]' = None,
|
|
444
|
-
) -> 'int':
|
|
445
|
-
return await _run_interactive_session(
|
|
446
|
-
queue,
|
|
447
|
-
json_mode,
|
|
448
|
-
config_path,
|
|
449
|
-
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")
|
|
450
110
|
)
|
|
451
111
|
|
|
112
|
+
def phase(message):
|
|
113
|
+
if phase_handle is not None:
|
|
114
|
+
phase_handle.write(message + "\n")
|
|
115
|
+
phase_handle.flush()
|
|
452
116
|
|
|
453
|
-
async def run_cli(args: 'argparse.Namespace') -> 'int':
|
|
454
|
-
queued_agent = None
|
|
455
|
-
worker = None
|
|
456
|
-
debug_dir = get_debug_dir()
|
|
457
|
-
phase_handle = None if debug_dir is None else (debug_dir / "phase.log").open("a", encoding="utf-8")
|
|
458
117
|
try:
|
|
459
118
|
if args.put is not None and args.call:
|
|
460
119
|
raise ValueError("--put and --call cannot be combined")
|
|
@@ -463,7 +122,8 @@ async def run_cli(args: 'argparse.Namespace') -> 'int':
|
|
|
463
122
|
configure_loguru()
|
|
464
123
|
config_path = args.config
|
|
465
124
|
if args.put is not None:
|
|
466
|
-
|
|
125
|
+
|
|
126
|
+
def emit_put_log(message):
|
|
467
127
|
print(message, flush=True)
|
|
468
128
|
|
|
469
129
|
call_spec = upload_codex_home(args.put, event_handler=emit_put_log)
|
|
@@ -475,17 +135,11 @@ async def run_cli(args: 'argparse.Namespace') -> 'int':
|
|
|
475
135
|
print(f"pycodex --call {shlex.quote(call_spec)}", flush=True)
|
|
476
136
|
return 0
|
|
477
137
|
if args.call:
|
|
478
|
-
|
|
479
|
-
phase_handle.write("bootstrap_called_home:start\n")
|
|
480
|
-
phase_handle.flush()
|
|
138
|
+
phase("bootstrap_called_home:start")
|
|
481
139
|
config_path = bootstrap_called_home(args.call)
|
|
482
|
-
|
|
483
|
-
phase_handle.write("bootstrap_called_home:done\n")
|
|
484
|
-
phase_handle.flush()
|
|
140
|
+
phase("bootstrap_called_home:done")
|
|
485
141
|
os.environ["CODEX_HOME"] = str(config_path.parent)
|
|
486
|
-
|
|
487
|
-
phase_handle.write("build_model:start\n")
|
|
488
|
-
phase_handle.flush()
|
|
142
|
+
phase("build_model:start")
|
|
489
143
|
model = build_model(
|
|
490
144
|
config_path=str(config_path),
|
|
491
145
|
profile=args.profile,
|
|
@@ -494,79 +148,227 @@ async def run_cli(args: 'argparse.Namespace') -> 'int':
|
|
|
494
148
|
use_chat_completion=args.use_chat_completion or None,
|
|
495
149
|
use_messages=args.use_messages,
|
|
496
150
|
)
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
phase_handle.write("build_agent:start\n")
|
|
500
|
-
phase_handle.flush()
|
|
151
|
+
phase("build_model:done")
|
|
152
|
+
phase("build_agent:start")
|
|
501
153
|
agent = build_agent(
|
|
502
154
|
model,
|
|
503
155
|
config_path=str(config_path),
|
|
504
156
|
profile=args.profile,
|
|
505
157
|
system_prompt=args.system_prompt,
|
|
506
|
-
session_mode="tui",
|
|
507
158
|
)
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
phase_handle.write("build_cli_queue:start\n")
|
|
511
|
-
phase_handle.flush()
|
|
512
|
-
queued_agent = build_cli_queue(agent)
|
|
513
|
-
if phase_handle is not None:
|
|
514
|
-
phase_handle.write("build_cli_queue:done\n")
|
|
515
|
-
phase_handle.flush()
|
|
159
|
+
phase("build_agent:done")
|
|
160
|
+
runtime = build_runtime(agent)
|
|
516
161
|
if should_run_interactive(args.prompt, sys.stdin.isatty()):
|
|
517
|
-
return await run_interactive_session(
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
phase_handle.write("submit_user_turn:start\n")
|
|
527
|
-
phase_handle.flush()
|
|
528
|
-
result = await queued_agent.submit_user_turn(prompt_text)
|
|
529
|
-
if phase_handle is not None:
|
|
530
|
-
phase_handle.write("submit_user_turn:done\n")
|
|
531
|
-
phase_handle.flush()
|
|
532
|
-
print(format_turn_output(result, args.json))
|
|
533
|
-
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
|
|
534
171
|
except Exception as exc:
|
|
535
|
-
|
|
536
|
-
phase_handle.write("fatal_exception\n")
|
|
537
|
-
phase_handle.flush()
|
|
172
|
+
phase("fatal_exception")
|
|
538
173
|
if debug_dir is not None:
|
|
539
174
|
(debug_dir / "fatal_error.txt").write_text(
|
|
540
175
|
traceback.format_exc(), encoding="utf-8"
|
|
541
176
|
)
|
|
542
|
-
print(
|
|
177
|
+
print(format_error(exc, indent=False), file=sys.stderr)
|
|
543
178
|
return 1
|
|
544
179
|
finally:
|
|
545
180
|
if phase_handle is not None:
|
|
546
181
|
phase_handle.close()
|
|
547
|
-
if
|
|
548
|
-
await
|
|
549
|
-
|
|
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)
|
|
218
|
+
|
|
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
|
+
)
|
|
550
339
|
|
|
551
|
-
def
|
|
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):
|
|
552
358
|
from loguru import logger
|
|
359
|
+
|
|
360
|
+
from .tools.ipython_tool import attach_ipython_tool
|
|
361
|
+
|
|
553
362
|
logger.remove()
|
|
554
363
|
logger.add(sys.stderr, level="INFO")
|
|
555
|
-
|
|
556
364
|
model = build_model(config_path)
|
|
557
365
|
agent = build_agent(client=model, config_path=config_path)
|
|
558
|
-
|
|
559
|
-
from pycodex.tools.ipython_tool import attach_ipython_tool
|
|
560
|
-
|
|
561
366
|
attach_ipython_tool(agent)
|
|
562
|
-
|
|
563
367
|
return agent
|
|
564
368
|
|
|
565
|
-
def main(argv: 'typing.Union[Sequence[str], None]' = None) -> 'int':
|
|
566
|
-
raw_args = list(argv) if argv is not None else None
|
|
567
|
-
if raw_args is None:
|
|
568
|
-
raw_args = sys.argv[1:]
|
|
569
369
|
|
|
370
|
+
def main(argv=None):
|
|
371
|
+
raw_args = list(argv) if argv is not None else sys.argv[1:]
|
|
570
372
|
if raw_args and raw_args[0] == "doctor":
|
|
571
373
|
from .doctor import build_doctor_parser, run_doctor_cli
|
|
572
374
|
|
|
@@ -579,10 +381,8 @@ def main(argv: 'typing.Union[Sequence[str], None]' = None) -> 'int':
|
|
|
579
381
|
except KeyboardInterrupt:
|
|
580
382
|
return 130
|
|
581
383
|
return 0
|
|
582
|
-
|
|
583
384
|
parser = build_parser()
|
|
584
385
|
args = parser.parse_args(raw_args)
|
|
585
|
-
|
|
586
386
|
try:
|
|
587
387
|
return asyncio.run(run_cli(args))
|
|
588
388
|
except ValueError as exc:
|