python-codex 0.2.7__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycodex/__init__.py +14 -14
- pycodex/agent.py +465 -499
- pycodex/bootstrap.py +417 -0
- pycodex/cli.py +236 -510
- pycodex/compat.py +19 -5
- pycodex/context.py +222 -212
- pycodex/doctor.py +52 -48
- pycodex/events.py +857 -0
- pycodex/feishu_card.py +217 -163
- pycodex/feishu_link.py +43 -83
- pycodex/model.py +324 -253
- pycodex/model_metadata.py +19 -7
- pycodex/portable.py +76 -45
- pycodex/portable_server.py +32 -24
- pycodex/prompts/models.json +245 -983
- pycodex/protocol.py +177 -137
- pycodex/runtime.py +579 -176
- pycodex/runtime_services.py +204 -157
- pycodex/tools/__init__.py +1 -1
- pycodex/tools/apply_patch_tool.py +69 -48
- pycodex/tools/base_tool.py +89 -42
- pycodex/tools/clock_tool.py +58 -25
- pycodex/tools/close_agent_tool.py +2 -2
- pycodex/tools/code_mode_manager.py +77 -64
- pycodex/tools/exec_command_tool.py +26 -11
- pycodex/tools/exec_tool.py +4 -4
- pycodex/tools/grep_files_tool.py +12 -10
- pycodex/tools/ipython_tool.py +10 -13
- pycodex/tools/list_dir_tool.py +13 -9
- pycodex/tools/read_file_tool.py +29 -17
- pycodex/tools/request_permissions_tool.py +15 -5
- pycodex/tools/request_user_input_tool.py +13 -104
- pycodex/tools/resume_agent_tool.py +2 -2
- pycodex/tools/send_input_tool.py +11 -8
- pycodex/tools/shell_command_tool.py +7 -5
- pycodex/tools/shell_tool.py +7 -5
- pycodex/tools/spawn_agent_tool.py +7 -4
- pycodex/tools/unified_exec_manager.py +102 -69
- pycodex/tools/update_plan_tool.py +8 -5
- pycodex/tools/view_image_tool.py +7 -5
- pycodex/tools/wait_agent_tool.py +27 -4
- pycodex/tools/wait_tool.py +5 -4
- pycodex/tools/web_search_tool.py +4 -2
- pycodex/tools/write_stdin_tool.py +12 -11
- pycodex/utils/__init__.py +2 -17
- pycodex/utils/compactor.py +41 -72
- pycodex/utils/debug.py +2 -2
- pycodex/utils/dotenv.py +6 -7
- pycodex/utils/event_helpers.py +190 -0
- pycodex/utils/get_env.py +27 -70
- pycodex/{image_utils.py → utils/image_utils.py} +8 -11
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/session_persist.py +217 -163
- pycodex/utils/truncation.py +21 -45
- python_codex-0.3.0.dist-info/METADATA +704 -0
- python_codex-0.3.0.dist-info/RECORD +90 -0
- responses_server/__init__.py +1 -5
- responses_server/__main__.py +0 -1
- responses_server/app.py +36 -31
- responses_server/config.py +23 -23
- responses_server/messages_api.py +51 -53
- responses_server/payload_processors.py +25 -20
- responses_server/server.py +11 -11
- responses_server/session_store.py +14 -11
- responses_server/stream_router.py +101 -98
- responses_server/tools/custom_adapter.py +17 -16
- responses_server/tools/web_search.py +39 -36
- responses_server/trajectory_dump.py +36 -14
- workspace_server/__main__.py +0 -1
- workspace_server/app.py +461 -375
- workspace_server/workspace.html +852 -228
- workspace_server/workspaces.html +94 -95
- workspace_server/workspaces.py +137 -79
- pycodex/collaboration.py +0 -20
- pycodex/interactive_session.py +0 -415
- pycodex/prompts/collaboration_default.md +0 -11
- pycodex/prompts/collaboration_plan.md +0 -128
- pycodex/utils/toolcall_visualize.py +0 -713
- pycodex/utils/visualize.py +0 -560
- python_codex-0.2.7.dist-info/METADATA +0 -455
- python_codex-0.2.7.dist-info/RECORD +0 -93
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/WHEEL +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/entry_points.txt +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/licenses/LICENSE +0 -0
pycodex/runtime_services.py
CHANGED
|
@@ -1,25 +1,45 @@
|
|
|
1
|
-
|
|
2
1
|
import asyncio
|
|
3
2
|
import json
|
|
4
3
|
import random
|
|
5
|
-
|
|
4
|
+
import typing
|
|
5
|
+
from dataclasses import dataclass
|
|
6
6
|
from typing import TYPE_CHECKING, Awaitable, Callable
|
|
7
7
|
|
|
8
8
|
from .compat import Literal
|
|
9
|
-
from .
|
|
9
|
+
from .events import (
|
|
10
|
+
CompactCompletedEvent,
|
|
11
|
+
CompactFailedEvent,
|
|
12
|
+
CompactStartedEvent,
|
|
13
|
+
Event,
|
|
14
|
+
TurnCompletedEvent,
|
|
15
|
+
TurnFailedEvent,
|
|
16
|
+
TurnInterruptedEvent,
|
|
17
|
+
TurnStartedEvent,
|
|
18
|
+
)
|
|
19
|
+
from .protocol import ConversationItem, ToolCall, ToolResult
|
|
10
20
|
from .utils import uuid7_string
|
|
11
|
-
import typing
|
|
12
21
|
|
|
13
22
|
if TYPE_CHECKING:
|
|
14
|
-
from .runtime import
|
|
23
|
+
from .runtime import AgentRuntime
|
|
15
24
|
|
|
16
25
|
PlanStatus = Literal["pending", "in_progress", "completed"]
|
|
26
|
+
AgentStatus = typing.Union[
|
|
27
|
+
Literal["pending_init", "running", "shutdown", "not_found"],
|
|
28
|
+
typing.Dict[str, typing.Union[str, None]],
|
|
29
|
+
]
|
|
17
30
|
PlanListener = Callable[[typing.Dict[str, object]], None]
|
|
18
|
-
|
|
19
|
-
[
|
|
20
|
-
|
|
31
|
+
AgentRuntimeBuilder = Callable[
|
|
32
|
+
[
|
|
33
|
+
typing.Union[str, None],
|
|
34
|
+
typing.Union[str, None],
|
|
35
|
+
typing.Tuple[ConversationItem, ...],
|
|
36
|
+
str,
|
|
37
|
+
],
|
|
38
|
+
"AgentRuntime",
|
|
39
|
+
]
|
|
40
|
+
AsyncJSONHandler = Callable[
|
|
41
|
+
[typing.Dict[str, object]], Awaitable[typing.Union[typing.Dict[str, object], None]]
|
|
21
42
|
]
|
|
22
|
-
AsyncJSONHandler = Callable[[typing.Dict[str, object]], Awaitable[typing.Union[typing.Dict[str, object], None]]]
|
|
23
43
|
|
|
24
44
|
DEFAULT_AGENT_NICKNAME_CANDIDATES = (
|
|
25
45
|
"Bacon",
|
|
@@ -114,22 +134,28 @@ DEFAULT_AGENT_NICKNAME_CANDIDATES = (
|
|
|
114
134
|
)
|
|
115
135
|
|
|
116
136
|
|
|
117
|
-
@dataclass(
|
|
137
|
+
@dataclass(
|
|
138
|
+
frozen=True,
|
|
139
|
+
)
|
|
118
140
|
class PlanItem:
|
|
119
|
-
step:
|
|
120
|
-
status:
|
|
141
|
+
step: "str"
|
|
142
|
+
status: "PlanStatus"
|
|
121
143
|
|
|
122
144
|
|
|
123
145
|
class PlanStore:
|
|
124
|
-
def __init__(self) ->
|
|
125
|
-
self._explanation:
|
|
126
|
-
self._plan:
|
|
127
|
-
self._listener:
|
|
146
|
+
def __init__(self) -> "None":
|
|
147
|
+
self._explanation: "typing.Union[str, None]" = None
|
|
148
|
+
self._plan: "typing.Tuple[PlanItem, ...]" = ()
|
|
149
|
+
self._listener: "PlanListener" = lambda _payload: None
|
|
128
150
|
|
|
129
|
-
def set_listener(self, listener:
|
|
151
|
+
def set_listener(self, listener: "typing.Union[PlanListener, None]") -> "None":
|
|
130
152
|
self._listener = listener or (lambda _payload: None)
|
|
131
153
|
|
|
132
|
-
def update(
|
|
154
|
+
def update(
|
|
155
|
+
self,
|
|
156
|
+
explanation: "typing.Union[str, None]",
|
|
157
|
+
plan: "typing.Tuple[PlanItem, ...]",
|
|
158
|
+
) -> "None":
|
|
133
159
|
in_progress = sum(1 for item in plan if item.status == "in_progress")
|
|
134
160
|
if in_progress > 1:
|
|
135
161
|
raise ValueError("at most one plan step can be in_progress")
|
|
@@ -139,30 +165,28 @@ class PlanStore:
|
|
|
139
165
|
{
|
|
140
166
|
"explanation": explanation,
|
|
141
167
|
"plan": [
|
|
142
|
-
{"step": item.step, "status": item.status}
|
|
143
|
-
for item in self._plan
|
|
168
|
+
{"step": item.step, "status": item.status} for item in self._plan
|
|
144
169
|
],
|
|
145
170
|
}
|
|
146
171
|
)
|
|
147
172
|
|
|
148
|
-
def snapshot(self) ->
|
|
173
|
+
def snapshot(self) -> "typing.Dict[str, object]":
|
|
149
174
|
return {
|
|
150
175
|
"explanation": self._explanation,
|
|
151
|
-
"plan": [
|
|
152
|
-
{"step": item.step, "status": item.status}
|
|
153
|
-
for item in self._plan
|
|
154
|
-
],
|
|
176
|
+
"plan": [{"step": item.step, "status": item.status} for item in self._plan],
|
|
155
177
|
}
|
|
156
178
|
|
|
157
179
|
|
|
158
180
|
class RequestUserInputManager:
|
|
159
|
-
def __init__(self) ->
|
|
160
|
-
self._handler:
|
|
181
|
+
def __init__(self) -> "None":
|
|
182
|
+
self._handler: "typing.Union[AsyncJSONHandler, None]" = None
|
|
161
183
|
|
|
162
|
-
def set_handler(self, handler:
|
|
184
|
+
def set_handler(self, handler: "typing.Union[AsyncJSONHandler, None]") -> "None":
|
|
163
185
|
self._handler = handler
|
|
164
186
|
|
|
165
|
-
async def request(
|
|
187
|
+
async def request(
|
|
188
|
+
self, payload: "typing.Dict[str, object]"
|
|
189
|
+
) -> "typing.Union[typing.Dict[str, object], None]":
|
|
166
190
|
handler = self._handler
|
|
167
191
|
if handler is None:
|
|
168
192
|
return None
|
|
@@ -170,13 +194,15 @@ class RequestUserInputManager:
|
|
|
170
194
|
|
|
171
195
|
|
|
172
196
|
class RequestPermissionsManager:
|
|
173
|
-
def __init__(self) ->
|
|
174
|
-
self._handler:
|
|
197
|
+
def __init__(self) -> "None":
|
|
198
|
+
self._handler: "typing.Union[AsyncJSONHandler, None]" = None
|
|
175
199
|
|
|
176
|
-
def set_handler(self, handler:
|
|
200
|
+
def set_handler(self, handler: "typing.Union[AsyncJSONHandler, None]") -> "None":
|
|
177
201
|
self._handler = handler
|
|
178
202
|
|
|
179
|
-
async def request(
|
|
203
|
+
async def request(
|
|
204
|
+
self, payload: "typing.Dict[str, object]"
|
|
205
|
+
) -> "typing.Union[typing.Dict[str, object], None]":
|
|
180
206
|
handler = self._handler
|
|
181
207
|
if handler is None:
|
|
182
208
|
return None
|
|
@@ -185,53 +211,58 @@ class RequestPermissionsManager:
|
|
|
185
211
|
|
|
186
212
|
@dataclass
|
|
187
213
|
class ManagedAgent:
|
|
188
|
-
agent_id:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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)
|
|
214
|
+
agent_id: "str"
|
|
215
|
+
runtime: '"AgentRuntime"'
|
|
216
|
+
nickname: "typing.Union[str, None]" = None
|
|
217
|
+
last_status: "AgentStatus" = "pending_init"
|
|
196
218
|
|
|
197
219
|
|
|
198
220
|
class SubAgentManager:
|
|
199
|
-
def __init__(self) ->
|
|
200
|
-
self.
|
|
201
|
-
self._agents:
|
|
202
|
-
self._condition =
|
|
203
|
-
self._available_nicknames:
|
|
221
|
+
def __init__(self) -> "None":
|
|
222
|
+
self._runtime_builder: "typing.Union[AgentRuntimeBuilder, None]" = None
|
|
223
|
+
self._agents: "typing.Dict[str, ManagedAgent]" = {}
|
|
224
|
+
self._condition = None
|
|
225
|
+
self._available_nicknames: "typing.List[str]" = []
|
|
204
226
|
self._nickname_random = random.Random()
|
|
205
227
|
|
|
206
|
-
def
|
|
207
|
-
self.
|
|
228
|
+
def _get_condition(self):
|
|
229
|
+
if self._condition is None:
|
|
230
|
+
self._condition = asyncio.Condition()
|
|
231
|
+
return self._condition
|
|
232
|
+
|
|
233
|
+
def set_runtime_builder(
|
|
234
|
+
self, builder: "typing.Union[AgentRuntimeBuilder, None]"
|
|
235
|
+
) -> "None":
|
|
236
|
+
self._runtime_builder = builder
|
|
208
237
|
|
|
209
238
|
async def spawn_agent(
|
|
210
239
|
self,
|
|
211
|
-
message:
|
|
212
|
-
items:
|
|
213
|
-
agent_type:
|
|
214
|
-
fork_context:
|
|
215
|
-
model:
|
|
216
|
-
reasoning_effort:
|
|
217
|
-
history:
|
|
218
|
-
) ->
|
|
219
|
-
builder = self.
|
|
240
|
+
message: "typing.Union[str, None]",
|
|
241
|
+
items: "typing.Union[typing.List[typing.Dict[str, object]], None]",
|
|
242
|
+
agent_type: "typing.Union[str, None]",
|
|
243
|
+
fork_context: "bool",
|
|
244
|
+
model: "typing.Union[str, None]",
|
|
245
|
+
reasoning_effort: "typing.Union[str, None]",
|
|
246
|
+
history: "typing.Tuple[ConversationItem, ...]",
|
|
247
|
+
) -> "typing.Dict[str, object]":
|
|
248
|
+
builder = self._runtime_builder
|
|
220
249
|
if builder is None:
|
|
221
|
-
raise RuntimeError(
|
|
250
|
+
raise RuntimeError(
|
|
251
|
+
"spawn_agent is unavailable before runtime initialization"
|
|
252
|
+
)
|
|
222
253
|
|
|
223
254
|
initial_history = _fork_context_history(history) if fork_context else ()
|
|
224
255
|
agent_id = uuid7_string()
|
|
225
|
-
|
|
226
|
-
|
|
256
|
+
runtime = builder(model, reasoning_effort, initial_history, agent_id)
|
|
257
|
+
await runtime.start()
|
|
227
258
|
nickname = self._next_nickname()
|
|
228
259
|
managed = ManagedAgent(
|
|
229
260
|
agent_id=agent_id,
|
|
230
|
-
|
|
231
|
-
worker_task=worker_task,
|
|
261
|
+
runtime=runtime,
|
|
232
262
|
nickname=nickname,
|
|
233
263
|
)
|
|
234
|
-
|
|
264
|
+
runtime.event_handler = lambda event: self._handle_agent_event(managed, event)
|
|
265
|
+
async with self._get_condition():
|
|
235
266
|
self._agents[agent_id] = managed
|
|
236
267
|
self._condition.notify_all()
|
|
237
268
|
|
|
@@ -246,58 +277,65 @@ class SubAgentManager:
|
|
|
246
277
|
|
|
247
278
|
async def send_input(
|
|
248
279
|
self,
|
|
249
|
-
agent_id:
|
|
250
|
-
prompt_text:
|
|
251
|
-
interrupt:
|
|
252
|
-
) ->
|
|
280
|
+
agent_id: "str",
|
|
281
|
+
prompt_text: "str",
|
|
282
|
+
interrupt: "bool",
|
|
283
|
+
) -> "typing.Dict[str, object]":
|
|
253
284
|
managed = self._agents.get(agent_id)
|
|
254
285
|
if managed is None:
|
|
255
286
|
raise RuntimeError(f"unknown agent: {agent_id}")
|
|
256
|
-
if managed.
|
|
287
|
+
if managed.runtime.agent.is_shutdown:
|
|
257
288
|
raise RuntimeError(f"agent is shutdown: {agent_id}")
|
|
258
289
|
|
|
259
|
-
submission_id, future = await managed.
|
|
290
|
+
submission_id, future = await managed.runtime.enqueue_user_turn(
|
|
260
291
|
prompt_text,
|
|
261
292
|
queue="steer" if interrupt else "enqueue",
|
|
262
293
|
)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
managed.error_message = None
|
|
266
|
-
managed.pending_submission_ids.add(submission_id)
|
|
267
|
-
asyncio.create_task(self._track_submission(managed, submission_id, future))
|
|
268
|
-
async with self._condition:
|
|
294
|
+
future.add_done_callback(self._submission_finished)
|
|
295
|
+
async with self._get_condition():
|
|
269
296
|
self._condition.notify_all()
|
|
270
297
|
return {"submission_id": submission_id}
|
|
271
298
|
|
|
272
|
-
async def resume_agent(self, agent_id:
|
|
299
|
+
async def resume_agent(self, agent_id: "str") -> "typing.Dict[str, object]":
|
|
273
300
|
managed = self._agents.get(agent_id)
|
|
274
301
|
if managed is None:
|
|
275
302
|
return {"status": "not_found"}
|
|
276
|
-
if managed.
|
|
277
|
-
managed.
|
|
278
|
-
managed.
|
|
279
|
-
managed.
|
|
280
|
-
|
|
281
|
-
async with self._condition:
|
|
303
|
+
if managed.runtime.agent.is_shutdown:
|
|
304
|
+
managed.runtime.resume()
|
|
305
|
+
managed.last_status = "pending_init"
|
|
306
|
+
await managed.runtime.start()
|
|
307
|
+
async with self._get_condition():
|
|
282
308
|
self._condition.notify_all()
|
|
283
309
|
return {"status": self._status_payload(managed)}
|
|
284
310
|
|
|
285
|
-
async def close_agent(self, agent_id:
|
|
311
|
+
async def close_agent(self, agent_id: "str") -> "typing.Dict[str, object]":
|
|
286
312
|
managed = self._agents.get(agent_id)
|
|
287
313
|
if managed is None:
|
|
288
314
|
return {"previous_status": "not_found"}
|
|
289
315
|
previous_status = self._status_payload(managed)
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
await managed.queue.shutdown()
|
|
293
|
-
await managed.worker_task
|
|
294
|
-
managed.state = "shutdown"
|
|
295
|
-
managed.pending_submission_ids.clear()
|
|
296
|
-
async with self._condition:
|
|
316
|
+
await managed.runtime.close()
|
|
317
|
+
async with self._get_condition():
|
|
297
318
|
self._condition.notify_all()
|
|
298
319
|
return {"previous_status": previous_status}
|
|
299
320
|
|
|
300
|
-
def
|
|
321
|
+
async def shutdown(self) -> "None":
|
|
322
|
+
errors = []
|
|
323
|
+
for agent_id in tuple(self._agents):
|
|
324
|
+
try:
|
|
325
|
+
await self.close_agent(agent_id)
|
|
326
|
+
except Exception as exc:
|
|
327
|
+
errors.append(exc)
|
|
328
|
+
if errors:
|
|
329
|
+
for error in errors[1:]:
|
|
330
|
+
asyncio.get_running_loop().call_exception_handler(
|
|
331
|
+
{
|
|
332
|
+
"message": "Sub-agent close failed",
|
|
333
|
+
"exception": error,
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
raise errors[0]
|
|
337
|
+
|
|
338
|
+
def _next_nickname(self) -> "str":
|
|
301
339
|
if not self._available_nicknames:
|
|
302
340
|
self._available_nicknames = list(DEFAULT_AGENT_NICKNAME_CANDIDATES)
|
|
303
341
|
self._nickname_random.shuffle(self._available_nicknames)
|
|
@@ -305,59 +343,73 @@ class SubAgentManager:
|
|
|
305
343
|
|
|
306
344
|
async def wait_agents(
|
|
307
345
|
self,
|
|
308
|
-
agent_ids:
|
|
309
|
-
timeout_ms:
|
|
310
|
-
) ->
|
|
346
|
+
agent_ids: "typing.List[str]",
|
|
347
|
+
timeout_ms: "int" = 30_000,
|
|
348
|
+
) -> "typing.Dict[str, object]":
|
|
311
349
|
timeout_seconds = max(timeout_ms, 1) / 1000.0
|
|
312
350
|
loop = asyncio.get_running_loop()
|
|
313
351
|
deadline = loop.time() + timeout_seconds
|
|
314
352
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return {"status": {}, "timed_out": True}
|
|
326
|
-
|
|
327
|
-
async with self._condition:
|
|
328
|
-
try:
|
|
329
|
-
await asyncio.wait_for(self._condition.wait(), timeout=remaining)
|
|
330
|
-
except asyncio.TimeoutError:
|
|
353
|
+
async with self._get_condition():
|
|
354
|
+
while True:
|
|
355
|
+
snapshot = {
|
|
356
|
+
agent_id: self._status_payload(self._agents.get(agent_id))
|
|
357
|
+
for agent_id in agent_ids
|
|
358
|
+
}
|
|
359
|
+
if any(self._is_final_status(status) for status in snapshot.values()):
|
|
360
|
+
return {"status": snapshot, "timed_out": False}
|
|
361
|
+
remaining = deadline - loop.time()
|
|
362
|
+
if remaining <= 0:
|
|
331
363
|
return {"status": {}, "timed_out": True}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
364
|
+
waiter = asyncio.create_task(self._condition.wait())
|
|
365
|
+
try:
|
|
366
|
+
done, _pending = await asyncio.wait({waiter}, timeout=remaining)
|
|
367
|
+
if not done:
|
|
368
|
+
return {"status": {}, "timed_out": True}
|
|
369
|
+
await waiter
|
|
370
|
+
finally:
|
|
371
|
+
if not waiter.done():
|
|
372
|
+
waiter.cancel()
|
|
373
|
+
# Condition.wait() must reacquire the lock before we leave
|
|
374
|
+
# the context. Python 3.6 wait_for() does not await that.
|
|
375
|
+
try:
|
|
376
|
+
await waiter
|
|
377
|
+
except asyncio.CancelledError:
|
|
378
|
+
pass
|
|
379
|
+
|
|
380
|
+
def _submission_finished(self, future: "asyncio.Future") -> "None":
|
|
381
|
+
if not future.cancelled():
|
|
382
|
+
future.exception()
|
|
383
|
+
asyncio.create_task(self._notify_waiters())
|
|
384
|
+
|
|
385
|
+
def _handle_agent_event(self, managed: "ManagedAgent", event: "Event") -> "None":
|
|
386
|
+
if isinstance(event, TurnCompletedEvent):
|
|
387
|
+
managed.last_status = {"completed": event.output_text}
|
|
388
|
+
elif isinstance(event, CompactCompletedEvent):
|
|
389
|
+
managed.last_status = {"completed": None}
|
|
390
|
+
elif isinstance(event, (TurnFailedEvent, CompactFailedEvent)):
|
|
391
|
+
managed.last_status = {
|
|
392
|
+
"errored": "{0}: {1}".format(
|
|
393
|
+
event.error_type,
|
|
394
|
+
event.error,
|
|
395
|
+
)
|
|
396
|
+
}
|
|
397
|
+
elif isinstance(event, TurnInterruptedEvent):
|
|
398
|
+
managed.last_status = {"errored": "TurnInterrupted: turn interrupted"}
|
|
399
|
+
elif not isinstance(event, (TurnStartedEvent, CompactStartedEvent)):
|
|
400
|
+
return
|
|
401
|
+
asyncio.create_task(self._notify_waiters())
|
|
402
|
+
|
|
403
|
+
async def _notify_waiters(self) -> "None":
|
|
404
|
+
async with self._get_condition():
|
|
405
|
+
self._condition.notify_all()
|
|
354
406
|
|
|
355
407
|
def _compose_prompt(
|
|
356
408
|
self,
|
|
357
|
-
message:
|
|
358
|
-
items:
|
|
359
|
-
) ->
|
|
360
|
-
parts:
|
|
409
|
+
message: "typing.Union[str, None]",
|
|
410
|
+
items: "typing.Union[typing.List[typing.Dict[str, object]], None]",
|
|
411
|
+
) -> "str":
|
|
412
|
+
parts: "typing.List[str]" = []
|
|
361
413
|
if message:
|
|
362
414
|
parts.append(message.strip())
|
|
363
415
|
for item in items or []:
|
|
@@ -374,18 +426,20 @@ class SubAgentManager:
|
|
|
374
426
|
parts.append(json.dumps(item, ensure_ascii=False))
|
|
375
427
|
return "\n\n".join(part for part in parts if part)
|
|
376
428
|
|
|
377
|
-
def _status_payload(
|
|
429
|
+
def _status_payload(
|
|
430
|
+
self, managed: "typing.Union[ManagedAgent, None]"
|
|
431
|
+
) -> "AgentStatus":
|
|
378
432
|
if managed is None:
|
|
379
433
|
return "not_found"
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
return
|
|
387
|
-
|
|
388
|
-
def _is_final_status(self, status:
|
|
434
|
+
agent = managed.runtime.agent
|
|
435
|
+
if agent.is_shutdown:
|
|
436
|
+
return "shutdown"
|
|
437
|
+
if managed.runtime.is_busy:
|
|
438
|
+
return "running"
|
|
439
|
+
status = managed.last_status
|
|
440
|
+
return dict(status) if isinstance(status, dict) else status
|
|
441
|
+
|
|
442
|
+
def _is_final_status(self, status: "AgentStatus") -> "bool":
|
|
389
443
|
if isinstance(status, str):
|
|
390
444
|
return status in {"shutdown", "not_found"}
|
|
391
445
|
if isinstance(status, dict):
|
|
@@ -394,8 +448,8 @@ class SubAgentManager:
|
|
|
394
448
|
|
|
395
449
|
|
|
396
450
|
def _fork_context_history(
|
|
397
|
-
history:
|
|
398
|
-
) ->
|
|
451
|
+
history: "typing.Tuple[ConversationItem, ...]",
|
|
452
|
+
) -> "typing.Tuple[ConversationItem, ...]":
|
|
399
453
|
call_ids = set()
|
|
400
454
|
result_ids = set()
|
|
401
455
|
for item in history:
|
|
@@ -416,19 +470,12 @@ def _fork_context_history(
|
|
|
416
470
|
|
|
417
471
|
|
|
418
472
|
class AgentRuntimeEnvironment:
|
|
419
|
-
def __init__(self) ->
|
|
473
|
+
def __init__(self) -> "None":
|
|
420
474
|
self.plan_store = PlanStore()
|
|
421
475
|
self.subagent_manager = SubAgentManager()
|
|
422
476
|
self.request_user_input_manager = RequestUserInputManager()
|
|
423
477
|
self.request_permissions_manager = RequestPermissionsManager()
|
|
424
478
|
|
|
425
479
|
|
|
426
|
-
def create_agent_runtime_environment() ->
|
|
480
|
+
def create_agent_runtime_environment() -> "AgentRuntimeEnvironment":
|
|
427
481
|
return AgentRuntimeEnvironment()
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
_RUNTIME_ENV = create_agent_runtime_environment()
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
def get_agent_runtime_environment() -> 'AgentRuntimeEnvironment':
|
|
434
|
-
return _RUNTIME_ENV
|
pycodex/tools/__init__.py
CHANGED
|
@@ -4,8 +4,8 @@ This package groups the local tool abstractions and concrete tool
|
|
|
4
4
|
implementations that back `pycodex`.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from .base_tool import BaseTool, Registry, ToolContext, ToolRegistry
|
|
8
7
|
from .apply_patch_tool import ApplyPatchTool
|
|
8
|
+
from .base_tool import BaseTool, Registry, ToolContext, ToolRegistry
|
|
9
9
|
from .clock_tool import ClockManager, ClockTool
|
|
10
10
|
from .close_agent_tool import CloseAgentTool
|
|
11
11
|
from .code_mode_manager import CodeModeManager
|