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/__init__.py +135 -0
- codex_sdk/codex.py +79 -0
- codex_sdk/codex_options.py +53 -0
- codex_sdk/events.py +107 -0
- codex_sdk/exec.py +396 -0
- codex_sdk/items.py +175 -0
- codex_sdk/output_schema_file.py +74 -0
- codex_sdk/py.typed +0 -0
- codex_sdk/thread.py +256 -0
- codex_sdk/thread_options.py +79 -0
- codex_sdk/turn_options.py +24 -0
- codex_sdk_py-0.0.3.dist-info/METADATA +423 -0
- codex_sdk_py-0.0.3.dist-info/RECORD +14 -0
- codex_sdk_py-0.0.3.dist-info/WHEEL +4 -0
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
|
+
"""
|