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/runtime_services.py
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
1
|
|
|
3
2
|
import asyncio
|
|
4
3
|
import json
|
|
5
4
|
import random
|
|
6
|
-
from collections.abc import Awaitable, Callable
|
|
7
5
|
from dataclasses import dataclass, field
|
|
8
|
-
from typing import TYPE_CHECKING,
|
|
6
|
+
from typing import TYPE_CHECKING, Awaitable, Callable
|
|
9
7
|
|
|
8
|
+
from .compat import Literal
|
|
10
9
|
from .protocol import ConversationItem, TurnResult
|
|
11
10
|
from .utils import uuid7_string
|
|
11
|
+
import typing
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from .runtime import AgentRuntime
|
|
15
15
|
|
|
16
16
|
PlanStatus = Literal["pending", "in_progress", "completed"]
|
|
17
|
-
PlanListener = Callable[[
|
|
17
|
+
PlanListener = Callable[[typing.Dict[str, object]], None]
|
|
18
18
|
RuntimeBuilder = Callable[
|
|
19
|
-
[str
|
|
19
|
+
[typing.Union[str, None], typing.Union[str, None], typing.Tuple[ConversationItem, ...], str],
|
|
20
20
|
"AgentRuntime",
|
|
21
21
|
]
|
|
22
|
-
AsyncJSONHandler = Callable[[
|
|
22
|
+
AsyncJSONHandler = Callable[[typing.Dict[str, object]], Awaitable[typing.Union[typing.Dict[str, object], None]]]
|
|
23
23
|
|
|
24
24
|
DEFAULT_AGENT_NICKNAME_CANDIDATES = (
|
|
25
25
|
"Bacon",
|
|
@@ -114,22 +114,22 @@ DEFAULT_AGENT_NICKNAME_CANDIDATES = (
|
|
|
114
114
|
)
|
|
115
115
|
|
|
116
116
|
|
|
117
|
-
@dataclass(frozen=True,
|
|
117
|
+
@dataclass(frozen=True, )
|
|
118
118
|
class PlanItem:
|
|
119
|
-
step: str
|
|
120
|
-
status: PlanStatus
|
|
119
|
+
step: 'str'
|
|
120
|
+
status: 'PlanStatus'
|
|
121
121
|
|
|
122
122
|
|
|
123
123
|
class PlanStore:
|
|
124
|
-
def __init__(self) -> None:
|
|
125
|
-
self._explanation: str
|
|
126
|
-
self._plan:
|
|
127
|
-
self._listener: PlanListener = lambda _payload: None
|
|
124
|
+
def __init__(self) -> 'None':
|
|
125
|
+
self._explanation: 'typing.Union[str, None]' = None
|
|
126
|
+
self._plan: 'typing.Tuple[PlanItem, ...]' = ()
|
|
127
|
+
self._listener: 'PlanListener' = lambda _payload: None
|
|
128
128
|
|
|
129
|
-
def set_listener(self, listener: PlanListener
|
|
129
|
+
def set_listener(self, listener: 'typing.Union[PlanListener, None]') -> 'None':
|
|
130
130
|
self._listener = listener or (lambda _payload: None)
|
|
131
131
|
|
|
132
|
-
def update(self, explanation: str
|
|
132
|
+
def update(self, explanation: 'typing.Union[str, None]', plan: 'typing.Tuple[PlanItem, ...]') -> 'None':
|
|
133
133
|
in_progress = sum(1 for item in plan if item.status == "in_progress")
|
|
134
134
|
if in_progress > 1:
|
|
135
135
|
raise ValueError("at most one plan step can be in_progress")
|
|
@@ -145,7 +145,7 @@ class PlanStore:
|
|
|
145
145
|
}
|
|
146
146
|
)
|
|
147
147
|
|
|
148
|
-
def snapshot(self) ->
|
|
148
|
+
def snapshot(self) -> 'typing.Dict[str, object]':
|
|
149
149
|
return {
|
|
150
150
|
"explanation": self._explanation,
|
|
151
151
|
"plan": [
|
|
@@ -156,13 +156,13 @@ class PlanStore:
|
|
|
156
156
|
|
|
157
157
|
|
|
158
158
|
class RequestUserInputManager:
|
|
159
|
-
def __init__(self) -> None:
|
|
160
|
-
self._handler: AsyncJSONHandler
|
|
159
|
+
def __init__(self) -> 'None':
|
|
160
|
+
self._handler: 'typing.Union[AsyncJSONHandler, None]' = None
|
|
161
161
|
|
|
162
|
-
def set_handler(self, handler: AsyncJSONHandler
|
|
162
|
+
def set_handler(self, handler: 'typing.Union[AsyncJSONHandler, None]') -> 'None':
|
|
163
163
|
self._handler = handler
|
|
164
164
|
|
|
165
|
-
async def request(self, payload:
|
|
165
|
+
async def request(self, payload: 'typing.Dict[str, object]') -> 'typing.Union[typing.Dict[str, object], None]':
|
|
166
166
|
handler = self._handler
|
|
167
167
|
if handler is None:
|
|
168
168
|
return None
|
|
@@ -170,52 +170,52 @@ class RequestUserInputManager:
|
|
|
170
170
|
|
|
171
171
|
|
|
172
172
|
class RequestPermissionsManager:
|
|
173
|
-
def __init__(self) -> None:
|
|
174
|
-
self._handler: AsyncJSONHandler
|
|
173
|
+
def __init__(self) -> 'None':
|
|
174
|
+
self._handler: 'typing.Union[AsyncJSONHandler, None]' = None
|
|
175
175
|
|
|
176
|
-
def set_handler(self, handler: AsyncJSONHandler
|
|
176
|
+
def set_handler(self, handler: 'typing.Union[AsyncJSONHandler, None]') -> 'None':
|
|
177
177
|
self._handler = handler
|
|
178
178
|
|
|
179
|
-
async def request(self, payload:
|
|
179
|
+
async def request(self, payload: 'typing.Dict[str, object]') -> 'typing.Union[typing.Dict[str, object], None]':
|
|
180
180
|
handler = self._handler
|
|
181
181
|
if handler is None:
|
|
182
182
|
return None
|
|
183
183
|
return await handler(payload)
|
|
184
184
|
|
|
185
185
|
|
|
186
|
-
@dataclass
|
|
186
|
+
@dataclass
|
|
187
187
|
class ManagedAgent:
|
|
188
|
-
agent_id: str
|
|
189
|
-
runtime: "AgentRuntime"
|
|
190
|
-
worker_task: asyncio.Task[None]
|
|
191
|
-
nickname: str
|
|
192
|
-
state: str = "pending_init"
|
|
193
|
-
completed_message: str
|
|
194
|
-
error_message: str
|
|
195
|
-
pending_submission_ids:
|
|
188
|
+
agent_id: 'str'
|
|
189
|
+
runtime: '"AgentRuntime"'
|
|
190
|
+
worker_task: 'asyncio.Task[None]'
|
|
191
|
+
nickname: 'typing.Union[str, None]' = None
|
|
192
|
+
state: 'str' = "pending_init"
|
|
193
|
+
completed_message: 'typing.Union[str, None]' = None
|
|
194
|
+
error_message: 'typing.Union[str, None]' = None
|
|
195
|
+
pending_submission_ids: 'typing.Set[str]' = field(default_factory=set)
|
|
196
196
|
|
|
197
197
|
|
|
198
198
|
class SubAgentManager:
|
|
199
|
-
def __init__(self) -> None:
|
|
200
|
-
self._runtime_builder: RuntimeBuilder
|
|
201
|
-
self._agents:
|
|
199
|
+
def __init__(self) -> 'None':
|
|
200
|
+
self._runtime_builder: 'typing.Union[RuntimeBuilder, None]' = None
|
|
201
|
+
self._agents: 'typing.Dict[str, ManagedAgent]' = {}
|
|
202
202
|
self._condition = asyncio.Condition()
|
|
203
|
-
self._available_nicknames:
|
|
203
|
+
self._available_nicknames: 'typing.List[str]' = []
|
|
204
204
|
self._nickname_random = random.Random()
|
|
205
205
|
|
|
206
|
-
def set_runtime_builder(self, builder: RuntimeBuilder
|
|
206
|
+
def set_runtime_builder(self, builder: 'typing.Union[RuntimeBuilder, None]') -> 'None':
|
|
207
207
|
self._runtime_builder = builder
|
|
208
208
|
|
|
209
209
|
async def spawn_agent(
|
|
210
210
|
self,
|
|
211
|
-
message: str
|
|
212
|
-
items:
|
|
213
|
-
agent_type: str
|
|
214
|
-
fork_context: bool,
|
|
215
|
-
model: str
|
|
216
|
-
reasoning_effort: str
|
|
217
|
-
history:
|
|
218
|
-
) ->
|
|
211
|
+
message: 'typing.Union[str, None]',
|
|
212
|
+
items: 'typing.Union[typing.List[typing.Dict[str, object]], None]',
|
|
213
|
+
agent_type: 'typing.Union[str, None]',
|
|
214
|
+
fork_context: 'bool',
|
|
215
|
+
model: 'typing.Union[str, None]',
|
|
216
|
+
reasoning_effort: 'typing.Union[str, None]',
|
|
217
|
+
history: 'typing.Tuple[ConversationItem, ...]',
|
|
218
|
+
) -> 'typing.Dict[str, object]':
|
|
219
219
|
builder = self._runtime_builder
|
|
220
220
|
if builder is None:
|
|
221
221
|
raise RuntimeError("spawn_agent is unavailable before runtime initialization")
|
|
@@ -246,10 +246,10 @@ class SubAgentManager:
|
|
|
246
246
|
|
|
247
247
|
async def send_input(
|
|
248
248
|
self,
|
|
249
|
-
agent_id: str,
|
|
250
|
-
prompt_text: str,
|
|
251
|
-
interrupt: bool,
|
|
252
|
-
) ->
|
|
249
|
+
agent_id: 'str',
|
|
250
|
+
prompt_text: 'str',
|
|
251
|
+
interrupt: 'bool',
|
|
252
|
+
) -> 'typing.Dict[str, object]':
|
|
253
253
|
managed = self._agents.get(agent_id)
|
|
254
254
|
if managed is None:
|
|
255
255
|
raise RuntimeError(f"unknown agent: {agent_id}")
|
|
@@ -269,7 +269,7 @@ class SubAgentManager:
|
|
|
269
269
|
self._condition.notify_all()
|
|
270
270
|
return {"submission_id": submission_id}
|
|
271
271
|
|
|
272
|
-
async def resume_agent(self, agent_id: str) ->
|
|
272
|
+
async def resume_agent(self, agent_id: 'str') -> 'typing.Dict[str, object]':
|
|
273
273
|
managed = self._agents.get(agent_id)
|
|
274
274
|
if managed is None:
|
|
275
275
|
return {"status": "not_found"}
|
|
@@ -282,7 +282,7 @@ class SubAgentManager:
|
|
|
282
282
|
self._condition.notify_all()
|
|
283
283
|
return {"status": self._status_payload(managed)}
|
|
284
284
|
|
|
285
|
-
async def close_agent(self, agent_id: str) ->
|
|
285
|
+
async def close_agent(self, agent_id: 'str') -> 'typing.Dict[str, object]':
|
|
286
286
|
managed = self._agents.get(agent_id)
|
|
287
287
|
if managed is None:
|
|
288
288
|
return {"status": "not_found"}
|
|
@@ -297,7 +297,7 @@ class SubAgentManager:
|
|
|
297
297
|
self._condition.notify_all()
|
|
298
298
|
return {"status": previous_status}
|
|
299
299
|
|
|
300
|
-
def _next_nickname(self) -> str:
|
|
300
|
+
def _next_nickname(self) -> 'str':
|
|
301
301
|
if not self._available_nicknames:
|
|
302
302
|
self._available_nicknames = list(DEFAULT_AGENT_NICKNAME_CANDIDATES)
|
|
303
303
|
self._nickname_random.shuffle(self._available_nicknames)
|
|
@@ -305,9 +305,9 @@ class SubAgentManager:
|
|
|
305
305
|
|
|
306
306
|
async def wait_agents(
|
|
307
307
|
self,
|
|
308
|
-
agent_ids:
|
|
309
|
-
timeout_ms: int = 30_000,
|
|
310
|
-
) ->
|
|
308
|
+
agent_ids: 'typing.List[str]',
|
|
309
|
+
timeout_ms: 'int' = 30_000,
|
|
310
|
+
) -> 'typing.Dict[str, object]':
|
|
311
311
|
timeout_seconds = max(timeout_ms, 1) / 1000.0
|
|
312
312
|
loop = asyncio.get_running_loop()
|
|
313
313
|
deadline = loop.time() + timeout_seconds
|
|
@@ -332,10 +332,10 @@ class SubAgentManager:
|
|
|
332
332
|
|
|
333
333
|
async def _track_submission(
|
|
334
334
|
self,
|
|
335
|
-
managed: ManagedAgent,
|
|
336
|
-
submission_id: str,
|
|
337
|
-
future: asyncio.Future[TurnResult
|
|
338
|
-
) -> None:
|
|
335
|
+
managed: 'ManagedAgent',
|
|
336
|
+
submission_id: 'str',
|
|
337
|
+
future: 'asyncio.Future[typing.Union[TurnResult, None]]',
|
|
338
|
+
) -> 'None':
|
|
339
339
|
try:
|
|
340
340
|
result = await future
|
|
341
341
|
except Exception as exc: # pragma: no cover - background safety
|
|
@@ -354,10 +354,10 @@ class SubAgentManager:
|
|
|
354
354
|
|
|
355
355
|
def _compose_prompt(
|
|
356
356
|
self,
|
|
357
|
-
message: str
|
|
358
|
-
items:
|
|
359
|
-
) -> str:
|
|
360
|
-
parts:
|
|
357
|
+
message: 'typing.Union[str, None]',
|
|
358
|
+
items: 'typing.Union[typing.List[typing.Dict[str, object]], None]',
|
|
359
|
+
) -> 'str':
|
|
360
|
+
parts: 'typing.List[str]' = []
|
|
361
361
|
if message:
|
|
362
362
|
parts.append(message.strip())
|
|
363
363
|
for item in items or []:
|
|
@@ -374,7 +374,7 @@ class SubAgentManager:
|
|
|
374
374
|
parts.append(json.dumps(item, ensure_ascii=False))
|
|
375
375
|
return "\n\n".join(part for part in parts if part)
|
|
376
376
|
|
|
377
|
-
def _status_payload(self, managed: ManagedAgent
|
|
377
|
+
def _status_payload(self, managed: 'typing.Union[ManagedAgent, None]') -> 'object':
|
|
378
378
|
if managed is None:
|
|
379
379
|
return "not_found"
|
|
380
380
|
if managed.error_message is not None:
|
|
@@ -385,7 +385,7 @@ class SubAgentManager:
|
|
|
385
385
|
return managed.state
|
|
386
386
|
return managed.state
|
|
387
387
|
|
|
388
|
-
def _is_final_status(self, status: object) -> bool:
|
|
388
|
+
def _is_final_status(self, status: 'object') -> 'bool':
|
|
389
389
|
if isinstance(status, str):
|
|
390
390
|
return status in {"shutdown", "not_found"}
|
|
391
391
|
if isinstance(status, dict):
|
|
@@ -394,19 +394,19 @@ class SubAgentManager:
|
|
|
394
394
|
|
|
395
395
|
|
|
396
396
|
class RuntimeEnvironment:
|
|
397
|
-
def __init__(self) -> None:
|
|
397
|
+
def __init__(self) -> 'None':
|
|
398
398
|
self.plan_store = PlanStore()
|
|
399
399
|
self.subagent_manager = SubAgentManager()
|
|
400
400
|
self.request_user_input_manager = RequestUserInputManager()
|
|
401
401
|
self.request_permissions_manager = RequestPermissionsManager()
|
|
402
402
|
|
|
403
403
|
|
|
404
|
-
def create_runtime_environment() -> RuntimeEnvironment:
|
|
404
|
+
def create_runtime_environment() -> 'RuntimeEnvironment':
|
|
405
405
|
return RuntimeEnvironment()
|
|
406
406
|
|
|
407
407
|
|
|
408
408
|
_RUNTIME_ENV = create_runtime_environment()
|
|
409
409
|
|
|
410
410
|
|
|
411
|
-
def get_runtime_environment() -> RuntimeEnvironment:
|
|
411
|
+
def get_runtime_environment() -> 'RuntimeEnvironment':
|
|
412
412
|
return _RUNTIME_ENV
|
|
@@ -11,8 +11,6 @@ Expected behavior:
|
|
|
11
11
|
same success/error text shape Codex expects.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
14
|
from dataclasses import dataclass
|
|
17
15
|
from pathlib import Path
|
|
18
16
|
|
|
@@ -20,6 +18,7 @@ from loguru import logger
|
|
|
20
18
|
|
|
21
19
|
from ..protocol import JSONValue
|
|
22
20
|
from .base_tool import BaseTool, ToolContext
|
|
21
|
+
import typing
|
|
23
22
|
|
|
24
23
|
APPLY_PATCH_LARK_GRAMMAR = """start: begin_patch hunk+ end_patch
|
|
25
24
|
begin_patch: \"*** Begin Patch\" LF
|
|
@@ -47,28 +46,28 @@ class ApplyPatchError(RuntimeError):
|
|
|
47
46
|
pass
|
|
48
47
|
|
|
49
48
|
|
|
50
|
-
@dataclass(frozen=True,
|
|
49
|
+
@dataclass(frozen=True, )
|
|
51
50
|
class _AddFileOp:
|
|
52
|
-
path: str
|
|
53
|
-
content: str
|
|
51
|
+
path: 'str'
|
|
52
|
+
content: 'str'
|
|
54
53
|
|
|
55
54
|
|
|
56
|
-
@dataclass(frozen=True,
|
|
55
|
+
@dataclass(frozen=True, )
|
|
57
56
|
class _DeleteFileOp:
|
|
58
|
-
path: str
|
|
57
|
+
path: 'str'
|
|
59
58
|
|
|
60
59
|
|
|
61
|
-
@dataclass(frozen=True,
|
|
60
|
+
@dataclass(frozen=True, )
|
|
62
61
|
class _UpdateSection:
|
|
63
|
-
lines:
|
|
64
|
-
anchor_end_of_file: bool = False
|
|
62
|
+
lines: 'typing.Tuple[str, ...]'
|
|
63
|
+
anchor_end_of_file: 'bool' = False
|
|
65
64
|
|
|
66
65
|
|
|
67
|
-
@dataclass(frozen=True,
|
|
66
|
+
@dataclass(frozen=True, )
|
|
68
67
|
class _UpdateFileOp:
|
|
69
|
-
path: str
|
|
70
|
-
move_to: str
|
|
71
|
-
sections:
|
|
68
|
+
path: 'str'
|
|
69
|
+
move_to: 'typing.Union[str, None]'
|
|
70
|
+
sections: 'typing.Tuple[_UpdateSection, ...]'
|
|
72
71
|
|
|
73
72
|
|
|
74
73
|
class ApplyPatchTool(BaseTool):
|
|
@@ -85,10 +84,10 @@ class ApplyPatchTool(BaseTool):
|
|
|
85
84
|
}
|
|
86
85
|
supports_parallel = False
|
|
87
86
|
|
|
88
|
-
def __init__(self, cwd: str
|
|
87
|
+
def __init__(self, cwd: 'typing.Union[typing.Union[str, Path], None]' = None) -> 'None':
|
|
89
88
|
self._workspace_root = Path(cwd or Path.cwd()).resolve()
|
|
90
89
|
|
|
91
|
-
async def run(self, context: ToolContext, args: JSONValue) -> JSONValue:
|
|
90
|
+
async def run(self, context: 'ToolContext', args: 'JSONValue') -> 'JSONValue':
|
|
92
91
|
del context
|
|
93
92
|
patch_text = str(args)
|
|
94
93
|
logger.debug("apply_patch workspace={} bytes={}", self._workspace_root, len(patch_text))
|
|
@@ -98,7 +97,7 @@ class ApplyPatchTool(BaseTool):
|
|
|
98
97
|
except ApplyPatchError as exc:
|
|
99
98
|
return self._format_result(str(exc), exit_code=1)
|
|
100
99
|
|
|
101
|
-
def _parse_patch(self, patch_text: str) ->
|
|
100
|
+
def _parse_patch(self, patch_text: 'str') -> 'typing.List[typing.Union[typing.Union[_AddFileOp, _DeleteFileOp], _UpdateFileOp]]':
|
|
102
101
|
lines = patch_text.splitlines()
|
|
103
102
|
if not lines:
|
|
104
103
|
raise ApplyPatchError("patch rejected: empty patch")
|
|
@@ -107,7 +106,7 @@ class ApplyPatchTool(BaseTool):
|
|
|
107
106
|
"apply_patch verification failed: missing '*** Begin Patch' header"
|
|
108
107
|
)
|
|
109
108
|
|
|
110
|
-
operations:
|
|
109
|
+
operations: 'typing.List[typing.Union[typing.Union[_AddFileOp, _DeleteFileOp], _UpdateFileOp]]' = []
|
|
111
110
|
index = 1
|
|
112
111
|
while index < len(lines):
|
|
113
112
|
line = lines[index]
|
|
@@ -122,9 +121,9 @@ class ApplyPatchTool(BaseTool):
|
|
|
122
121
|
return operations
|
|
123
122
|
|
|
124
123
|
if line.startswith("*** Add File: "):
|
|
125
|
-
path = line
|
|
124
|
+
path = line[len("*** Add File: ") :]
|
|
126
125
|
index += 1
|
|
127
|
-
content_lines:
|
|
126
|
+
content_lines: 'typing.List[str]' = []
|
|
128
127
|
while index < len(lines) and not lines[index].startswith("*** "):
|
|
129
128
|
entry = lines[index]
|
|
130
129
|
if not entry.startswith("+"):
|
|
@@ -141,21 +140,21 @@ class ApplyPatchTool(BaseTool):
|
|
|
141
140
|
continue
|
|
142
141
|
|
|
143
142
|
if line.startswith("*** Delete File: "):
|
|
144
|
-
path = line
|
|
143
|
+
path = line[len("*** Delete File: ") :]
|
|
145
144
|
operations.append(_DeleteFileOp(path=path))
|
|
146
145
|
index += 1
|
|
147
146
|
continue
|
|
148
147
|
|
|
149
148
|
if line.startswith("*** Update File: "):
|
|
150
|
-
path = line
|
|
149
|
+
path = line[len("*** Update File: ") :]
|
|
151
150
|
index += 1
|
|
152
151
|
move_to = None
|
|
153
152
|
if index < len(lines) and lines[index].startswith("*** Move to: "):
|
|
154
|
-
move_to = lines[index]
|
|
153
|
+
move_to = lines[index][len("*** Move to: ") :]
|
|
155
154
|
index += 1
|
|
156
155
|
|
|
157
|
-
sections:
|
|
158
|
-
current_lines:
|
|
156
|
+
sections: 'typing.List[_UpdateSection]' = []
|
|
157
|
+
current_lines: 'typing.List[str]' = []
|
|
159
158
|
saw_hunk_header = False
|
|
160
159
|
anchor_end_of_file = False
|
|
161
160
|
while index < len(lines):
|
|
@@ -221,10 +220,10 @@ class ApplyPatchTool(BaseTool):
|
|
|
221
220
|
|
|
222
221
|
def _apply_operations(
|
|
223
222
|
self,
|
|
224
|
-
operations:
|
|
225
|
-
) -> str:
|
|
226
|
-
preview:
|
|
227
|
-
summaries:
|
|
223
|
+
operations: 'typing.List[typing.Union[typing.Union[_AddFileOp, _DeleteFileOp], _UpdateFileOp]]',
|
|
224
|
+
) -> 'str':
|
|
225
|
+
preview: 'typing.Dict[Path, typing.Union[str, None]]' = {}
|
|
226
|
+
summaries: 'typing.Dict[Path, str]' = {}
|
|
228
227
|
|
|
229
228
|
for operation in operations:
|
|
230
229
|
if isinstance(operation, _AddFileOp):
|
|
@@ -254,7 +253,7 @@ class ApplyPatchTool(BaseTool):
|
|
|
254
253
|
self._write_preview(preview)
|
|
255
254
|
return self._format_success(summaries)
|
|
256
255
|
|
|
257
|
-
def _read_preview_file(self, path: Path, preview:
|
|
256
|
+
def _read_preview_file(self, path: 'Path', preview: 'typing.Dict[Path, typing.Union[str, None]]') -> 'str':
|
|
258
257
|
if path in preview:
|
|
259
258
|
content = preview[path]
|
|
260
259
|
if content is None:
|
|
@@ -271,10 +270,10 @@ class ApplyPatchTool(BaseTool):
|
|
|
271
270
|
|
|
272
271
|
def _apply_update(
|
|
273
272
|
self,
|
|
274
|
-
path: Path,
|
|
275
|
-
original_text: str,
|
|
276
|
-
sections:
|
|
277
|
-
) -> str:
|
|
273
|
+
path: 'Path',
|
|
274
|
+
original_text: 'str',
|
|
275
|
+
sections: 'typing.Tuple[_UpdateSection, ...]',
|
|
276
|
+
) -> 'str':
|
|
278
277
|
lines = original_text.splitlines()
|
|
279
278
|
cursor = 0
|
|
280
279
|
for section in sections:
|
|
@@ -295,11 +294,11 @@ class ApplyPatchTool(BaseTool):
|
|
|
295
294
|
|
|
296
295
|
def _find_match(
|
|
297
296
|
self,
|
|
298
|
-
lines:
|
|
299
|
-
old_block:
|
|
300
|
-
cursor: int,
|
|
301
|
-
anchor_end_of_file: bool,
|
|
302
|
-
) -> int
|
|
297
|
+
lines: 'typing.List[str]',
|
|
298
|
+
old_block: 'typing.List[str]',
|
|
299
|
+
cursor: 'int',
|
|
300
|
+
anchor_end_of_file: 'bool',
|
|
301
|
+
) -> 'typing.Union[int, None]':
|
|
303
302
|
if anchor_end_of_file:
|
|
304
303
|
start = len(lines) - len(old_block)
|
|
305
304
|
if start >= 0 and lines[start : start + len(old_block)] == old_block:
|
|
@@ -318,7 +317,7 @@ class ApplyPatchTool(BaseTool):
|
|
|
318
317
|
return start
|
|
319
318
|
return None
|
|
320
319
|
|
|
321
|
-
def _write_preview(self, preview:
|
|
320
|
+
def _write_preview(self, preview: 'typing.Dict[Path, typing.Union[str, None]]') -> 'None':
|
|
322
321
|
for path, content in preview.items():
|
|
323
322
|
if content is None:
|
|
324
323
|
if path.exists():
|
|
@@ -327,17 +326,17 @@ class ApplyPatchTool(BaseTool):
|
|
|
327
326
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
328
327
|
path.write_text(content, encoding="utf-8")
|
|
329
328
|
|
|
330
|
-
def _format_success(self, summaries:
|
|
329
|
+
def _format_success(self, summaries: 'typing.Dict[Path, str]') -> 'str':
|
|
331
330
|
buckets = {"A": [], "M": [], "D": []}
|
|
332
331
|
for path, status in summaries.items():
|
|
333
332
|
buckets[status].append(path.relative_to(self._workspace_root).as_posix())
|
|
334
|
-
lines = ["Success
|
|
333
|
+
lines = ["Success:"]
|
|
335
334
|
for status in ("A", "M", "D"):
|
|
336
335
|
for rel_path in sorted(buckets[status]):
|
|
337
336
|
lines.append(f"{status} {rel_path}")
|
|
338
|
-
return "
|
|
337
|
+
return " ".join(lines) + "\n"
|
|
339
338
|
|
|
340
|
-
def _format_result(self, output: str, exit_code: int) -> str:
|
|
339
|
+
def _format_result(self, output: 'str', exit_code: 'int') -> 'str':
|
|
341
340
|
return (
|
|
342
341
|
f"Exit code: {exit_code}\n"
|
|
343
342
|
"Wall time: 0 seconds\n"
|
|
@@ -345,7 +344,7 @@ class ApplyPatchTool(BaseTool):
|
|
|
345
344
|
f"{output}"
|
|
346
345
|
)
|
|
347
346
|
|
|
348
|
-
def _resolve_workspace_path(self, path_text: str) -> Path:
|
|
347
|
+
def _resolve_workspace_path(self, path_text: 'str') -> 'Path':
|
|
349
348
|
path = Path(path_text)
|
|
350
349
|
resolved = path if path.is_absolute() else self._workspace_root / path
|
|
351
350
|
resolved = resolved.resolve()
|
|
@@ -357,7 +356,7 @@ class ApplyPatchTool(BaseTool):
|
|
|
357
356
|
) from exc
|
|
358
357
|
return resolved
|
|
359
358
|
|
|
360
|
-
def _join_lines(self, lines:
|
|
359
|
+
def _join_lines(self, lines: 'typing.List[str]') -> 'str':
|
|
361
360
|
if not lines:
|
|
362
361
|
return ""
|
|
363
362
|
return "\n".join(lines) + "\n"
|