python-codex 0.1.2__py3-none-any.whl → 0.1.4__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 +89 -51
- pycodex/cli.py +152 -45
- pycodex/collaboration.py +6 -7
- pycodex/compat.py +99 -0
- pycodex/context.py +110 -87
- pycodex/doctor.py +40 -40
- pycodex/model.py +429 -90
- pycodex/portable.py +33 -33
- pycodex/portable_server.py +22 -21
- pycodex/prompts/models.json +30 -0
- 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 +45 -46
- 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/__init__.py +4 -0
- pycodex/utils/compactor.py +189 -0
- pycodex/utils/dotenv.py +6 -6
- pycodex/utils/get_env.py +37 -33
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/session_persist.py +483 -0
- pycodex/utils/visualize.py +197 -83
- {python_codex-0.1.2.dist-info → python_codex-0.1.4.dist-info}/METADATA +32 -11
- python_codex-0.1.4.dist-info/RECORD +76 -0
- {python_codex-0.1.2.dist-info → python_codex-0.1.4.dist-info}/WHEEL +1 -1
- responses_server/app.py +32 -20
- responses_server/config.py +17 -17
- responses_server/payload_processors.py +26 -17
- responses_server/server.py +11 -11
- responses_server/session_store.py +10 -10
- responses_server/stream_router.py +83 -64
- 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.4.dist-info}/entry_points.txt +0 -0
- {python_codex-0.1.2.dist-info → python_codex-0.1.4.dist-info}/licenses/LICENSE +0 -0
pycodex/tools/base_tool.py
CHANGED
|
@@ -10,8 +10,6 @@ Expected behavior:
|
|
|
10
10
|
model, and dispatches `ToolCall` executions back into `ToolResult`s.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
|
|
15
13
|
import inspect
|
|
16
14
|
from abc import ABC, abstractmethod
|
|
17
15
|
from dataclasses import dataclass
|
|
@@ -20,6 +18,7 @@ import json
|
|
|
20
18
|
from pathlib import Path
|
|
21
19
|
|
|
22
20
|
from ..protocol import ConversationItem, JSONDict, JSONValue, ToolCall, ToolResult, ToolSpec
|
|
21
|
+
import typing
|
|
23
22
|
|
|
24
23
|
EXEC_TOOLS_SNAPSHOT_PATH = (
|
|
25
24
|
Path(__file__).resolve().parent.parent / "prompts" / "exec_tools.json"
|
|
@@ -27,8 +26,8 @@ EXEC_TOOLS_SNAPSHOT_PATH = (
|
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
@lru_cache(maxsize=1)
|
|
30
|
-
def _load_exec_tool_payloads() ->
|
|
31
|
-
payloads:
|
|
29
|
+
def _load_exec_tool_payloads() -> 'typing.Dict[str, JSONDict]':
|
|
30
|
+
payloads: 'typing.Dict[str, JSONDict]' = {}
|
|
32
31
|
for payload in json.loads(EXEC_TOOLS_SNAPSHOT_PATH.read_text()):
|
|
33
32
|
if not isinstance(payload, dict):
|
|
34
33
|
continue
|
|
@@ -41,36 +40,36 @@ def _load_exec_tool_payloads() -> dict[str, JSONDict]:
|
|
|
41
40
|
return payloads
|
|
42
41
|
|
|
43
42
|
|
|
44
|
-
@dataclass(frozen=True,
|
|
43
|
+
@dataclass(frozen=True, )
|
|
45
44
|
class ToolContext:
|
|
46
|
-
turn_id: str
|
|
47
|
-
history:
|
|
48
|
-
collaboration_mode: str = "default"
|
|
45
|
+
turn_id: 'str'
|
|
46
|
+
history: 'typing.Tuple[ConversationItem, ...]'
|
|
47
|
+
collaboration_mode: 'str' = "default"
|
|
49
48
|
|
|
50
49
|
|
|
51
50
|
class StructuredToolOutput:
|
|
52
51
|
def __init__(
|
|
53
52
|
self,
|
|
54
|
-
output: JSONValue,
|
|
55
|
-
content_items:
|
|
56
|
-
success: bool
|
|
57
|
-
) -> None:
|
|
53
|
+
output: 'JSONValue',
|
|
54
|
+
content_items: 'typing.Union[typing.Union[typing.Tuple[JSONDict, ...], typing.List[JSONDict]], None]' = None,
|
|
55
|
+
success: 'typing.Union[bool, None]' = None,
|
|
56
|
+
) -> 'None':
|
|
58
57
|
self.output = output
|
|
59
58
|
self.content_items = None if content_items is None else tuple(content_items)
|
|
60
59
|
self.success = success
|
|
61
60
|
|
|
62
61
|
|
|
63
62
|
class BaseTool(ABC):
|
|
64
|
-
name: str
|
|
65
|
-
description: str
|
|
66
|
-
input_schema: JSONDict
|
|
67
|
-
tool_type: str = "function"
|
|
68
|
-
format: JSONDict
|
|
69
|
-
options: JSONDict
|
|
70
|
-
output_schema: JSONDict
|
|
71
|
-
supports_parallel: bool = True
|
|
72
|
-
|
|
73
|
-
def spec(self) -> ToolSpec:
|
|
63
|
+
name: 'str'
|
|
64
|
+
description: 'str'
|
|
65
|
+
input_schema: 'typing.Union[JSONDict, None]' = None
|
|
66
|
+
tool_type: 'str' = "function"
|
|
67
|
+
format: 'typing.Union[JSONDict, None]' = None
|
|
68
|
+
options: 'typing.Union[JSONDict, None]' = None
|
|
69
|
+
output_schema: 'typing.Union[JSONDict, None]' = None
|
|
70
|
+
supports_parallel: 'bool' = True
|
|
71
|
+
|
|
72
|
+
def spec(self) -> 'ToolSpec':
|
|
74
73
|
return ToolSpec(
|
|
75
74
|
name=self.name,
|
|
76
75
|
description=self.description,
|
|
@@ -83,32 +82,32 @@ class BaseTool(ABC):
|
|
|
83
82
|
raw_payload=self.raw_payload(),
|
|
84
83
|
)
|
|
85
84
|
|
|
86
|
-
def serialize(self) -> JSONDict:
|
|
85
|
+
def serialize(self) -> 'JSONDict':
|
|
87
86
|
return self.spec().serialize()
|
|
88
87
|
|
|
89
|
-
def raw_payload(self) -> JSONDict
|
|
88
|
+
def raw_payload(self) -> 'typing.Union[JSONDict, None]':
|
|
90
89
|
return _load_exec_tool_payloads().get(self.name)
|
|
91
90
|
|
|
92
91
|
@abstractmethod
|
|
93
|
-
async def run(self, context: ToolContext, args: JSONValue) -> JSONValue:
|
|
92
|
+
async def run(self, context: 'ToolContext', args: 'JSONValue') -> 'JSONValue':
|
|
94
93
|
raise NotImplementedError
|
|
95
94
|
|
|
96
95
|
|
|
97
96
|
class ToolRegistry:
|
|
98
|
-
def __init__(self) -> None:
|
|
99
|
-
self._tools:
|
|
97
|
+
def __init__(self) -> 'None':
|
|
98
|
+
self._tools: 'typing.Dict[str, BaseTool]' = {}
|
|
100
99
|
|
|
101
|
-
def register(self, tool: BaseTool) -> None:
|
|
100
|
+
def register(self, tool: 'BaseTool') -> 'None':
|
|
102
101
|
self._tools[tool.name] = tool
|
|
103
102
|
|
|
104
|
-
def model_visible_specs(self) ->
|
|
103
|
+
def model_visible_specs(self) -> 'typing.List[ToolSpec]':
|
|
105
104
|
return [tool.spec() for tool in self._tools.values()]
|
|
106
105
|
|
|
107
|
-
def supports_parallel(self, tool_name: str) -> bool:
|
|
106
|
+
def supports_parallel(self, tool_name: 'str') -> 'bool':
|
|
108
107
|
tool = self._tools.get(tool_name)
|
|
109
108
|
return False if tool is None else tool.supports_parallel
|
|
110
109
|
|
|
111
|
-
async def execute(self, call: ToolCall, context: ToolContext) -> ToolResult:
|
|
110
|
+
async def execute(self, call: 'ToolCall', context: 'ToolContext') -> 'ToolResult':
|
|
112
111
|
tool = self._tools.get(call.name)
|
|
113
112
|
if tool is None:
|
|
114
113
|
return ToolResult(
|
|
@@ -149,19 +148,19 @@ class ToolRegistry:
|
|
|
149
148
|
tool_type=call.tool_type,
|
|
150
149
|
)
|
|
151
150
|
|
|
152
|
-
def __contains__(self, tool_name: str) -> bool:
|
|
151
|
+
def __contains__(self, tool_name: 'str') -> 'bool':
|
|
153
152
|
return tool_name in self._tools
|
|
154
153
|
|
|
155
|
-
def __len__(self) -> int:
|
|
154
|
+
def __len__(self) -> 'int':
|
|
156
155
|
return len(self._tools)
|
|
157
156
|
|
|
158
|
-
def names(self) ->
|
|
157
|
+
def names(self) -> 'typing.Tuple[str, ...]':
|
|
159
158
|
return tuple(self._tools)
|
|
160
159
|
|
|
161
|
-
def get_tool(self, tool_name: str) -> BaseTool
|
|
160
|
+
def get_tool(self, tool_name: 'str') -> 'typing.Union[BaseTool, None]':
|
|
162
161
|
return self._tools.get(tool_name)
|
|
163
162
|
|
|
164
|
-
def tools(self) ->
|
|
163
|
+
def tools(self) -> 'typing.Tuple[BaseTool, ...]':
|
|
165
164
|
return tuple(self._tools.values())
|
|
166
165
|
|
|
167
166
|
|
|
@@ -8,8 +8,6 @@ Expected behavior:
|
|
|
8
8
|
- Return the agent status observed at close time.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
11
|
from ..protocol import JSONDict, JSONValue
|
|
14
12
|
from ..runtime_services import SubAgentManager
|
|
15
13
|
from .agent_tool_schemas import AGENT_STATUS_SCHEMA
|
|
@@ -44,10 +42,10 @@ class CloseAgentTool(BaseTool):
|
|
|
44
42
|
output_schema = CLOSE_AGENT_OUTPUT_SCHEMA
|
|
45
43
|
supports_parallel = False
|
|
46
44
|
|
|
47
|
-
def __init__(self, subagent_manager: SubAgentManager) -> None:
|
|
45
|
+
def __init__(self, subagent_manager: 'SubAgentManager') -> 'None':
|
|
48
46
|
self._subagent_manager = subagent_manager
|
|
49
47
|
|
|
50
|
-
async def run(self, context: ToolContext, args: JSONDict) -> JSONValue:
|
|
48
|
+
async def run(self, context: 'ToolContext', args: 'JSONDict') -> 'JSONValue':
|
|
51
49
|
del context
|
|
52
50
|
agent_id = str(args.get("id", "")).strip()
|
|
53
51
|
if not agent_id:
|
|
@@ -10,8 +10,6 @@ Expected behavior:
|
|
|
10
10
|
`wait`.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
|
|
15
13
|
import asyncio
|
|
16
14
|
import json
|
|
17
15
|
import math
|
|
@@ -21,8 +19,10 @@ from pathlib import Path
|
|
|
21
19
|
|
|
22
20
|
from loguru import logger
|
|
23
21
|
|
|
22
|
+
from ..compat import is_ascii, stream_writer_is_closing
|
|
24
23
|
from ..protocol import JSONDict, JSONValue, ToolCall
|
|
25
24
|
from .base_tool import StructuredToolOutput, ToolContext, ToolRegistry
|
|
25
|
+
import typing
|
|
26
26
|
|
|
27
27
|
DEFAULT_WAIT_YIELD_TIME_MS = 10_000
|
|
28
28
|
DEFAULT_MAX_OUTPUT_TOKENS = 10_000
|
|
@@ -31,41 +31,41 @@ EXEC_PRAGMA_PREFIX = "// @exec:"
|
|
|
31
31
|
WAIT_COMPLETION_GRACE_SECONDS = 0.02
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
@dataclass
|
|
34
|
+
@dataclass
|
|
35
35
|
class ExecCell:
|
|
36
|
-
cell_id: str
|
|
37
|
-
process: asyncio.subprocess.Process
|
|
38
|
-
started_at: float
|
|
39
|
-
output_items:
|
|
40
|
-
delivered_count: int = 0
|
|
41
|
-
reader_task: asyncio.Task
|
|
42
|
-
stderr_task: asyncio.Task
|
|
43
|
-
yield_event: asyncio.Event = field(default_factory=asyncio.Event)
|
|
44
|
-
output_event: asyncio.Event = field(default_factory=asyncio.Event)
|
|
45
|
-
done_event: asyncio.Event = field(default_factory=asyncio.Event)
|
|
46
|
-
completed: bool = False
|
|
47
|
-
terminated: bool = False
|
|
48
|
-
error_text: str
|
|
49
|
-
stderr_chunks:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@dataclass(frozen=True,
|
|
36
|
+
cell_id: 'str'
|
|
37
|
+
process: 'asyncio.subprocess.Process'
|
|
38
|
+
started_at: 'float'
|
|
39
|
+
output_items: 'typing.List[JSONDict]' = field(default_factory=list)
|
|
40
|
+
delivered_count: 'int' = 0
|
|
41
|
+
reader_task: 'typing.Union[asyncio.Task, None]' = None
|
|
42
|
+
stderr_task: 'typing.Union[asyncio.Task, None]' = None
|
|
43
|
+
yield_event: 'asyncio.Event' = field(default_factory=asyncio.Event)
|
|
44
|
+
output_event: 'asyncio.Event' = field(default_factory=asyncio.Event)
|
|
45
|
+
done_event: 'asyncio.Event' = field(default_factory=asyncio.Event)
|
|
46
|
+
completed: 'bool' = False
|
|
47
|
+
terminated: 'bool' = False
|
|
48
|
+
error_text: 'typing.Union[str, None]' = None
|
|
49
|
+
stderr_chunks: 'typing.List[str]' = field(default_factory=list)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True, )
|
|
53
53
|
class ParsedExecSource:
|
|
54
|
-
code: str
|
|
55
|
-
yield_time_ms: int
|
|
56
|
-
max_output_tokens: int
|
|
54
|
+
code: 'str'
|
|
55
|
+
yield_time_ms: 'typing.Union[int, None]'
|
|
56
|
+
max_output_tokens: 'typing.Union[int, None]'
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
class CodeModeManager:
|
|
60
|
-
def __init__(self, registry: ToolRegistry, cwd: str
|
|
60
|
+
def __init__(self, registry: 'ToolRegistry', cwd: 'typing.Union[typing.Union[str, Path], None]' = None) -> 'None':
|
|
61
61
|
self._registry = registry
|
|
62
62
|
self._default_cwd = Path(cwd or Path.cwd()).resolve()
|
|
63
63
|
self._runtime_script = Path(__file__).with_name("exec_runtime.js")
|
|
64
|
-
self._stored_values:
|
|
65
|
-
self._cells:
|
|
64
|
+
self._stored_values: 'typing.Dict[str, JSONValue]' = {}
|
|
65
|
+
self._cells: 'typing.Dict[str, ExecCell]' = {}
|
|
66
66
|
self._lock = asyncio.Lock()
|
|
67
67
|
|
|
68
|
-
async def exec(self, source: str, context: ToolContext) -> StructuredToolOutput
|
|
68
|
+
async def exec(self, source: 'str', context: 'ToolContext') -> 'typing.Union[StructuredToolOutput, str]':
|
|
69
69
|
try:
|
|
70
70
|
parsed = self._parse_exec_source(source)
|
|
71
71
|
except ValueError as exc:
|
|
@@ -77,11 +77,11 @@ class CodeModeManager:
|
|
|
77
77
|
|
|
78
78
|
async def wait(
|
|
79
79
|
self,
|
|
80
|
-
cell_id: str,
|
|
81
|
-
yield_time_ms: int,
|
|
82
|
-
max_tokens: int
|
|
83
|
-
terminate: bool,
|
|
84
|
-
) -> StructuredToolOutput
|
|
80
|
+
cell_id: 'str',
|
|
81
|
+
yield_time_ms: 'int',
|
|
82
|
+
max_tokens: 'typing.Union[int, None]',
|
|
83
|
+
terminate: 'bool',
|
|
84
|
+
) -> 'typing.Union[StructuredToolOutput, str]':
|
|
85
85
|
cell = self._cells.get(cell_id)
|
|
86
86
|
if cell is None:
|
|
87
87
|
return f"Error: unknown exec cell `{cell_id}`."
|
|
@@ -98,8 +98,8 @@ class CodeModeManager:
|
|
|
98
98
|
await self._wait_for_wait(cell, yield_time_ms)
|
|
99
99
|
return await self._snapshot_cell(cell, max_tokens)
|
|
100
100
|
|
|
101
|
-
def enabled_tools(self) ->
|
|
102
|
-
enabled:
|
|
101
|
+
def enabled_tools(self) -> 'typing.List[typing.Dict[str, str]]':
|
|
102
|
+
enabled: 'typing.List[typing.Dict[str, str]]' = []
|
|
103
103
|
for tool in self._registry.tools():
|
|
104
104
|
if tool.name in {"exec", "wait"}:
|
|
105
105
|
continue
|
|
@@ -116,7 +116,7 @@ class CodeModeManager:
|
|
|
116
116
|
enabled.sort(key=lambda item: item["tool_name"])
|
|
117
117
|
return enabled
|
|
118
118
|
|
|
119
|
-
async def _start_cell(self, code: str, context: ToolContext) -> ExecCell:
|
|
119
|
+
async def _start_cell(self, code: 'str', context: 'ToolContext') -> 'ExecCell':
|
|
120
120
|
cell_id = uuid.uuid4().hex[:10]
|
|
121
121
|
process = await asyncio.create_subprocess_exec(
|
|
122
122
|
"node",
|
|
@@ -147,7 +147,7 @@ class CodeModeManager:
|
|
|
147
147
|
logger.debug("exec start cell_id={} cwd={}", cell_id, self._default_cwd)
|
|
148
148
|
return cell
|
|
149
149
|
|
|
150
|
-
async def _read_stdout(self, cell: ExecCell, context: ToolContext) -> None:
|
|
150
|
+
async def _read_stdout(self, cell: 'ExecCell', context: 'ToolContext') -> 'None':
|
|
151
151
|
stream = cell.process.stdout
|
|
152
152
|
if stream is None:
|
|
153
153
|
cell.error_text = "missing stdout pipe"
|
|
@@ -181,7 +181,7 @@ class CodeModeManager:
|
|
|
181
181
|
cell.output_event.set()
|
|
182
182
|
continue
|
|
183
183
|
if msg_type == "output_image":
|
|
184
|
-
image_item: JSONDict = {
|
|
184
|
+
image_item: 'JSONDict' = {
|
|
185
185
|
"type": "input_image",
|
|
186
186
|
"image_url": str(message.get("image_url", "")),
|
|
187
187
|
}
|
|
@@ -220,7 +220,7 @@ class CodeModeManager:
|
|
|
220
220
|
cell.done_event.set()
|
|
221
221
|
cell.output_event.set()
|
|
222
222
|
|
|
223
|
-
async def _read_stderr(self, cell: ExecCell) -> None:
|
|
223
|
+
async def _read_stderr(self, cell: 'ExecCell') -> 'None':
|
|
224
224
|
stream = cell.process.stderr
|
|
225
225
|
if stream is None:
|
|
226
226
|
return
|
|
@@ -232,10 +232,10 @@ class CodeModeManager:
|
|
|
232
232
|
|
|
233
233
|
async def _handle_nested_tool_call(
|
|
234
234
|
self,
|
|
235
|
-
cell: ExecCell,
|
|
236
|
-
context: ToolContext,
|
|
237
|
-
message: JSONDict,
|
|
238
|
-
) -> None:
|
|
235
|
+
cell: 'ExecCell',
|
|
236
|
+
context: 'ToolContext',
|
|
237
|
+
message: 'JSONDict',
|
|
238
|
+
) -> 'None':
|
|
239
239
|
tool_name = str(message.get("tool_name", ""))
|
|
240
240
|
request_id = str(message.get("id", ""))
|
|
241
241
|
tool = self._registry.get_tool(tool_name)
|
|
@@ -287,14 +287,14 @@ class CodeModeManager:
|
|
|
287
287
|
}
|
|
288
288
|
await self._send_message(cell, payload)
|
|
289
289
|
|
|
290
|
-
async def _send_message(self, cell: ExecCell, payload: JSONDict) -> None:
|
|
290
|
+
async def _send_message(self, cell: 'ExecCell', payload: 'JSONDict') -> 'None':
|
|
291
291
|
stdin = cell.process.stdin
|
|
292
|
-
if stdin is None or stdin
|
|
292
|
+
if stdin is None or stream_writer_is_closing(stdin):
|
|
293
293
|
return
|
|
294
294
|
stdin.write((json.dumps(payload, ensure_ascii=False) + "\n").encode("utf-8"))
|
|
295
295
|
await stdin.drain()
|
|
296
296
|
|
|
297
|
-
async def _wait_for_exec(self, cell: ExecCell, yield_time_ms: int
|
|
297
|
+
async def _wait_for_exec(self, cell: 'ExecCell', yield_time_ms: 'typing.Union[int, None]') -> 'None':
|
|
298
298
|
done_task = asyncio.create_task(cell.done_event.wait())
|
|
299
299
|
yield_task = asyncio.create_task(cell.yield_event.wait())
|
|
300
300
|
tasks = {done_task, yield_task}
|
|
@@ -314,7 +314,7 @@ class CodeModeManager:
|
|
|
314
314
|
task.cancel()
|
|
315
315
|
cell.yield_event.clear()
|
|
316
316
|
|
|
317
|
-
async def _wait_for_wait(self, cell: ExecCell, yield_time_ms: int) -> None:
|
|
317
|
+
async def _wait_for_wait(self, cell: 'ExecCell', yield_time_ms: 'int') -> 'None':
|
|
318
318
|
loop = asyncio.get_running_loop()
|
|
319
319
|
deadline = loop.time() + max(yield_time_ms, 1) / 1000.0
|
|
320
320
|
initial_count = cell.delivered_count
|
|
@@ -357,9 +357,9 @@ class CodeModeManager:
|
|
|
357
357
|
|
|
358
358
|
async def _wait_for_completion_grace(
|
|
359
359
|
self,
|
|
360
|
-
cell: ExecCell,
|
|
361
|
-
timeout_seconds: float,
|
|
362
|
-
) -> None:
|
|
360
|
+
cell: 'ExecCell',
|
|
361
|
+
timeout_seconds: 'float',
|
|
362
|
+
) -> 'None':
|
|
363
363
|
if timeout_seconds <= 0:
|
|
364
364
|
return
|
|
365
365
|
done_task = asyncio.create_task(cell.done_event.wait())
|
|
@@ -378,9 +378,9 @@ class CodeModeManager:
|
|
|
378
378
|
|
|
379
379
|
async def _snapshot_cell(
|
|
380
380
|
self,
|
|
381
|
-
cell: ExecCell,
|
|
382
|
-
max_tokens: int
|
|
383
|
-
) -> StructuredToolOutput:
|
|
381
|
+
cell: 'ExecCell',
|
|
382
|
+
max_tokens: 'typing.Union[int, None]',
|
|
383
|
+
) -> 'StructuredToolOutput':
|
|
384
384
|
if cell.process.returncode is not None and cell.reader_task is not None:
|
|
385
385
|
await cell.reader_task
|
|
386
386
|
|
|
@@ -421,13 +421,13 @@ class CodeModeManager:
|
|
|
421
421
|
|
|
422
422
|
def _truncate_content_items(
|
|
423
423
|
self,
|
|
424
|
-
items:
|
|
425
|
-
max_tokens: int
|
|
426
|
-
) ->
|
|
424
|
+
items: 'typing.List[JSONDict]',
|
|
425
|
+
max_tokens: 'typing.Union[int, None]',
|
|
426
|
+
) -> 'typing.List[JSONDict]':
|
|
427
427
|
token_budget = DEFAULT_MAX_OUTPUT_TOKENS if max_tokens is None else max_tokens
|
|
428
428
|
max_chars = max(1, token_budget) * CHARS_PER_TOKEN
|
|
429
429
|
total_chars = 0
|
|
430
|
-
truncated:
|
|
430
|
+
truncated: 'typing.List[JSONDict]' = []
|
|
431
431
|
for item in items:
|
|
432
432
|
if item.get("type") != "input_text":
|
|
433
433
|
truncated.append(item)
|
|
@@ -449,7 +449,7 @@ class CodeModeManager:
|
|
|
449
449
|
total_chars += len(text)
|
|
450
450
|
return truncated
|
|
451
451
|
|
|
452
|
-
def _status_text(self, cell: ExecCell) -> str:
|
|
452
|
+
def _status_text(self, cell: 'ExecCell') -> 'str':
|
|
453
453
|
if cell.terminated:
|
|
454
454
|
return "Script terminated"
|
|
455
455
|
if not cell.done_event.is_set():
|
|
@@ -458,7 +458,7 @@ class CodeModeManager:
|
|
|
458
458
|
return "Script failed"
|
|
459
459
|
return "Script completed"
|
|
460
460
|
|
|
461
|
-
def _parse_exec_source(self, input_text: str) -> ParsedExecSource:
|
|
461
|
+
def _parse_exec_source(self, input_text: 'str') -> 'ParsedExecSource':
|
|
462
462
|
if not input_text.strip():
|
|
463
463
|
raise ValueError(
|
|
464
464
|
"exec expects raw JavaScript source text (non-empty)."
|
|
@@ -499,13 +499,13 @@ class CodeModeManager:
|
|
|
499
499
|
max_output_tokens=max_output_tokens,
|
|
500
500
|
)
|
|
501
501
|
|
|
502
|
-
def _normalize_identifier(self, tool_name: str) -> str:
|
|
502
|
+
def _normalize_identifier(self, tool_name: 'str') -> 'str':
|
|
503
503
|
identifier = []
|
|
504
504
|
for index, char in enumerate(tool_name):
|
|
505
505
|
is_valid = (
|
|
506
506
|
char == "_"
|
|
507
507
|
or char == "$"
|
|
508
|
-
or (char
|
|
508
|
+
or (is_ascii(char) and char.isalnum() and (index != 0 or char.isalpha()))
|
|
509
509
|
)
|
|
510
510
|
if is_valid:
|
|
511
511
|
identifier.append(char)
|
|
@@ -513,7 +513,7 @@ class CodeModeManager:
|
|
|
513
513
|
identifier.append("_")
|
|
514
514
|
return "".join(identifier) or "_"
|
|
515
515
|
|
|
516
|
-
def _coerce_optional_text(self, value: JSONValue) -> str
|
|
516
|
+
def _coerce_optional_text(self, value: 'JSONValue') -> 'typing.Union[str, None]':
|
|
517
517
|
if value in (None, ""):
|
|
518
518
|
return None
|
|
519
519
|
return str(value)
|
|
@@ -10,8 +10,6 @@ Expected behavior:
|
|
|
10
10
|
`write_stdin`.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
|
|
15
13
|
from ..protocol import JSONDict, JSONValue
|
|
16
14
|
from .base_tool import BaseTool, ToolContext
|
|
17
15
|
from .unified_exec_manager import (
|
|
@@ -21,6 +19,7 @@ from .unified_exec_manager import (
|
|
|
21
19
|
UNIFIED_EXEC_OUTPUT_SCHEMA,
|
|
22
20
|
UnifiedExecManager,
|
|
23
21
|
)
|
|
22
|
+
import typing
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
class ExecCommandTool(BaseTool):
|
|
@@ -64,10 +63,10 @@ class ExecCommandTool(BaseTool):
|
|
|
64
63
|
output_schema = UNIFIED_EXEC_OUTPUT_SCHEMA
|
|
65
64
|
supports_parallel = False
|
|
66
65
|
|
|
67
|
-
def __init__(self, manager: UnifiedExecManager) -> None:
|
|
66
|
+
def __init__(self, manager: 'UnifiedExecManager') -> 'None':
|
|
68
67
|
self._manager = manager
|
|
69
68
|
|
|
70
|
-
async def run(self, context: ToolContext, args: JSONDict) -> JSONValue:
|
|
69
|
+
async def run(self, context: 'ToolContext', args: 'JSONDict') -> 'JSONValue':
|
|
71
70
|
del context
|
|
72
71
|
cmd = str(args.get("cmd", "")).strip()
|
|
73
72
|
if not cmd:
|
|
@@ -83,13 +82,13 @@ class ExecCommandTool(BaseTool):
|
|
|
83
82
|
max_output_tokens=self._optional_int(args, "max_output_tokens"),
|
|
84
83
|
)
|
|
85
84
|
|
|
86
|
-
def _optional_string(self, args: JSONDict, key: str) -> str
|
|
85
|
+
def _optional_string(self, args: 'JSONDict', key: 'str') -> 'typing.Union[str, None]':
|
|
87
86
|
value = args.get(key)
|
|
88
87
|
if value in (None, ""):
|
|
89
88
|
return None
|
|
90
89
|
return str(value)
|
|
91
90
|
|
|
92
|
-
def _optional_int(self, args: JSONDict, key: str) -> int
|
|
91
|
+
def _optional_int(self, args: 'JSONDict', key: 'str') -> 'typing.Union[int, None]':
|
|
93
92
|
value = args.get(key)
|
|
94
93
|
if value in (None, ""):
|
|
95
94
|
return None
|
pycodex/tools/exec_runtime.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const readline = require('
|
|
2
|
-
const { stdin, stdout } = require('
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
const { stdin, stdout } = require('process');
|
|
3
3
|
|
|
4
4
|
const pending = new Map();
|
|
5
5
|
let storedValues = {};
|
|
@@ -41,7 +41,7 @@ function image(value) {
|
|
|
41
41
|
send({
|
|
42
42
|
type: 'output_image',
|
|
43
43
|
image_url: value.image_url,
|
|
44
|
-
detail: value.detail
|
|
44
|
+
detail: value.detail === undefined ? null : value.detail,
|
|
45
45
|
});
|
|
46
46
|
return;
|
|
47
47
|
}
|
pycodex/tools/exec_tool.py
CHANGED
|
@@ -11,8 +11,6 @@ Expected behavior:
|
|
|
11
11
|
resumed via `wait`.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
14
|
from ..protocol import JSONValue
|
|
17
15
|
from .base_tool import BaseTool, ToolContext
|
|
18
16
|
from .code_mode_manager import CodeModeManager
|
|
@@ -41,8 +39,8 @@ class ExecTool(BaseTool):
|
|
|
41
39
|
}
|
|
42
40
|
supports_parallel = False
|
|
43
41
|
|
|
44
|
-
def __init__(self, manager: CodeModeManager) -> None:
|
|
42
|
+
def __init__(self, manager: 'CodeModeManager') -> 'None':
|
|
45
43
|
self._manager = manager
|
|
46
44
|
|
|
47
|
-
async def run(self, context: ToolContext, args: JSONValue) -> JSONValue:
|
|
45
|
+
async def run(self, context: 'ToolContext', args: 'JSONValue') -> 'JSONValue':
|
|
48
46
|
return await self._manager.exec(str(args), context)
|
pycodex/tools/grep_files_tool.py
CHANGED
|
@@ -9,8 +9,6 @@ Expected behavior:
|
|
|
9
9
|
parameters rather than delegating to a shell transcript.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
12
|
import asyncio
|
|
15
13
|
import fnmatch
|
|
16
14
|
import re
|
|
@@ -18,6 +16,7 @@ from pathlib import Path
|
|
|
18
16
|
|
|
19
17
|
from ..protocol import JSONDict, JSONValue
|
|
20
18
|
from .base_tool import BaseTool, ToolContext
|
|
19
|
+
import typing
|
|
21
20
|
|
|
22
21
|
DEFAULT_LIMIT = 100
|
|
23
22
|
MAX_LIMIT = 2000
|
|
@@ -41,10 +40,10 @@ class GrepFilesTool(BaseTool):
|
|
|
41
40
|
"required": ["pattern"],
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
def __init__(self, cwd: str
|
|
43
|
+
def __init__(self, cwd: 'typing.Union[typing.Union[str, Path], None]' = None) -> 'None':
|
|
45
44
|
self._working_directory = Path(cwd or Path.cwd()).resolve()
|
|
46
45
|
|
|
47
|
-
async def run(self, context: ToolContext, args: JSONDict) -> JSONValue:
|
|
46
|
+
async def run(self, context: 'ToolContext', args: 'JSONDict') -> 'JSONValue':
|
|
48
47
|
del context
|
|
49
48
|
pattern = str(args.get("pattern", "")).strip()
|
|
50
49
|
include = str(args.get("include", "")).strip() or None
|
|
@@ -111,13 +110,13 @@ class GrepFilesTool(BaseTool):
|
|
|
111
110
|
|
|
112
111
|
def _search_with_python(
|
|
113
112
|
self,
|
|
114
|
-
pattern: str,
|
|
115
|
-
include: str
|
|
116
|
-
search_path: Path,
|
|
117
|
-
limit: int,
|
|
118
|
-
) ->
|
|
113
|
+
pattern: 'str',
|
|
114
|
+
include: 'typing.Union[str, None]',
|
|
115
|
+
search_path: 'Path',
|
|
116
|
+
limit: 'int',
|
|
117
|
+
) -> 'typing.List[str]':
|
|
119
118
|
regex = re.compile(pattern)
|
|
120
|
-
candidates:
|
|
119
|
+
candidates: 'typing.List[Path]' = []
|
|
121
120
|
|
|
122
121
|
if search_path.is_file():
|
|
123
122
|
candidates = [search_path]
|
|
@@ -141,7 +140,7 @@ class GrepFilesTool(BaseTool):
|
|
|
141
140
|
matches.sort(key=lambda path: path.stat().st_mtime, reverse=True)
|
|
142
141
|
return [str(path) for path in matches[:limit]]
|
|
143
142
|
|
|
144
|
-
def _resolve_path(self, path_arg) -> Path:
|
|
143
|
+
def _resolve_path(self, path_arg) -> 'Path':
|
|
145
144
|
if path_arg in (None, ""):
|
|
146
145
|
return self._working_directory
|
|
147
146
|
path = Path(str(path_arg))
|
pycodex/tools/list_dir_tool.py
CHANGED
|
@@ -9,13 +9,12 @@ Expected behavior:
|
|
|
9
9
|
commands like `find` or `ls -R`.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
12
|
from collections import deque
|
|
15
13
|
from pathlib import Path
|
|
16
14
|
|
|
17
15
|
from ..protocol import JSONDict, JSONValue
|
|
18
16
|
from .base_tool import BaseTool, ToolContext
|
|
17
|
+
import typing
|
|
19
18
|
|
|
20
19
|
MAX_ENTRY_LENGTH = 500
|
|
21
20
|
INDENTATION_SPACES = 2
|
|
@@ -38,7 +37,7 @@ class ListDirTool(BaseTool):
|
|
|
38
37
|
"required": ["dir_path"],
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
async def run(self, context: ToolContext, args: JSONDict) -> JSONValue:
|
|
40
|
+
async def run(self, context: 'ToolContext', args: 'JSONDict') -> 'JSONValue':
|
|
42
41
|
del context
|
|
43
42
|
dir_path = Path(str(args.get("dir_path", "")))
|
|
44
43
|
offset = int(args.get("offset", 1))
|
|
@@ -74,8 +73,8 @@ class ListDirTool(BaseTool):
|
|
|
74
73
|
lines.append(f"More than {len(selected)} entries found")
|
|
75
74
|
return "\n".join(lines)
|
|
76
75
|
|
|
77
|
-
def _collect_entries(self, root: Path, depth: int) ->
|
|
78
|
-
entries:
|
|
76
|
+
def _collect_entries(self, root: 'Path', depth: 'int') -> 'typing.List[typing.Dict[str, object]]':
|
|
77
|
+
entries: 'typing.List[typing.Dict[str, object]]' = []
|
|
79
78
|
queue = deque([(root, Path(), depth)])
|
|
80
79
|
|
|
81
80
|
while queue:
|
|
@@ -106,7 +105,7 @@ class ListDirTool(BaseTool):
|
|
|
106
105
|
entries.sort(key=lambda entry: entry["name"])
|
|
107
106
|
return entries
|
|
108
107
|
|
|
109
|
-
def _entry_kind(self, path: Path) -> str:
|
|
108
|
+
def _entry_kind(self, path: 'Path') -> 'str':
|
|
110
109
|
if path.is_symlink():
|
|
111
110
|
return "symlink"
|
|
112
111
|
if path.is_dir():
|
|
@@ -115,14 +114,14 @@ class ListDirTool(BaseTool):
|
|
|
115
114
|
return "file"
|
|
116
115
|
return "other"
|
|
117
116
|
|
|
118
|
-
def _format_entry_name(self, path: Path) -> str:
|
|
117
|
+
def _format_entry_name(self, path: 'Path') -> 'str':
|
|
119
118
|
text = path.as_posix()
|
|
120
119
|
return text[:MAX_ENTRY_LENGTH]
|
|
121
120
|
|
|
122
|
-
def _format_component(self, name: str) -> str:
|
|
121
|
+
def _format_component(self, name: 'str') -> 'str':
|
|
123
122
|
return name[:MAX_ENTRY_LENGTH]
|
|
124
123
|
|
|
125
|
-
def _format_entry_line(self, entry:
|
|
124
|
+
def _format_entry_line(self, entry: 'typing.Dict[str, object]') -> 'str':
|
|
126
125
|
indent = " " * (int(entry["depth"]) * INDENTATION_SPACES)
|
|
127
126
|
name = str(entry["display_name"])
|
|
128
127
|
kind = str(entry["kind"])
|