python-codex 0.1.2__py3-none-any.whl → 0.1.3__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.
Files changed (56) hide show
  1. pycodex/__init__.py +5 -1
  2. pycodex/agent.py +39 -41
  3. pycodex/cli.py +43 -42
  4. pycodex/collaboration.py +6 -7
  5. pycodex/compat.py +99 -0
  6. pycodex/context.py +87 -87
  7. pycodex/doctor.py +40 -40
  8. pycodex/model.py +69 -69
  9. pycodex/portable.py +33 -33
  10. pycodex/portable_server.py +22 -21
  11. pycodex/protocol.py +84 -86
  12. pycodex/runtime.py +36 -35
  13. pycodex/runtime_services.py +69 -69
  14. pycodex/tools/agent_tool_schemas.py +0 -2
  15. pycodex/tools/apply_patch_tool.py +43 -44
  16. pycodex/tools/base_tool.py +35 -36
  17. pycodex/tools/close_agent_tool.py +2 -4
  18. pycodex/tools/code_mode_manager.py +61 -61
  19. pycodex/tools/exec_command_tool.py +5 -6
  20. pycodex/tools/exec_runtime.js +3 -3
  21. pycodex/tools/exec_tool.py +2 -4
  22. pycodex/tools/grep_files_tool.py +10 -11
  23. pycodex/tools/list_dir_tool.py +8 -9
  24. pycodex/tools/read_file_tool.py +13 -14
  25. pycodex/tools/request_permissions_tool.py +2 -4
  26. pycodex/tools/request_user_input_tool.py +13 -14
  27. pycodex/tools/resume_agent_tool.py +2 -4
  28. pycodex/tools/send_input_tool.py +8 -9
  29. pycodex/tools/shell_command_tool.py +5 -6
  30. pycodex/tools/shell_tool.py +5 -6
  31. pycodex/tools/spawn_agent_tool.py +4 -5
  32. pycodex/tools/unified_exec_manager.py +62 -61
  33. pycodex/tools/update_plan_tool.py +4 -5
  34. pycodex/tools/view_image_tool.py +4 -5
  35. pycodex/tools/wait_agent_tool.py +2 -4
  36. pycodex/tools/wait_tool.py +4 -5
  37. pycodex/tools/web_search_tool.py +1 -3
  38. pycodex/tools/write_stdin_tool.py +4 -5
  39. pycodex/utils/dotenv.py +6 -6
  40. pycodex/utils/get_env.py +37 -33
  41. pycodex/utils/random_ids.py +1 -2
  42. pycodex/utils/visualize.py +79 -79
  43. {python_codex-0.1.2.dist-info → python_codex-0.1.3.dist-info}/METADATA +15 -9
  44. python_codex-0.1.3.dist-info/RECORD +74 -0
  45. {python_codex-0.1.2.dist-info → python_codex-0.1.3.dist-info}/WHEEL +1 -1
  46. responses_server/app.py +29 -19
  47. responses_server/config.py +17 -17
  48. responses_server/payload_processors.py +16 -16
  49. responses_server/server.py +11 -11
  50. responses_server/session_store.py +10 -10
  51. responses_server/stream_router.py +58 -58
  52. responses_server/tools/custom_adapter.py +12 -12
  53. responses_server/tools/web_search.py +33 -33
  54. python_codex-0.1.2.dist-info/RECORD +0 -73
  55. {python_codex-0.1.2.dist-info → python_codex-0.1.3.dist-info}/entry_points.txt +0 -0
  56. {python_codex-0.1.2.dist-info → python_codex-0.1.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,56 +1,57 @@
1
- from __future__ import annotations
2
1
 
3
2
  import argparse
4
3
  import hashlib
5
4
  import json
6
5
  import threading
7
- from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
6
+ from http.server import BaseHTTPRequestHandler
8
7
  from pathlib import Path
9
8
  from urllib.parse import unquote, urlparse
10
9
 
10
+ from .compat import ThreadingHTTPServer
11
11
  from .portable import (
12
12
  DEFAULT_STORAGE_SERVER,
13
13
  HEALTHCHECK_PATH,
14
14
  STORAGE_API_PREFIX,
15
15
  _call_id_from_payload,
16
16
  )
17
+ import typing
17
18
 
18
19
 
19
20
  class CodexStorageServer:
20
21
  def __init__(
21
22
  self,
22
- root: str | Path,
23
- host: str = "127.0.0.1",
24
- port: int = 5577,
25
- ) -> None:
23
+ root: 'typing.Union[str, Path]',
24
+ host: 'str' = "127.0.0.1",
25
+ port: 'int' = 5577,
26
+ ) -> 'None':
26
27
  self._root = Path(root).resolve()
