python-codex 0.1.12__py3-none-any.whl → 0.1.13__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 +10 -8
- pycodex/agent.py +41 -17
- pycodex/cli.py +198 -145
- pycodex/compat.py +8 -4
- pycodex/feishu_card.py +693 -0
- pycodex/feishu_link.py +342 -0
- pycodex/model.py +89 -7
- pycodex/prompts/models.json +4 -4
- pycodex/protocol.py +17 -17
- pycodex/runtime.py +9 -14
- pycodex/runtime_services.py +45 -23
- pycodex/tools/apply_patch_tool.py +11 -12
- pycodex/tools/ipython_tool.py +144 -0
- pycodex/tools/unified_exec_manager.py +3 -0
- pycodex/utils/__init__.py +2 -13
- pycodex/utils/async_bridge.py +54 -0
- pycodex/utils/compactor.py +22 -9
- pycodex/utils/session_persist.py +57 -38
- pycodex/utils/toolcall_visualize.py +713 -0
- pycodex/utils/visualize.py +223 -861
- {python_codex-0.1.12.dist-info → python_codex-0.1.13.dist-info}/METADATA +1 -1
- {python_codex-0.1.12.dist-info → python_codex-0.1.13.dist-info}/RECORD +25 -20
- {python_codex-0.1.12.dist-info → python_codex-0.1.13.dist-info}/WHEEL +0 -0
- {python_codex-0.1.12.dist-info → python_codex-0.1.13.dist-info}/entry_points.txt +0 -0
- {python_codex-0.1.12.dist-info → python_codex-0.1.13.dist-info}/licenses/LICENSE +0 -0
pycodex/__init__.py
CHANGED
|
@@ -2,12 +2,13 @@ from .compat import patch_asyncio
|
|
|
2
2
|
|
|
3
3
|
patch_asyncio()
|
|
4
4
|
|
|
5
|
-
from .agent import
|
|
5
|
+
from .agent import Agent
|
|
6
6
|
from .context import ContextConfig, ContextManager
|
|
7
7
|
from .model import (
|
|
8
8
|
ModelClient,
|
|
9
9
|
NOOP_MODEL_STREAM_EVENT_HANDLER,
|
|
10
10
|
ResponsesApiError,
|
|
11
|
+
ResponsesIncompleteError,
|
|
11
12
|
ResponsesModelClient,
|
|
12
13
|
ResponsesProviderConfig,
|
|
13
14
|
)
|
|
@@ -26,14 +27,14 @@ from .protocol import (
|
|
|
26
27
|
TurnResult,
|
|
27
28
|
UserMessage,
|
|
28
29
|
)
|
|
29
|
-
from .runtime import
|
|
30
|
+
from .runtime import CliSubmissionQueue
|
|
30
31
|
from .runtime_services import (
|
|
31
32
|
PlanStore,
|
|
32
33
|
RequestPermissionsManager,
|
|
33
34
|
RequestUserInputManager,
|
|
34
35
|
SubAgentManager,
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
create_agent_runtime_environment,
|
|
37
|
+
get_agent_runtime_environment,
|
|
37
38
|
)
|
|
38
39
|
from .tools import (
|
|
39
40
|
ApplyPatchTool,
|
|
@@ -90,13 +91,13 @@ def debug(stop: 'bool' = False):
|
|
|
90
91
|
|
|
91
92
|
__all__ = [
|
|
92
93
|
"AgentEvent",
|
|
93
|
-
"
|
|
94
|
-
"
|
|
94
|
+
"Agent",
|
|
95
|
+
"CliSubmissionQueue",
|
|
95
96
|
"ApplyPatchTool",
|
|
96
97
|
"AssistantMessage",
|
|
97
98
|
"BaseTool",
|
|
98
99
|
"CloseAgentTool",
|
|
99
|
-
"
|
|
100
|
+
"create_agent_runtime_environment",
|
|
100
101
|
"CodeModeManager",
|
|
101
102
|
"ContextConfig",
|
|
102
103
|
"ContextManager",
|
|
@@ -120,6 +121,7 @@ __all__ = [
|
|
|
120
121
|
"RequestUserInputManager",
|
|
121
122
|
"ResumeAgentTool",
|
|
122
123
|
"ResponsesApiError",
|
|
124
|
+
"ResponsesIncompleteError",
|
|
123
125
|
"ResponsesModelClient",
|
|
124
126
|
"ResponsesProviderConfig",
|
|
125
127
|
"SendInputTool",
|
|
@@ -142,5 +144,5 @@ __all__ = [
|
|
|
142
144
|
"WaitTool",
|
|
143
145
|
"WebSearchTool",
|
|
144
146
|
"WriteStdinTool",
|
|
145
|
-
"
|
|
147
|
+
"get_agent_runtime_environment",
|
|
146
148
|
]
|
pycodex/agent.py
CHANGED
|
@@ -23,10 +23,11 @@ import typing
|
|
|
23
23
|
|
|
24
24
|
if typing.TYPE_CHECKING:
|
|
25
25
|
from .utils.session_persist import SessionRolloutRecorder
|
|
26
|
+
from .runtime_services import AgentRuntimeEnvironment
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
EventHandler = Callable[[AgentEvent], None]
|
|
29
|
-
|
|
30
|
+
BASE_EVENT_HANDLER: 'EventHandler' = lambda _event: None
|
|
30
31
|
_REQUESTED_TOKENS_RE = re.compile(
|
|
31
32
|
r"requested\s+([0-9,]+)\s+tokens",
|
|
32
33
|
re.IGNORECASE,
|
|
@@ -39,13 +40,19 @@ _MAX_CONTEXT_TOKENS_RE = re.compile(
|
|
|
39
40
|
r"maximum\s+context\s+length\s+is\s+([0-9,]+)\s+tokens",
|
|
40
41
|
re.IGNORECASE,
|
|
41
42
|
)
|
|
43
|
+
_CONTEXT_LENGTH_ERROR_MARKERS = (
|
|
44
|
+
"context_length_exceeded",
|
|
45
|
+
"maximum context length",
|
|
46
|
+
"exceeds the context window",
|
|
47
|
+
"exceeded the context window",
|
|
48
|
+
)
|
|
42
49
|
|
|
43
50
|
|
|
44
51
|
class TurnInterrupted(RuntimeError):
|
|
45
52
|
pass
|
|
46
53
|
|
|
47
54
|
|
|
48
|
-
class
|
|
55
|
+
class Agent:
|
|
49
56
|
"""Minimal Python port of Codex's turn loop.
|
|
50
57
|
|
|
51
58
|
The core idea mirrors the Rust implementation:
|
|
@@ -60,9 +67,10 @@ class AgentLoop:
|
|
|
60
67
|
tool_registry: 'ToolRegistry',
|
|
61
68
|
context_manager: 'typing.Union[ContextManager, None]' = None,
|
|
62
69
|
parallel_tool_calls: 'bool' = True,
|
|
63
|
-
event_handler: 'EventHandler' =
|
|
70
|
+
event_handler: 'EventHandler' = BASE_EVENT_HANDLER,
|
|
64
71
|
initial_history: 'typing.Tuple[ConversationItem, ...]' = (),
|
|
65
72
|
rollout_recorder: 'typing.Union[SessionRolloutRecorder, None]' = None,
|
|
73
|
+
runtime_environment: 'AgentRuntimeEnvironment' = None,
|
|
66
74
|
) -> 'None':
|
|
67
75
|
self._model_client = model_client
|
|
68
76
|
self._tool_registry = tool_registry
|
|
@@ -75,6 +83,7 @@ class AgentLoop:
|
|
|
75
83
|
self._context_manager.resolve_auto_compact_token_limit()
|
|
76
84
|
)
|
|
77
85
|
self._last_total_usage_tokens: 'typing.Union[int, None]' = None
|
|
86
|
+
self.runtime_environment = runtime_environment
|
|
78
87
|
self.interrupt_asap = False
|
|
79
88
|
|
|
80
89
|
@property
|
|
@@ -82,7 +91,7 @@ class AgentLoop:
|
|
|
82
91
|
return tuple(self._history)
|
|
83
92
|
|
|
84
93
|
def set_event_handler(
|
|
85
|
-
self, event_handler: 'EventHandler' =
|
|
94
|
+
self, event_handler: 'EventHandler' = BASE_EVENT_HANDLER
|
|
86
95
|
) -> 'None':
|
|
87
96
|
self._event_handler = event_handler
|
|
88
97
|
|
|
@@ -98,6 +107,11 @@ class AgentLoop:
|
|
|
98
107
|
) -> 'None':
|
|
99
108
|
self._rollout_recorder = rollout_recorder
|
|
100
109
|
|
|
110
|
+
def ask(self, text: 'str') -> 'TurnResult':
|
|
111
|
+
from .utils.async_bridge import run_async
|
|
112
|
+
|
|
113
|
+
return run_async(self.run_turn([text]))
|
|
114
|
+
|
|
101
115
|
def _raise_if_interrupt_requested(
|
|
102
116
|
self,
|
|
103
117
|
turn_id: 'str',
|
|
@@ -342,17 +356,26 @@ class AgentLoop:
|
|
|
342
356
|
lambda event: self._handle_model_stream_event(turn_id, event),
|
|
343
357
|
)
|
|
344
358
|
except Exception as exc:
|
|
345
|
-
|
|
346
|
-
if
|
|
359
|
+
error_message = str(exc)
|
|
360
|
+
if (
|
|
361
|
+
not _is_context_length_error_message(error_message)
|
|
362
|
+
or attempted_context_compact
|
|
363
|
+
):
|
|
347
364
|
raise
|
|
348
365
|
attempted_context_compact = True
|
|
349
|
-
|
|
350
|
-
|
|
366
|
+
context_usage = _usage_from_context_length_error(error_message)
|
|
367
|
+
if context_usage is not None:
|
|
368
|
+
self._remember_token_usage(context_usage)
|
|
369
|
+
self._emit("token_count", turn_id, usage=context_usage)
|
|
351
370
|
await self._run_auto_compact(
|
|
352
371
|
turn_id,
|
|
353
372
|
phase="context_length_exceeded",
|
|
354
|
-
total_tokens=
|
|
355
|
-
|
|
373
|
+
total_tokens=(
|
|
374
|
+
context_usage.get("total_tokens")
|
|
375
|
+
if context_usage is not None
|
|
376
|
+
else None
|
|
377
|
+
),
|
|
378
|
+
token_limit=_context_length_error_token_limit(error_message),
|
|
356
379
|
prune_tool_results_on_context_error=True,
|
|
357
380
|
)
|
|
358
381
|
self._raise_if_interrupt_requested(turn_id, iteration)
|
|
@@ -385,7 +408,7 @@ class AgentLoop:
|
|
|
385
408
|
token_limit: 'typing.Union[int, None]' = None,
|
|
386
409
|
prune_tool_results_on_context_error: 'bool' = False,
|
|
387
410
|
) -> 'None':
|
|
388
|
-
from .utils.compactor import
|
|
411
|
+
from .utils.compactor import compact_agent
|
|
389
412
|
|
|
390
413
|
payload: 'typing.Dict[str, object]' = {"phase": phase}
|
|
391
414
|
if total_tokens is not None:
|
|
@@ -403,7 +426,7 @@ class AgentLoop:
|
|
|
403
426
|
self._emit("stream_error", turn_id, **event.payload)
|
|
404
427
|
|
|
405
428
|
try:
|
|
406
|
-
compact_result = await
|
|
429
|
+
compact_result = await compact_agent(
|
|
407
430
|
self,
|
|
408
431
|
handle_compact_stream_event,
|
|
409
432
|
prune_tool_results_on_context_error,
|
|
@@ -477,11 +500,7 @@ class AgentLoop:
|
|
|
477
500
|
def _usage_from_context_length_error(
|
|
478
501
|
message: 'str',
|
|
479
502
|
) -> 'typing.Union[typing.Dict[str, int], None]':
|
|
480
|
-
|
|
481
|
-
if (
|
|
482
|
-
"context_length_exceeded" not in lower
|
|
483
|
-
and "maximum context length" not in lower
|
|
484
|
-
):
|
|
503
|
+
if not _is_context_length_error_message(message):
|
|
485
504
|
return None
|
|
486
505
|
|
|
487
506
|
requested_match = _REQUESTED_TOKENS_RE.search(message)
|
|
@@ -498,6 +517,11 @@ def _usage_from_context_length_error(
|
|
|
498
517
|
return usage
|
|
499
518
|
|
|
500
519
|
|
|
520
|
+
def _is_context_length_error_message(message: 'str') -> 'bool':
|
|
521
|
+
lower = message.lower()
|
|
522
|
+
return any(marker in lower for marker in _CONTEXT_LENGTH_ERROR_MARKERS)
|
|
523
|
+
|
|
524
|
+
|
|
501
525
|
def _context_length_error_token_limit(message: 'str') -> 'typing.Union[int, None]':
|
|
502
526
|
limit_match = _MAX_CONTEXT_TOKENS_RE.search(message)
|
|
503
527
|
if limit_match is None:
|