oauth-codex 2.3.0__tar.gz → 2.3.1__tar.gz
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.
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/PKG-INFO +7 -1
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/README.md +6 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/pyproject.toml +1 -1
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/_client.py +230 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/_exceptions.py +93 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/core_types.py +16 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex.egg-info/PKG-INFO +7 -1
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex.egg-info/SOURCES.txt +2 -0
- oauth_codex-2.3.1/tests/test_client_authentication.py +46 -0
- oauth_codex-2.3.1/tests/test_public_api_docstrings.py +48 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/setup.cfg +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/__init__.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/_base_client.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/_engine.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/_models.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/_module_client.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/_resource.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/_types.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/_version.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/auth/__init__.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/auth/config.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/auth/pkce.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/auth/store.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/auth/token_manager.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/compat_store.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/errors.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/py.typed +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/__init__.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/_wrappers.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/files.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/models.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/responses/__init__.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/responses/_helpers.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/responses/input_tokens.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/responses/responses.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/vector_stores/__init__.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/vector_stores/file_batches.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/vector_stores/files.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/vector_stores/vector_stores.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/store.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/tooling.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/__init__.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/file_deleted.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/file_object.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/responses/__init__.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/responses/input_token_count_response.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/responses/response.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/responses/response_stream_event.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/shared/__init__.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/shared/model_capabilities.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/shared/usage.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/vector_stores/__init__.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/vector_stores/vector_store.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/vector_stores/vector_store_deleted.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/vector_stores/vector_store_file.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/vector_stores/vector_store_file_batch.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/vector_stores/vector_store_search_response.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/version.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex.egg-info/dependency_links.txt +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex.egg-info/requires.txt +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex.egg-info/top_level.txt +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/tests/test_engine_stream_and_continuity.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/tests/test_generate_async.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/tests/test_generate_sync.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/tests/test_public_surface.py +0 -0
- {oauth_codex-2.3.0 → oauth_codex-2.3.1}/tests/test_tooling_strict_schema.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oauth-codex
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: Codex OAuth-based Python SDK with a single Client and generate-first API
|
|
5
5
|
Author: Codex
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -41,6 +41,12 @@ text = client.generate([{"role": "user", "content": "hello"}])
|
|
|
41
41
|
print(text)
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
Authenticate immediately during client construction:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
client = Client(authenticate_on_init=True)
|
|
48
|
+
```
|
|
49
|
+
|
|
44
50
|
## Image Input
|
|
45
51
|
|
|
46
52
|
```python
|
|
@@ -26,6 +26,29 @@ StructuredOutputSchema = type[BaseModel] | dict[str, Any]
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class OAuthCodexClient(SyncAPIClient):
|
|
29
|
+
"""Public single-entry client for oauth-codex.
|
|
30
|
+
|
|
31
|
+
This client wraps the OAuth-backed responses engine and exposes a
|
|
32
|
+
generate-first workflow:
|
|
33
|
+
|
|
34
|
+
- `generate` and `agenerate` return final text, or a validated JSON object
|
|
35
|
+
when `output_schema` is provided.
|
|
36
|
+
- `stream` and `astream` yield text deltas while still supporting
|
|
37
|
+
automatic tool-call continuation rounds.
|
|
38
|
+
|
|
39
|
+
Public attributes:
|
|
40
|
+
default_model: Model used when `model` is omitted. Defaults to
|
|
41
|
+
`"gpt-5.3-codex"`.
|
|
42
|
+
max_tool_rounds: Maximum number of automatic tool continuation rounds
|
|
43
|
+
before raising `RuntimeError`.
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
from oauth_codex import Client
|
|
47
|
+
|
|
48
|
+
client = Client(authenticate_on_init=True)
|
|
49
|
+
text = client.generate([{"role": "user", "content": "hello"}])
|
|
50
|
+
"""
|
|
51
|
+
|
|
29
52
|
def __init__(
|
|
30
53
|
self,
|
|
31
54
|
*,
|
|
@@ -40,7 +63,28 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
40
63
|
on_request_end: Any | None = None,
|
|
41
64
|
on_auth_refresh: Any | None = None,
|
|
42
65
|
on_error: Any | None = None,
|
|
66
|
+
authenticate_on_init: bool = False,
|
|
43
67
|
) -> None:
|
|
68
|
+
"""Create a `Client` instance.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
oauth_config: Optional OAuth endpoint/client configuration.
|
|
72
|
+
token_store: Token persistence backend. If omitted, a fallback
|
|
73
|
+
keyring/file store is used.
|
|
74
|
+
base_url: Base URL for Codex backend requests.
|
|
75
|
+
chatgpt_base_url: Legacy alias for `base_url`. `base_url` takes
|
|
76
|
+
precedence when both are provided.
|
|
77
|
+
timeout: Request timeout in seconds.
|
|
78
|
+
max_retries: Number of retry attempts for retryable failures.
|
|
79
|
+
compat_storage_dir: Optional local compatibility storage path.
|
|
80
|
+
on_request_start: Optional callback invoked when a request starts.
|
|
81
|
+
on_request_end: Optional callback invoked when a request ends.
|
|
82
|
+
on_auth_refresh: Optional callback invoked after token refresh.
|
|
83
|
+
on_error: Optional callback invoked when request/auth errors occur.
|
|
84
|
+
authenticate_on_init: When `True`, perform authentication during
|
|
85
|
+
client construction. If no stored token exists, interactive
|
|
86
|
+
login is started immediately.
|
|
87
|
+
"""
|
|
44
88
|
super().__init__()
|
|
45
89
|
|
|
46
90
|
resolved_base_url = (
|
|
@@ -63,23 +107,76 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
63
107
|
)
|
|
64
108
|
self.default_model = DEFAULT_MODEL
|
|
65
109
|
self.max_tool_rounds = DEFAULT_MAX_TOOL_ROUNDS
|
|
110
|
+
if authenticate_on_init:
|
|
111
|
+
self.authenticate()
|
|
66
112
|
|
|
67
113
|
def is_authenticated(self) -> bool:
|
|
114
|
+
"""Return whether usable OAuth tokens are currently available.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
`True` when a token is available, otherwise `False`.
|
|
118
|
+
"""
|
|
68
119
|
return self._engine.is_authenticated()
|
|
69
120
|
|
|
70
121
|
def is_expired(self, *, leeway_seconds: int = 60) -> bool:
|
|
122
|
+
"""Return whether the current token is expired or near expiry.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
leeway_seconds: Safety margin in seconds added before expiry check.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
`True` if the token is expired within the given leeway.
|
|
129
|
+
"""
|
|
71
130
|
return self._engine.is_expired(leeway_seconds=leeway_seconds)
|
|
72
131
|
|
|
73
132
|
def refresh_if_needed(self, *, force: bool = False) -> bool:
|
|
133
|
+
"""Refresh tokens when needed.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
force: Refresh even when token is not currently expired.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
`True` if refresh happened and succeeded, otherwise `False`.
|
|
140
|
+
"""
|
|
74
141
|
return self._engine.refresh_if_needed(force=force)
|
|
75
142
|
|
|
76
143
|
async def arefresh_if_needed(self, *, force: bool = False) -> bool:
|
|
144
|
+
"""Async version of `refresh_if_needed`.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
force: Refresh even when token is not currently expired.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
`True` if refresh happened and succeeded, otherwise `False`.
|
|
151
|
+
"""
|
|
77
152
|
return await self._engine.arefresh_if_needed(force=force)
|
|
78
153
|
|
|
154
|
+
def authenticate(self) -> None:
|
|
155
|
+
"""Ensure usable OAuth credentials are available now.
|
|
156
|
+
|
|
157
|
+
This loads stored credentials, starts interactive login when missing,
|
|
158
|
+
and refreshes tokens when expired.
|
|
159
|
+
"""
|
|
160
|
+
self._engine._ensure_authenticated_sync()
|
|
161
|
+
|
|
79
162
|
def login(self) -> None:
|
|
163
|
+
"""Run interactive OAuth login flow in the current thread.
|
|
164
|
+
|
|
165
|
+
Raises:
|
|
166
|
+
OAuthCallbackParseError: If callback URL parsing fails.
|
|
167
|
+
OAuthStateMismatchError: If OAuth state verification fails.
|
|
168
|
+
TokenExchangeError: If code-to-token exchange fails.
|
|
169
|
+
"""
|
|
80
170
|
self._engine.login()
|
|
81
171
|
|
|
82
172
|
async def alogin(self) -> None:
|
|
173
|
+
"""Run interactive OAuth login flow without blocking the event loop.
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
OAuthCallbackParseError: If callback URL parsing fails.
|
|
177
|
+
OAuthStateMismatchError: If OAuth state verification fails.
|
|
178
|
+
TokenExchangeError: If code-to-token exchange fails.
|
|
179
|
+
"""
|
|
83
180
|
await asyncio.to_thread(self._engine.login)
|
|
84
181
|
|
|
85
182
|
def generate(
|
|
@@ -95,6 +192,53 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
95
192
|
output_schema: StructuredOutputSchema | None = None,
|
|
96
193
|
strict_output: bool | None = None,
|
|
97
194
|
) -> str | dict[str, Any]:
|
|
195
|
+
"""Generate a final response with automatic tool execution.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
messages: Non-empty list of response input messages.
|
|
199
|
+
tools: Optional list of Python callables used for automatic
|
|
200
|
+
function-calling rounds.
|
|
201
|
+
model: Optional model override. Uses `default_model` when omitted.
|
|
202
|
+
reasoning_effort: Reasoning intensity (`"low"`, `"medium"`,
|
|
203
|
+
`"high"`).
|
|
204
|
+
temperature: Sampling temperature.
|
|
205
|
+
top_p: Nucleus sampling probability.
|
|
206
|
+
max_output_tokens: Maximum generated output tokens.
|
|
207
|
+
output_schema: Optional structured-output schema. Accepts a
|
|
208
|
+
Pydantic model type or JSON schema dict.
|
|
209
|
+
strict_output: Strict schema mode. When `output_schema` is set and
|
|
210
|
+
`strict_output` is omitted, strict mode is enabled by default.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Final text output, or a validated JSON object when `output_schema`
|
|
214
|
+
is provided.
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
ValueError: If `messages` is missing/empty, or structured output is
|
|
218
|
+
not valid JSON.
|
|
219
|
+
TypeError: If `messages` is not a list, `tools` are invalid, or
|
|
220
|
+
structured output is not a JSON object.
|
|
221
|
+
RuntimeError: If automatic tool execution exceeds
|
|
222
|
+
`max_tool_rounds`.
|
|
223
|
+
pydantic.ValidationError: If a Pydantic `output_schema` fails
|
|
224
|
+
strict validation.
|
|
225
|
+
|
|
226
|
+
Examples:
|
|
227
|
+
Basic text generation:
|
|
228
|
+
text = client.generate([{"role": "user", "content": "hello"}])
|
|
229
|
+
|
|
230
|
+
Structured output:
|
|
231
|
+
from pydantic import BaseModel
|
|
232
|
+
|
|
233
|
+
class Summary(BaseModel):
|
|
234
|
+
title: str
|
|
235
|
+
score: int
|
|
236
|
+
|
|
237
|
+
data = client.generate(
|
|
238
|
+
[{"role": "user", "content": "Return JSON"}],
|
|
239
|
+
output_schema=Summary,
|
|
240
|
+
)
|
|
241
|
+
"""
|
|
98
242
|
messages = self._normalize_initial_messages(messages)
|
|
99
243
|
normalized_tools, tools_by_name = self._normalize_tools(tools)
|
|
100
244
|
response_format, effective_strict_output = self._resolve_structured_output_options(
|
|
@@ -155,6 +299,40 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
155
299
|
output_schema: StructuredOutputSchema | None = None,
|
|
156
300
|
strict_output: bool | None = None,
|
|
157
301
|
) -> str | dict[str, Any]:
|
|
302
|
+
"""Async response generation with automatic tool execution.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
messages: Non-empty list of response input messages.
|
|
306
|
+
tools: Optional list of Python callables used for automatic
|
|
307
|
+
function-calling rounds.
|
|
308
|
+
model: Optional model override. Uses `default_model` when omitted.
|
|
309
|
+
reasoning_effort: Reasoning intensity (`"low"`, `"medium"`,
|
|
310
|
+
`"high"`).
|
|
311
|
+
temperature: Sampling temperature.
|
|
312
|
+
top_p: Nucleus sampling probability.
|
|
313
|
+
max_output_tokens: Maximum generated output tokens.
|
|
314
|
+
output_schema: Optional structured-output schema. Accepts a
|
|
315
|
+
Pydantic model type or JSON schema dict.
|
|
316
|
+
strict_output: Strict schema mode. When `output_schema` is set and
|
|
317
|
+
`strict_output` is omitted, strict mode is enabled by default.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
Final text output, or a validated JSON object when `output_schema`
|
|
321
|
+
is provided.
|
|
322
|
+
|
|
323
|
+
Raises:
|
|
324
|
+
ValueError: If `messages` is missing/empty, or structured output is
|
|
325
|
+
not valid JSON.
|
|
326
|
+
TypeError: If `messages` is not a list, `tools` are invalid, or
|
|
327
|
+
structured output is not a JSON object.
|
|
328
|
+
RuntimeError: If automatic tool execution exceeds
|
|
329
|
+
`max_tool_rounds`.
|
|
330
|
+
pydantic.ValidationError: If a Pydantic `output_schema` fails
|
|
331
|
+
strict validation.
|
|
332
|
+
|
|
333
|
+
Examples:
|
|
334
|
+
text = await client.agenerate([{"role": "user", "content": "hello"}])
|
|
335
|
+
"""
|
|
158
336
|
messages = self._normalize_initial_messages(messages)
|
|
159
337
|
normalized_tools, tools_by_name = self._normalize_tools(tools)
|
|
160
338
|
response_format, effective_strict_output = self._resolve_structured_output_options(
|
|
@@ -215,6 +393,32 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
215
393
|
output_schema: StructuredOutputSchema | None = None,
|
|
216
394
|
strict_output: bool | None = None,
|
|
217
395
|
) -> Iterator[str]:
|
|
396
|
+
"""Stream text deltas with automatic tool execution rounds.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
messages: Non-empty list of response input messages.
|
|
400
|
+
tools: Optional list of Python callables used for automatic
|
|
401
|
+
function-calling rounds.
|
|
402
|
+
model: Optional model override. Uses `default_model` when omitted.
|
|
403
|
+
reasoning_effort: Reasoning intensity (`"low"`, `"medium"`,
|
|
404
|
+
`"high"`).
|
|
405
|
+
temperature: Sampling temperature.
|
|
406
|
+
top_p: Nucleus sampling probability.
|
|
407
|
+
max_output_tokens: Maximum generated output tokens.
|
|
408
|
+
output_schema: Optional structured-output schema forwarded to the
|
|
409
|
+
backend. Stream output is still text deltas only.
|
|
410
|
+
strict_output: Strict schema mode. When `output_schema` is set and
|
|
411
|
+
`strict_output` is omitted, strict mode is enabled by default.
|
|
412
|
+
|
|
413
|
+
Yields:
|
|
414
|
+
Text delta chunks as they arrive.
|
|
415
|
+
|
|
416
|
+
Raises:
|
|
417
|
+
ValueError: If `messages` is missing/empty.
|
|
418
|
+
TypeError: If `messages` is not a list or `tools` are invalid.
|
|
419
|
+
RuntimeError: If automatic tool execution exceeds
|
|
420
|
+
`max_tool_rounds`.
|
|
421
|
+
"""
|
|
218
422
|
messages = self._normalize_initial_messages(messages)
|
|
219
423
|
normalized_tools, tools_by_name = self._normalize_tools(tools)
|
|
220
424
|
response_format, effective_strict_output = self._resolve_structured_output_options(
|
|
@@ -275,6 +479,32 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
275
479
|
output_schema: StructuredOutputSchema | None = None,
|
|
276
480
|
strict_output: bool | None = None,
|
|
277
481
|
) -> AsyncIterator[str]:
|
|
482
|
+
"""Async stream of text deltas with automatic tool execution rounds.
|
|
483
|
+
|
|
484
|
+
Args:
|
|
485
|
+
messages: Non-empty list of response input messages.
|
|
486
|
+
tools: Optional list of Python callables used for automatic
|
|
487
|
+
function-calling rounds.
|
|
488
|
+
model: Optional model override. Uses `default_model` when omitted.
|
|
489
|
+
reasoning_effort: Reasoning intensity (`"low"`, `"medium"`,
|
|
490
|
+
`"high"`).
|
|
491
|
+
temperature: Sampling temperature.
|
|
492
|
+
top_p: Nucleus sampling probability.
|
|
493
|
+
max_output_tokens: Maximum generated output tokens.
|
|
494
|
+
output_schema: Optional structured-output schema forwarded to the
|
|
495
|
+
backend. Stream output is still text deltas only.
|
|
496
|
+
strict_output: Strict schema mode. When `output_schema` is set and
|
|
497
|
+
`strict_output` is omitted, strict mode is enabled by default.
|
|
498
|
+
|
|
499
|
+
Yields:
|
|
500
|
+
Text delta chunks as they arrive.
|
|
501
|
+
|
|
502
|
+
Raises:
|
|
503
|
+
ValueError: If `messages` is missing/empty.
|
|
504
|
+
TypeError: If `messages` is not a list or `tools` are invalid.
|
|
505
|
+
RuntimeError: If automatic tool execution exceeds
|
|
506
|
+
`max_tool_rounds`.
|
|
507
|
+
"""
|
|
278
508
|
messages = self._normalize_initial_messages(messages)
|
|
279
509
|
normalized_tools, tools_by_name = self._normalize_tools(tools)
|
|
280
510
|
response_format, effective_strict_output = self._resolve_structured_output_options(
|
|
@@ -6,14 +6,29 @@ import httpx
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class OAuthCodexError(Exception):
|
|
9
|
+
"""Base class for all public oauth-codex exceptions."""
|
|
10
|
+
|
|
9
11
|
pass
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class OpenAIError(OAuthCodexError):
|
|
15
|
+
"""Compatibility base error alias for OpenAI-style exception handling."""
|
|
16
|
+
|
|
13
17
|
pass
|
|
14
18
|
|
|
15
19
|
|
|
16
20
|
class APIError(OpenAIError):
|
|
21
|
+
"""Base error for request/response failures returned by the API layer.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
message: Human-readable error message.
|
|
25
|
+
request: Originating `httpx.Request` when available.
|
|
26
|
+
body: Raw parsed error body when available.
|
|
27
|
+
code: Provider error code extracted from `body` when present.
|
|
28
|
+
param: Provider parameter field extracted from `body` when present.
|
|
29
|
+
type: Provider error type extracted from `body` when present.
|
|
30
|
+
"""
|
|
31
|
+
|
|
17
32
|
def __init__(self, message: str, request: httpx.Request | None = None, *, body: object | None = None) -> None:
|
|
18
33
|
super().__init__(message)
|
|
19
34
|
self.message = message
|
|
@@ -32,6 +47,14 @@ class APIError(OpenAIError):
|
|
|
32
47
|
|
|
33
48
|
|
|
34
49
|
class APIStatusError(APIError):
|
|
50
|
+
"""HTTP status error with access to response metadata.
|
|
51
|
+
|
|
52
|
+
Attributes:
|
|
53
|
+
response: Full `httpx.Response`.
|
|
54
|
+
status_code: HTTP status code from the response.
|
|
55
|
+
request_id: Request identifier from `x-request-id` header when present.
|
|
56
|
+
"""
|
|
57
|
+
|
|
35
58
|
def __init__(self, message: str, *, response: httpx.Response, body: object | None = None) -> None:
|
|
36
59
|
super().__init__(message, response.request, body=body)
|
|
37
60
|
self.response = response
|
|
@@ -40,48 +63,70 @@ class APIStatusError(APIError):
|
|
|
40
63
|
|
|
41
64
|
|
|
42
65
|
class APIConnectionError(APIError):
|
|
66
|
+
"""Network-level connection failure while talking to the API."""
|
|
67
|
+
|
|
43
68
|
def __init__(self, *, message: str = "Connection error.", request: httpx.Request | None = None) -> None:
|
|
44
69
|
super().__init__(message, request, body=None)
|
|
45
70
|
|
|
46
71
|
|
|
47
72
|
class APITimeoutError(APIConnectionError):
|
|
73
|
+
"""Request timeout while waiting for API response."""
|
|
74
|
+
|
|
48
75
|
def __init__(self, request: httpx.Request | None = None) -> None:
|
|
49
76
|
super().__init__(message="Request timed out.", request=request)
|
|
50
77
|
|
|
51
78
|
|
|
52
79
|
class BadRequestError(APIStatusError):
|
|
80
|
+
"""HTTP 400 response from the API."""
|
|
81
|
+
|
|
53
82
|
pass
|
|
54
83
|
|
|
55
84
|
|
|
56
85
|
class AuthenticationError(APIStatusError):
|
|
86
|
+
"""HTTP 401 response from the API."""
|
|
87
|
+
|
|
57
88
|
pass
|
|
58
89
|
|
|
59
90
|
|
|
60
91
|
class PermissionDeniedError(APIStatusError):
|
|
92
|
+
"""HTTP 403 response from the API."""
|
|
93
|
+
|
|
61
94
|
pass
|
|
62
95
|
|
|
63
96
|
|
|
64
97
|
class NotFoundError(APIStatusError):
|
|
98
|
+
"""HTTP 404 response from the API."""
|
|
99
|
+
|
|
65
100
|
pass
|
|
66
101
|
|
|
67
102
|
|
|
68
103
|
class ConflictError(APIStatusError):
|
|
104
|
+
"""HTTP 409 response from the API."""
|
|
105
|
+
|
|
69
106
|
pass
|
|
70
107
|
|
|
71
108
|
|
|
72
109
|
class UnprocessableEntityError(APIStatusError):
|
|
110
|
+
"""HTTP 422 response from the API."""
|
|
111
|
+
|
|
73
112
|
pass
|
|
74
113
|
|
|
75
114
|
|
|
76
115
|
class RateLimitError(APIStatusError):
|
|
116
|
+
"""HTTP 429 response from the API."""
|
|
117
|
+
|
|
77
118
|
pass
|
|
78
119
|
|
|
79
120
|
|
|
80
121
|
class InternalServerError(APIStatusError):
|
|
122
|
+
"""HTTP 5xx server error returned by the API."""
|
|
123
|
+
|
|
81
124
|
pass
|
|
82
125
|
|
|
83
126
|
|
|
84
127
|
class APIResponseValidationError(APIError):
|
|
128
|
+
"""API response payload failed SDK-side schema validation."""
|
|
129
|
+
|
|
85
130
|
def __init__(self, response: httpx.Response, body: object | None, *, message: str | None = None) -> None:
|
|
86
131
|
super().__init__(message or "Data returned by API invalid for expected schema.", response.request, body=body)
|
|
87
132
|
self.response = response
|
|
@@ -89,56 +134,95 @@ class APIResponseValidationError(APIError):
|
|
|
89
134
|
|
|
90
135
|
|
|
91
136
|
class OAuthCallbackParseError(OAuthCodexError):
|
|
137
|
+
"""OAuth browser callback could not be parsed."""
|
|
138
|
+
|
|
92
139
|
pass
|
|
93
140
|
|
|
94
141
|
|
|
95
142
|
class OAuthStateMismatchError(OAuthCodexError):
|
|
143
|
+
"""OAuth callback state does not match the initiated login request."""
|
|
144
|
+
|
|
96
145
|
pass
|
|
97
146
|
|
|
98
147
|
|
|
99
148
|
class TokenExchangeError(OAuthCodexError):
|
|
149
|
+
"""OAuth authorization code exchange for tokens failed."""
|
|
150
|
+
|
|
100
151
|
pass
|
|
101
152
|
|
|
102
153
|
|
|
103
154
|
class TokenRefreshError(OAuthCodexError):
|
|
155
|
+
"""Refreshing an existing OAuth token failed."""
|
|
156
|
+
|
|
104
157
|
pass
|
|
105
158
|
|
|
106
159
|
|
|
107
160
|
class AuthRequiredError(OAuthCodexError):
|
|
161
|
+
"""Authenticated call was attempted without valid credentials."""
|
|
162
|
+
|
|
108
163
|
pass
|
|
109
164
|
|
|
110
165
|
|
|
111
166
|
class ParameterValidationError(OAuthCodexError):
|
|
167
|
+
"""Input parameters failed SDK validation before sending request."""
|
|
168
|
+
|
|
112
169
|
pass
|
|
113
170
|
|
|
114
171
|
|
|
115
172
|
class ModelValidationError(OAuthCodexError):
|
|
173
|
+
"""Model capability or model name validation failed."""
|
|
174
|
+
|
|
116
175
|
pass
|
|
117
176
|
|
|
118
177
|
|
|
119
178
|
class ContinuityError(OAuthCodexError):
|
|
179
|
+
"""Response continuity state is invalid for continuation requests."""
|
|
180
|
+
|
|
120
181
|
pass
|
|
121
182
|
|
|
122
183
|
|
|
123
184
|
class TokenStoreError(OAuthCodexError):
|
|
185
|
+
"""Base class for token persistence layer failures.
|
|
186
|
+
|
|
187
|
+
Attributes:
|
|
188
|
+
cause: Original exception raised by the storage backend, when present.
|
|
189
|
+
"""
|
|
190
|
+
|
|
124
191
|
def __init__(self, message: str, *, cause: Exception | None = None) -> None:
|
|
125
192
|
super().__init__(message)
|
|
126
193
|
self.cause = cause
|
|
127
194
|
|
|
128
195
|
|
|
129
196
|
class TokenStoreReadError(TokenStoreError):
|
|
197
|
+
"""Token load/read operation failed."""
|
|
198
|
+
|
|
130
199
|
pass
|
|
131
200
|
|
|
132
201
|
|
|
133
202
|
class TokenStoreWriteError(TokenStoreError):
|
|
203
|
+
"""Token save/write operation failed."""
|
|
204
|
+
|
|
134
205
|
pass
|
|
135
206
|
|
|
136
207
|
|
|
137
208
|
class TokenStoreDeleteError(TokenStoreError):
|
|
209
|
+
"""Token delete operation failed."""
|
|
210
|
+
|
|
138
211
|
pass
|
|
139
212
|
|
|
140
213
|
|
|
141
214
|
class SDKRequestError(OAuthCodexError):
|
|
215
|
+
"""Normalized SDK error for provider and compatibility request failures.
|
|
216
|
+
|
|
217
|
+
Attributes:
|
|
218
|
+
status_code: HTTP-like status code when available.
|
|
219
|
+
provider_code: Provider-specific machine-readable error code.
|
|
220
|
+
user_message: Human-readable message intended for callers.
|
|
221
|
+
retryable: Whether the request may succeed on retry.
|
|
222
|
+
request_id: Provider request identifier when available.
|
|
223
|
+
raw_error: Original provider payload or underlying exception data.
|
|
224
|
+
"""
|
|
225
|
+
|
|
142
226
|
def __init__(
|
|
143
227
|
self,
|
|
144
228
|
*,
|
|
@@ -159,6 +243,8 @@ class SDKRequestError(OAuthCodexError):
|
|
|
159
243
|
|
|
160
244
|
|
|
161
245
|
class NotSupportedError(SDKRequestError):
|
|
246
|
+
"""Requested operation is not supported by the active backend/profile."""
|
|
247
|
+
|
|
162
248
|
def __init__(self, message: str, *, code: str = "not_supported") -> None:
|
|
163
249
|
super().__init__(
|
|
164
250
|
status_code=400,
|
|
@@ -170,6 +256,13 @@ class NotSupportedError(SDKRequestError):
|
|
|
170
256
|
|
|
171
257
|
|
|
172
258
|
class ToolCallRequiredError(OAuthCodexError):
|
|
259
|
+
"""Model response requires tool execution before completion.
|
|
260
|
+
|
|
261
|
+
Attributes:
|
|
262
|
+
message: Human-readable explanation for the tool requirement.
|
|
263
|
+
tool_calls: Tool call payloads that must be executed.
|
|
264
|
+
"""
|
|
265
|
+
|
|
173
266
|
def __init__(self, message: str, tool_calls: list[dict[str, Any]] | None = None) -> None:
|
|
174
267
|
super().__init__(message)
|
|
175
268
|
self.message = message
|
|
@@ -1,10 +1,26 @@
|
|
|
1
|
+
"""Core public data types used by oauth-codex.
|
|
2
|
+
|
|
3
|
+
`Message` represents a single response input message dictionary, and
|
|
4
|
+
`listMessage` is the list container passed to `Client.generate`,
|
|
5
|
+
`Client.stream`, `Client.agenerate`, and `Client.astream`.
|
|
6
|
+
|
|
7
|
+
Recommended minimal message shape:
|
|
8
|
+
|
|
9
|
+
[{"role": "user", "content": "hello"}]
|
|
10
|
+
|
|
11
|
+
`content` may also be a list of input items such as `input_text` and
|
|
12
|
+
`input_image`.
|
|
13
|
+
"""
|
|
14
|
+
|
|
1
15
|
from __future__ import annotations
|
|
2
16
|
|
|
3
17
|
from dataclasses import dataclass, field
|
|
4
18
|
from pathlib import Path
|
|
5
19
|
from typing import Any, Callable, Literal, Protocol, TypeAlias
|
|
6
20
|
|
|
21
|
+
#: A single response input message item (for example role/content dict).
|
|
7
22
|
Message: TypeAlias = dict[str, Any]
|
|
23
|
+
#: List of response input messages accepted by `Client` generation methods.
|
|
8
24
|
listMessage: TypeAlias = list[Message]
|
|
9
25
|
ValidationMode: TypeAlias = Literal["warn", "error", "ignore"]
|
|
10
26
|
StoreBehavior: TypeAlias = Literal["auto_disable", "error", "passthrough"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oauth-codex
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: Codex OAuth-based Python SDK with a single Client and generate-first API
|
|
5
5
|
Author: Codex
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -41,6 +41,12 @@ text = client.generate([{"role": "user", "content": "hello"}])
|
|
|
41
41
|
print(text)
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
Authenticate immediately during client construction:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
client = Client(authenticate_on_init=True)
|
|
48
|
+
```
|
|
49
|
+
|
|
44
50
|
## Image Input
|
|
45
51
|
|
|
46
52
|
```python
|
|
@@ -55,8 +55,10 @@ src/oauth_codex/types/vector_stores/vector_store_deleted.py
|
|
|
55
55
|
src/oauth_codex/types/vector_stores/vector_store_file.py
|
|
56
56
|
src/oauth_codex/types/vector_stores/vector_store_file_batch.py
|
|
57
57
|
src/oauth_codex/types/vector_stores/vector_store_search_response.py
|
|
58
|
+
tests/test_client_authentication.py
|
|
58
59
|
tests/test_engine_stream_and_continuity.py
|
|
59
60
|
tests/test_generate_async.py
|
|
60
61
|
tests/test_generate_sync.py
|
|
62
|
+
tests/test_public_api_docstrings.py
|
|
61
63
|
tests/test_public_surface.py
|
|
62
64
|
tests/test_tooling_strict_schema.py
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from conftest import InMemoryTokenStore
|
|
6
|
+
from oauth_codex import Client
|
|
7
|
+
from oauth_codex._client import _EngineClient
|
|
8
|
+
from oauth_codex.core_types import OAuthTokens
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _tokens() -> OAuthTokens:
|
|
12
|
+
return OAuthTokens(access_token="a", refresh_token="r", expires_at=9_999_999_999)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_client_does_not_authenticate_on_init_by_default(
|
|
16
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
17
|
+
) -> None:
|
|
18
|
+
ensure_calls = 0
|
|
19
|
+
|
|
20
|
+
def fake_ensure(self: _EngineClient) -> OAuthTokens:
|
|
21
|
+
nonlocal ensure_calls
|
|
22
|
+
ensure_calls += 1
|
|
23
|
+
return _tokens()
|
|
24
|
+
|
|
25
|
+
monkeypatch.setattr(_EngineClient, "_ensure_authenticated_sync", fake_ensure)
|
|
26
|
+
|
|
27
|
+
Client(token_store=InMemoryTokenStore(_tokens()))
|
|
28
|
+
|
|
29
|
+
assert ensure_calls == 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_client_authenticates_on_init_when_enabled(
|
|
33
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
34
|
+
) -> None:
|
|
35
|
+
ensure_calls = 0
|
|
36
|
+
|
|
37
|
+
def fake_ensure(self: _EngineClient) -> OAuthTokens:
|
|
38
|
+
nonlocal ensure_calls
|
|
39
|
+
ensure_calls += 1
|
|
40
|
+
return _tokens()
|
|
41
|
+
|
|
42
|
+
monkeypatch.setattr(_EngineClient, "_ensure_authenticated_sync", fake_ensure)
|
|
43
|
+
|
|
44
|
+
Client(token_store=InMemoryTokenStore(), authenticate_on_init=True)
|
|
45
|
+
|
|
46
|
+
assert ensure_calls == 1
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
|
|
5
|
+
import oauth_codex
|
|
6
|
+
import oauth_codex.core_types as core_types
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_client_class_docstring_exists() -> None:
|
|
10
|
+
doc = inspect.getdoc(oauth_codex.Client)
|
|
11
|
+
assert doc is not None
|
|
12
|
+
assert "default_model" in doc
|
|
13
|
+
assert "max_tool_rounds" in doc
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_generate_docstring_includes_key_sections() -> None:
|
|
17
|
+
doc = inspect.getdoc(oauth_codex.Client.generate)
|
|
18
|
+
assert doc is not None
|
|
19
|
+
for keyword in ("messages", "tools", "output_schema", "Returns", "Raises"):
|
|
20
|
+
assert keyword in doc
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_agenerate_docstring_includes_key_sections() -> None:
|
|
24
|
+
doc = inspect.getdoc(oauth_codex.Client.agenerate)
|
|
25
|
+
assert doc is not None
|
|
26
|
+
for keyword in ("messages", "tools", "output_schema", "Returns", "Raises"):
|
|
27
|
+
assert keyword in doc
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_stream_docstring_includes_streaming_context() -> None:
|
|
31
|
+
doc = inspect.getdoc(oauth_codex.Client.stream)
|
|
32
|
+
assert doc is not None
|
|
33
|
+
for keyword in ("messages", "tools", "output_schema", "Yields", "Raises"):
|
|
34
|
+
assert keyword in doc
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_root_exported_exceptions_have_docstrings() -> None:
|
|
38
|
+
for name in oauth_codex.__all__:
|
|
39
|
+
exported = getattr(oauth_codex, name)
|
|
40
|
+
if inspect.isclass(exported) and issubclass(exported, Exception):
|
|
41
|
+
assert inspect.getdoc(exported), f"missing docstring for {name}"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_core_types_module_doc_mentions_list_message() -> None:
|
|
45
|
+
doc = inspect.getdoc(core_types)
|
|
46
|
+
assert doc is not None
|
|
47
|
+
assert "Message" in doc
|
|
48
|
+
assert "listMessage" in doc
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/vector_stores/file_batches.py
RENAMED
|
File without changes
|
|
File without changes
|
{oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/vector_stores/vector_stores.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/responses/response_stream_event.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/vector_stores/vector_store_deleted.py
RENAMED
|
File without changes
|
{oauth_codex-2.3.0 → oauth_codex-2.3.1}/src/oauth_codex/types/vector_stores/vector_store_file.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|