27
28
  self._root.mkdir(parents=True, exist_ok=True)
28
29
  self._objects_dir = self._root / "objects"
29
30
  self._objects_dir.mkdir(parents=True, exist_ok=True)
30
31
  self._server = ThreadingHTTPServer((host, port), self._build_handler())
31
- self._thread: threading.Thread | None = None
32
+ self._thread: 'typing.Union[threading.Thread, None]' = None
32
33
 
33
34
  @property
34
- def host(self) -> str:
35
+ def host(self) -> 'str':
35
36
  return str(self._server.server_address[0])
36
37
 
37
38
  @property
38
- def port(self) -> int:
39
+ def port(self) -> 'int':
39
40
  return int(self._server.server_address[1])
40
41
 
41
42
  @property
42
- def server_address(self) -> str:
43
+ def server_address(self) -> 'str':
43
44
  return f"{self.host}:{self.port}"
44
45
 
45
46
  @property
46
- def base_url(self) -> str:
47
+ def base_url(self) -> 'str':
47
48
  return f"http://{self.server_address}{STORAGE_API_PREFIX}"
48
49
 
49
50
  @property
50
- def root(self) -> Path:
51
+ def root(self) -> 'Path':
51
52
  return self._root
52
53
 
53
- def start(self) -> None:
54
+ def start(self) -> 'None':
54
55
  if self._thread is not None:
55
56
  return
56
57
  self._thread = threading.Thread(
@@ -60,7 +61,7 @@ class CodexStorageServer:
60
61
  )
61
62
  self._thread.start()
62
63
 
63
- def stop(self) -> None:
64
+ def stop(self) -> 'None':
64
65
  self._server.shutdown()
65
66
  self._server.server_close()
66
67
  if self._thread is not None:
@@ -71,7 +72,7 @@ class CodexStorageServer:
71
72
  server = self
72
73
 
73
74
  class Handler(BaseHTTPRequestHandler):
74
- def do_GET(self) -> None: # noqa: N802
75
+ def do_GET(self) -> 'None': # noqa: N802
75
76
  path = urlparse(self.path).path
76
77
  if path == HEALTHCHECK_PATH:
77
78
  self._send_json(200, {"ok": True})
@@ -101,7 +102,7 @@ class CodexStorageServer:
101
102
  self.end_headers()
102
103
  self.wfile.write(payload)
103
104
 
104
- def do_POST(self) -> None: # noqa: N802
105
+ def do_POST(self) -> 'None': # noqa: N802
105
106
  path = urlparse(self.path).path
106
107
  if path != f"{STORAGE_API_PREFIX}/put":
107
108
  self._send_json(404, {"error": "not found"})
@@ -138,10 +139,10 @@ class CodexStorageServer:
138
139
  },
139
140
  )
140
141
 
141
- def log_message(self, _format: str, *_args) -> None:
142
+ def log_message(self, _format: 'str', *_args) -> 'None':
142
143
  return
143
144
 
144
- def _send_json(self, status: int, payload: dict[str, object]) -> None:
145
+ def _send_json(self, status: 'int', payload: 'typing.Dict[str, object]') -> 'None':
145
146
  body = json.dumps(payload).encode("utf-8")
146
147
  self.send_response(status)
147
148
  self.send_header("Content-Type", "application/json")
@@ -151,11 +152,11 @@ class CodexStorageServer:
151
152
 
