oauth-codex 2.2.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.2.0 → oauth_codex-2.3.1}/PKG-INFO +23 -9
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/README.md +22 -8
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/pyproject.toml +1 -1
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/__init__.py +2 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/_client.py +249 -77
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/_exceptions.py +93 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/_version.py +1 -1
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/core_types.py +17 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex.egg-info/PKG-INFO +23 -9
- {oauth_codex-2.2.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.2.0 → oauth_codex-2.3.1}/tests/test_generate_async.py +58 -7
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/tests/test_generate_sync.py +64 -25
- oauth_codex-2.3.1/tests/test_public_api_docstrings.py +48 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/tests/test_public_surface.py +2 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/setup.cfg +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/_base_client.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/_engine.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/_models.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/_module_client.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/_resource.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/_types.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/auth/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/auth/config.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/auth/pkce.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/auth/store.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/auth/token_manager.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/compat_store.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/errors.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/py.typed +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/_wrappers.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/files.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/models.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/responses/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/responses/_helpers.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/responses/input_tokens.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/responses/responses.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/vector_stores/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/vector_stores/file_batches.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/vector_stores/files.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/resources/vector_stores/vector_stores.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/store.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/tooling.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/file_deleted.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/file_object.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/responses/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/responses/input_token_count_response.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/responses/response.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/responses/response_stream_event.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/shared/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/shared/model_capabilities.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/shared/usage.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/vector_stores/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/vector_stores/vector_store.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/vector_stores/vector_store_deleted.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/vector_stores/vector_store_file.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/vector_stores/vector_store_file_batch.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/types/vector_stores/vector_store_search_response.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex/version.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex.egg-info/dependency_links.txt +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex.egg-info/requires.txt +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/src/oauth_codex.egg-info/top_level.txt +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.1}/tests/test_engine_stream_and_continuity.py +0 -0
- {oauth_codex-2.2.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
|
+
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
|
|
@@ -37,16 +37,30 @@ pip install oauth-codex
|
|
|
37
37
|
from oauth_codex import Client
|
|
38
38
|
|
|
39
39
|
client = Client()
|
|
40
|
-
text = client.generate("hello")
|
|
40
|
+
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
|
|
47
53
|
text = client.generate(
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
[
|
|
55
|
+
{
|
|
56
|
+
"role": "user",
|
|
57
|
+
"content": [
|
|
58
|
+
{"type": "input_text", "text": "Describe this image"},
|
|
59
|
+
{"type": "input_image", "image_url": "https://example.com/cat.png"},
|
|
60
|
+
{"type": "input_image", "image_url": "data:image/jpeg;base64,..."},
|
|
61
|
+
],
|
|
62
|
+
}
|
|
63
|
+
],
|
|
50
64
|
)
|
|
51
65
|
print(text)
|
|
52
66
|
```
|
|
@@ -58,7 +72,7 @@ def add(a: int, b: int) -> dict:
|
|
|
58
72
|
return {"sum": a + b}
|
|
59
73
|
|
|
60
74
|
text = client.generate(
|
|
61
|
-
"Calculate 2+3",
|
|
75
|
+
[{"role": "user", "content": "Calculate 2+3"}],
|
|
62
76
|
tools=[add],
|
|
63
77
|
)
|
|
64
78
|
print(text)
|
|
@@ -78,7 +92,7 @@ def tool(input: ToolInput) -> str:
|
|
|
78
92
|
return f"Tool received query: {input.query}"
|
|
79
93
|
|
|
80
94
|
|
|
81
|
-
text = client.generate("Use the tool", tools=[tool])
|
|
95
|
+
text = client.generate([{"role": "user", "content": "Use the tool"}], tools=[tool])
|
|
82
96
|
print(text)
|
|
83
97
|
```
|
|
84
98
|
|
|
@@ -100,7 +114,7 @@ class Summary(BaseModel):
|
|
|
100
114
|
|
|
101
115
|
client = Client()
|
|
102
116
|
out = client.generate(
|
|
103
|
-
"Return JSON with title and score",
|
|
117
|
+
[{"role": "user", "content": "Return JSON with title and score"}],
|
|
104
118
|
output_schema=Summary,
|
|
105
119
|
)
|
|
106
120
|
print(out) # {"title": "...", "score": 1}
|
|
@@ -110,7 +124,7 @@ You can also pass a raw JSON schema object.
|
|
|
110
124
|
|
|
111
125
|
```python
|
|
112
126
|
out = client.generate(
|
|
113
|
-
"Return {\"ok\": true}",
|
|
127
|
+
[{"role": "user", "content": "Return {\"ok\": true}"}],
|
|
114
128
|
output_schema={
|
|
115
129
|
"type": "object",
|
|
116
130
|
"properties": {"ok": {"type": "boolean"}},
|
|
@@ -131,7 +145,7 @@ from oauth_codex import Client
|
|
|
131
145
|
|
|
132
146
|
async def main() -> None:
|
|
133
147
|
client = Client()
|
|
134
|
-
text = await client.agenerate("hello async")
|
|
148
|
+
text = await client.agenerate([{"role": "user", "content": "hello async"}])
|
|
135
149
|
print(text)
|
|
136
150
|
|
|
137
151
|
|
|
@@ -23,16 +23,30 @@ pip install oauth-codex
|
|
|
23
23
|
from oauth_codex import Client
|
|
24
24
|
|
|
25
25
|
client = Client()
|
|
26
|
-
text = client.generate("hello")
|
|
26
|
+
text = client.generate([{"role": "user", "content": "hello"}])
|
|
27
27
|
print(text)
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
Authenticate immediately during client construction:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
client = Client(authenticate_on_init=True)
|
|
34
|
+
```
|
|
35
|
+
|
|
30
36
|
## Image Input
|
|
31
37
|
|
|
32
38
|
```python
|
|
33
39
|
text = client.generate(
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
[
|
|
41
|
+
{
|
|
42
|
+
"role": "user",
|
|
43
|
+
"content": [
|
|
44
|
+
{"type": "input_text", "text": "Describe this image"},
|
|
45
|
+
{"type": "input_image", "image_url": "https://example.com/cat.png"},
|
|
46
|
+
{"type": "input_image", "image_url": "data:image/jpeg;base64,..."},
|
|
47
|
+
],
|
|
48
|
+
}
|
|
49
|
+
],
|
|
36
50
|
)
|
|
37
51
|
print(text)
|
|
38
52
|
```
|
|
@@ -44,7 +58,7 @@ def add(a: int, b: int) -> dict:
|
|
|
44
58
|
return {"sum": a + b}
|
|
45
59
|
|
|
46
60
|
text = client.generate(
|
|
47
|
-
"Calculate 2+3",
|
|
61
|
+
[{"role": "user", "content": "Calculate 2+3"}],
|
|
48
62
|
tools=[add],
|
|
49
63
|
)
|
|
50
64
|
print(text)
|
|
@@ -64,7 +78,7 @@ def tool(input: ToolInput) -> str:
|
|
|
64
78
|
return f"Tool received query: {input.query}"
|
|
65
79
|
|
|
66
80
|
|
|
67
|
-
text = client.generate("Use the tool", tools=[tool])
|
|
81
|
+
text = client.generate([{"role": "user", "content": "Use the tool"}], tools=[tool])
|
|
68
82
|
print(text)
|
|
69
83
|
```
|
|
70
84
|
|
|
@@ -86,7 +100,7 @@ class Summary(BaseModel):
|
|
|
86
100
|
|
|
87
101
|
client = Client()
|
|
88
102
|
out = client.generate(
|
|
89
|
-
"Return JSON with title and score",
|
|
103
|
+
[{"role": "user", "content": "Return JSON with title and score"}],
|
|
90
104
|
output_schema=Summary,
|
|
91
105
|
)
|
|
92
106
|
print(out) # {"title": "...", "score": 1}
|
|
@@ -96,7 +110,7 @@ You can also pass a raw JSON schema object.
|
|
|
96
110
|
|
|
97
111
|
```python
|
|
98
112
|
out = client.generate(
|
|
99
|
-
"Return {\"ok\": true}",
|
|
113
|
+
[{"role": "user", "content": "Return {\"ok\": true}"}],
|
|
100
114
|
output_schema={
|
|
101
115
|
"type": "object",
|
|
102
116
|
"properties": {"ok": {"type": "boolean"}},
|
|
@@ -117,7 +131,7 @@ from oauth_codex import Client
|
|
|
117
131
|
|
|
118
132
|
async def main() -> None:
|
|
119
133
|
client = Client()
|
|
120
|
-
text = await client.agenerate("hello async")
|
|
134
|
+
text = await client.agenerate([{"role": "user", "content": "hello async"}])
|
|
121
135
|
print(text)
|
|
122
136
|
|
|
123
137
|
|
|
@@ -34,6 +34,7 @@ from ._exceptions import (
|
|
|
34
34
|
UnprocessableEntityError,
|
|
35
35
|
)
|
|
36
36
|
from ._version import __title__, __version__
|
|
37
|
+
from .core_types import listMessage
|
|
37
38
|
|
|
38
39
|
__all__ = [
|
|
39
40
|
"types",
|
|
@@ -41,6 +42,7 @@ __all__ = [
|
|
|
41
42
|
"__version__",
|
|
42
43
|
"Client",
|
|
43
44
|
"OAuthCodexClient",
|
|
45
|
+
"listMessage",
|
|
44
46
|
"OAuthCodexError",
|
|
45
47
|
"OpenAIError",
|
|
46
48
|
"APIError",
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import base64
|
|
4
3
|
import asyncio
|
|
5
4
|
import inspect
|
|
6
5
|
import json
|
|
7
|
-
import mimetypes
|
|
8
|
-
from pathlib import Path
|
|
9
6
|
from typing import Any, AsyncIterator, Callable, Iterator, get_type_hints
|
|
10
7
|
|
|
11
8
|
from pydantic import BaseModel
|
|
@@ -15,11 +12,10 @@ from ._engine import OAuthCodexClient as _EngineClient
|
|
|
15
12
|
from .auth.config import OAuthConfig
|
|
16
13
|
from .core_types import (
|
|
17
14
|
GenerateResult,
|
|
18
|
-
ImageInput,
|
|
19
|
-
Message,
|
|
20
15
|
ReasoningEffort,
|
|
21
16
|
ToolCall,
|
|
22
17
|
ToolResult,
|
|
18
|
+
listMessage,
|
|
23
19
|
)
|
|
24
20
|
from .store import FallbackTokenStore
|
|
25
21
|
from .tooling import build_strict_response_format, callable_to_tool_schema, normalize_tool_output
|
|
@@ -30,6 +26,29 @@ StructuredOutputSchema = type[BaseModel] | dict[str, Any]
|
|
|
30
26
|
|
|
31
27
|
|
|
32
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
|
+
|
|
33
52
|
def __init__(
|
|
34
53
|
self,
|
|
35
54
|
*,
|
|
@@ -44,7 +63,28 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
44
63
|
on_request_end: Any | None = None,
|
|
45
64
|
on_auth_refresh: Any | None = None,
|
|
46
65
|
on_error: Any | None = None,
|
|
66
|
+
authenticate_on_init: bool = False,
|
|
47
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
|
+
"""
|
|
48
88
|
super().__init__()
|
|
49
89
|
|
|
50
90
|
resolved_base_url = (
|
|
@@ -67,30 +107,82 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
67
107
|
)
|
|
68
108
|
self.default_model = DEFAULT_MODEL
|
|
69
109
|
self.max_tool_rounds = DEFAULT_MAX_TOOL_ROUNDS
|
|
110
|
+
if authenticate_on_init:
|
|
111
|
+
self.authenticate()
|
|
70
112
|
|
|
71
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
|
+
"""
|
|
72
119
|
return self._engine.is_authenticated()
|
|
73
120
|
|
|
74
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
|
+
"""
|
|
75
130
|
return self._engine.is_expired(leeway_seconds=leeway_seconds)
|
|
76
131
|
|
|
77
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
|
+
"""
|
|
78
141
|
return self._engine.refresh_if_needed(force=force)
|
|
79
142
|
|
|
80
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
|
+
"""
|
|
81
152
|
return await self._engine.arefresh_if_needed(force=force)
|
|
82
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
|
+
|
|
83
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
|
+
"""
|
|
84
170
|
self._engine.login()
|
|
85
171
|
|
|
86
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
|
+
"""
|
|
87
180
|
await asyncio.to_thread(self._engine.login)
|
|
88
181
|
|
|
89
182
|
def generate(
|
|
90
183
|
self,
|
|
91
|
-
|
|
184
|
+
messages: listMessage | None = None,
|
|
92
185
|
*,
|
|
93
|
-
images: ImageInput | list[ImageInput] | None = None,
|
|
94
186
|
tools: list[Callable[..., Any]] | None = None,
|
|
95
187
|
model: str | None = None,
|
|
96
188
|
reasoning_effort: ReasoningEffort = "medium",
|
|
@@ -100,7 +192,54 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
100
192
|
output_schema: StructuredOutputSchema | None = None,
|
|
101
193
|
strict_output: bool | None = None,
|
|
102
194
|
) -> str | dict[str, Any]:
|
|
103
|
-
|
|
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
|
+
"""
|
|
242
|
+
messages = self._normalize_initial_messages(messages)
|
|
104
243
|
normalized_tools, tools_by_name = self._normalize_tools(tools)
|
|
105
244
|
response_format, effective_strict_output = self._resolve_structured_output_options(
|
|
106
245
|
output_schema=output_schema,
|
|
@@ -149,9 +288,8 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
149
288
|
|
|
150
289
|
async def agenerate(
|
|
151
290
|
self,
|
|
152
|
-
|
|
291
|
+
messages: listMessage | None = None,
|
|
153
292
|
*,
|
|
154
|
-
images: ImageInput | list[ImageInput] | None = None,
|
|
155
293
|
tools: list[Callable[..., Any]] | None = None,
|
|
156
294
|
model: str | None = None,
|
|
157
295
|
reasoning_effort: ReasoningEffort = "medium",
|
|
@@ -161,7 +299,41 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
161
299
|
output_schema: StructuredOutputSchema | None = None,
|
|
162
300
|
strict_output: bool | None = None,
|
|
163
301
|
) -> str | dict[str, Any]:
|
|
164
|
-
|
|
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
|
+
"""
|
|
336
|
+
messages = self._normalize_initial_messages(messages)
|
|
165
337
|
normalized_tools, tools_by_name = self._normalize_tools(tools)
|
|
166
338
|
response_format, effective_strict_output = self._resolve_structured_output_options(
|
|
167
339
|
output_schema=output_schema,
|
|
@@ -210,9 +382,8 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
210
382
|
|
|
211
383
|
def stream(
|
|
212
384
|
self,
|
|
213
|
-
|
|
385
|
+
messages: listMessage | None = None,
|
|
214
386
|
*,
|
|
215
|
-
images: ImageInput | list[ImageInput] | None = None,
|
|
216
387
|
tools: list[Callable[..., Any]] | None = None,
|
|
217
388
|
model: str | None = None,
|
|
218
389
|
reasoning_effort: ReasoningEffort = "medium",
|
|
@@ -222,7 +393,33 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
222
393
|
output_schema: StructuredOutputSchema | None = None,
|
|
223
394
|
strict_output: bool | None = None,
|
|
224
395
|
) -> Iterator[str]:
|
|
225
|
-
|
|
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
|
+
"""
|
|
422
|
+
messages = self._normalize_initial_messages(messages)
|
|
226
423
|
normalized_tools, tools_by_name = self._normalize_tools(tools)
|
|
227
424
|
response_format, effective_strict_output = self._resolve_structured_output_options(
|
|
228
425
|
output_schema=output_schema,
|
|
@@ -271,9 +468,8 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
271
468
|
|
|
272
469
|
async def astream(
|
|
273
470
|
self,
|
|
274
|
-
|
|
471
|
+
messages: listMessage | None = None,
|
|
275
472
|
*,
|
|
276
|
-
images: ImageInput | list[ImageInput] | None = None,
|
|
277
473
|
tools: list[Callable[..., Any]] | None = None,
|
|
278
474
|
model: str | None = None,
|
|
279
475
|
reasoning_effort: ReasoningEffort = "medium",
|
|
@@ -283,7 +479,33 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
283
479
|
output_schema: StructuredOutputSchema | None = None,
|
|
284
480
|
strict_output: bool | None = None,
|
|
285
481
|
) -> AsyncIterator[str]:
|
|
286
|
-
|
|
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
|
+
"""
|
|
508
|
+
messages = self._normalize_initial_messages(messages)
|
|
287
509
|
normalized_tools, tools_by_name = self._normalize_tools(tools)
|
|
288
510
|
response_format, effective_strict_output = self._resolve_structured_output_options(
|
|
289
511
|
output_schema=output_schema,
|
|
@@ -375,10 +597,10 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
375
597
|
def _messages_for_round(
|
|
376
598
|
self,
|
|
377
599
|
*,
|
|
378
|
-
messages:
|
|
600
|
+
messages: listMessage,
|
|
379
601
|
previous_response_id: str | None,
|
|
380
602
|
tool_results: list[ToolResult] | None,
|
|
381
|
-
) ->
|
|
603
|
+
) -> listMessage:
|
|
382
604
|
if self._is_tool_continuation_round(
|
|
383
605
|
previous_response_id=previous_response_id,
|
|
384
606
|
tool_results=tool_results,
|
|
@@ -386,64 +608,14 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
386
608
|
return []
|
|
387
609
|
return messages
|
|
388
610
|
|
|
389
|
-
def
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if prompt is not None and not isinstance(prompt, str):
|
|
398
|
-
raise TypeError("prompt must be a string")
|
|
399
|
-
if prompt is None and not image_urls:
|
|
400
|
-
raise ValueError("Either prompt or images must be provided")
|
|
401
|
-
|
|
402
|
-
content: list[dict[str, Any]] = []
|
|
403
|
-
if prompt:
|
|
404
|
-
content.append({"type": "input_text", "text": prompt})
|
|
405
|
-
for image_url in image_urls:
|
|
406
|
-
content.append({"type": "input_image", "image_url": image_url})
|
|
407
|
-
|
|
408
|
-
if not content:
|
|
409
|
-
raise ValueError("Either prompt or images must be provided")
|
|
410
|
-
if len(content) == 1 and content[0]["type"] == "input_text":
|
|
411
|
-
return [{"role": "user", "content": content[0]["text"]}]
|
|
412
|
-
return [{"role": "user", "content": content}]
|
|
413
|
-
|
|
414
|
-
def _normalize_images(self, images: ImageInput | list[ImageInput] | None) -> list[str]:
|
|
415
|
-
if images is None:
|
|
416
|
-
return []
|
|
417
|
-
|
|
418
|
-
raw_items: list[ImageInput]
|
|
419
|
-
if isinstance(images, (str, Path)):
|
|
420
|
-
raw_items = [images]
|
|
421
|
-
elif isinstance(images, list):
|
|
422
|
-
raw_items = images
|
|
423
|
-
else:
|
|
424
|
-
raise TypeError("images must be a string/Path or list of string/Path")
|
|
425
|
-
|
|
426
|
-
normalized: list[str] = []
|
|
427
|
-
for item in raw_items:
|
|
428
|
-
normalized.append(self._coerce_image_to_url(item))
|
|
429
|
-
return normalized
|
|
430
|
-
|
|
431
|
-
def _coerce_image_to_url(self, image: ImageInput) -> str:
|
|
432
|
-
if isinstance(image, Path):
|
|
433
|
-
return self._path_to_data_url(image)
|
|
434
|
-
|
|
435
|
-
value = image.strip()
|
|
436
|
-
if value.startswith(("http://", "https://", "data:")):
|
|
437
|
-
return value
|
|
438
|
-
return self._path_to_data_url(Path(value).expanduser())
|
|
439
|
-
|
|
440
|
-
def _path_to_data_url(self, path: Path) -> str:
|
|
441
|
-
if not path.is_file():
|
|
442
|
-
raise FileNotFoundError(f"image file not found: {path}")
|
|
443
|
-
|
|
444
|
-
mime_type = mimetypes.guess_type(path.name)[0] or "application/octet-stream"
|
|
445
|
-
encoded = base64.b64encode(path.read_bytes()).decode("ascii")
|
|
446
|
-
return f"data:{mime_type};base64,{encoded}"
|
|
611
|
+
def _normalize_initial_messages(self, messages: listMessage | None) -> listMessage:
|
|
612
|
+
if messages is None:
|
|
613
|
+
raise ValueError("`messages` must be a non-empty list")
|
|
614
|
+
if not isinstance(messages, list):
|
|
615
|
+
raise TypeError("`messages` must be a non-empty list")
|
|
616
|
+
if not messages:
|
|
617
|
+
raise ValueError("`messages` must be a non-empty list")
|
|
618
|
+
return messages
|
|
447
619
|
|
|
448
620
|
def _normalize_tools(
|
|
449
621
|
self,
|