python-codex 0.1.2__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 +43 -42
- 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 +69 -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 +2 -4
- 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 +62 -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 +37 -33
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/visualize.py +79 -79
- {python_codex-0.1.2.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.2.dist-info → python_codex-0.1.3.dist-info}/WHEEL +1 -1
- responses_server/app.py +29 -19
- responses_server/config.py +17 -17
- responses_server/payload_processors.py +16 -16
- responses_server/server.py +11 -11
- responses_server/session_store.py +10 -10
- responses_server/stream_router.py +58 -58
- responses_server/tools/custom_adapter.py +12 -12
- responses_server/tools/web_search.py +33 -33
- python_codex-0.1.2.dist-info/RECORD +0 -73
- {python_codex-0.1.2.dist-info → python_codex-0.1.3.dist-info}/entry_points.txt +0 -0
- {python_codex-0.1.2.dist-info → python_codex-0.1.3.dist-info}/licenses/LICENSE +0 -0
pycodex/__init__.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
from .compat import patch_asyncio
|
|
2
|
+
|
|
3
|
+
patch_asyncio()
|
|
4
|
+
|
|
1
5
|
from .agent import AgentLoop
|
|
2
6
|
from .context import ContextConfig, ContextManager
|
|
3
7
|
from .model import (
|
|
@@ -60,7 +64,7 @@ from .tools import (
|
|
|
60
64
|
WriteStdinTool,
|
|
61
65
|
)
|
|
62
66
|
|
|
63
|
-
def debug(stop: bool = False):
|
|
67
|
+
def debug(stop: 'bool' = False):
|
|
64
68
|
|
|
65
69
|
import socket
|
|
66
70
|
|
pycodex/agent.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
1
|
|
|
3
2
|
import asyncio
|
|
4
3
|
import json
|
|
5
|
-
from
|
|
4
|
+
from typing import Callable
|
|
6
5
|
|
|
7
6
|
from .context import ContextManager
|
|
8
7
|
from .model import ModelClient
|
|
@@ -19,10 +18,11 @@ from .protocol import (
|
|
|
19
18
|
)
|
|
20
19
|
from .tools import ToolContext, ToolRegistry
|
|
21
20
|
from .utils import uuid7_string
|
|
21
|
+
import typing
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
EventHandler = Callable[[AgentEvent], None]
|
|
25
|
-
NOOP_EVENT_HANDLER: EventHandler = lambda _event: None
|
|
25
|
+
NOOP_EVENT_HANDLER: 'EventHandler' = lambda _event: None
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class TurnInterrupted(RuntimeError):
|
|
@@ -40,47 +40,47 @@ class AgentLoop:
|
|
|
40
40
|
|
|
41
41
|
def __init__(
|
|
42
42
|
self,
|
|
43
|
-
model_client: ModelClient,
|
|
44
|
-
tool_registry: ToolRegistry,
|
|
45
|
-
context_manager: ContextManager
|
|
46
|
-
parallel_tool_calls: bool = True,
|
|
47
|
-
event_handler: EventHandler = NOOP_EVENT_HANDLER,
|
|
48
|
-
initial_history:
|
|
49
|
-
) -> None:
|
|
43
|
+
model_client: 'ModelClient',
|
|
44
|
+
tool_registry: 'ToolRegistry',
|
|
45
|
+
context_manager: 'typing.Union[ContextManager, None]' = None,
|
|
46
|
+
parallel_tool_calls: 'bool' = True,
|
|
47
|
+
event_handler: 'EventHandler' = NOOP_EVENT_HANDLER,
|
|
48
|
+
initial_history: 'typing.Tuple[ConversationItem, ...]' = (),
|
|
49
|
+
) -> 'None':
|
|
50
50
|
self._model_client = model_client
|
|
51
51
|
self._tool_registry = tool_registry
|
|
52
52
|
self._context_manager = context_manager or ContextManager()
|
|
53
53
|
self._parallel_tool_calls = parallel_tool_calls
|
|
54
54
|
self._event_handler = event_handler
|
|
55
|
-
self._history:
|
|
55
|
+
self._history: 'typing.List[ConversationItem]' = list(initial_history)
|
|
56
56
|
self.interrupt_asap = False
|
|
57
57
|
|
|
58
58
|
@property
|
|
59
|
-
def history(self) ->
|
|
59
|
+
def history(self) -> 'typing.Tuple[ConversationItem, ...]':
|
|
60
60
|
return tuple(self._history)
|
|
61
61
|
|
|
62
62
|
def set_event_handler(
|
|
63
|
-
self, event_handler: EventHandler = NOOP_EVENT_HANDLER
|
|
64
|
-
) -> None:
|
|
63
|
+
self, event_handler: 'EventHandler' = NOOP_EVENT_HANDLER
|
|
64
|
+
) -> 'None':
|
|
65
65
|
self._event_handler = event_handler
|
|
66
66
|
|
|
67
67
|
def _raise_if_interrupt_requested(
|
|
68
68
|
self,
|
|
69
|
-
turn_id: str,
|
|
70
|
-
iteration: int,
|
|
71
|
-
output_text: str
|
|
72
|
-
) -> None:
|
|
69
|
+
turn_id: 'str',
|
|
70
|
+
iteration: 'int',
|
|
71
|
+
output_text: 'typing.Union[str, None]' = None,
|
|
72
|
+
) -> 'None':
|
|
73
73
|
if self.interrupt_asap:
|
|
74
74
|
self.interrupt_asap = False
|
|
75
|
-
payload:
|
|
75
|
+
payload: 'typing.Dict[str, object]' = {"iteration": iteration}
|
|
76
76
|
if output_text is not None:
|
|
77
77
|
payload["output_text"] = output_text
|
|
78
78
|
self._emit("turn_interrupted", turn_id, **payload)
|
|
79
79
|
raise TurnInterrupted("turn interrupted")
|
|
80
80
|
|
|
81
81
|
async def run_turn(
|
|
82
|
-
self, texts:
|
|
83
|
-
) -> TurnResult:
|
|
82
|
+
self, texts: 'typing.List[str]', turn_id: 'typing.Union[str, None]' = None
|
|
83
|
+
) -> 'TurnResult':
|
|
84
84
|
turn_id = turn_id or uuid7_string()
|
|
85
85
|
self.interrupt_asap = False
|
|
86
86
|
for text in texts:
|
|
@@ -93,10 +93,8 @@ class AgentLoop:
|
|
|
93
93
|
user_texts=list(texts),
|
|
94
94
|
)
|
|
95
95
|
|
|
96
|
-
last_assistant_message: str
|
|
97
|
-
final_response_items:
|
|
98
|
-
AssistantMessage | ToolCall | ReasoningItem, ...
|
|
99
|
-
] = ()
|
|
96
|
+
last_assistant_message: 'typing.Union[str, None]' = None
|
|
97
|
+
final_response_items: 'typing.Tuple[\n typing.Union[typing.Union[AssistantMessage, ToolCall], ReasoningItem], ...\n]' = ()
|
|
100
98
|
|
|
101
99
|
iteration = 0
|
|
102
100
|
try:
|
|
@@ -132,7 +130,7 @@ class AgentLoop:
|
|
|
132
130
|
item_count=len(response.items),
|
|
133
131
|
)
|
|
134
132
|
|
|
135
|
-
tool_calls:
|
|
133
|
+
tool_calls: 'typing.List[ToolCall]' = []
|
|
136
134
|
for item in response.items:
|
|
137
135
|
self._history.append(item)
|
|
138
136
|
if isinstance(item, AssistantMessage):
|
|
@@ -182,11 +180,11 @@ class AgentLoop:
|
|
|
182
180
|
|
|
183
181
|
async def _execute_tool_batch(
|
|
184
182
|
self,
|
|
185
|
-
turn_id: str,
|
|
186
|
-
tool_calls:
|
|
187
|
-
) ->
|
|
188
|
-
results:
|
|
189
|
-
parallel_batch:
|
|
183
|
+
turn_id: 'str',
|
|
184
|
+
tool_calls: 'typing.List[ToolCall]',
|
|
185
|
+
) -> 'typing.List[ToolResult]':
|
|
186
|
+
results: 'typing.List[ToolResult]' = []
|
|
187
|
+
parallel_batch: 'typing.List[ToolCall]' = []
|
|
190
188
|
|
|
191
189
|
for call in tool_calls:
|
|
192
190
|
can_run_parallel = (
|
|
@@ -224,10 +222,10 @@ class AgentLoop:
|
|
|
224
222
|
|
|
225
223
|
async def _run_single_tool(
|
|
226
224
|
self,
|
|
227
|
-
turn_id: str,
|
|
228
|
-
call: ToolCall,
|
|
229
|
-
prior_results:
|
|
230
|
-
) -> ToolResult:
|
|
225
|
+
turn_id: 'str',
|
|
226
|
+
call: 'ToolCall',
|
|
227
|
+
prior_results: 'typing.Tuple[ToolResult, ...]' = (),
|
|
228
|
+
) -> 'ToolResult':
|
|
231
229
|
self._emit("tool_started", turn_id, tool_name=call.name, call_id=call.call_id)
|
|
232
230
|
result = await self._tool_registry.execute(
|
|
233
231
|
call,
|
|
@@ -237,7 +235,7 @@ class AgentLoop:
|
|
|
237
235
|
collaboration_mode=self._context_manager.collaboration_mode,
|
|
238
236
|
),
|
|
239
237
|
)
|
|
240
|
-
payload:
|
|
238
|
+
payload: 'typing.Dict[str, object]' = {
|
|
241
239
|
"tool_name": call.name,
|
|
242
240
|
"call_id": call.call_id,
|
|
243
241
|
"is_error": result.is_error,
|
|
@@ -247,12 +245,12 @@ class AgentLoop:
|
|
|
247
245
|
self._emit("tool_completed", turn_id, **payload)
|
|
248
246
|
return result
|
|
249
247
|
|
|
250
|
-
def _emit(self, kind: str, turn_id: str, **payload: object) -> None:
|
|
248
|
+
def _emit(self, kind: 'str', turn_id: 'str', **payload: 'object') -> 'None':
|
|
251
249
|
self._event_handler(
|
|
252
250
|
AgentEvent(kind=kind, turn_id=turn_id, payload=dict(payload))
|
|
253
251
|
)
|
|
254
252
|
|
|
255
|
-
def _handle_model_stream_event(self, turn_id: str, event: ModelStreamEvent) -> None:
|
|
253
|
+
def _handle_model_stream_event(self, turn_id: 'str', event: 'ModelStreamEvent') -> 'None':
|
|
256
254
|
if event.kind == "assistant_delta":
|
|
257
255
|
self._emit("assistant_delta", turn_id, **event.payload)
|
|
258
256
|
elif event.kind == "tool_call":
|
|
@@ -260,9 +258,9 @@ class AgentLoop:
|
|
|
260
258
|
|
|
261
259
|
def _build_follow_up_messages(
|
|
262
260
|
self,
|
|
263
|
-
tool_results:
|
|
264
|
-
) ->
|
|
265
|
-
follow_ups:
|
|
261
|
+
tool_results: 'typing.List[ToolResult]',
|
|
262
|
+
) -> 'typing.List[UserMessage]':
|
|
263
|
+
follow_ups: 'typing.List[UserMessage]' = []
|
|
266
264
|
for result in tool_results:
|
|
267
265
|
statuses = None
|
|
268
266
|
if (
|
pycodex/cli.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
1
|
|
|
3
2
|
import atexit
|
|
4
3
|
import argparse
|
|
@@ -10,10 +9,11 @@ import sys
|
|
|
10
9
|
import tempfile
|
|
11
10
|
from dataclasses import asdict, replace
|
|
12
11
|
from pathlib import Path
|
|
13
|
-
from typing import
|
|
12
|
+
from typing import Sequence
|
|
14
13
|
|
|
15
14
|
from .agent import AgentLoop
|
|
16
15
|
from .collaboration import DEFAULT_COLLABORATION_MODE, CollaborationMode
|
|
16
|
+
from .compat import Literal
|
|
17
17
|
from .context import ContextManager
|
|
18
18
|
from .model import ResponsesModelClient, ResponsesProviderConfig
|
|
19
19
|
from .portable import bootstrap_called_home, upload_codex_home
|
|
@@ -21,6 +21,7 @@ from .protocol import AgentEvent
|
|
|
21
21
|
from .runtime import AgentRuntime
|
|
22
22
|
from .runtime_services import RuntimeEnvironment, create_runtime_environment
|
|
23
23
|
from .utils import CliSessionView, load_codex_dotenv
|
|
24
|
+
import typing
|
|
24
25
|
|
|
25
26
|
EXIT_COMMANDS = {"/exit", "/quit"}
|
|
26
27
|
HISTORY_COMMAND = "/history"
|
|
@@ -40,7 +41,7 @@ def launch_chat_completion_compat_server(*args, **kwargs):
|
|
|
40
41
|
return launch_compat_server(*args, **kwargs)
|
|
41
42
|
|
|
42
43
|
|
|
43
|
-
def configure_loguru() -> None:
|
|
44
|
+
def configure_loguru() -> 'None':
|
|
44
45
|
try:
|
|
45
46
|
from loguru import logger
|
|
46
47
|
except ImportError: # pragma: no cover - dependency may be absent in minimal envs
|
|
@@ -61,7 +62,7 @@ def configure_loguru() -> None:
|
|
|
61
62
|
logger.add(sys.stderr, level="DEBUG")
|
|
62
63
|
|
|
63
64
|
|
|
64
|
-
def build_parser() -> argparse.ArgumentParser:
|
|
65
|
+
def build_parser() -> 'argparse.ArgumentParser':
|
|
65
66
|
parser = argparse.ArgumentParser(
|
|
66
67
|
prog="pycodex",
|
|
67
68
|
description="Minimal Codex-style local CLI backed by ~/.codex/config.toml.",
|
|
@@ -131,11 +132,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
131
132
|
return parser
|
|
132
133
|
|
|
133
134
|
|
|
134
|
-
def should_run_interactive(prompt_parts: Sequence[str], stdin_is_tty: bool) -> bool:
|
|
135
|
+
def should_run_interactive(prompt_parts: 'Sequence[str]', stdin_is_tty: 'bool') -> 'bool':
|
|
135
136
|
return not prompt_parts and stdin_is_tty
|
|
136
137
|
|
|
137
138
|
|
|
138
|
-
def resolve_prompt_text(prompt_parts: Sequence[str]) -> str:
|
|
139
|
+
def resolve_prompt_text(prompt_parts: 'Sequence[str]') -> 'str':
|
|
139
140
|
if prompt_parts:
|
|
140
141
|
return " ".join(prompt_parts).strip()
|
|
141
142
|
|
|
@@ -148,8 +149,8 @@ def resolve_prompt_text(prompt_parts: Sequence[str]) -> str:
|
|
|
148
149
|
|
|
149
150
|
|
|
150
151
|
def get_tools(
|
|
151
|
-
runtime_environment: RuntimeEnvironment
|
|
152
|
-
exec_mode: bool = False,
|
|
152
|
+
runtime_environment: 'typing.Union[RuntimeEnvironment, None]' = None,
|
|
153
|
+
exec_mode: 'bool' = False,
|
|
153
154
|
):
|
|
154
155
|
from .tools import (
|
|
155
156
|
ApplyPatchTool,
|
|
@@ -243,7 +244,7 @@ def get_tools(
|
|
|
243
244
|
return registry
|
|
244
245
|
|
|
245
246
|
|
|
246
|
-
def get_subagent_tools(runtime_environment: RuntimeEnvironment
|
|
247
|
+
def get_subagent_tools(runtime_environment: 'typing.Union[RuntimeEnvironment, None]' = None):
|
|
247
248
|
from .tools import (
|
|
248
249
|
ApplyPatchTool,
|
|
249
250
|
ExecCommandTool,
|
|
@@ -268,13 +269,13 @@ def get_subagent_tools(runtime_environment: RuntimeEnvironment | None = None):
|
|
|
268
269
|
|
|
269
270
|
|
|
270
271
|
def build_runtime(
|
|
271
|
-
config_path: str,
|
|
272
|
-
profile: str
|
|
273
|
-
system_prompt: str
|
|
272
|
+
config_path: 'str',
|
|
273
|
+
profile: 'typing.Union[str, None]',
|
|
274
|
+
system_prompt: 'typing.Union[str, None]',
|
|
274
275
|
client,
|
|
275
|
-
session_mode: CliSessionMode = "exec",
|
|
276
|
-
collaboration_mode: CollaborationMode = DEFAULT_COLLABORATION_MODE,
|
|
277
|
-
) -> AgentRuntime:
|
|
276
|
+
session_mode: 'CliSessionMode' = "exec",
|
|
277
|
+
collaboration_mode: 'CollaborationMode' = DEFAULT_COLLABORATION_MODE,
|
|
278
|
+
) -> 'AgentRuntime':
|
|
278
279
|
use_tui_context = session_mode == "tui"
|
|
279
280
|
context_manager = ContextManager.from_codex_config(
|
|
280
281
|
config_path,
|
|
@@ -295,11 +296,11 @@ def build_runtime(
|
|
|
295
296
|
|
|
296
297
|
def make_subagent_runtime_builder(base_client):
|
|
297
298
|
def build_subagent_runtime(
|
|
298
|
-
model_override: str
|
|
299
|
-
reasoning_effort_override: str
|
|
299
|
+
model_override: 'typing.Union[str, None]',
|
|
300
|
+
reasoning_effort_override: 'typing.Union[str, None]',
|
|
300
301
|
initial_history=(),
|
|
301
|
-
session_id: str
|
|
302
|
-
) -> AgentRuntime:
|
|
302
|
+
session_id: 'typing.Union[str, None]' = None,
|
|
303
|
+
) -> 'AgentRuntime':
|
|
303
304
|
nested_client = base_client.with_overrides(
|
|
304
305
|
model_override,
|
|
305
306
|
reasoning_effort_override,
|
|
@@ -335,19 +336,19 @@ def build_runtime(
|
|
|
335
336
|
)
|
|
336
337
|
|
|
337
338
|
|
|
338
|
-
def format_turn_output(result, json_mode: bool) -> str:
|
|
339
|
+
def format_turn_output(result, json_mode: 'bool') -> 'str':
|
|
339
340
|
if json_mode:
|
|
340
341
|
return json.dumps(asdict(result), ensure_ascii=False, indent=2)
|
|
341
342
|
return result.output_text or ""
|
|
342
343
|
|
|
343
344
|
|
|
344
345
|
def _build_model_client(
|
|
345
|
-
config_path: str,
|
|
346
|
-
profile: str
|
|
347
|
-
timeout_seconds: float,
|
|
348
|
-
managed_responses_base_url: str
|
|
349
|
-
vllm_endpoint: str
|
|
350
|
-
use_chat_completion: bool = False,
|
|
346
|
+
config_path: 'str',
|
|
347
|
+
profile: 'typing.Union[str, None]',
|
|
348
|
+
timeout_seconds: 'float',
|
|
349
|
+
managed_responses_base_url: 'typing.Union[str, None]' = None,
|
|
350
|
+
vllm_endpoint: 'typing.Union[str, None]' = None,
|
|
351
|
+
use_chat_completion: 'bool' = False,
|
|
351
352
|
):
|
|
352
353
|
load_codex_dotenv(config_path)
|
|
353
354
|
provider_config = ResponsesProviderConfig.from_codex_config(
|
|
@@ -393,13 +394,13 @@ def _build_model_client(
|
|
|
393
394
|
|
|
394
395
|
|
|
395
396
|
async def prompt_request_user_input(
|
|
396
|
-
view: CliSessionView,
|
|
397
|
-
payload:
|
|
398
|
-
) ->
|
|
397
|
+
view: 'CliSessionView',
|
|
398
|
+
payload: 'typing.Dict[str, object]',
|
|
399
|
+
) -> 'typing.Union[typing.Dict[str, object], None]':
|
|
399
400
|
view.finish_stream()
|
|
400
401
|
view.pause_spinner()
|
|
401
402
|
view.write_line("[request_user_input] waiting for user response")
|
|
402
|
-
answers:
|
|
403
|
+
answers: 'typing.Dict[str, typing.Dict[str, typing.List[str]]]' = {}
|
|
403
404
|
try:
|
|
404
405
|
for question in payload.get("questions", []):
|
|
405
406
|
if not isinstance(question, dict):
|
|
@@ -456,9 +457,9 @@ async def prompt_request_user_input(
|
|
|
456
457
|
|
|
457
458
|
|
|
458
459
|
async def prompt_request_permissions(
|
|
459
|
-
view: CliSessionView,
|
|
460
|
-
payload:
|
|
461
|
-
) ->
|
|
460
|
+
view: 'CliSessionView',
|
|
461
|
+
payload: 'typing.Dict[str, object]',
|
|
462
|
+
) -> 'typing.Union[typing.Dict[str, object], None]':
|
|
462
463
|
view.finish_stream()
|
|
463
464
|
view.pause_spinner()
|
|
464
465
|
view.write_line("[request_permissions] user approval required")
|
|
@@ -495,14 +496,14 @@ async def prompt_request_permissions(
|
|
|
495
496
|
|
|
496
497
|
|
|
497
498
|
async def run_interactive_session(
|
|
498
|
-
runtime: AgentRuntime,
|
|
499
|
-
json_mode: bool,
|
|
500
|
-
) -> int:
|
|
499
|
+
runtime: 'AgentRuntime',
|
|
500
|
+
json_mode: 'bool',
|
|
501
|
+
) -> 'int':
|
|
501
502
|
worker = asyncio.create_task(runtime.run_forever())
|
|
502
503
|
view = CliSessionView()
|
|
503
504
|
model_client = runtime._agent_loop._model_client
|
|
504
505
|
runtime.set_event_handler(view.handle_event)
|
|
505
|
-
pending_turn_tasks:
|
|
506
|
+
pending_turn_tasks: 'typing.Set[asyncio.Task[None]]' = set()
|
|
506
507
|
runtime_environment = runtime.runtime_environment
|
|
507
508
|
if runtime_environment is None:
|
|
508
509
|
runtime_environment = create_runtime_environment()
|
|
@@ -517,13 +518,13 @@ async def run_interactive_session(
|
|
|
517
518
|
view.write_line("Extra commands: /history, /title, /model")
|
|
518
519
|
try:
|
|
519
520
|
|
|
520
|
-
def has_pending_turn_tasks() -> bool:
|
|
521
|
+
def has_pending_turn_tasks() -> 'bool':
|
|
521
522
|
pending_turn_tasks.difference_update(
|
|
522
523
|
task for task in tuple(pending_turn_tasks) if task.done()
|
|
523
524
|
)
|
|
524
525
|
return bool(pending_turn_tasks)
|
|
525
526
|
|
|
526
|
-
async def wait_for_turn_result(future) -> None:
|
|
527
|
+
async def wait_for_turn_result(future) -> 'None':
|
|
527
528
|
try:
|
|
528
529
|
result = await future
|
|
529
530
|
except Exception as exc: # pragma: no cover - defensive surface
|
|
@@ -618,7 +619,7 @@ async def run_interactive_session(
|
|
|
618
619
|
return 0
|
|
619
620
|
|
|
620
621
|
|
|
621
|
-
async def run_cli(args: argparse.Namespace) -> int:
|
|
622
|
+
async def run_cli(args: 'argparse.Namespace') -> 'int':
|
|
622
623
|
runtime = None
|
|
623
624
|
worker = None
|
|
624
625
|
try:
|
|
@@ -628,7 +629,7 @@ async def run_cli(args: argparse.Namespace) -> int:
|
|
|
628
629
|
raise ValueError("--put does not accept prompt text")
|
|
629
630
|
configure_loguru()
|
|
630
631
|
if args.put is not None:
|
|
631
|
-
def emit_put_log(message: str) -> None:
|
|
632
|
+
def emit_put_log(message: 'str') -> 'None':
|
|
632
633
|
print(message, flush=True)
|
|
633
634
|
|
|
634
635
|
call_spec = upload_codex_home(args.put, event_handler=emit_put_log)
|
|
@@ -678,7 +679,7 @@ async def run_cli(args: argparse.Namespace) -> int:
|
|
|
678
679
|
await worker
|
|
679
680
|
|
|
680
681
|
|
|
681
|
-
def main(argv: Sequence[str]
|
|
682
|
+
def main(argv: 'typing.Union[Sequence[str], None]' = None) -> 'int':
|
|
682
683
|
raw_args = list(argv) if argv is not None else None
|
|
683
684
|
if raw_args is None:
|
|
684
685
|
raw_args = sys.argv[1:]
|
pycodex/collaboration.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
1
|
|
|
3
|
-
from
|
|
2
|
+
from .compat import Literal
|
|
3
|
+
import typing
|
|
4
4
|
|
|
5
5
|
CollaborationMode = Literal["default", "plan", "execute", "pair_programming"]
|
|
6
6
|
|
|
7
|
-
DEFAULT_COLLABORATION_MODE: CollaborationMode = "default"
|
|
8
|
-
PLAN_COLLABORATION_MODE: CollaborationMode = "plan"
|
|
7
|
+
DEFAULT_COLLABORATION_MODE: 'CollaborationMode' = "default"
|
|
8
|
+
PLAN_COLLABORATION_MODE: 'CollaborationMode' = "plan"
|
|
9
9
|
|
|
10
|
-
_MODE_DISPLAY_NAMES:
|
|
10
|
+
_MODE_DISPLAY_NAMES: 'typing.Dict[str, str]' = {
|
|
11
11
|
"default": "Default",
|
|
12
12
|
"plan": "Plan",
|
|
13
13
|
"execute": "Execute",
|
|
@@ -15,7 +15,6 @@ _MODE_DISPLAY_NAMES: dict[str, str] = {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def collaboration_mode_display_name(mode: str
|
|
18
|
+
def collaboration_mode_display_name(mode: 'typing.Union[str, None]') -> 'str':
|
|
19
19
|
normalized = (mode or DEFAULT_COLLABORATION_MODE).strip().lower()
|
|
20
20
|
return _MODE_DISPLAY_NAMES.get(normalized, normalized.replace("_", " ").title())
|
|
21
|
-
|
pycodex/compat.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import functools
|
|
3
|
+
import shlex
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from http.server import ThreadingHTTPServer
|
|
7
|
+
except ImportError: # pragma: no cover - Python 3.6 path
|
|
8
|
+
from http.server import HTTPServer
|
|
9
|
+
from socketserver import ThreadingMixIn
|
|
10
|
+
|
|
11
|
+
class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
|
|
12
|
+
daemon_threads = True
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from importlib import metadata as importlib_metadata
|
|
16
|
+
except ImportError: # pragma: no cover - Python 3.6 path
|
|
17
|
+
import importlib_metadata # type: ignore
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from typing import Literal, Protocol, TypeAlias
|
|
21
|
+
except ImportError: # pragma: no cover - Python 3.6 path
|
|
22
|
+
from typing_extensions import Literal, Protocol # type: ignore
|
|
23
|
+
try:
|
|
24
|
+
from typing_extensions import TypeAlias # type: ignore
|
|
25
|
+
except ImportError: # pragma: no cover - old typing_extensions
|
|
26
|
+
TypeAlias = object
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def patch_asyncio():
|
|
30
|
+
if not hasattr(asyncio, "create_task"):
|
|
31
|
+
asyncio.create_task = asyncio.ensure_future
|
|
32
|
+
|
|
33
|
+
if not hasattr(asyncio, "get_running_loop"):
|
|
34
|
+
def get_running_loop():
|
|
35
|
+
return asyncio.get_event_loop()
|
|
36
|
+
|
|
37
|
+
asyncio.get_running_loop = get_running_loop
|
|
38
|
+
|
|
39
|
+
if not hasattr(asyncio, "to_thread"):
|
|
40
|
+
async def to_thread(func, *args, **kwargs):
|
|
41
|
+
loop = asyncio.get_event_loop()
|
|
42
|
+
call = functools.partial(func, *args, **kwargs)
|
|
43
|
+
return await loop.run_in_executor(None, call)
|
|
44
|
+
|
|
45
|
+
asyncio.to_thread = to_thread
|
|
46
|
+
|
|
47
|
+
if not hasattr(asyncio, "run"):
|
|
48
|
+
def run(main):
|
|
49
|
+
loop = asyncio.new_event_loop()
|
|
50
|
+
try:
|
|
51
|
+
asyncio.set_event_loop(loop)
|
|
52
|
+
return loop.run_until_complete(main)
|
|
53
|
+
finally:
|
|
54
|
+
all_tasks = getattr(asyncio.Task, "all_tasks", None)
|
|
55
|
+
if all_tasks is not None:
|
|
56
|
+
pending = all_tasks(loop=loop)
|
|
57
|
+
else:
|
|
58
|
+
pending = asyncio.all_tasks(loop)
|
|
59
|
+
for task in pending:
|
|
60
|
+
task.cancel()
|
|
61
|
+
if pending:
|
|
62
|
+
loop.run_until_complete(
|
|
63
|
+
asyncio.gather(*pending, return_exceptions=True)
|
|
64
|
+
)
|
|
65
|
+
shutdown_asyncgens = getattr(loop, "shutdown_asyncgens", None)
|
|
66
|
+
if shutdown_asyncgens is not None:
|
|
67
|
+
loop.run_until_complete(shutdown_asyncgens())
|
|
68
|
+
asyncio.set_event_loop(None)
|
|
69
|
+
loop.close()
|
|
70
|
+
|
|
71
|
+
asyncio.run = run
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def shlex_join(parts):
|
|
75
|
+
join = getattr(shlex, "join", None)
|
|
76
|
+
if join is not None:
|
|
77
|
+
return join(parts)
|
|
78
|
+
return " ".join(shlex.quote(part) for part in parts)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def stream_writer_is_closing(writer):
|
|
82
|
+
method = getattr(writer, "is_closing", None)
|
|
83
|
+
if callable(method):
|
|
84
|
+
return method()
|
|
85
|
+
transport = getattr(writer, "transport", None)
|
|
86
|
+
if transport is None:
|
|
87
|
+
return False
|
|
88
|
+
transport_is_closing = getattr(transport, "is_closing", None)
|
|
89
|
+
if callable(transport_is_closing):
|
|
90
|
+
return transport_is_closing()
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def is_ascii(text):
|
|
95
|
+
try:
|
|
96
|
+
text.encode("ascii")
|
|
97
|
+
except UnicodeEncodeError:
|
|
98
|
+
return False
|
|
99
|
+
return True
|