Skip to content

Commit 12a387a

Browse files
committed
feat(exec): redesign Codex thread API around typed streams
1 parent ac195ad commit 12a387a

9 files changed

Lines changed: 409 additions & 380 deletions

File tree

README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ from codex import Codex
4242
client = Codex()
4343
thread = client.start_thread()
4444

45-
result = thread.run("Diagnose the failing tests and propose a fix")
46-
print(result.final_response)
45+
summary = thread.run_text("Diagnose the failing tests and propose a fix")
46+
print(summary)
4747
```
4848

4949
More exec-based examples: [docs/exec_api.md](docs/exec_api.md)
@@ -85,8 +85,8 @@ schema = {
8585

8686
client = Codex()
8787
thread = client.start_thread()
88-
result = thread.run("Summarize repository status", TurnOptions(output_schema=schema))
89-
print(result.final_response)
88+
payload = thread.run_json("Summarize repository status", TurnOptions(output_schema=schema))
89+
print(payload["summary"])
9090
```
9191

9292
### `AppServerClient`
@@ -124,14 +124,17 @@ with AppServerClient.connect_stdio() as client:
124124

125125
```python
126126
from codex import Codex
127+
from codex.protocol import types as protocol
127128

128129
client = Codex()
129130
thread = client.start_thread()
130131

131-
stream = thread.run_streamed("Investigate this bug")
132-
for event in stream.events:
133-
if event["type"] == "item.completed":
134-
print(event["item"])
132+
stream = thread.run("Investigate this bug")
133+
for event in stream:
134+
if isinstance(event, protocol.AgentMessageDeltaEventMsg):
135+
print(event.delta, end="", flush=True)
136+
137+
print()
135138
```
136139

137140
### App-server stream

codex/__init__.py

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,6 @@
2020
)
2121
from codex.codex import Codex
2222
from codex.errors import CodexError, CodexExecError, CodexParseError, ThreadRunError
23-
from codex.events import (
24-
ItemCompletedEvent,
25-
ItemStartedEvent,
26-
ItemUpdatedEvent,
27-
ThreadError,
28-
ThreadErrorEvent,
29-
ThreadEvent,
30-
ThreadStartedEvent,
31-
TurnCompletedEvent,
32-
TurnFailedEvent,
33-
TurnStartedEvent,
34-
Usage,
35-
)
36-
from codex.items import (
37-
AgentMessageItem,
38-
CommandExecutionItem,
39-
ErrorItem,
40-
FileChangeItem,
41-
McpToolCallItem,
42-
ReasoningItem,
43-
ThreadItem,
44-
TodoListItem,
45-
WebSearchItem,
46-
)
4723
from codex.options import (
4824
ApprovalMode,
4925
CancelSignal,
@@ -56,7 +32,7 @@
5632
TurnOptions,
5733
WebSearchMode,
5834
)
59-
from codex.thread import Input, RunResult, RunStreamedResult, Thread, UserInput
35+
from codex.thread import ExecTurnStream, Input, Thread, UserInput
6036

6137
__version__ = "1.0.1"
6238

@@ -83,8 +59,7 @@
8359
"CodexParseError",
8460
"ThreadRunError",
8561
"Thread",
86-
"RunResult",
87-
"RunStreamedResult",
62+
"ExecTurnStream",
8863
"Input",
8964
"UserInput",
9065
"CodexOptions",
@@ -97,24 +72,4 @@
9772
"CodexConfigValue",
9873
"CodexConfigObject",
9974
"CancelSignal",
100-
"ThreadEvent",
101-
"ThreadStartedEvent",
102-
"TurnStartedEvent",
103-
"TurnCompletedEvent",
104-
"TurnFailedEvent",
105-
"ItemStartedEvent",
106-
"ItemUpdatedEvent",
107-
"ItemCompletedEvent",
108-
"ThreadError",
109-
"ThreadErrorEvent",
110-
"Usage",
111-
"ThreadItem",
112-
"AgentMessageItem",
113-
"ReasoningItem",
114-
"CommandExecutionItem",
115-
"FileChangeItem",
116-
"McpToolCallItem",
117-
"WebSearchItem",
118-
"TodoListItem",
119-
"ErrorItem",
12075
]

