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 CHANGED
@@ -2,12 +2,13 @@ from .compat import patch_asyncio
2
2
 
3
3
  patch_asyncio()
4
4
 
5
- from .agent import AgentLoop
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 AgentRuntime
30
+ from .runtime import CliSubmissionQueue
30
31
  from .runtime_services import (
31
32
  PlanStore,
32
33
  RequestPermissionsManager,
33
34
  RequestUserInputManager,
34
35
  SubAgentManager,
35
- create_runtime_environment,
36
- get_runtime_environment,
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
- "AgentLoop",
94
- "AgentRuntime",
94
+ "Agent",
95
+ "CliSubmissionQueue",
95
96
  "ApplyPatchTool",
96
97
  "AssistantMessage",
97
98
  "BaseTool",
98
99
  "CloseAgentTool",
99
- "create_runtime_environment",
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
- "get_runtime_environment",
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
- NOOP_EVENT_HANDLER: 'EventHandler' = lambda _event: None
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 AgentLoop:
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' = NOOP_EVENT_HANDLER,
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' = NOOP_EVENT_HANDLER
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
- context_usage = _usage_from_context_length_error(str(exc))
346
- if context_usage is None or attempted_context_compact:
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
- self._remember_token_usage(context_usage)
350
- self._emit("token_count", turn_id, usage=context_usage)
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=context_usage.get("total_tokens"),
355
- token_limit=_context_length_error_token_limit(str(exc)),
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 compact_agent_loop
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 compact_agent_loop(
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
- lower = message.lower()
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: