Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .castiron.stats.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
schema_version: 1
generation_id: c4a244c7-627d-4a8f-af05-30a24293e753
openapi_spec_hash: 3a0031a277862428a7586125d5cec8c8
openapi_transformed_spec_hash: 951060c87157ffcd2b99d6ec617d2f6b
generation_id: 55efa5f7-488d-441d-9d8a-122049a1529a
openapi_spec_hash: dd4d5f4645d390dcaa730d96ff8256b9
openapi_transformed_spec_hash: 46069ae7031d4c47bc28d2e565e74465
config_hash: 18f602826c88bad76c6fdf08ab67247a
codegen_sha: db6da626ba89030cc5028be84182cfe435e58153
codegen_hash: 18f31bca2b47a4635831babef446409eb6eccf6db702432e4a9cd0c0226a8b6b
public_codegen_sha: f85015561af80beec73f1c34169d9e502399d3a8
codegen_sha: c3ce80b15a62c6073590d3d3dae24c3add7c078d
codegen_hash: 3558f0e3ddec29671f90a9da98e765a85fce601f27cc11fea5a4357490a845fb
public_codegen_sha: 37caadca9e7294870cac83ccc132d1df69d06b31
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "3.17.0"
".": "3.18.0"
}
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [3.18.0](https://github.com/openai/openai-python/compare/v3.17.0...v3.18.0) (2026-09-22)


### Features

* **api:** add GPT-6 Sol and Luna model identifiers ([#3935](https://github.com/openai/openai-python/issues/3935)) ([455ce1b](https://github.com/openai/openai-python/commit/455ce1b5da067e5d7631c86a571ca3b0d03b65cd))

## [3.17.0](https://github.com/openai/openai-python/compare/v3.16.2...v3.17.0) (2026-09-22)


Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,8 @@ Certain errors are automatically retried 2 times by default, with a short expone
Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict,
429 Rate Limit, and >=500 Internal errors are all retried by default.

Requests are retried only when their body can be safely resent.

You can use the `max_retries` option to configure or disable retry settings:

```python
Expand Down Expand Up @@ -780,8 +782,8 @@ client.with_options(max_retries=5).chat.completions.create(
integer, such as `1000`, for a larger retry budget. Other values raise an error
before a request is sent.

Transport failures are retried. Application exceptions raised by custom transports
or hooks propagate unchanged, including task-executor cancellation signals.
Application exceptions raised by custom transports or hooks propagate unchanged,
including task-executor cancellation signals.

## Timeouts

Expand Down
2 changes: 2 additions & 0 deletions api_reference/openapi.transformed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96374,6 +96374,8 @@ components:
type: string
enum:
- gpt-6-astra
- gpt-6-sol
- gpt-6-luna
- gpt-5.6-sol
- gpt-5.6-terra
- gpt-5.6-luna
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "openai"
version = "3.17.0"
version = "3.18.0"
description = "The official Python library for the openai API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
74 changes: 68 additions & 6 deletions src/openai/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,66 @@
log: logging.Logger = logging.getLogger(__name__)
log.addFilter(SensitiveHeadersFilter())


class _RequestBodyReplay:
def __init__(self, options: FinalRequestOptions) -> None:
self._positions: list[tuple[object, int]] = []
self._replayable = all(
self._capture(content) for content in (options.content, *_iter_file_contents(options.files))
)

def _capture(self, content: object) -> bool:
if content is None or isinstance(content, (bytes, bytearray)):
return True

if not callable(getattr(content, "read", None)):
# Iterable protocols do not guarantee a fresh iterator: a wrapper can
# return the same stored generator. Only trust known concrete containers.
return type(content) in (list, tuple)

seekable = getattr(content, "seekable", None)
tell = getattr(content, "tell", None)
seek = getattr(content, "seek", None)
if not callable(seekable) or not callable(tell) or not callable(seek):
return False
try:
if not seekable():
return False
position = tell()
except (OSError, ValueError):
return False
if not isinstance(position, int):
return False
self._positions.append((content, position))
return True

def rewind(self) -> bool:
# Reject a non-replayable body before moving any of its streams.
if not self._replayable:
return False
for content, position in self._positions:
seek = getattr(content, "seek", None)
if not callable(seek):
return False
try:
seek(position)
except (OSError, ValueError):
return False
return True


def _iter_file_contents(files: HttpxRequestFiles | None) -> Iterator[object]:
if files is None:
return

entries = files.items() if isinstance(files, Mapping) else files
for _, file in entries:
if isinstance(file, tuple) and len(file) > 1:
yield file[1]
else:
yield file


# TODO: make base page type vars covariant
SyncPageT = TypeVar("SyncPageT", bound="BaseSyncPage[Any]")
AsyncPageT = TypeVar("AsyncPageT", bound="BaseAsyncPage[Any]")
Expand Down Expand Up @@ -1062,6 +1122,7 @@ def request(
for retries_taken in range(max_retries + 1):
options = model_copy(input_options)
options = self._prepare_options(options)
request_body_replay = _RequestBodyReplay(options)

remaining_retries = max_retries - retries_taken
request = self._build_request(options, retries_taken=retries_taken)
Expand Down Expand Up @@ -1091,7 +1152,7 @@ def request(
except timeout_exceptions() as err:
log.debug("Encountered a timeout exception: %s", type(err).__name__)

if remaining_retries > 0:
if remaining_retries > 0 and request_body_replay.rewind():
self._sleep_for_retry(
retries_taken=retries_taken,
max_retries=max_retries,
Expand All @@ -1108,7 +1169,7 @@ def request(
except request_exceptions() as err:
log.debug("Encountered exception: %s", type(err).__name__)

if remaining_retries > 0:
if remaining_retries > 0 and request_body_replay.rewind():
self._sleep_for_retry(
retries_taken=retries_taken,
max_retries=max_retries,
Expand All @@ -1132,7 +1193,7 @@ def request(
except status_exceptions() as err: # thrown on 4xx and 5xx status code
log.debug("Encountered an HTTP status error: %i", response.status_code)

if remaining_retries > 0 and self._should_retry(err.response):
if remaining_retries > 0 and self._should_retry(err.response) and request_body_replay.rewind():
err.response.close()
self._sleep_for_retry(
retries_taken=retries_taken,
Expand Down Expand Up @@ -1687,6 +1748,7 @@ async def request(
for retries_taken in range(max_retries + 1):
options = model_copy(input_options)
options = await self._prepare_options(options)
request_body_replay = _RequestBodyReplay(options)

remaining_retries = max_retries - retries_taken
request = self._build_request(options, retries_taken=retries_taken)
Expand Down Expand Up @@ -1715,7 +1777,7 @@ async def request(
except timeout_exceptions() as err:
log.debug("Encountered a timeout exception: %s", type(err).__name__)

if remaining_retries > 0:
if remaining_retries > 0 and request_body_replay.rewind():
await self._sleep_for_retry(
retries_taken=retries_taken,
max_retries=max_retries,
Expand All @@ -1732,7 +1794,7 @@ async def request(
except request_exceptions() as err:
log.debug("Encountered exception: %s", type(err).__name__)

if remaining_retries > 0:
if remaining_retries > 0 and request_body_replay.rewind():
await self._sleep_for_retry(
retries_taken=retries_taken,
max_retries=max_retries,
Expand All @@ -1756,7 +1818,7 @@ async def request(
except status_exceptions() as err: # thrown on 4xx and 5xx status code
log.debug("Encountered an HTTP status error: %i", response.status_code)

if remaining_retries > 0 and self._should_retry(err.response):
if remaining_retries > 0 and self._should_retry(err.response) and request_body_replay.rewind():
await err.response.aclose()
await self._sleep_for_retry(
retries_taken=retries_taken,
Expand Down
2 changes: 1 addition & 1 deletion src/openai/_version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__title__ = "openai"
__version__ = "3.17.0" # x-release-please-version
__version__ = "3.18.0" # x-release-please-version
24 changes: 24 additions & 0 deletions src/openai/resources/beta/responses/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ def create(
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down Expand Up @@ -528,6 +530,8 @@ def create(
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down Expand Up @@ -931,6 +935,8 @@ def create(
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down Expand Up @@ -1332,6 +1338,8 @@ def create(
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down Expand Up @@ -1816,6 +1824,8 @@ def compact(
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down Expand Up @@ -2111,6 +2121,8 @@ async def create(
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down Expand Up @@ -2515,6 +2527,8 @@ async def create(
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down Expand Up @@ -2918,6 +2932,8 @@ async def create(
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down Expand Up @@ -3319,6 +3335,8 @@ async def create(
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down Expand Up @@ -3803,6 +3821,8 @@ async def compact(
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down Expand Up @@ -5178,6 +5198,8 @@ def create(
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down Expand Up @@ -5429,6 +5451,8 @@ async def create(
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down
4 changes: 4 additions & 0 deletions src/openai/resources/responses/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1748,6 +1748,8 @@ def compact(
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down Expand Up @@ -3635,6 +3637,8 @@ async def compact(
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down
2 changes: 2 additions & 0 deletions src/openai/types/beta/beta_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ class BetaResponse(BaseModel):
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down
2 changes: 2 additions & 0 deletions src/openai/types/beta/beta_responses_client_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,8 @@ class ResponseCreate(BaseModel):
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down
2 changes: 2 additions & 0 deletions src/openai/types/beta/beta_responses_client_event_param.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,8 @@ class ResponseCreate(TypedDict, total=False):
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down
2 changes: 2 additions & 0 deletions src/openai/types/beta/response_compact_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class ResponseCompactParams(TypedDict, total=False):
Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down
2 changes: 2 additions & 0 deletions src/openai/types/beta/response_create_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ class ResponseCreateParamsBase(TypedDict, total=False):
model: Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down
2 changes: 2 additions & 0 deletions src/openai/types/responses/response_compact_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class ResponseCompactParams(TypedDict, total=False):
Union[
Literal[
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand Down
Loading
Loading