codex-sdk-py 0.0.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.
codex_sdk/thread.py ADDED
@@ -0,0 +1,256 @@
1
+ """
2
+ Thread class for managing conversations with the Codex agent.
3
+
4
+ Corresponds to: src/thread.ts
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from collections.abc import AsyncGenerator
11
+ from dataclasses import dataclass
12
+ from typing import TYPE_CHECKING, Literal, TypedDict
13
+
14
+ from .events import ThreadError, ThreadEvent, Usage
15
+ from .exec import CodexExec, CodexExecArgs
16
+ from .items import ThreadItem
17
+ from .output_schema_file import create_output_schema_file
18
+
19
+ if TYPE_CHECKING:
20
+ from .codex_options import CodexOptions
21
+ from .thread_options import ThreadOptions
22
+ from .turn_options import TurnOptions
23
+
24
+
25
+ # Input types
26
+ class TextUserInput(TypedDict):
27
+ """Text input for user messages."""
28
+
29
+ type: Literal["text"]
30
+ text: str
31
+
32
+
33
+ class ImageUserInput(TypedDict):
34
+ """Local image input."""
35
+
36
+ type: Literal["local_image"]
37
+ path: str
38
+
39
+
40
+ UserInput = TextUserInput | ImageUserInput
41
+ """An input to send to the agent."""
42
+
43
+ Input = str | list[UserInput]
44
+ """Either a string prompt or a list of user inputs."""
45
+
46
+
47
+ @dataclass
48
+ class Turn:
49
+ """Completed turn result."""
50
+
51
+ items: list[ThreadItem]
52
+ """All items produced during the turn."""
53
+
54
+ final_response: str
55
+ """The agent's final response text."""
56
+
57
+ usage: Usage | None
58
+ """Token usage statistics."""
59
+
60
+
61
+ # Alias for Turn to describe the result of run()
62
+ RunResult = Turn
63
+
64
+
65
+ @dataclass
66
+ class StreamedTurn:
67
+ """The result of the runStreamed method."""
68
+
69
+ events: AsyncGenerator[ThreadEvent, None]
70
+ """Async generator yielding events as they are produced."""
71
+
72
+
73
+ # Alias for StreamedTurn to describe the result of run_streamed()
74
+ RunStreamedResult = StreamedTurn
75
+
76
+
77
+ def _normalize_input(input_data: Input) -> tuple[str, list[str]]:
78
+ """
79
+ Normalize input to prompt string and image paths.
80
+
81
+ Args:
82
+ input_data: Either a string or list of user inputs.
83
+
84
+ Returns:
85
+ Tuple of (prompt_string, image_paths).
86
+ """
87
+ if isinstance(input_data, str):
88
+ return input_data, []
89
+
90
+ prompt_parts: list[str] = []
91
+ images: list[str] = []
92
+
93
+ for item in input_data:
94
+ if item["type"] == "text":
95
+ prompt_parts.append(item["text"])
96
+ elif item["type"] == "local_image":
97
+ images.append(item["path"])
98
+
99
+ return "\n\n".join(prompt_parts), images
100
+
101
+
102
+ class Thread:
103
+ """
104
+ Represents a thread of conversation with the agent.
105
+
106
+ One thread can have multiple consecutive turns.
107
+ """
108
+
109
+ def __init__(
110
+ self,
111
+ exec_instance: CodexExec,
112
+ options: CodexOptions,
113
+ thread_options: ThreadOptions,
114
+ thread_id: str | None = None,
115
+ ) -> None:
116
+ """
117
+ Initialize a Thread instance.
118
+
119
+ This is an internal constructor. Use Codex.start_thread() or
120
+ Codex.resume_thread() to create Thread instances.
121
+
122
+ Args:
123
+ exec_instance: The CodexExec instance for running CLI commands.
124
+ options: Global Codex options.
125
+ thread_options: Thread-specific options.
126
+ thread_id: Optional thread ID for resuming conversations.
127
+ """
128
+ self._exec = exec_instance
129
+ self._options = options
130
+ self._thread_options = thread_options
131
+ self._id = thread_id
132
+
133
+ @property
134
+ def id(self) -> str | None:
135
+ """
136
+ Returns the ID of the thread.
137
+
138
+ Populated after the first turn starts.
139
+ """
140
+ return self._id
141
+
142
+ async def run_streamed(
143
+ self,
144
+ input_data: Input,
145
+ turn_options: TurnOptions | None = None,
146
+ ) -> StreamedTurn:
147
+ """
148
+ Provides the input to the agent and streams events as they are produced during the turn.
149
+
150
+ Args:
151
+ input_data: The user input (string or structured input with images).
152
+ turn_options: Optional turn-specific configuration.
153
+
154
+ Returns:
155
+ StreamedTurn containing an async generator of events.
156
+ """
157
+ return StreamedTurn(events=self._run_streamed_internal(input_data, turn_options))
158
+
159
+ async def _run_streamed_internal(
160
+ self,
161
+ input_data: Input,
162
+ turn_options: TurnOptions | None = None,
163
+ ) -> AsyncGenerator[ThreadEvent, None]:
164
+ """
165
+ Internal implementation of run_streamed.
166
+
167
+ Yields:
168
+ ThreadEvent objects as they are produced.
169
+ """
170
+ turn_options = turn_options or {}
171
+
172
+ # Create output schema file if needed
173
+ schema_file = await create_output_schema_file(turn_options.get("output_schema"))
174
+
175
+ prompt, images = _normalize_input(input_data)
176
+
177
+ exec_args = CodexExecArgs(
178
+ input=prompt,
179
+ base_url=self._options.get("base_url"),
180
+ api_key=self._options.get("api_key"),
181
+ thread_id=self._id,
182
+ images=images if images else None,
183
+ model=self._thread_options.get("model"),
184
+ sandbox_mode=self._thread_options.get("sandbox_mode"),
185
+ working_directory=self._thread_options.get("working_directory"),
186
+ skip_git_repo_check=self._thread_options.get("skip_git_repo_check"),
187
+ output_schema_file=schema_file.schema_path,
188
+ model_reasoning_effort=self._thread_options.get("model_reasoning_effort"),
189
+ cancel_event=turn_options.get("cancel_event"),
190
+ network_access_enabled=self._thread_options.get("network_access_enabled"),
191
+ web_search_mode=self._thread_options.get("web_search_mode"),
192
+ web_search_enabled=self._thread_options.get("web_search_enabled"),
193
+ approval_policy=self._thread_options.get("approval_policy"),
194
+ additional_directories=self._thread_options.get("additional_directories"),
195
+ )
196
+
197
+ try:
198
+ async for line in self._exec.run(exec_args):
199
+ try:
200
+ parsed: ThreadEvent = json.loads(line)
201
+ except json.JSONDecodeError as e:
202
+ raise ValueError(f"Failed to parse item: {line}") from e
203
+
204
+ # Capture thread ID from first event
205
+ if parsed.get("type") == "thread.started":
206
+ self._id = parsed.get("thread_id")
207
+
208
+ yield parsed
209
+ finally:
210
+ await schema_file.cleanup()
211
+
212
+ async def run(
213
+ self,
214
+ input_data: Input,
215
+ turn_options: TurnOptions | None = None,
216
+ ) -> Turn:
217
+ """
218
+ Provides the input to the agent and returns the completed turn.
219
+
220
+ Args:
221
+ input_data: The user input (string or structured input with images).
222
+ turn_options: Optional turn-specific configuration.
223
+
224
+ Returns:
225
+ Turn object with items, final response, and usage.
226
+
227
+ Raises:
228
+ RuntimeError: If the turn fails.
229
+ """
230
+ items: list[ThreadItem] = []
231
+ final_response: str = ""
232
+ usage: Usage | None = None
233
+ turn_failure: ThreadError | None = None
234
+
235
+ streamed = await self.run_streamed(input_data, turn_options)
236
+ async for event in streamed.events:
237
+ event_type = event.get("type")
238
+
239
+ if event_type == "item.completed":
240
+ item = event.get("item")
241
+ if item:
242
+ if item.get("type") == "agent_message":
243
+ final_response = item.get("text", "")
244
+ items.append(item)
245
+
246
+ elif event_type == "turn.completed":
247
+ usage = event.get("usage")
248
+
249
+ elif event_type == "turn.failed":
250
+ turn_failure = event.get("error")
251
+ break
252
+
253
+ if turn_failure:
254
+ raise RuntimeError(turn_failure.get("message", "Turn failed"))
255
+
256
+ return Turn(items=items, final_response=final_response, usage=usage)
@@ -0,0 +1,79 @@
1
+ """
2
+ Thread options type definitions.
3
+
4
+ Corresponds to: src/threadOptions.ts
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from enum import StrEnum
10
+ from typing import TypedDict
11
+
12
+
13
+ class ApprovalMode(StrEnum):
14
+ """Approval policy for agent actions."""
15
+
16
+ NEVER = "never"
17
+ ON_REQUEST = "on-request"
18
+ ON_FAILURE = "on-failure"
19
+ UNTRUSTED = "untrusted"
20
+
21
+
22
+ class SandboxMode(StrEnum):
23
+ """Sandbox execution mode for the Codex CLI."""
24
+
25
+ READ_ONLY = "read-only"
26
+ WORKSPACE_WRITE = "workspace-write"
27
+ DANGER_FULL_ACCESS = "danger-full-access"
28
+
29
+
30
+ class ModelReasoningEffort(StrEnum):
31
+ """Reasoning effort level for the model."""
32
+
33
+ MINIMAL = "minimal"
34
+ LOW = "low"
35
+ MEDIUM = "medium"
36
+ HIGH = "high"
37
+ XHIGH = "xhigh"
38
+
39
+
40
+ class WebSearchMode(StrEnum):
41
+ """Web search configuration mode."""
42
+
43
+ DISABLED = "disabled"
44
+ CACHED = "cached"
45
+ LIVE = "live"
46
+
47
+
48
+ class ThreadOptions(TypedDict, total=False):
49
+ """Configuration options for thread creation."""
50
+
51
+ model: str
52
+ """Model to use for the thread."""
53
+
54
+ sandbox_mode: SandboxMode
55
+ """Sandbox execution mode."""
56
+
57
+ working_directory: str
58
+ """Working directory for the agent."""
59
+
60
+ skip_git_repo_check: bool
61
+ """Skip Git repository validation."""
62
+
63
+ model_reasoning_effort: ModelReasoningEffort
64
+ """Reasoning effort level."""
65
+
66
+ network_access_enabled: bool
67
+ """Enable network access for the agent."""
68
+
69
+ web_search_mode: WebSearchMode
70
+ """Web search configuration mode."""
71
+
72
+ web_search_enabled: bool
73
+ """Enable web search capability (legacy)."""
74
+
75
+ approval_policy: ApprovalMode
76
+ """Approval policy for agent actions."""
77
+
78
+ additional_directories: list[str]
79
+ """Additional directories accessible to the agent."""
@@ -0,0 +1,24 @@
1
+ """
2
+ Turn options type definitions.
3
+
4
+ Corresponds to: src/turnOptions.ts
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ from typing import Any, TypedDict
11
+
12
+
13
+ class TurnOptions(TypedDict, total=False):
14
+ """Configuration options for individual turns."""
15
+
16
+ output_schema: Any
17
+ """JSON schema describing the expected agent output."""
18
+
19
+ cancel_event: asyncio.Event
20
+ """
21
+ Event to signal cancellation of the turn.
22
+ Set the event to cancel the ongoing turn.
23
+ This is the Python equivalent of AbortSignal.
24
+ """