oauth-codex 2.2.0__tar.gz → 2.3.0__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.0}/PKG-INFO +17 -9
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/README.md +16 -8
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/pyproject.toml +1 -1
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/__init__.py +2 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/_client.py +19 -77
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/_version.py +1 -1
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/core_types.py +1 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex.egg-info/PKG-INFO +17 -9
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/tests/test_generate_async.py +58 -7
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/tests/test_generate_sync.py +64 -25
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/tests/test_public_surface.py +2 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/setup.cfg +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/_base_client.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/_engine.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/_exceptions.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/_models.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/_module_client.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/_resource.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/_types.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/auth/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/auth/config.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/auth/pkce.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/auth/store.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/auth/token_manager.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/compat_store.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/errors.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/py.typed +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/resources/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/resources/_wrappers.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/resources/files.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/resources/models.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/resources/responses/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/resources/responses/_helpers.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/resources/responses/input_tokens.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/resources/responses/responses.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/resources/vector_stores/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/resources/vector_stores/file_batches.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/resources/vector_stores/files.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/resources/vector_stores/vector_stores.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/store.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/tooling.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/file_deleted.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/file_object.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/responses/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/responses/input_token_count_response.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/responses/response.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/responses/response_stream_event.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/shared/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/shared/model_capabilities.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/shared/usage.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/vector_stores/__init__.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/vector_stores/vector_store.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/vector_stores/vector_store_deleted.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/vector_stores/vector_store_file.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/vector_stores/vector_store_file_batch.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/vector_stores/vector_store_search_response.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex/version.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex.egg-info/SOURCES.txt +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex.egg-info/dependency_links.txt +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex.egg-info/requires.txt +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/src/oauth_codex.egg-info/top_level.txt +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/tests/test_engine_stream_and_continuity.py +0 -0
- {oauth_codex-2.2.0 → oauth_codex-2.3.0}/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.0
|
|
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,7 +37,7 @@ 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
|
|
|
@@ -45,8 +45,16 @@ print(text)
|
|
|
45
45
|
|
|
46
46
|
```python
|
|
47
47
|
text = client.generate(
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
[
|
|
49
|
+
{
|
|
50
|
+
"role": "user",
|
|
51
|
+
"content": [
|
|
52
|
+
{"type": "input_text", "text": "Describe this image"},
|
|
53
|
+
{"type": "input_image", "image_url": "https://example.com/cat.png"},
|
|
54
|
+
{"type": "input_image", "image_url": "data:image/jpeg;base64,..."},
|
|
55
|
+
],
|
|
56
|
+
}
|
|
57
|
+
],
|
|
50
58
|
)
|
|
51
59
|
print(text)
|
|
52
60
|
```
|
|
@@ -58,7 +66,7 @@ def add(a: int, b: int) -> dict:
|
|
|
58
66
|
return {"sum": a + b}
|
|
59
67
|
|
|
60
68
|
text = client.generate(
|
|
61
|
-
"Calculate 2+3",
|
|
69
|
+
[{"role": "user", "content": "Calculate 2+3"}],
|
|
62
70
|
tools=[add],
|
|
63
71
|
)
|
|
64
72
|
print(text)
|
|
@@ -78,7 +86,7 @@ def tool(input: ToolInput) -> str:
|
|
|
78
86
|
return f"Tool received query: {input.query}"
|
|
79
87
|
|
|
80
88
|
|
|
81
|
-
text = client.generate("Use the tool", tools=[tool])
|
|
89
|
+
text = client.generate([{"role": "user", "content": "Use the tool"}], tools=[tool])
|
|
82
90
|
print(text)
|
|
83
91
|
```
|
|
84
92
|
|
|
@@ -100,7 +108,7 @@ class Summary(BaseModel):
|
|
|
100
108
|
|
|
101
109
|
client = Client()
|
|
102
110
|
out = client.generate(
|
|
103
|
-
"Return JSON with title and score",
|
|
111
|
+
[{"role": "user", "content": "Return JSON with title and score"}],
|
|
104
112
|
output_schema=Summary,
|
|
105
113
|
)
|
|
106
114
|
print(out) # {"title": "...", "score": 1}
|
|
@@ -110,7 +118,7 @@ You can also pass a raw JSON schema object.
|
|
|
110
118
|
|
|
111
119
|
```python
|
|
112
120
|
out = client.generate(
|
|
113
|
-
"Return {\"ok\": true}",
|
|
121
|
+
[{"role": "user", "content": "Return {\"ok\": true}"}],
|
|
114
122
|
output_schema={
|
|
115
123
|
"type": "object",
|
|
116
124
|
"properties": {"ok": {"type": "boolean"}},
|
|
@@ -131,7 +139,7 @@ from oauth_codex import Client
|
|
|
131
139
|
|
|
132
140
|
async def main() -> None:
|
|
133
141
|
client = Client()
|
|
134
|
-
text = await client.agenerate("hello async")
|
|
142
|
+
text = await client.agenerate([{"role": "user", "content": "hello async"}])
|
|
135
143
|
print(text)
|
|
136
144
|
|
|
137
145
|
|
|
@@ -23,7 +23,7 @@ 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
|
|
|
@@ -31,8 +31,16 @@ print(text)
|
|
|
31
31
|
|
|
32
32
|
```python
|
|
33
33
|
text = client.generate(
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
[
|
|
35
|
+
{
|
|
36
|
+
"role": "user",
|
|
37
|
+
"content": [
|
|
38
|
+
{"type": "input_text", "text": "Describe this image"},
|
|
39
|
+
{"type": "input_image", "image_url": "https://example.com/cat.png"},
|
|
40
|
+
{"type": "input_image", "image_url": "data:image/jpeg;base64,..."},
|
|
41
|
+
],
|
|
42
|
+
}
|
|
43
|
+
],
|
|
36
44
|
)
|
|
37
45
|
print(text)
|
|
38
46
|
```
|
|
@@ -44,7 +52,7 @@ def add(a: int, b: int) -> dict:
|
|
|
44
52
|
return {"sum": a + b}
|
|
45
53
|
|
|
46
54
|
text = client.generate(
|
|
47
|
-
"Calculate 2+3",
|
|
55
|
+
[{"role": "user", "content": "Calculate 2+3"}],
|
|
48
56
|
tools=[add],
|
|
49
57
|
)
|
|
50
58
|
print(text)
|
|
@@ -64,7 +72,7 @@ def tool(input: ToolInput) -> str:
|
|
|
64
72
|
return f"Tool received query: {input.query}"
|
|
65
73
|
|
|
66
74
|
|
|
67
|
-
text = client.generate("Use the tool", tools=[tool])
|
|
75
|
+
text = client.generate([{"role": "user", "content": "Use the tool"}], tools=[tool])
|
|
68
76
|
print(text)
|
|
69
77
|
```
|
|
70
78
|
|
|
@@ -86,7 +94,7 @@ class Summary(BaseModel):
|
|
|
86
94
|
|
|
87
95
|
client = Client()
|
|
88
96
|
out = client.generate(
|
|
89
|
-
"Return JSON with title and score",
|
|
97
|
+
[{"role": "user", "content": "Return JSON with title and score"}],
|
|
90
98
|
output_schema=Summary,
|
|
91
99
|
)
|
|
92
100
|
print(out) # {"title": "...", "score": 1}
|
|
@@ -96,7 +104,7 @@ You can also pass a raw JSON schema object.
|
|
|
96
104
|
|
|
97
105
|
```python
|
|
98
106
|
out = client.generate(
|
|
99
|
-
"Return {\"ok\": true}",
|
|
107
|
+
[{"role": "user", "content": "Return {\"ok\": true}"}],
|
|
100
108
|
output_schema={
|
|
101
109
|
"type": "object",
|
|
102
110
|
"properties": {"ok": {"type": "boolean"}},
|
|
@@ -117,7 +125,7 @@ from oauth_codex import Client
|
|
|
117
125
|
|
|
118
126
|
async def main() -> None:
|
|
119
127
|
client = Client()
|
|
120
|
-
text = await client.agenerate("hello async")
|
|
128
|
+
text = await client.agenerate([{"role": "user", "content": "hello async"}])
|
|
121
129
|
print(text)
|
|
122
130
|
|
|
123
131
|
|
|
@@ -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
|
|
@@ -88,9 +84,8 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
88
84
|
|
|
89
85
|
def generate(
|
|
90
86
|
self,
|
|
91
|
-
|
|
87
|
+
messages: listMessage | None = None,
|
|
92
88
|
*,
|
|
93
|
-
images: ImageInput | list[ImageInput] | None = None,
|
|
94
89
|
tools: list[Callable[..., Any]] | None = None,
|
|
95
90
|
model: str | None = None,
|
|
96
91
|
reasoning_effort: ReasoningEffort = "medium",
|
|
@@ -100,7 +95,7 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
100
95
|
output_schema: StructuredOutputSchema | None = None,
|
|
101
96
|
strict_output: bool | None = None,
|
|
102
97
|
) -> str | dict[str, Any]:
|
|
103
|
-
messages = self.
|
|
98
|
+
messages = self._normalize_initial_messages(messages)
|
|
104
99
|
normalized_tools, tools_by_name = self._normalize_tools(tools)
|
|
105
100
|
response_format, effective_strict_output = self._resolve_structured_output_options(
|
|
106
101
|
output_schema=output_schema,
|
|
@@ -149,9 +144,8 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
149
144
|
|
|
150
145
|
async def agenerate(
|
|
151
146
|
self,
|
|
152
|
-
|
|
147
|
+
messages: listMessage | None = None,
|
|
153
148
|
*,
|
|
154
|
-
images: ImageInput | list[ImageInput] | None = None,
|
|
155
149
|
tools: list[Callable[..., Any]] | None = None,
|
|
156
150
|
model: str | None = None,
|
|
157
151
|
reasoning_effort: ReasoningEffort = "medium",
|
|
@@ -161,7 +155,7 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
161
155
|
output_schema: StructuredOutputSchema | None = None,
|
|
162
156
|
strict_output: bool | None = None,
|
|
163
157
|
) -> str | dict[str, Any]:
|
|
164
|
-
messages = self.
|
|
158
|
+
messages = self._normalize_initial_messages(messages)
|
|
165
159
|
normalized_tools, tools_by_name = self._normalize_tools(tools)
|
|
166
160
|
response_format, effective_strict_output = self._resolve_structured_output_options(
|
|
167
161
|
output_schema=output_schema,
|
|
@@ -210,9 +204,8 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
210
204
|
|
|
211
205
|
def stream(
|
|
212
206
|
self,
|
|
213
|
-
|
|
207
|
+
messages: listMessage | None = None,
|
|
214
208
|
*,
|
|
215
|
-
images: ImageInput | list[ImageInput] | None = None,
|
|
216
209
|
tools: list[Callable[..., Any]] | None = None,
|
|
217
210
|
model: str | None = None,
|
|
218
211
|
reasoning_effort: ReasoningEffort = "medium",
|
|
@@ -222,7 +215,7 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
222
215
|
output_schema: StructuredOutputSchema | None = None,
|
|
223
216
|
strict_output: bool | None = None,
|
|
224
217
|
) -> Iterator[str]:
|
|
225
|
-
messages = self.
|
|
218
|
+
messages = self._normalize_initial_messages(messages)
|
|
226
219
|
normalized_tools, tools_by_name = self._normalize_tools(tools)
|
|
227
220
|
response_format, effective_strict_output = self._resolve_structured_output_options(
|
|
228
221
|
output_schema=output_schema,
|
|
@@ -271,9 +264,8 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
271
264
|
|
|
272
265
|
async def astream(
|
|
273
266
|
self,
|
|
274
|
-
|
|
267
|
+
messages: listMessage | None = None,
|
|
275
268
|
*,
|
|
276
|
-
images: ImageInput | list[ImageInput] | None = None,
|
|
277
269
|
tools: list[Callable[..., Any]] | None = None,
|
|
278
270
|
model: str | None = None,
|
|
279
271
|
reasoning_effort: ReasoningEffort = "medium",
|
|
@@ -283,7 +275,7 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
283
275
|
output_schema: StructuredOutputSchema | None = None,
|
|
284
276
|
strict_output: bool | None = None,
|
|
285
277
|
) -> AsyncIterator[str]:
|
|
286
|
-
messages = self.
|
|
278
|
+
messages = self._normalize_initial_messages(messages)
|
|
287
279
|
normalized_tools, tools_by_name = self._normalize_tools(tools)
|
|
288
280
|
response_format, effective_strict_output = self._resolve_structured_output_options(
|
|
289
281
|
output_schema=output_schema,
|
|
@@ -375,10 +367,10 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
375
367
|
def _messages_for_round(
|
|
376
368
|
self,
|
|
377
369
|
*,
|
|
378
|
-
messages:
|
|
370
|
+
messages: listMessage,
|
|
379
371
|
previous_response_id: str | None,
|
|
380
372
|
tool_results: list[ToolResult] | None,
|
|
381
|
-
) ->
|
|
373
|
+
) -> listMessage:
|
|
382
374
|
if self._is_tool_continuation_round(
|
|
383
375
|
previous_response_id=previous_response_id,
|
|
384
376
|
tool_results=tool_results,
|
|
@@ -386,64 +378,14 @@ class OAuthCodexClient(SyncAPIClient):
|
|
|
386
378
|
return []
|
|
387
379
|
return messages
|
|
388
380
|
|
|
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}"
|
|
381
|
+
def _normalize_initial_messages(self, messages: listMessage | None) -> listMessage:
|
|
382
|
+
if messages is None:
|
|
383
|
+
raise ValueError("`messages` must be a non-empty list")
|
|
384
|
+
if not isinstance(messages, list):
|
|
385
|
+
raise TypeError("`messages` must be a non-empty list")
|
|
386
|
+
if not messages:
|
|
387
|
+
raise ValueError("`messages` must be a non-empty list")
|
|
388
|
+
return messages
|
|
447
389
|
|
|
448
390
|
def _normalize_tools(
|
|
449
391
|
self,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__title__ = "oauth-codex"
|
|
2
|
-
__version__ = "2.
|
|
2
|
+
__version__ = "2.3.0"
|
|
@@ -5,6 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Any, Callable, Literal, Protocol, TypeAlias
|
|
6
6
|
|
|
7
7
|
Message: TypeAlias = dict[str, Any]
|
|
8
|
+
listMessage: TypeAlias = list[Message]
|
|
8
9
|
ValidationMode: TypeAlias = Literal["warn", "error", "ignore"]
|
|
9
10
|
StoreBehavior: TypeAlias = Literal["auto_disable", "error", "passthrough"]
|
|
10
11
|
TruncationMode: TypeAlias = Literal["auto", "disabled"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oauth-codex
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
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,7 +37,7 @@ 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
|
|
|
@@ -45,8 +45,16 @@ print(text)
|
|
|
45
45
|
|
|
46
46
|
```python
|
|
47
47
|
text = client.generate(
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
[
|
|
49
|
+
{
|
|
50
|
+
"role": "user",
|
|
51
|
+
"content": [
|
|
52
|
+
{"type": "input_text", "text": "Describe this image"},
|
|
53
|
+
{"type": "input_image", "image_url": "https://example.com/cat.png"},
|
|
54
|
+
{"type": "input_image", "image_url": "data:image/jpeg;base64,..."},
|
|
55
|
+
],
|
|
56
|
+
}
|
|
57
|
+
],
|
|
50
58
|
)
|
|
51
59
|
print(text)
|
|
52
60
|
```
|
|
@@ -58,7 +66,7 @@ def add(a: int, b: int) -> dict:
|
|
|
58
66
|
return {"sum": a + b}
|
|
59
67
|
|
|
60
68
|
text = client.generate(
|
|
61
|
-
"Calculate 2+3",
|
|
69
|
+
[{"role": "user", "content": "Calculate 2+3"}],
|
|
62
70
|
tools=[add],
|
|
63
71
|
)
|
|
64
72
|
print(text)
|
|
@@ -78,7 +86,7 @@ def tool(input: ToolInput) -> str:
|
|
|
78
86
|
return f"Tool received query: {input.query}"
|
|
79
87
|
|
|
80
88
|
|
|
81
|
-
text = client.generate("Use the tool", tools=[tool])
|
|
89
|
+
text = client.generate([{"role": "user", "content": "Use the tool"}], tools=[tool])
|
|
82
90
|
print(text)
|
|
83
91
|
```
|
|
84
92
|
|
|
@@ -100,7 +108,7 @@ class Summary(BaseModel):
|
|
|
100
108
|
|
|
101
109
|
client = Client()
|
|
102
110
|
out = client.generate(
|
|
103
|
-
"Return JSON with title and score",
|
|
111
|
+
[{"role": "user", "content": "Return JSON with title and score"}],
|
|
104
112
|
output_schema=Summary,
|
|
105
113
|
)
|
|
106
114
|
print(out) # {"title": "...", "score": 1}
|
|
@@ -110,7 +118,7 @@ You can also pass a raw JSON schema object.
|
|
|
110
118
|
|
|
111
119
|
```python
|
|
112
120
|
out = client.generate(
|
|
113
|
-
"Return {\"ok\": true}",
|
|
121
|
+
[{"role": "user", "content": "Return {\"ok\": true}"}],
|
|
114
122
|
output_schema={
|
|
115
123
|
"type": "object",
|
|
116
124
|
"properties": {"ok": {"type": "boolean"}},
|
|
@@ -131,7 +139,7 @@ from oauth_codex import Client
|
|
|
131
139
|
|
|
132
140
|
async def main() -> None:
|
|
133
141
|
client = Client()
|
|
134
|
-
text = await client.agenerate("hello async")
|
|
142
|
+
text = await client.agenerate([{"role": "user", "content": "hello async"}])
|
|
135
143
|
print(text)
|
|
136
144
|
|
|
137
145
|
|
|
@@ -25,6 +25,51 @@ def _client() -> Client:
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
@pytest.mark.asyncio
|
|
29
|
+
async def test_agenerate_rejects_non_list_messages() -> None:
|
|
30
|
+
client = _client()
|
|
31
|
+
|
|
32
|
+
with pytest.raises(TypeError, match="non-empty list"):
|
|
33
|
+
await client.agenerate("hello") # type: ignore[arg-type]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.mark.asyncio
|
|
37
|
+
async def test_agenerate_rejects_empty_messages() -> None:
|
|
38
|
+
client = _client()
|
|
39
|
+
|
|
40
|
+
with pytest.raises(ValueError, match="non-empty list"):
|
|
41
|
+
await client.agenerate([])
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.mark.asyncio
|
|
45
|
+
async def test_agenerate_preserves_mixed_content_message_order(
|
|
46
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
47
|
+
) -> None:
|
|
48
|
+
client = _client()
|
|
49
|
+
captured: dict[str, object] = {}
|
|
50
|
+
|
|
51
|
+
async def fake_agenerate(**kwargs):
|
|
52
|
+
captured.update(kwargs)
|
|
53
|
+
return GenerateResult(text="ok", tool_calls=[], finish_reason="stop")
|
|
54
|
+
|
|
55
|
+
monkeypatch.setattr(client._engine, "agenerate", fake_agenerate)
|
|
56
|
+
|
|
57
|
+
mixed_messages = [
|
|
58
|
+
{
|
|
59
|
+
"role": "user",
|
|
60
|
+
"content": [
|
|
61
|
+
{"type": "input_text", "text": "describe"},
|
|
62
|
+
{"type": "input_image", "image_url": "https://example.com/cat.png"},
|
|
63
|
+
{"type": "input_text", "text": "focus on the cat"},
|
|
64
|
+
],
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
out = await client.agenerate(messages=mixed_messages)
|
|
68
|
+
|
|
69
|
+
assert out == "ok"
|
|
70
|
+
assert captured["messages"] == mixed_messages
|
|
71
|
+
|
|
72
|
+
|
|
28
73
|
@pytest.mark.asyncio
|
|
29
74
|
async def test_agenerate_auto_function_calling(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
30
75
|
client = _client()
|
|
@@ -46,7 +91,7 @@ async def test_agenerate_auto_function_calling(monkeypatch: pytest.MonkeyPatch)
|
|
|
46
91
|
async def add_async(a: int, b: int) -> dict[str, int]:
|
|
47
92
|
return {"sum": a + b}
|
|
48
93
|
|
|
49
|
-
out = await client.agenerate("5+7", tools=[add_async])
|
|
94
|
+
out = await client.agenerate([{"role": "user", "content": "5+7"}], tools=[add_async])
|
|
50
95
|
|
|
51
96
|
assert out == "12"
|
|
52
97
|
assert calls[1]["previous_response_id"] == "resp_1"
|
|
@@ -86,7 +131,7 @@ async def test_astream_supports_tool_calls(monkeypatch: pytest.MonkeyPatch) -> N
|
|
|
86
131
|
return {"product": a * b}
|
|
87
132
|
|
|
88
133
|
out: list[str] = []
|
|
89
|
-
async for delta in client.astream("calc", tools=[mul]):
|
|
134
|
+
async for delta in client.astream([{"role": "user", "content": "calc"}], tools=[mul]):
|
|
90
135
|
out.append(delta)
|
|
91
136
|
|
|
92
137
|
assert out == ["X", "Y"]
|
|
@@ -117,7 +162,7 @@ async def test_agenerate_supports_single_pydantic_tool_input(monkeypatch: pytest
|
|
|
117
162
|
def tool(input: ToolInput) -> str:
|
|
118
163
|
return f"Tool received query: {input.query}"
|
|
119
164
|
|
|
120
|
-
out = await client.agenerate("run", tools=[tool])
|
|
165
|
+
out = await client.agenerate([{"role": "user", "content": "run"}], tools=[tool])
|
|
121
166
|
|
|
122
167
|
assert out == "done"
|
|
123
168
|
tool_results = calls[1]["tool_results"]
|
|
@@ -142,7 +187,10 @@ async def test_agenerate_supports_structured_output_with_pydantic_schema(
|
|
|
142
187
|
|
|
143
188
|
monkeypatch.setattr(client._engine, "agenerate", fake_agenerate)
|
|
144
189
|
|
|
145
|
-
out = await client.agenerate(
|
|
190
|
+
out = await client.agenerate(
|
|
191
|
+
[{"role": "user", "content": "return json"}],
|
|
192
|
+
output_schema=StructuredOutput,
|
|
193
|
+
)
|
|
146
194
|
|
|
147
195
|
assert out == {"answer": "ok", "count": 1}
|
|
148
196
|
response_format = captured["response_format"]
|
|
@@ -171,7 +219,7 @@ async def test_agenerate_structured_output_rejects_invalid_json(
|
|
|
171
219
|
|
|
172
220
|
with pytest.raises(ValueError, match="valid JSON"):
|
|
173
221
|
await client.agenerate(
|
|
174
|
-
"return json",
|
|
222
|
+
[{"role": "user", "content": "return json"}],
|
|
175
223
|
output_schema={"type": "object", "properties": {"ok": {"type": "boolean"}}},
|
|
176
224
|
)
|
|
177
225
|
|
|
@@ -193,7 +241,10 @@ async def test_agenerate_structured_output_enforces_pydantic_strict_validation(
|
|
|
193
241
|
monkeypatch.setattr(client._engine, "agenerate", fake_agenerate)
|
|
194
242
|
|
|
195
243
|
with pytest.raises(ValidationError):
|
|
196
|
-
await client.agenerate(
|
|
244
|
+
await client.agenerate(
|
|
245
|
+
[{"role": "user", "content": "return json"}],
|
|
246
|
+
output_schema=StructuredOutput,
|
|
247
|
+
)
|
|
197
248
|
|
|
198
249
|
|
|
199
250
|
@pytest.mark.asyncio
|
|
@@ -217,7 +268,7 @@ async def test_astream_accepts_output_schema_and_keeps_text_stream(
|
|
|
217
268
|
|
|
218
269
|
out: list[str] = []
|
|
219
270
|
async for delta in client.astream(
|
|
220
|
-
"return json",
|
|
271
|
+
messages=[{"role": "user", "content": "return json"}],
|
|
221
272
|
output_schema={"type": "object", "properties": {"ok": {"type": "boolean"}}},
|
|
222
273
|
):
|
|
223
274
|
out.append(delta)
|
|
@@ -39,32 +39,64 @@ def test_generate_uses_default_model_and_reasoning_effort(monkeypatch: pytest.Mo
|
|
|
39
39
|
|
|
40
40
|
monkeypatch.setattr(client._engine, "generate", fake_generate)
|
|
41
41
|
|
|
42
|
-
out = client.generate("hello")
|
|
42
|
+
out = client.generate([{"role": "user", "content": "hello"}])
|
|
43
43
|
|
|
44
44
|
assert out == "ok"
|
|
45
45
|
assert captured["model"] == "gpt-5.3-codex"
|
|
46
46
|
assert captured["reasoning"] == {"effort": "medium"}
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
def
|
|
50
|
-
|
|
49
|
+
def test_generate_rejects_non_list_messages() -> None:
|
|
50
|
+
client = _client()
|
|
51
|
+
|
|
52
|
+
with pytest.raises(TypeError, match="non-empty list"):
|
|
53
|
+
client.generate("hello") # type: ignore[arg-type]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_generate_rejects_empty_messages() -> None:
|
|
57
|
+
client = _client()
|
|
58
|
+
|
|
59
|
+
with pytest.raises(ValueError, match="non-empty list"):
|
|
60
|
+
client.generate([])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_generate_rejects_removed_prompt_and_images_kwargs() -> None:
|
|
64
|
+
client = _client()
|
|
65
|
+
|
|
66
|
+
with pytest.raises(TypeError, match="prompt"):
|
|
67
|
+
client.generate(prompt="hello") # type: ignore[call-arg]
|
|
68
|
+
|
|
69
|
+
with pytest.raises(TypeError, match="images"):
|
|
70
|
+
client.generate( # type: ignore[call-arg]
|
|
71
|
+
[{"role": "user", "content": "hello"}],
|
|
72
|
+
images=["https://example.com/cat.png"],
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_generate_preserves_mixed_content_message_order(
|
|
77
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
51
78
|
) -> None:
|
|
52
79
|
client = _client()
|
|
53
80
|
captured: dict[str, object] = {}
|
|
54
81
|
|
|
55
|
-
image_path = tmp_path / "photo.png"
|
|
56
|
-
image_path.write_bytes(b"PNGDATA")
|
|
57
|
-
|
|
58
82
|
def fake_generate(**kwargs):
|
|
59
83
|
captured.update(kwargs)
|
|
60
84
|
return GenerateResult(text="ok", tool_calls=[], finish_reason="stop")
|
|
61
85
|
|
|
62
86
|
monkeypatch.setattr(client._engine, "generate", fake_generate)
|
|
63
87
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
88
|
+
mixed_messages = [
|
|
89
|
+
{
|
|
90
|
+
"role": "user",
|
|
91
|
+
"content": [
|
|
92
|
+
{"type": "input_text", "text": "describe"},
|
|
93
|
+
{"type": "input_image", "image_url": "https://example.com/cat.png"},
|
|
94
|
+
{"type": "input_text", "text": "focus on the cat"},
|
|
95
|
+
{"type": "input_image", "image_url": "data:image/png;base64,AAAA"},
|
|
96
|
+
],
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
out = client.generate(messages=mixed_messages)
|
|
68
100
|
|
|
69
101
|
assert out == "ok"
|
|
70
102
|
messages = captured["messages"]
|
|
@@ -72,8 +104,9 @@ def test_generate_accepts_url_and_local_image_inputs(
|
|
|
72
104
|
content = messages[0]["content"]
|
|
73
105
|
assert content[0]["type"] == "input_text"
|
|
74
106
|
assert content[1] == {"type": "input_image", "image_url": "https://example.com/cat.png"}
|
|
75
|
-
assert content[2]
|
|
76
|
-
assert content[
|
|
107
|
+
assert content[2] == {"type": "input_text", "text": "focus on the cat"}
|
|
108
|
+
assert content[3] == {"type": "input_image", "image_url": "data:image/png;base64,AAAA"}
|
|
109
|
+
assert messages == mixed_messages
|
|
77
110
|
|
|
78
111
|
|
|
79
112
|
def test_generate_auto_function_calling(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
@@ -96,7 +129,7 @@ def test_generate_auto_function_calling(monkeypatch: pytest.MonkeyPatch) -> None
|
|
|
96
129
|
def add(a: int, b: int) -> dict[str, int]:
|
|
97
130
|
return {"sum": a + b}
|
|
98
131
|
|
|
99
|
-
out = client.generate("2+3", tools=[add])
|
|
132
|
+
out = client.generate([{"role": "user", "content": "2+3"}], tools=[add])
|
|
100
133
|
|
|
101
134
|
assert out == "5"
|
|
102
135
|
assert calls[1]["previous_response_id"] == "resp_1"
|
|
@@ -129,7 +162,7 @@ def test_generate_replays_messages_when_tool_round_has_no_response_id(
|
|
|
129
162
|
def add(a: int, b: int) -> dict[str, int]:
|
|
130
163
|
return {"sum": a + b}
|
|
131
164
|
|
|
132
|
-
out = client.generate("1+2", tools=[add])
|
|
165
|
+
out = client.generate([{"role": "user", "content": "1+2"}], tools=[add])
|
|
133
166
|
|
|
134
167
|
assert out == "3"
|
|
135
168
|
assert calls[1]["previous_response_id"] is None
|
|
@@ -157,7 +190,7 @@ def test_generate_tool_failure_is_forwarded_to_model(monkeypatch: pytest.MonkeyP
|
|
|
157
190
|
_ = x
|
|
158
191
|
raise ValueError("boom")
|
|
159
192
|
|
|
160
|
-
out = client.generate("run", tools=[bad_tool])
|
|
193
|
+
out = client.generate([{"role": "user", "content": "run"}], tools=[bad_tool])
|
|
161
194
|
|
|
162
195
|
assert out == "handled"
|
|
163
196
|
tool_results = calls[1]["tool_results"]
|
|
@@ -184,7 +217,7 @@ def test_generate_wraps_string_tool_output_as_dict(monkeypatch: pytest.MonkeyPat
|
|
|
184
217
|
def echo(query: str) -> str:
|
|
185
218
|
return query
|
|
186
219
|
|
|
187
|
-
out = client.generate("run once", tools=[echo])
|
|
220
|
+
out = client.generate([{"role": "user", "content": "run once"}], tools=[echo])
|
|
188
221
|
|
|
189
222
|
assert out == "done"
|
|
190
223
|
tool_results = calls[1]["tool_results"]
|
|
@@ -213,7 +246,7 @@ def test_generate_supports_single_pydantic_tool_input_with_flat_payload(
|
|
|
213
246
|
def tool(input: ToolInputWithDescription) -> str:
|
|
214
247
|
return f"Tool received query: {input.query}"
|
|
215
248
|
|
|
216
|
-
out = client.generate("run", tools=[tool])
|
|
249
|
+
out = client.generate([{"role": "user", "content": "run"}], tools=[tool])
|
|
217
250
|
|
|
218
251
|
assert out == "done"
|
|
219
252
|
first_round_tools = calls[0]["tools"]
|
|
@@ -253,7 +286,7 @@ def test_generate_supports_single_pydantic_tool_input_with_nested_payload(
|
|
|
253
286
|
def tool(input: ToolInput) -> str:
|
|
254
287
|
return f"Tool received query: {input.query}"
|
|
255
288
|
|
|
256
|
-
out = client.generate("run", tools=[tool])
|
|
289
|
+
out = client.generate([{"role": "user", "content": "run"}], tools=[tool])
|
|
257
290
|
|
|
258
291
|
assert out == "done"
|
|
259
292
|
tool_results = calls[1]["tool_results"]
|
|
@@ -279,7 +312,7 @@ def test_generate_raises_when_tool_round_limit_exceeded(monkeypatch: pytest.Monk
|
|
|
279
312
|
return {"ok": True}
|
|
280
313
|
|
|
281
314
|
with pytest.raises(RuntimeError, match="exceeded"):
|
|
282
|
-
client.generate("loop", tools=[loop])
|
|
315
|
+
client.generate([{"role": "user", "content": "loop"}], tools=[loop])
|
|
283
316
|
|
|
284
317
|
|
|
285
318
|
def test_generate_supports_structured_output_with_pydantic_schema(
|
|
@@ -299,7 +332,10 @@ def test_generate_supports_structured_output_with_pydantic_schema(
|
|
|
299
332
|
|
|
300
333
|
monkeypatch.setattr(client._engine, "generate", fake_generate)
|
|
301
334
|
|
|
302
|
-
out = client.generate(
|
|
335
|
+
out = client.generate(
|
|
336
|
+
[{"role": "user", "content": "return json"}],
|
|
337
|
+
output_schema=StructuredOutput,
|
|
338
|
+
)
|
|
303
339
|
|
|
304
340
|
assert out == {"answer": "ok", "count": 1}
|
|
305
341
|
response_format = captured["response_format"]
|
|
@@ -329,7 +365,7 @@ def test_generate_supports_structured_output_with_raw_schema_dict(
|
|
|
329
365
|
monkeypatch.setattr(client._engine, "generate", fake_generate)
|
|
330
366
|
|
|
331
367
|
out = client.generate(
|
|
332
|
-
"return json",
|
|
368
|
+
[{"role": "user", "content": "return json"}],
|
|
333
369
|
output_schema={
|
|
334
370
|
"type": "object",
|
|
335
371
|
"properties": {"ok": {"type": "boolean"}},
|
|
@@ -360,7 +396,7 @@ def test_generate_structured_output_rejects_invalid_json(monkeypatch: pytest.Mon
|
|
|
360
396
|
|
|
361
397
|
with pytest.raises(ValueError, match="valid JSON"):
|
|
362
398
|
client.generate(
|
|
363
|
-
"return json",
|
|
399
|
+
[{"role": "user", "content": "return json"}],
|
|
364
400
|
output_schema={"type": "object", "properties": {"ok": {"type": "boolean"}}},
|
|
365
401
|
)
|
|
366
402
|
|
|
@@ -381,7 +417,10 @@ def test_generate_structured_output_enforces_pydantic_strict_validation(
|
|
|
381
417
|
monkeypatch.setattr(client._engine, "generate", fake_generate)
|
|
382
418
|
|
|
383
419
|
with pytest.raises(ValidationError):
|
|
384
|
-
client.generate(
|
|
420
|
+
client.generate(
|
|
421
|
+
[{"role": "user", "content": "return json"}],
|
|
422
|
+
output_schema=StructuredOutput,
|
|
423
|
+
)
|
|
385
424
|
|
|
386
425
|
|
|
387
426
|
def test_stream_supports_tool_calls(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
@@ -408,7 +447,7 @@ def test_stream_supports_tool_calls(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
408
447
|
def add(a: int, b: int) -> dict[str, int]:
|
|
409
448
|
return {"sum": a + b}
|
|
410
449
|
|
|
411
|
-
out = list(client.stream("calc", tools=[add]))
|
|
450
|
+
out = list(client.stream([{"role": "user", "content": "calc"}], tools=[add]))
|
|
412
451
|
|
|
413
452
|
assert out == ["A", "B"]
|
|
414
453
|
assert calls[1]["previous_response_id"] == "resp_1"
|
|
@@ -433,7 +472,7 @@ def test_stream_accepts_output_schema_and_keeps_text_stream(
|
|
|
433
472
|
|
|
434
473
|
out = list(
|
|
435
474
|
client.stream(
|
|
436
|
-
"return json",
|
|
475
|
+
messages=[{"role": "user", "content": "return json"}],
|
|
437
476
|
output_schema={"type": "object", "properties": {"ok": {"type": "boolean"}}},
|
|
438
477
|
)
|
|
439
478
|
)
|
|
@@ -3,11 +3,13 @@ from __future__ import annotations
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
5
|
import oauth_codex
|
|
6
|
+
from oauth_codex.core_types import listMessage
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
def test_single_client_public_surface() -> None:
|
|
9
10
|
assert oauth_codex.Client is oauth_codex.OAuthCodexClient
|
|
10
11
|
assert oauth_codex.Client is not None
|
|
12
|
+
assert oauth_codex.listMessage is listMessage
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
def test_removed_async_and_module_level_exports() -> None:
|
|
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.2.0 → oauth_codex-2.3.0}/src/oauth_codex/resources/vector_stores/file_batches.py
RENAMED
|
File without changes
|
|
File without changes
|
{oauth_codex-2.2.0 → oauth_codex-2.3.0}/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.2.0 → oauth_codex-2.3.0}/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.2.0 → oauth_codex-2.3.0}/src/oauth_codex/types/vector_stores/vector_store_deleted.py
RENAMED
|
File without changes
|
{oauth_codex-2.2.0 → oauth_codex-2.3.0}/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
|