codex/events.py

Lines changed: 33 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,35 @@
1-
from __future__ import annotations
2-
3-
from typing import Literal, TypedDict
4-
5-
from codex.items import ThreadItem
6-
7-
8-
class ThreadStartedEvent(TypedDict):
9-
type: Literal["thread.started"]
10-
thread_id: str
11-
12-
13-
class TurnStartedEvent(TypedDict):
14-
type: Literal["turn.started"]
15-
16-
17-
class Usage(TypedDict):
18-
input_tokens: int
19-
cached_input_tokens: int
20-
output_tokens: int
21-
22-
23-
class TurnCompletedEvent(TypedDict):
24-
type: Literal["turn.completed"]
25-
usage: Usage
26-
27-
28-
class ThreadError(TypedDict):
29-
message: str
30-
31-
32-
class TurnFailedEvent(TypedDict):
33-
type: Literal["turn.failed"]
34-
error: ThreadError
35-
36-
37-
class ItemStartedEvent(TypedDict):
38-
type: Literal["item.started"]
39-
item: ThreadItem
40-
41-
42-
class ItemUpdatedEvent(TypedDict):
43-
type: Literal["item.updated"]
44-
item: ThreadItem
45-
46-
47-
class ItemCompletedEvent(TypedDict):
48-
type: Literal["item.completed"]
49-
item: ThreadItem
50-
51-
52-
class ThreadErrorEvent(TypedDict):
53-
type: Literal["error"]
54-
message: str
1+
"""Typed exec-event re-exports backed by generated protocol models."""
552

3+
from __future__ import annotations
564

57-
ThreadEvent = (
58-
ThreadStartedEvent
59-
| TurnStartedEvent
60-
| TurnCompletedEvent
61-
| TurnFailedEvent
62-
| ItemStartedEvent
63-
| ItemUpdatedEvent
64-
| ItemCompletedEvent
65-
| ThreadErrorEvent
66-
)
5+
from pydantic import BaseModel
6+
7+
from codex.protocol import types as protocol
8+
9+
ExecEvent = BaseModel
10+
SessionConfiguredEvent = protocol.SessionConfiguredEventMsg
11+
TaskStartedEvent = protocol.TaskStartedEventMsg
12+
TaskCompletedEvent = protocol.TaskCompleteEventMsg
13+
AgentMessageEvent = protocol.AgentMessageEventMsg
14+
AgentMessageDeltaEvent = protocol.AgentMessageDeltaEventMsg
15+
TokenCountEvent = protocol.TokenCountEventMsg
16+
ItemStartedEvent = protocol.ItemStartedEventMsg
17+
ItemCompletedEvent = protocol.ItemCompletedEventMsg
18+
ErrorEvent = protocol.ErrorEventMsg
19+
StreamErrorEvent = protocol.StreamErrorEventMsg
20+
TurnAbortedEvent = protocol.TurnAbortedEventMsg
21+
22+
__all__ = [
23+
"ExecEvent",
24+
"SessionConfiguredEvent",
25+
"TaskStartedEvent",
26+
"TaskCompletedEvent",
27+
"AgentMessageEvent",
28+
"AgentMessageDeltaEvent",
29+
"TokenCountEvent",
30+
"ItemStartedEvent",
31+
"ItemCompletedEvent",
32+
"ErrorEvent",
33+
"StreamErrorEvent",
34+
"TurnAbortedEvent",
35+
]

codex/items.py