152
153
  return Handler
153
154
 
154
- def _object_path(self, call_id: str) -> Path:
155
+ def _object_path(self, call_id: 'str') -> 'Path':
155
156
  return self._objects_dir / f"{call_id}.bin"
156
157
 
157
158
 
158
- def build_parser() -> argparse.ArgumentParser:
159
+ def build_parser() -> 'argparse.ArgumentParser':
159
160
  parser = argparse.ArgumentParser(
160
161
  prog="python -m pycodex.portable_server",
161
162
  description="Run a pycodex remote storage service for --put/--call testing.",
@@ -179,7 +180,7 @@ def build_parser() -> argparse.ArgumentParser:
179
180
  return parser
180
181
 
181
182
 
182
- def main(argv: list[str] | None = None) -> int:
183
+ def main(argv: 'typing.Union[typing.List[str], None]' = None) -> 'int':
183
184
  parser = build_parser()
184
185
  args = parser.parse_args(argv)
185
186
  server = CodexStorageServer(args.root, host=args.host, port=args.port)
pycodex/protocol.py CHANGED
@@ -14,35 +14,35 @@
14
14
  本文件只定义这些抽象之间传递的数据结构,不包含具体执行逻辑。
15
15
  """
16
16
 
17
- from __future__ import annotations
18
-
19
17
  from copy import deepcopy
20
18
  import json
21
19
  from dataclasses import dataclass, field
22
- from typing import Any, Literal, TypeAlias
20
+ from typing import Any
21
+ from .compat import Literal, TypeAlias
22
+ import typing
23
23
 
24
- JSONValue: TypeAlias = Any
25
- JSONDict: TypeAlias = dict[str, Any]
24
+ JSONValue: 'TypeAlias' = Any
25
+ JSONDict: 'TypeAlias' = typing.Dict[str, Any]
26
26
 
27
27
 
28
- @dataclass(frozen=True, slots=True)
28
+ @dataclass(frozen=True, )
29
29
  class ToolSpec:
30
30
  """何时:AgentLoop 准备发起一轮模型请求时,随 `Prompt.tools` 一起发送。
31
31
  发送方:AgentLoop。
32
32
  接收方:ModelClient。
33
33
  """
34
34
 
35
- name: str
36
- description: str
37
- input_schema: JSONDict | None = None
38
- tool_type: Literal["function", "custom", "web_search"] = "function"
39
- format: JSONDict | None = None
40
- options: JSONDict | None = None
41
- output_schema: JSONDict | None = None
42
- supports_parallel: bool = True
43
- raw_payload: JSONDict | None = None
44
-
45
- def serialize(self) -> JSONDict:
35
+ name: 'str'
36
+ description: 'str'
37
+ input_schema: 'typing.Union[JSONDict, None]' = None
38
+ tool_type: 'Literal["function", "custom", "web_search"]' = "function"
39
+ format: 'typing.Union[JSONDict, None]' = None
40
+ options: 'typing.Union[JSONDict, None]' = None
41
+ output_schema: 'typing.Union[JSONDict, None]' = None
42
+ supports_parallel: 'bool' = True
43
+ raw_payload: 'typing.Union[JSONDict, None]' = None
44
+
45
+ def serialize(self) -> 'JSONDict':
46
46
  if self.raw_payload is not None:
47
47
  return deepcopy(self.raw_payload)
48
48
  if self.tool_type == "web_search":
@@ -76,17 +76,17 @@ class ToolSpec:
76
76
  return payload
77
77
 
78
78
 
79
- @dataclass(frozen=True, slots=True)
79
+ @dataclass(frozen=True, )
80
80
  class UserMessage:
81
81
  """何时:外部发起一个新的用户 turn 时创建,并写入会话历史。
82
82
  发送方:外部调用方创建,AgentLoop 转发。
83
83
  接收方:AgentLoop 先接收,随后 ModelClient 在 `Prompt.input` 中看到它。
84
84
  """
85
85
 
86
- text: str
87
- role: Literal["user"] = "user"
86
+ text: 'str'
87
+ role: 'Literal["user"]' = "user"
88
88
 
89
- def serialize(self) -> JSONDict:
89
+ def serialize(self) -> 'JSONDict':
90
90
  return {
91
91
  "type": "message",
92
92
  "role": self.role,
@@ -94,17 +94,17 @@ class UserMessage:
94
94
  }
95
95
 
96
96
 
97
- @dataclass(frozen=True, slots=True)
97
+ @dataclass(frozen=True, )
98
98
  class AssistantMessage:
99
99
  """何时:模型要直接输出自然语言内容时产生,可作为中间文本或最终回复。
100
100
  发送方:ModelClient。
101
101
  接收方:AgentLoop。
102
102
  """
103
103
 
104
- text: str
105
- role: Literal["assistant"] = "assistant"
104
+ text: 'str'
105
+ role: 'Literal["assistant"]' = "assistant"
106
106
 
107
- def serialize(self) -> JSONDict:
107
+ def serialize(self) -> 'JSONDict':
108
108
  return {
109
109
  "type": "message",
110
110
  "role": self.role,
@@ -112,18 +112,18 @@ class AssistantMessage:
112
112
  }
113
113
 
114
114
 
115
- @dataclass(frozen=True, slots=True)
115
+ @dataclass(frozen=True, )
116
116
  class ContextMessage:
117
117
  """何时:ContextManager 为单轮模型请求注入额外上下文时构造。
118
118
  发送方:ContextManager。
119
119
  接收方:ModelClient。
120
120
  """
121
121
 
122
- text: str | None = None
123
- role: Literal["user", "developer"] = "user"
124
- content_items: tuple[JSONDict, ...] | None = None
122
+ text: 'typing.Union[str, None]' = None
123
+ role: 'Literal["user", "developer"]' = "user"
124
+ content_items: 'typing.Union[typing.Tuple[JSONDict, ...], None]' = None
125
125
 
126
- def serialize(self) -> JSONDict:
126
+ def serialize(self) -> 'JSONDict':
127
127
  if self.content_items is not None:
128
128
  content = list(self.content_items)
129
129
  else:
@@ -137,20 +137,20 @@ class ContextMessage:
137
137
  }
138
138
 
139
139
 
140
- @dataclass(frozen=True, slots=True)
140
+ @dataclass(frozen=True, )
141
141
  class ToolCall:
142
142
  """何时:模型决定调用工具而不是只输出文本时产生。
143
143
  发送方:ModelClient。
144
144
  接收方:AgentLoop,随后由它转给 ToolRegistry 执行。
145
145
  """
146
146
 
147
- call_id: str
148
- name: str
149
- arguments: JSONValue
150
- tool_type: Literal["function", "custom"] = "function"
151
- kind: Literal["tool_call"] = "tool_call"
147
+ call_id: 'str'
148
+ name: 'str'
149
+ arguments: 'JSONValue'
150
+ tool_type: 'Literal["function", "custom"]' = "function"
151
+ kind: 'Literal["tool_call"]' = "tool_call"
152
152
 
153
- def serialize(self) -> JSONDict:
153
+ def serialize(self) -> 'JSONDict':
154
154
  if self.tool_type == "custom":
155
155
  return {
156
156
  "type": "custom_tool_call",
@@ -170,7 +170,7 @@ class ToolCall:
170
170
  }
171
171
 
172
172
 
173
- @dataclass(frozen=True, slots=True)
173
+ @dataclass(frozen=True, )
174
174
  class ReasoningItem:
175
175
  """何时:模型在一次 Responses 采样里产出 reasoning item 时产生。
176
176
  发送方:ModelClient。
@@ -178,30 +178,30 @@ class ReasoningItem:
178
178
  ModelClient。
179
179
  """
180
180
 
181
- payload: JSONDict
182
- kind: Literal["reasoning"] = "reasoning"
181
+ payload: 'JSONDict'
182
+ kind: 'Literal["reasoning"]' = "reasoning"
183
183
 
184
- def serialize(self) -> JSONDict:
184
+ def serialize(self) -> 'JSONDict':
185
185
  return deepcopy(self.payload)
186
186
 
187
187
 
188
- @dataclass(frozen=True, slots=True)
188
+ @dataclass(frozen=True, )
189
189
  class ToolResult:
190
190
  """何时:某个 `ToolCall` 执行完成后产生,用于喂回下一轮模型调用。
191
191
  发送方:ToolRegistry 产出,AgentLoop 追加并转发。
192
192
  接收方:AgentLoop 先接收,随后 ModelClient 在下一轮 `Prompt.input` 中看到它。
193
193
  """
194
194
 
195
- call_id: str
196
- name: str
197
- output: JSONValue
198
- content_items: tuple[JSONDict, ...] | None = None
199
- success: bool | None = None
200
- is_error: bool = False
201
- tool_type: Literal["function", "custom"] = "function"
202
- kind: Literal["tool_result"] = "tool_result"
195
+ call_id: 'str'
196
+ name: 'str'
197
+ output: 'JSONValue'
198
+ content_items: 'typing.Union[typing.Tuple[JSONDict, ...], None]' = None
199
+ success: 'typing.Union[bool, None]' = None
200
+ is_error: 'bool' = False
201
+ tool_type: 'Literal["function", "custom"]' = "function"
202
+ kind: 'Literal["tool_result"]' = "tool_result"
203
203
 
204
- def output_text(self) -> str:
204
+ def output_text(self) -> 'str':
205
205
  if self.content_items is not None:
206
206
  text_parts = [
207
207
  str(item.get("text", ""))
@@ -217,8 +217,8 @@ class ToolResult:
217
217
  return self.output
218
218
  return json.dumps(self.output, ensure_ascii=False)
219
219
 
220
- def serialize(self) -> JSONDict:
221
- payload_output: JSONValue
220
+ def serialize(self) -> 'JSONDict':
221
+ payload_output: 'JSONValue'
222
222
  if self.content_items is not None:
223
223
  payload_output = list(self.content_items)
224
224
  elif isinstance(self.output, str):
@@ -247,86 +247,84 @@ class ToolResult:
247
247
  return payload
248
248
 
249
249
 
250
- ConversationItem: TypeAlias = (
251
- UserMessage | AssistantMessage | ContextMessage | ToolCall | ReasoningItem | ToolResult
252
- )
253
- ModelOutputItem: TypeAlias = AssistantMessage | ToolCall | ReasoningItem
254
- Operation: TypeAlias = "UserTurnOp | ShutdownOp"
250
+ ConversationItem: 'TypeAlias' = typing.Union[typing.Union[typing.Union[typing.Union[typing.Union[UserMessage, AssistantMessage], ContextMessage], ToolCall], ReasoningItem], ToolResult]
251
+ ModelOutputItem: 'TypeAlias' = typing.Union[typing.Union[AssistantMessage, ToolCall], ReasoningItem]
252
+ Operation: 'TypeAlias' = "UserTurnOp | ShutdownOp"
255
253
 
256
254
 
257
- @dataclass(frozen=True, slots=True)
255
+ @dataclass(frozen=True, )
258
256
  class Prompt:
259
257
  """何时:AgentLoop 每发起一轮模型采样前构造。
260
258
  发送方:AgentLoop。
261
259
  接收方:ModelClient。
262
260
  """
263
261
 
264
- input: list[ConversationItem]
265
- tools: list[ToolSpec]
266
- parallel_tool_calls: bool = True
267
- base_instructions: str | None = None
268
- turn_id: str | None = None
269
- turn_metadata: JSONDict | None = None
262
+ input: 'typing.List[ConversationItem]'
263
+ tools: 'typing.List[ToolSpec]'
264
+ parallel_tool_calls: 'bool' = True
265
+ base_instructions: 'typing.Union[str, None]' = None
266
+ turn_id: 'typing.Union[str, None]' = None
267
+ turn_metadata: 'typing.Union[JSONDict, None]' = None
270
268
 
271
269
 
272
- @dataclass(frozen=True, slots=True)
270
+ @dataclass(frozen=True, )
273
271
  class ModelResponse:
274
272
  """何时:ModelClient 完成一轮 `Prompt` 处理后返回。
275
273
  发送方:ModelClient。
276
274
  接收方:AgentLoop。
277
275
  """
278
276
 
279
- items: list[ModelOutputItem]
277
+ items: 'typing.List[ModelOutputItem]'
280
278
 
281
279
 
282
- @dataclass(frozen=True, slots=True)
280
+ @dataclass(frozen=True, )
283
281
  class ModelStreamEvent:
284
282
  """何时:ModelClient 处理 `Prompt` 的过程中有流式中间结果时产生。
285
283
  发送方:ModelClient。
286
284
  接收方:AgentLoop。
287
285
  """
288
286
 
289
- kind: str
290
- payload: JSONDict = field(default_factory=dict)
287
+ kind: 'str'
288
+ payload: 'JSONDict' = field(default_factory=dict)
291
289
 
292
290
 
293
- @dataclass(frozen=True, slots=True)
291
+ @dataclass(frozen=True, )
294
292
  class TurnResult:
295
293
  """何时:一个 turn 已经收敛,AgentLoop 决定结束本轮时返回。
296
294
  发送方:AgentLoop。
297
295
  接收方:外部调用方。
298
296
  """
299
297
 
300
- turn_id: str
301
- output_text: str | None
302
- iterations: int
303
- response_items: tuple[ModelOutputItem, ...]
304
- history: tuple[ConversationItem, ...]
298
+ turn_id: 'str'
299
+ output_text: 'typing.Union[str, None]'
300
+ iterations: 'int'
301
+ response_items: 'typing.Tuple[ModelOutputItem, ...]'
302
+ history: 'typing.Tuple[ConversationItem, ...]'
305
303
 
306
304
 
307
- @dataclass(frozen=True, slots=True)
305
+ @dataclass(frozen=True, )
308
306
  class AgentEvent:
309
307
  """何时:主循环运行过程中发生阶段性事件时发出,例如模型调用、工具开始/结束。
310
308
  发送方:AgentLoop。
311
309
  接收方:可选的事件观察者 / 回调。
312
310
  """
313
311
 
314
- kind: str
315
- turn_id: str
316
- payload: dict[str, object] = field(default_factory=dict)
312
+ kind: 'str'
313
+ turn_id: 'str'
314
+ payload: 'typing.Dict[str, object]' = field(default_factory=dict)
317
315
 
318
316
 
319
- @dataclass(frozen=True, slots=True)
317
+ @dataclass(frozen=True, )
320
318
  class UserTurnOp:
321
319
  """何时:运行时要提交一个新的用户请求时创建。
322
320
  发送方:外部运行时调用方。
323
321
  接收方:AgentRuntime。
324
322
  """
325
323
 
326
- texts: list[str]
324
+ texts: 'typing.List[str]'
327
325
 
328
326
 
329
- @dataclass(frozen=True, slots=True)
327
+ @dataclass(frozen=True, )
330
328
  class ShutdownOp:
331
329
  """何时:运行时要停止外层提交循环时创建。
332
330
  发送方:外部运行时调用方。
@@ -336,12 +334,12 @@ class ShutdownOp:
336
334
  pass
337
335
 
338
336
 
339
- @dataclass(frozen=True, slots=True)
337
+ @dataclass(frozen=True, )
340
338
  class Submission:
341
339
  """何时:任意运行时操作要进入 AgentRuntime 队列时创建。
342
340
  发送方:外部运行时调用方。
343
341
  接收方:AgentRuntime 的提交队列 / 外层循环。
344
342
  """
345
343
 
346
- id: str
347
- op: Operation
344
+ id: 'str'
345
+ op: 'Operation'
pycodex/runtime.py CHANGED
@@ -1,44 +1,45 @@
1
- from __future__ import annotations
2
1
 
3
2
  import asyncio
4
3
  from collections import deque
5
4
  from dataclasses import dataclass
6
- from typing import TYPE_CHECKING, Literal
5
+ from typing import TYPE_CHECKING
7
6
 
8
7
  from .agent import AgentLoop, EventHandler, NOOP_EVENT_HANDLER, TurnInterrupted
8
+ from .compat import Literal
9
9
  from .protocol import AgentEvent, Operation, ShutdownOp, Submission, TurnResult, UserTurnOp
10
10
  from .utils import uuid7_string
11
+ import typing
11
12
 
12
13
  if TYPE_CHECKING:
13
14
  from .runtime_services import RuntimeEnvironment
14
15
 
15
16
 
16
- @dataclass(slots=True)
17
+ @dataclass
17
18
  class _QueuedSubmission:
18
- submission: Submission
19
- turn_id: str
20
- futures: list[asyncio.Future[TurnResult | None]]
19
+ submission: 'Submission'
20
+ turn_id: 'str'
21
+ futures: 'typing.List[asyncio.Future[typing.Union[TurnResult, None]]]'
21
22
 
22
23
 
23
24
  class AgentRuntime:
24
25
  """Thin outer queue that mirrors the Rust `submission_loop` shape."""
25
26
 
26
- def __init__(self, agent_loop: AgentLoop, runtime_environment: RuntimeEnvironment | None = None) -> None:
27
+ def __init__(self, agent_loop: 'AgentLoop', runtime_environment: 'typing.Union[RuntimeEnvironment, None]' = None) -> 'None':
27
28
  self._agent_loop = agent_loop
28
29
  self.runtime_environment = runtime_environment
29
- self._enqueue_queue: deque[_QueuedSubmission] = deque()
30
- self._steer_queue: deque[_QueuedSubmission] = deque()
30
+ self._enqueue_queue: 'deque[_QueuedSubmission]' = deque()
31
+ self._steer_queue: 'deque[_QueuedSubmission]' = deque()
31
32
  self._queue_lock = asyncio.Lock()
32
33
  self._queue_event = asyncio.Event()
33
- self._current_submission: _QueuedSubmission | None = None
34
- self._current_task: asyncio.Task[TurnResult] | None = None
34
+ self._current_submission: 'typing.Union[_QueuedSubmission, None]' = None
35
+ self._current_task: 'typing.Union[asyncio.Task[TurnResult], None]' = None
35
36
  self._event_handler = NOOP_EVENT_HANDLER
36
37
  self._agent_loop.set_event_handler(self._handle_agent_event)
37
38
 
38
- def set_event_handler(self, event_handler: EventHandler = NOOP_EVENT_HANDLER) -> None:
39
+ def set_event_handler(self, event_handler: 'EventHandler' = NOOP_EVENT_HANDLER) -> 'None':
39
40
  self._event_handler = event_handler
40
41
 
41
- async def submit_user_turn(self, text: str) -> TurnResult:
42
+ async def submit_user_turn(self, text: 'str') -> 'TurnResult':
42
43
  _submission_id, future = await self.enqueue_user_turn(text, queue="enqueue")
43
44
  result = await future
44
45
  assert result is not None
@@ -46,19 +47,19 @@ class AgentRuntime:
46
47
 
47
48
  async def enqueue_user_turn(
48
49
  self,
49
- text: str,
50
- queue: Literal["enqueue", "steer"] = "enqueue",
51
- ) -> tuple[str, asyncio.Future[TurnResult | None]]:
52
- future: asyncio.Future[TurnResult | None] = asyncio.get_running_loop().create_future()
50
+ text: 'str',
51
+ queue: 'Literal["enqueue", "steer"]' = "enqueue",
52
+ ) -> 'typing.Tuple[str, asyncio.Future[typing.Union[TurnResult, None]]]':
53
+ future: 'asyncio.Future[typing.Union[TurnResult, None]]' = asyncio.get_running_loop().create_future()
53
54
  return await self._enqueue_user_turn_to_queue(
54
55
  text,
55
56
  future,
56
57
  queue=queue,
57
58
  )
