gpt-codex-client 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gpt_codex_client/__init__.py +68 -0
- gpt_codex_client/_async_auth.py +160 -0
- gpt_codex_client/_async_client.py +183 -0
- gpt_codex_client/_async_models.py +83 -0
- gpt_codex_client/_async_responses.py +146 -0
- gpt_codex_client/_async_stream.py +108 -0
- gpt_codex_client/_auth.py +290 -0
- gpt_codex_client/_chat.py +224 -0
- gpt_codex_client/_client.py +182 -0
- gpt_codex_client/_config.py +196 -0
- gpt_codex_client/_converters.py +178 -0
- gpt_codex_client/_errors.py +126 -0
- gpt_codex_client/_models.py +110 -0
- gpt_codex_client/_responses.py +193 -0
- gpt_codex_client/_stream.py +170 -0
- gpt_codex_client/_types.py +228 -0
- gpt_codex_client/py.typed +1 -0
- gpt_codex_client-0.1.0.dist-info/METADATA +164 -0
- gpt_codex_client-0.1.0.dist-info/RECORD +21 -0
- gpt_codex_client-0.1.0.dist-info/WHEEL +4 -0
- gpt_codex_client-0.1.0.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ._async_auth import afinish_login, aget_token, alogin, arefresh
|
|
4
|
+
from ._async_client import AsyncCodexClient
|
|
5
|
+
from ._auth import PendingLogin, finish_login, get_token, login, refresh, start_login
|
|
6
|
+
from ._client import CodexClient
|
|
7
|
+
from ._config import Token, build_headers, get_account_id
|
|
8
|
+
from ._errors import (
|
|
9
|
+
APIConnectionError,
|
|
10
|
+
APIError,
|
|
11
|
+
APITimeoutError,
|
|
12
|
+
AuthError,
|
|
13
|
+
CodexError,
|
|
14
|
+
InvalidRequestError,
|
|
15
|
+
RateLimitError,
|
|
16
|
+
ServerError,
|
|
17
|
+
StreamError,
|
|
18
|
+
)
|
|
19
|
+
from ._types import (
|
|
20
|
+
ChatCompletion,
|
|
21
|
+
ChatCompletionChunk,
|
|
22
|
+
FunctionTool,
|
|
23
|
+
Model,
|
|
24
|
+
ParsedResponse,
|
|
25
|
+
Reasoning,
|
|
26
|
+
Response,
|
|
27
|
+
ResponseStreamEvent,
|
|
28
|
+
TextConfig,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
__version__ = "0.1.0"
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"APIConnectionError",
|
|
35
|
+
"APIError",
|
|
36
|
+
"APITimeoutError",
|
|
37
|
+
"AsyncCodexClient",
|
|
38
|
+
"AuthError",
|
|
39
|
+
"ChatCompletion",
|
|
40
|
+
"ChatCompletionChunk",
|
|
41
|
+
"CodexClient",
|
|
42
|
+
"CodexError",
|
|
43
|
+
"FunctionTool",
|
|
44
|
+
"InvalidRequestError",
|
|
45
|
+
"Model",
|
|
46
|
+
"ParsedResponse",
|
|
47
|
+
"PendingLogin",
|
|
48
|
+
"RateLimitError",
|
|
49
|
+
"Reasoning",
|
|
50
|
+
"Response",
|
|
51
|
+
"ResponseStreamEvent",
|
|
52
|
+
"ServerError",
|
|
53
|
+
"StreamError",
|
|
54
|
+
"TextConfig",
|
|
55
|
+
"Token",
|
|
56
|
+
"__version__",
|
|
57
|
+
"afinish_login",
|
|
58
|
+
"aget_token",
|
|
59
|
+
"alogin",
|
|
60
|
+
"arefresh",
|
|
61
|
+
"build_headers",
|
|
62
|
+
"finish_login",
|
|
63
|
+
"get_account_id",
|
|
64
|
+
"get_token",
|
|
65
|
+
"login",
|
|
66
|
+
"refresh",
|
|
67
|
+
"start_login",
|
|
68
|
+
]
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from ._auth import (
|
|
10
|
+
LoginHandler,
|
|
11
|
+
PendingLogin,
|
|
12
|
+
_code_from_callback,
|
|
13
|
+
_token_from_response,
|
|
14
|
+
start_login,
|
|
15
|
+
)
|
|
16
|
+
from ._config import DEFAULT_TOKEN_PATH, TOKEN_URL, Token, get_client_id, load_token, save_token
|
|
17
|
+
from ._errors import AuthError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def afinish_login(
|
|
21
|
+
callback_url: str,
|
|
22
|
+
pending: PendingLogin,
|
|
23
|
+
*,
|
|
24
|
+
token_path: str | Path = DEFAULT_TOKEN_PATH,
|
|
25
|
+
http_client: httpx.AsyncClient | None = None,
|
|
26
|
+
timeout: float | httpx.Timeout | None = 120.0,
|
|
27
|
+
) -> Token:
|
|
28
|
+
code = _code_from_callback(callback_url, pending.state)
|
|
29
|
+
owns_client = http_client is None
|
|
30
|
+
client = http_client or httpx.AsyncClient(timeout=timeout)
|
|
31
|
+
try:
|
|
32
|
+
response = await client.post(
|
|
33
|
+
TOKEN_URL,
|
|
34
|
+
data={
|
|
35
|
+
"grant_type": "authorization_code",
|
|
36
|
+
"client_id": pending.client_id or get_client_id(),
|
|
37
|
+
"code": code,
|
|
38
|
+
"redirect_uri": pending.redirect_uri,
|
|
39
|
+
"code_verifier": pending.verifier,
|
|
40
|
+
},
|
|
41
|
+
timeout=timeout,
|
|
42
|
+
)
|
|
43
|
+
token = _token_from_response(response)
|
|
44
|
+
save_token(token, token_path)
|
|
45
|
+
return token
|
|
46
|
+
finally:
|
|
47
|
+
if owns_client:
|
|
48
|
+
await client.aclose()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def alogin(
|
|
52
|
+
*,
|
|
53
|
+
headless: bool = False,
|
|
54
|
+
no_browser: bool = False,
|
|
55
|
+
token_path: str | Path = DEFAULT_TOKEN_PATH,
|
|
56
|
+
login_handler: LoginHandler | None = None,
|
|
57
|
+
client_id: str | None = None,
|
|
58
|
+
http_client: httpx.AsyncClient | None = None,
|
|
59
|
+
timeout: float | httpx.Timeout | None = 120.0,
|
|
60
|
+
) -> Token:
|
|
61
|
+
if login_handler is not None:
|
|
62
|
+
pending = start_login(client_id=client_id)
|
|
63
|
+
callback_url = login_handler(pending.url)
|
|
64
|
+
return await afinish_login(
|
|
65
|
+
callback_url,
|
|
66
|
+
pending,
|
|
67
|
+
token_path=token_path,
|
|
68
|
+
http_client=http_client,
|
|
69
|
+
timeout=timeout,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
from ._auth import login
|
|
73
|
+
|
|
74
|
+
def run_sync_login() -> Token:
|
|
75
|
+
return login(
|
|
76
|
+
headless=headless,
|
|
77
|
+
no_browser=no_browser,
|
|
78
|
+
token_path=token_path,
|
|
79
|
+
login_handler=None,
|
|
80
|
+
client_id=client_id,
|
|
81
|
+
timeout=timeout,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return await asyncio.to_thread(run_sync_login)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def arefresh(
|
|
88
|
+
*,
|
|
89
|
+
token_path: str | Path = DEFAULT_TOKEN_PATH,
|
|
90
|
+
refresh_token: str | None = None,
|
|
91
|
+
client_id: str | None = None,
|
|
92
|
+
http_client: httpx.AsyncClient | None = None,
|
|
93
|
+
timeout: float | httpx.Timeout | None = 120.0,
|
|
94
|
+
) -> Token:
|
|
95
|
+
cached = load_token(token_path)
|
|
96
|
+
resolved_refresh_token = refresh_token or (cached.refresh_token if cached is not None else None)
|
|
97
|
+
if not resolved_refresh_token:
|
|
98
|
+
raise AuthError("No refresh token is available")
|
|
99
|
+
|
|
100
|
+
owns_client = http_client is None
|
|
101
|
+
client = http_client or httpx.AsyncClient(timeout=timeout)
|
|
102
|
+
try:
|
|
103
|
+
response = await client.post(
|
|
104
|
+
TOKEN_URL,
|
|
105
|
+
data={
|
|
106
|
+
"grant_type": "refresh_token",
|
|
107
|
+
"client_id": get_client_id(client_id),
|
|
108
|
+
"refresh_token": resolved_refresh_token,
|
|
109
|
+
},
|
|
110
|
+
timeout=timeout,
|
|
111
|
+
)
|
|
112
|
+
token = _token_from_response(response)
|
|
113
|
+
if token.refresh_token is None:
|
|
114
|
+
token.refresh_token = resolved_refresh_token
|
|
115
|
+
save_token(token, token_path)
|
|
116
|
+
return token
|
|
117
|
+
finally:
|
|
118
|
+
if owns_client:
|
|
119
|
+
await client.aclose()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def aget_token(
|
|
123
|
+
*,
|
|
124
|
+
token_path: str | Path = DEFAULT_TOKEN_PATH,
|
|
125
|
+
headless: bool = False,
|
|
126
|
+
no_browser: bool = False,
|
|
127
|
+
login_handler: LoginHandler | None = None,
|
|
128
|
+
client_id: str | None = None,
|
|
129
|
+
http_client: httpx.AsyncClient | None = None,
|
|
130
|
+
timeout: float | httpx.Timeout | None = 120.0,
|
|
131
|
+
) -> Token:
|
|
132
|
+
cached = load_token(token_path)
|
|
133
|
+
if cached is not None and not cached.is_expired():
|
|
134
|
+
return cached
|
|
135
|
+
|
|
136
|
+
if cached is not None and cached.refresh_token:
|
|
137
|
+
try:
|
|
138
|
+
return await arefresh(
|
|
139
|
+
token_path=token_path,
|
|
140
|
+
refresh_token=cached.refresh_token,
|
|
141
|
+
client_id=client_id,
|
|
142
|
+
http_client=http_client,
|
|
143
|
+
timeout=timeout,
|
|
144
|
+
)
|
|
145
|
+
except AuthError:
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
return await alogin(
|
|
149
|
+
headless=headless,
|
|
150
|
+
no_browser=no_browser,
|
|
151
|
+
token_path=token_path,
|
|
152
|
+
login_handler=login_handler,
|
|
153
|
+
client_id=client_id,
|
|
154
|
+
http_client=http_client,
|
|
155
|
+
timeout=timeout,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _unused(value: Any) -> None:
|
|
160
|
+
return None
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from types import TracebackType
|
|
6
|
+
from typing import Any
|
|
7
|
+
from urllib.parse import urljoin
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from ._async_auth import aget_token
|
|
12
|
+
from ._async_models import AsyncModelsResource
|
|
13
|
+
from ._async_responses import AsyncResponsesResource
|
|
14
|
+
from ._async_stream import AsyncResponseStream
|
|
15
|
+
from ._auth import LoginHandler
|
|
16
|
+
from ._chat import AsyncChatResource
|
|
17
|
+
from ._config import (
|
|
18
|
+
DEFAULT_BASE_URL,
|
|
19
|
+
DEFAULT_TOKEN_PATH,
|
|
20
|
+
Token,
|
|
21
|
+
build_headers,
|
|
22
|
+
get_client_version,
|
|
23
|
+
)
|
|
24
|
+
from ._errors import (
|
|
25
|
+
APIConnectionError,
|
|
26
|
+
APIError,
|
|
27
|
+
APITimeoutError,
|
|
28
|
+
CodexError,
|
|
29
|
+
error_from_response,
|
|
30
|
+
is_retryable_error,
|
|
31
|
+
)
|
|
32
|
+
from ._types import JsonObject
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AsyncCodexClient:
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
*,
|
|
39
|
+
headless: bool = False,
|
|
40
|
+
no_browser: bool = False,
|
|
41
|
+
token_path: str | Path = DEFAULT_TOKEN_PATH,
|
|
42
|
+
login_handler: LoginHandler | None = None,
|
|
43
|
+
auth_client_id: str | None = None,
|
|
44
|
+
client_version: str | None = None,
|
|
45
|
+
models_manifest_url: str | None = None,
|
|
46
|
+
timeout: float = 120.0,
|
|
47
|
+
max_retries: int = 2,
|
|
48
|
+
default_headers: dict[str, str] | None = None,
|
|
49
|
+
http_client: httpx.AsyncClient | None = None,
|
|
50
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
51
|
+
) -> None:
|
|
52
|
+
self.headless = headless
|
|
53
|
+
self.no_browser = no_browser
|
|
54
|
+
self.token_path = token_path
|
|
55
|
+
self.login_handler = login_handler
|
|
56
|
+
self.auth_client_id = auth_client_id
|
|
57
|
+
self.client_version = get_client_version(client_version)
|
|
58
|
+
self.models_manifest_url = models_manifest_url
|
|
59
|
+
self.timeout = timeout
|
|
60
|
+
self.max_retries = max_retries
|
|
61
|
+
self.default_headers = default_headers or {}
|
|
62
|
+
self.base_url = base_url.rstrip("/") + "/"
|
|
63
|
+
self._http_client = http_client or httpx.AsyncClient(timeout=timeout)
|
|
64
|
+
self._owns_http_client = http_client is None
|
|
65
|
+
self._token: Token | None = None
|
|
66
|
+
|
|
67
|
+
self.responses = AsyncResponsesResource(self)
|
|
68
|
+
self.chat = AsyncChatResource(self)
|
|
69
|
+
self.models = AsyncModelsResource(self, manifest_url=models_manifest_url)
|
|
70
|
+
|
|
71
|
+
async def __aenter__(self) -> AsyncCodexClient:
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
async def __aexit__(
|
|
75
|
+
self,
|
|
76
|
+
exc_type: type[BaseException] | None,
|
|
77
|
+
exc: BaseException | None,
|
|
78
|
+
traceback: TracebackType | None,
|
|
79
|
+
) -> None:
|
|
80
|
+
await self.aclose()
|
|
81
|
+
|
|
82
|
+
async def aclose(self) -> None:
|
|
83
|
+
if self._owns_http_client:
|
|
84
|
+
await self._http_client.aclose()
|
|
85
|
+
|
|
86
|
+
async def _request(
|
|
87
|
+
self,
|
|
88
|
+
method: str,
|
|
89
|
+
path: str,
|
|
90
|
+
*,
|
|
91
|
+
json: JsonObject | None = None,
|
|
92
|
+
timeout: float | None = None,
|
|
93
|
+
extra_headers: dict[str, str] | None = None,
|
|
94
|
+
) -> JsonObject:
|
|
95
|
+
url = urljoin(self.base_url, path.lstrip("/"))
|
|
96
|
+
attempts = self.max_retries + 1
|
|
97
|
+
for attempt in range(attempts):
|
|
98
|
+
try:
|
|
99
|
+
response = await self._http_client.request(
|
|
100
|
+
method,
|
|
101
|
+
url,
|
|
102
|
+
json=json,
|
|
103
|
+
params=self._params(),
|
|
104
|
+
headers=await self._headers(extra_headers),
|
|
105
|
+
timeout=timeout or self.timeout,
|
|
106
|
+
)
|
|
107
|
+
except httpx.TimeoutException as exc:
|
|
108
|
+
if attempt + 1 < attempts:
|
|
109
|
+
await self._sleep_before_retry(attempt, None)
|
|
110
|
+
continue
|
|
111
|
+
raise APITimeoutError(str(exc)) from exc
|
|
112
|
+
except httpx.RequestError as exc:
|
|
113
|
+
if attempt + 1 < attempts:
|
|
114
|
+
await self._sleep_before_retry(attempt, None)
|
|
115
|
+
continue
|
|
116
|
+
raise APIConnectionError(str(exc)) from exc
|
|
117
|
+
|
|
118
|
+
if response.status_code < 400:
|
|
119
|
+
return _json_object(response)
|
|
120
|
+
error = error_from_response(response)
|
|
121
|
+
if attempt + 1 < attempts and is_retryable_error(error):
|
|
122
|
+
retry_after = error.retry_after if isinstance(error, APIError) else None
|
|
123
|
+
await self._sleep_before_retry(attempt, retry_after)
|
|
124
|
+
continue
|
|
125
|
+
raise error
|
|
126
|
+
raise APIConnectionError("Request retry loop exited unexpectedly")
|
|
127
|
+
|
|
128
|
+
async def _stream(
|
|
129
|
+
self,
|
|
130
|
+
method: str,
|
|
131
|
+
path: str,
|
|
132
|
+
*,
|
|
133
|
+
json: JsonObject | None = None,
|
|
134
|
+
timeout: float | None = None,
|
|
135
|
+
extra_headers: dict[str, str] | None = None,
|
|
136
|
+
) -> AsyncResponseStream:
|
|
137
|
+
url = urljoin(self.base_url, path.lstrip("/"))
|
|
138
|
+
manager = self._http_client.stream(
|
|
139
|
+
method,
|
|
140
|
+
url,
|
|
141
|
+
json=json,
|
|
142
|
+
params=self._params(),
|
|
143
|
+
headers=await self._headers(extra_headers),
|
|
144
|
+
timeout=timeout or self.timeout,
|
|
145
|
+
)
|
|
146
|
+
return AsyncResponseStream(manager)
|
|
147
|
+
|
|
148
|
+
def _params(self) -> dict[str, str]:
|
|
149
|
+
return {"client_version": self.client_version}
|
|
150
|
+
|
|
151
|
+
async def _headers(self, extra_headers: dict[str, str] | None = None) -> dict[str, str]:
|
|
152
|
+
token = self._token
|
|
153
|
+
if token is None or token.is_expired():
|
|
154
|
+
token = await aget_token(
|
|
155
|
+
token_path=self.token_path,
|
|
156
|
+
headless=self.headless,
|
|
157
|
+
no_browser=self.no_browser,
|
|
158
|
+
login_handler=self.login_handler,
|
|
159
|
+
client_id=self.auth_client_id,
|
|
160
|
+
http_client=self._http_client,
|
|
161
|
+
timeout=self.timeout,
|
|
162
|
+
)
|
|
163
|
+
self._token = token
|
|
164
|
+
headers = build_headers(token, default_headers=self.default_headers)
|
|
165
|
+
if extra_headers:
|
|
166
|
+
headers.update(extra_headers)
|
|
167
|
+
return headers
|
|
168
|
+
|
|
169
|
+
async def _sleep_before_retry(self, attempt: int, retry_after: float | None) -> None:
|
|
170
|
+
delay = retry_after if retry_after is not None else min(2.0, 0.25 * (2**attempt))
|
|
171
|
+
await asyncio.sleep(delay)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _json_object(response: httpx.Response) -> JsonObject:
|
|
175
|
+
if not response.content:
|
|
176
|
+
return {}
|
|
177
|
+
try:
|
|
178
|
+
payload: Any = response.json()
|
|
179
|
+
except ValueError as exc:
|
|
180
|
+
raise CodexError("Response was not valid JSON") from exc
|
|
181
|
+
if not isinstance(payload, dict):
|
|
182
|
+
raise CodexError("Response JSON was not an object")
|
|
183
|
+
return payload
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from ._config import get_models_manifest_url
|
|
9
|
+
from ._errors import (
|
|
10
|
+
APIConnectionError,
|
|
11
|
+
APIError,
|
|
12
|
+
APITimeoutError,
|
|
13
|
+
CodexError,
|
|
14
|
+
error_from_response,
|
|
15
|
+
is_retryable_error,
|
|
16
|
+
)
|
|
17
|
+
from ._models import _models_from_payload
|
|
18
|
+
from ._types import JsonObject, Model
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AsyncModelsResource:
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
client: Any,
|
|
25
|
+
*,
|
|
26
|
+
ttl_seconds: float = 300.0,
|
|
27
|
+
manifest_url: str | None = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
self._client = client
|
|
30
|
+
self._ttl_seconds = ttl_seconds
|
|
31
|
+
self._manifest_url = get_models_manifest_url(manifest_url)
|
|
32
|
+
self._cache: list[Model] | None = None
|
|
33
|
+
self._cache_at = 0.0
|
|
34
|
+
|
|
35
|
+
async def list(
|
|
36
|
+
self, *, force_refresh: bool = False, timeout: float | None = None
|
|
37
|
+
) -> list[Model]:
|
|
38
|
+
if (
|
|
39
|
+
not force_refresh
|
|
40
|
+
and self._cache is not None
|
|
41
|
+
and time.time() - self._cache_at < self._ttl_seconds
|
|
42
|
+
):
|
|
43
|
+
return list(self._cache)
|
|
44
|
+
payload = await self._fetch_manifest(timeout=timeout)
|
|
45
|
+
models = _models_from_payload(payload)
|
|
46
|
+
self._cache = models
|
|
47
|
+
self._cache_at = time.time()
|
|
48
|
+
return list(models)
|
|
49
|
+
|
|
50
|
+
async def _fetch_manifest(self, *, timeout: float | None = None) -> JsonObject:
|
|
51
|
+
attempts = self._client.max_retries + 1
|
|
52
|
+
for attempt in range(attempts):
|
|
53
|
+
try:
|
|
54
|
+
response = await self._client._http_client.get(
|
|
55
|
+
self._manifest_url,
|
|
56
|
+
headers={"accept": "application/json"},
|
|
57
|
+
timeout=timeout or self._client.timeout,
|
|
58
|
+
)
|
|
59
|
+
except httpx.TimeoutException as exc:
|
|
60
|
+
if attempt + 1 < attempts:
|
|
61
|
+
await self._client._sleep_before_retry(attempt, None)
|
|
62
|
+
continue
|
|
63
|
+
raise APITimeoutError(str(exc)) from exc
|
|
64
|
+
except httpx.RequestError as exc:
|
|
65
|
+
if attempt + 1 < attempts:
|
|
66
|
+
await self._client._sleep_before_retry(attempt, None)
|
|
67
|
+
continue
|
|
68
|
+
raise APIConnectionError(str(exc)) from exc
|
|
69
|
+
if response.status_code < 400:
|
|
70
|
+
try:
|
|
71
|
+
payload = response.json()
|
|
72
|
+
except ValueError as exc:
|
|
73
|
+
raise CodexError("Models manifest response was not valid JSON") from exc
|
|
74
|
+
if not isinstance(payload, dict):
|
|
75
|
+
raise CodexError("Models manifest JSON was not an object")
|
|
76
|
+
return payload
|
|
77
|
+
error = error_from_response(response)
|
|
78
|
+
if attempt + 1 < attempts and is_retryable_error(error):
|
|
79
|
+
retry_after = error.retry_after if isinstance(error, APIError) else None
|
|
80
|
+
await self._client._sleep_before_retry(attempt, retry_after)
|
|
81
|
+
continue
|
|
82
|
+
raise error
|
|
83
|
+
raise APIConnectionError("Models manifest retry loop exited unexpectedly")
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal, TypeVar, cast, overload
|
|
4
|
+
|
|
5
|
+
from ._async_stream import AsyncResponseStream
|
|
6
|
+
from ._converters import response_request_body
|
|
7
|
+
from ._responses import _parse_output, _text_config_for_format
|
|
8
|
+
from ._types import FunctionTool, JsonObject, ParsedResponse, Reasoning, Response, TextConfig
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AsyncResponsesResource:
|
|
14
|
+
def __init__(self, client: Any) -> None:
|
|
15
|
+
self._client = client
|
|
16
|
+
|
|
17
|
+
@overload
|
|
18
|
+
async def create(
|
|
19
|
+
self,
|
|
20
|
+
*,
|
|
21
|
+
model: str,
|
|
22
|
+
input: str | list[Any],
|
|
23
|
+
instructions: str | None = None,
|
|
24
|
+
stream: Literal[False] = False,
|
|
25
|
+
tools: list[FunctionTool | JsonObject] | None = None,
|
|
26
|
+
tool_choice: str | JsonObject | None = None,
|
|
27
|
+
parallel_tool_calls: bool | None = None,
|
|
28
|
+
reasoning: Reasoning | JsonObject | None = None,
|
|
29
|
+
text: TextConfig | JsonObject | None = None,
|
|
30
|
+
include: list[str] | None = None,
|
|
31
|
+
previous_response_id: str | None = None,
|
|
32
|
+
timeout: float | None = None,
|
|
33
|
+
extra_headers: dict[str, str] | None = None,
|
|
34
|
+
) -> Response: ...
|
|
35
|
+
|
|
36
|
+
@overload
|
|
37
|
+
async def create(
|
|
38
|
+
self,
|
|
39
|
+
*,
|
|
40
|
+
model: str,
|
|
41
|
+
input: str | list[Any],
|
|
42
|
+
instructions: str | None = None,
|
|
43
|
+
stream: Literal[True],
|
|
44
|
+
tools: list[FunctionTool | JsonObject] | None = None,
|
|
45
|
+
tool_choice: str | JsonObject | None = None,
|
|
46
|
+
parallel_tool_calls: bool | None = None,
|
|
47
|
+
reasoning: Reasoning | JsonObject | None = None,
|
|
48
|
+
text: TextConfig | JsonObject | None = None,
|
|
49
|
+
include: list[str] | None = None,
|
|
50
|
+
previous_response_id: str | None = None,
|
|
51
|
+
timeout: float | None = None,
|
|
52
|
+
extra_headers: dict[str, str] | None = None,
|
|
53
|
+
) -> AsyncResponseStream: ...
|
|
54
|
+
|
|
55
|
+
async def create(
|
|
56
|
+
self,
|
|
57
|
+
*,
|
|
58
|
+
model: str,
|
|
59
|
+
input: str | list[Any],
|
|
60
|
+
instructions: str | None = None,
|
|
61
|
+
stream: bool = False,
|
|
62
|
+
tools: list[FunctionTool | JsonObject] | None = None,
|
|
63
|
+
tool_choice: str | JsonObject | None = None,
|
|
64
|
+
parallel_tool_calls: bool | None = None,
|
|
65
|
+
reasoning: Reasoning | JsonObject | None = None,
|
|
66
|
+
text: TextConfig | JsonObject | None = None,
|
|
67
|
+
include: list[str] | None = None,
|
|
68
|
+
previous_response_id: str | None = None,
|
|
69
|
+
timeout: float | None = None,
|
|
70
|
+
extra_headers: dict[str, str] | None = None,
|
|
71
|
+
) -> Response | AsyncResponseStream:
|
|
72
|
+
body = response_request_body(
|
|
73
|
+
model=model,
|
|
74
|
+
input=input,
|
|
75
|
+
instructions=instructions,
|
|
76
|
+
stream=True,
|
|
77
|
+
tools=tools,
|
|
78
|
+
tool_choice=tool_choice,
|
|
79
|
+
parallel_tool_calls=parallel_tool_calls,
|
|
80
|
+
reasoning=reasoning,
|
|
81
|
+
text=text,
|
|
82
|
+
include=include,
|
|
83
|
+
previous_response_id=previous_response_id,
|
|
84
|
+
)
|
|
85
|
+
if stream:
|
|
86
|
+
return cast(
|
|
87
|
+
AsyncResponseStream,
|
|
88
|
+
await self._client._stream(
|
|
89
|
+
"POST",
|
|
90
|
+
"/responses",
|
|
91
|
+
json=body,
|
|
92
|
+
timeout=timeout,
|
|
93
|
+
extra_headers=extra_headers,
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
response_stream = cast(
|
|
97
|
+
AsyncResponseStream,
|
|
98
|
+
await self._client._stream(
|
|
99
|
+
"POST",
|
|
100
|
+
"/responses",
|
|
101
|
+
json=body,
|
|
102
|
+
timeout=timeout,
|
|
103
|
+
extra_headers=extra_headers,
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
async with response_stream as opened_stream:
|
|
107
|
+
async for _event in opened_stream:
|
|
108
|
+
pass
|
|
109
|
+
return await opened_stream.get_final_response()
|
|
110
|
+
|
|
111
|
+
async def parse(
|
|
112
|
+
self,
|
|
113
|
+
*,
|
|
114
|
+
model: str,
|
|
115
|
+
input: str | list[Any],
|
|
116
|
+
text_format: type[T] | JsonObject,
|
|
117
|
+
instructions: str | None = None,
|
|
118
|
+
tools: list[FunctionTool | JsonObject] | None = None,
|
|
119
|
+
tool_choice: str | JsonObject | None = None,
|
|
120
|
+
parallel_tool_calls: bool | None = None,
|
|
121
|
+
reasoning: Reasoning | JsonObject | None = None,
|
|
122
|
+
include: list[str] | None = None,
|
|
123
|
+
previous_response_id: str | None = None,
|
|
124
|
+
timeout: float | None = None,
|
|
125
|
+
extra_headers: dict[str, str] | None = None,
|
|
126
|
+
) -> ParsedResponse[T]:
|
|
127
|
+
text_config = _text_config_for_format(text_format)
|
|
128
|
+
response = await self.create(
|
|
129
|
+
model=model,
|
|
130
|
+
input=input,
|
|
131
|
+
instructions=instructions,
|
|
132
|
+
tools=tools,
|
|
133
|
+
tool_choice=tool_choice,
|
|
134
|
+
parallel_tool_calls=parallel_tool_calls,
|
|
135
|
+
reasoning=reasoning,
|
|
136
|
+
text=text_config,
|
|
137
|
+
include=include,
|
|
138
|
+
previous_response_id=previous_response_id,
|
|
139
|
+
timeout=timeout,
|
|
140
|
+
extra_headers=extra_headers,
|
|
141
|
+
)
|
|
142
|
+
if isinstance(response, AsyncResponseStream):
|
|
143
|
+
raise TypeError("parse() does not support stream=True")
|
|
144
|
+
return ParsedResponse(
|
|
145
|
+
response=response, parsed=_parse_output(response.output_text, text_format)
|
|
146
|
+
)
|