Lines changed: 23 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,25 @@
1-
from __future__ import annotations
2-
3-
from typing import Literal, NotRequired, TypedDict
4-
5-
CommandExecutionStatus = Literal["in_progress", "completed", "failed"]
6-
PatchChangeKind = Literal["add", "delete", "update"]
7-
PatchApplyStatus = Literal["completed", "failed"]
8-
McpToolCallStatus = Literal["in_progress", "completed", "failed"]
9-
10-
11-
class CommandExecutionItem(TypedDict):
12-
id: str
13-
type: Literal["command_execution"]
14-
command: str
15-
aggregated_output: str
16-
status: CommandExecutionStatus
17-
exit_code: NotRequired[int]
18-
19-
20-
class FileUpdateChange(TypedDict):
21-
path: str
22-
kind: PatchChangeKind
23-
24-
25-
class FileChangeItem(TypedDict):
26-
id: str
27-
type: Literal["file_change"]
28-
changes: list[FileUpdateChange]
29-
status: PatchApplyStatus
30-
31-
32-
class McpTextContent(TypedDict):
33-
type: Literal["text"]
34-
text: str
35-
36-
37-
class McpImageContent(TypedDict):
38-
type: Literal["image"]
39-
data: str
40-
mimeType: str
41-
42-
43-
McpContentBlock = McpTextContent | McpImageContent | dict[str, object]
44-
45-
46-
class McpToolCallResult(TypedDict):
47-
content: list[McpContentBlock]
48-
structured_content: object
49-
50-
51-
class McpToolCallError(TypedDict):
52-
message: str
53-
54-
55-
class McpToolCallItem(TypedDict):
56-
id: str
57-
type: Literal["mcp_tool_call"]
58-
server: str
59-
tool: str
60-
arguments: NotRequired[object]
61-
result: NotRequired[McpToolCallResult]
62-
error: NotRequired[McpToolCallError]
63-
status: McpToolCallStatus
64-
65-
66-
class AgentMessageItem(TypedDict):
67-
id: str
68-
type: Literal["agent_message"]
69-
text: str
70-
71-
72-
class ReasoningItem(TypedDict):
73-
id: str
74-
type: Literal["reasoning"]
75-
text: str
76-
77-
78-
class WebSearchItem(TypedDict):
79-
id: str
80-
type: Literal["web_search"]
81-
query: str
82-
83-
84-
class ErrorItem(TypedDict):
85-
id: str
86-
type: Literal["error"]
87-
message: str
88-
89-
90-
class TodoItem(TypedDict):
91-
text: str
92-
completed: bool
93-
94-
95-
class TodoListItem(TypedDict):
96-
id: str
97-
type: Literal["todo_list"]
98-
items: list[TodoItem]
1+
"""Typed exec turn-item re-exports backed by generated protocol models."""
992

3+
from __future__ import annotations
1004

101-
ThreadItem = (
102-
AgentMessageItem
103-
| ReasoningItem
104-
| CommandExecutionItem
105-
| FileChangeItem
106-
| McpToolCallItem
107-
| WebSearchItem
108-
| TodoListItem
109-
| ErrorItem
110-
)
5+
from codex.protocol import types as protocol
6+
7+
TurnItem = protocol.TurnItem
8+
UserMessageItem = protocol.UserMessageTurnItem
9+
AgentMessageItem = protocol.AgentMessageTurnItem
10+
PlanItem = protocol.PlanTurnItem
11+
ReasoningItem = protocol.ReasoningTurnItem
12+
WebSearchItem = protocol.WebSearchTurnItem
13+
ImageGenerationItem = protocol.ImageGenerationTurnItem
14+
ContextCompactionItem = protocol.ContextCompactionTurnItem
15+
16+
__all__ = [
17+
"TurnItem",
18+
"UserMessageItem",
19+
"AgentMessageItem",
20+
"PlanItem",
21+
"ReasoningItem",
22+
"WebSearchItem",
23+
"ImageGenerationItem",
24+
"ContextCompactionItem",
25+
]

0 commit comments

Comments
 (0)