58
59
 
59
- async def shutdown(self) -> None:
60
+ async def shutdown(self) -> 'None':
60
61
  submission = Submission(id=uuid7_string(), op=ShutdownOp())
61
- future: asyncio.Future[TurnResult | None] = asyncio.get_running_loop().create_future()
62
+ future: 'asyncio.Future[typing.Union[TurnResult, None]]' = asyncio.get_running_loop().create_future()
62
63
  self._enqueue_queue.append(
63
64
  _QueuedSubmission(
64
65
  submission=submission,
@@ -69,7 +70,7 @@ class AgentRuntime:
69
70
  self._queue_event.set()
70
71
  await future
71
72
 
72
- async def run_forever(self) -> None:
73
+ async def run_forever(self) -> 'None':
73
74
  while True:
74
75
  queued = await self._next_submission()
75
76
  submission = queued.submission
@@ -114,7 +115,7 @@ class AgentRuntime:
114
115
  self._current_submission = None
115
116
 
116
117
  @staticmethod
117
- def operation_name(op: Operation) -> str:
118
+ def operation_name(op: 'Operation') -> 'str':
118
119
  if isinstance(op, UserTurnOp):
119
120
  return "user_turn"
120
121
  if isinstance(op, ShutdownOp):
@@ -123,10 +124,10 @@ class AgentRuntime:
123
124
 
124
125
  async def _enqueue_user_turn_to_queue(
125
126
  self,
126
- text: str,
127
- future: asyncio.Future[TurnResult | None],
128
- queue: Literal["enqueue", "steer"],
129
- ) -> tuple[str, asyncio.Future[TurnResult | None]]:
127
+ text: 'str',
128
+ future: 'asyncio.Future[typing.Union[TurnResult, None]]',
129
+ queue: 'Literal["enqueue", "steer"]',
130
+ ) -> 'typing.Tuple[str, asyncio.Future[typing.Union[TurnResult, None]]]':
130
131
  if queue == "steer" and self._has_active_turn():
131
132
  self._agent_loop.interrupt_asap = True
132
133
 
@@ -154,10 +155,10 @@ class AgentRuntime:
154
155
  self._queue_event.set()
155
156
  return submission.id, future
156
157
 
157
- async def _next_submission(self) -> _QueuedSubmission:
158
+ async def _next_submission(self) -> '_QueuedSubmission':
158
159
  while True:
159
160
  async with self._queue_lock:
160
- queued: _QueuedSubmission | None = None
161
+ queued: 'typing.Union[_QueuedSubmission, None]' = None
161
162
  if self._steer_queue:
162
163
  queued = self._steer_queue.popleft()
163
164
  elif self._enqueue_queue:
@@ -171,27 +172,27 @@ class AgentRuntime:
171
172
 
172
173
  @staticmethod
173
174
  def _finish_submission_result(
174
- queued: _QueuedSubmission,
175
- result: TurnResult | None,
176
- ) -> None:
175
+ queued: '_QueuedSubmission',
176
+ result: 'typing.Union[TurnResult, None]',
177
+ ) -> 'None':
177
178
  for future in queued.futures:
178
179
  if not future.done():
179
180
  future.set_result(result)
180
181
 
181
182
  @staticmethod
182
183
  def _finish_submission_exception(
183
- queued: _QueuedSubmission,
184
- exc: Exception,
185
- ) -> None:
184
+ queued: '_QueuedSubmission',
185
+ exc: 'Exception',
186
+ ) -> 'None':
186
187
  for future in queued.futures:
187
188
  if not future.done():
188
189
  future.set_exception(exc)
189
190
 
190
- def _has_active_turn(self) -> bool:
191
+ def _has_active_turn(self) -> 'bool':
191
192
  current_task = self._current_task
192
193
  return current_task is not None and not current_task.done()
193
194
 
194
- def _handle_agent_event(self, event: AgentEvent) -> None:
195
+ def _handle_agent_event(self, event: 'AgentEvent') -> 'None':
195
196
  queued = self._current_submission
196
197
  if queued is None:
197
198
  self._event_handler(event)