Skip to content
Open
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
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,18 @@ jobs:
run: rye build

- name: Get GitHub OIDC Token
if: github.repository == 'stainless-sdks/openai-python'
if: |-
github.repository == 'stainless-sdks/openai-python' &&
!startsWith(github.ref, 'refs/heads/stl/')
id: github-oidc
uses: actions/github-script@v8
with:
script: core.setOutput('github_token', await core.getIDToken());

- name: Upload tarball
if: github.repository == 'stainless-sdks/openai-python'
if: |-
github.repository == 'stainless-sdks/openai-python' &&
!startsWith(github.ref, 'refs/heads/stl/')
env:
URL: https://pkg.stainless.com/s
AUTH: ${{ steps.github-oidc.outputs.github_token }}
Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "2.26.0"
".": "2.28.0"
}
8 changes: 4 additions & 4 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 148
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-9c802d45a9bf2a896b5fd22ac22bba185e8a145bd40ed242df9bb87a05e954eb.yml
openapi_spec_hash: 97984ed69285e660b7d5c810c69ed449
config_hash: 8240b8a7a7fc145a45b93bda435612d6
configured_endpoints: 152
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-cb3e4451108eed58d59cff25bf77ec0dc960ec9c6f3dba68f90e7a9847c09d21.yml
openapi_spec_hash: dec6d9be64a5ba8f474a1f2a7a4fafef
config_hash: e922f01e25accd07d8fd3641c37fbd62
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
# Changelog

## 2.28.0 (2026-03-13)

Full Changelog: [v2.27.0...v2.28.0](https://github.com/openai/openai-python/compare/v2.27.0...v2.28.0)

### Features

* **api:** custom voices ([50dc060](https://github.com/openai/openai-python/commit/50dc060b55767615419219ef567d31210517e613))

## 2.27.0 (2026-03-13)

Full Changelog: [v2.26.0...v2.27.0](https://github.com/openai/openai-python/compare/v2.26.0...v2.27.0)

### Features

* **api:** api update ([60ab24a](https://github.com/openai/openai-python/commit/60ab24ae722a7fa280eb4b2273da4ded1f930231))
* **api:** manual updates ([b244b09](https://github.com/openai/openai-python/commit/b244b0946045aaa0dbfa8c0ce5164b64e1156834))
* **api:** manual updates ([d806635](https://github.com/openai/openai-python/commit/d806635081a736cc81344bf1e62b57956a88d093))
* **api:** sora api improvements: character api, video extensions/edits, higher resolution exports. ([58b70d3](https://github.com/openai/openai-python/commit/58b70d304a4b2cf70eae4db4b448d439fc8b8ba3))


### Bug Fixes

* **api:** repair merged videos resource ([742d8ee](https://github.com/openai/openai-python/commit/742d8ee1f969ee1bbb39ba9d799dcd5c480d8ddb))


### Chores

* **internal:** codegen related update ([4e6498e](https://github.com/openai/openai-python/commit/4e6498e2d222dd35d76bb397ba976ff53c852e12))
* **internal:** codegen related update ([93af129](https://github.com/openai/openai-python/commit/93af129e8919de6d3aee19329c8bdef0532bd20a))
* match http protocol with ws protocol instead of wss ([026f9de](https://github.com/openai/openai-python/commit/026f9de35d2aa74f35c91261eb5ea43d4ab1b8ba))
* use proper capitalization for WebSockets ([a2f9b07](https://github.com/openai/openai-python/commit/a2f9b0722597627e8d01aa05c27a52015072726b))

## 2.26.0 (2026-03-05)

Full Changelog: [v2.25.0...v2.26.0](https://github.com/openai/openai-python/compare/v2.25.0...v2.26.0)
Expand Down
9 changes: 8 additions & 1 deletion api.md
Original file line number Diff line number Diff line change
Expand Up @@ -859,12 +859,15 @@ Types:

```python
from openai.types import (
ImageInputReferenceParam,
Video,
VideoCreateError,
VideoModel,
VideoSeconds,
VideoSize,
VideoDeleteResponse,
VideoCreateCharacterResponse,
VideoGetCharacterResponse,
)
```

Expand All @@ -874,7 +877,11 @@ Methods:
- <code title="get /videos/{video_id}">client.videos.<a href="./src/openai/resources/videos.py">retrieve</a>(video_id) -> <a href="./src/openai/types/video.py">Video</a></code>
- <code title="get /videos">client.videos.<a href="./src/openai/resources/videos.py">list</a>(\*\*<a href="src/openai/types/video_list_params.py">params</a>) -> <a href="./src/openai/types/video.py">SyncConversationCursorPage[Video]</a></code>
- <code title="delete /videos/{video_id}">client.videos.<a href="./src/openai/resources/videos.py">delete</a>(video_id) -> <a href="./src/openai/types/video_delete_response.py">VideoDeleteResponse</a></code>
- <code title="post /videos/characters">client.videos.<a href="./src/openai/resources/videos.py">create_character</a>(\*\*<a href="src/openai/types/video_create_character_params.py">params</a>) -> <a href="./src/openai/types/video_create_character_response.py">VideoCreateCharacterResponse</a></code>
- <code title="get /videos/{video_id}/content">client.videos.<a href="./src/openai/resources/videos.py">download_content</a>(video_id, \*\*<a href="src/openai/types/video_download_content_params.py">params</a>) -> HttpxBinaryResponseContent</code>
- <code title="post /videos/edits">client.videos.<a href="./src/openai/resources/videos.py">edit</a>(\*\*<a href="src/openai/types/video_edit_params.py">params</a>) -> <a href="./src/openai/types/video.py">Video</a></code>
- <code title="post /videos/extensions">client.videos.<a href="./src/openai/resources/videos.py">extend</a>(\*\*<a href="src/openai/types/video_extend_params.py">params</a>) -> <a href="./src/openai/types/video.py">Video</a></code>
- <code title="get /videos/characters/{character_id}">client.videos.<a href="./src/openai/resources/videos.py">get_character</a>(character_id) -> <a href="./src/openai/types/video_get_character_response.py">VideoGetCharacterResponse</a></code>
- <code title="post /videos/{video_id}/remix">client.videos.<a href="./src/openai/resources/videos.py">remix</a>(video_id, \*\*<a href="src/openai/types/video_remix_params.py">params</a>) -> <a href="./src/openai/types/video.py">Video</a></code>
- <code>client.videos.<a href="./src/openai/resources/videos.py">create_and_poll</a>(\*args) -> Video</code>

- <code>client.videos.<a href="./src/openai/resources/videos.py">poll</a>(\*args) -> Video</code>
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 = "2.26.0"
version = "2.28.0"
description = "The official Python library for the openai API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion src/openai/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def model_dump(
exclude_defaults=exclude_defaults,
# warnings are not supported in Pydantic v1
warnings=True if PYDANTIC_V1 else warnings,
by_alias=by_alias,
by_alias=by_alias if by_alias is not None else True,
)
return cast(
"dict[str, Any]",
Expand Down
2 changes: 1 addition & 1 deletion src/openai/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "openai"
__version__ = "2.26.0" # x-release-please-version
__version__ = "2.28.0" # x-release-please-version
18 changes: 8 additions & 10 deletions src/openai/resources/audio/speech.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ def create(
*,
input: str,
model: Union[str, SpeechModel],
voice: Union[
str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"]
],
voice: speech_create_params.Voice,
instructions: str | Omit = omit,
response_format: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] | Omit = omit,
speed: float | Omit = omit,
Expand All @@ -80,8 +78,9 @@ def create(
voice: The voice to use when generating the audio. Supported built-in voices are
`alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `onyx`, `nova`, `sage`,
`shimmer`, `verse`, `marin`, and `cedar`. Previews of the voices are available
in the
`shimmer`, `verse`, `marin`, and `cedar`. You may also provide a custom voice
object with an `id`, for example `{ "id": "voice_1234" }`. Previews of the
voices are available in the
[Text to speech guide](https://platform.openai.com/docs/guides/text-to-speech#voice-options).
instructions: Control the voice of your generated audio with additional instructions. Does not
Expand Down Expand Up @@ -153,9 +152,7 @@ async def create(
*,
input: str,
model: Union[str, SpeechModel],
voice: Union[
str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"]
],
voice: speech_create_params.Voice,
instructions: str | Omit = omit,
response_format: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] | Omit = omit,
speed: float | Omit = omit,
Expand All @@ -181,8 +178,9 @@ async def create(
voice: The voice to use when generating the audio. Supported built-in voices are
`alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `onyx`, `nova`, `sage`,
`shimmer`, `verse`, `marin`, and `cedar`. Previews of the voices are available
in the
`shimmer`, `verse`, `marin`, and `cedar`. You may also provide a custom voice
object with an `id`, for example `{ "id": "voice_1234" }`. Previews of the
voices are available in the
[Text to speech guide](https://platform.openai.com/docs/guides/text-to-speech#voice-options).
instructions: Control the voice of your generated audio with additional instructions. Does not
Expand Down
34 changes: 19 additions & 15 deletions src/openai/resources/realtime/realtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,16 @@
AsyncClientSecretsWithStreamingResponse,
)
from ...types.realtime import session_update_event_param
from ...types.websocket_connection_options import WebsocketConnectionOptions
from ...types.websocket_connection_options import WebSocketConnectionOptions
from ...types.realtime.realtime_client_event import RealtimeClientEvent
from ...types.realtime.realtime_server_event import RealtimeServerEvent
from ...types.realtime.conversation_item_param import ConversationItemParam
from ...types.realtime.realtime_client_event_param import RealtimeClientEventParam
from ...types.realtime.realtime_response_create_params_param import RealtimeResponseCreateParamsParam

if TYPE_CHECKING:
from websockets.sync.client import ClientConnection as WebsocketConnection
from websockets.asyncio.client import ClientConnection as AsyncWebsocketConnection
from websockets.sync.client import ClientConnection as WebSocketConnection
from websockets.asyncio.client import ClientConnection as AsyncWebSocketConnection

from ..._client import OpenAI, AsyncOpenAI

Expand Down Expand Up @@ -96,7 +96,7 @@ def connect(
model: str | Omit = omit,
extra_query: Query = {},
extra_headers: Headers = {},
websocket_connection_options: WebsocketConnectionOptions = {},
websocket_connection_options: WebSocketConnectionOptions = {},
) -> RealtimeConnectionManager:
"""
The Realtime API enables you to build low-latency, multi-modal conversational experiences. It currently supports text and audio as both input and output, as well as function calling.
Expand Down Expand Up @@ -156,7 +156,7 @@ def connect(
model: str | Omit = omit,
extra_query: Query = {},
extra_headers: Headers = {},
websocket_connection_options: WebsocketConnectionOptions = {},
websocket_connection_options: WebSocketConnectionOptions = {},
) -> AsyncRealtimeConnectionManager:
"""
The Realtime API enables you to build low-latency, multi-modal conversational experiences. It currently supports text and audio as both input and output, as well as function calling.
Expand Down Expand Up @@ -240,9 +240,9 @@ class AsyncRealtimeConnection:
conversation: AsyncRealtimeConversationResource
output_audio_buffer: AsyncRealtimeOutputAudioBufferResource

_connection: AsyncWebsocketConnection
_connection: AsyncWebSocketConnection

def __init__(self, connection: AsyncWebsocketConnection) -> None:
def __init__(self, connection: AsyncWebSocketConnection) -> None:
self._connection = connection

self.session = AsyncRealtimeSessionResource(self)
Expand Down Expand Up @@ -281,7 +281,7 @@ async def recv_bytes(self) -> bytes:
then you can call `.parse_event(data)`.
"""
message = await self._connection.recv(decode=False)
log.debug(f"Received websocket message: %s", message)
log.debug(f"Received WebSocket message: %s", message)
return message

async def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None:
Expand Down Expand Up @@ -334,7 +334,7 @@ def __init__(
model: str | Omit = omit,
extra_query: Query,
extra_headers: Headers,
websocket_connection_options: WebsocketConnectionOptions,
websocket_connection_options: WebSocketConnectionOptions,
) -> None:
self.__client = client
self.__call_id = call_id
Expand Down Expand Up @@ -408,7 +408,9 @@ def _prepare_url(self) -> httpx.URL:
if self.__client.websocket_base_url is not None:
base_url = httpx.URL(self.__client.websocket_base_url)
else:
base_url = self.__client._base_url.copy_with(scheme="wss")
scheme = self.__client._base_url.scheme
ws_scheme = "ws" if scheme == "http" else "wss"
base_url = self.__client._base_url.copy_with(scheme=ws_scheme)

merge_raw_path = base_url.raw_path.rstrip(b"/") + b"/realtime"
return base_url.copy_with(raw_path=merge_raw_path)
Expand All @@ -429,9 +431,9 @@ class RealtimeConnection:
conversation: RealtimeConversationResource
output_audio_buffer: RealtimeOutputAudioBufferResource

_connection: WebsocketConnection
_connection: WebSocketConnection

def __init__(self, connection: WebsocketConnection) -> None:
def __init__(self, connection: WebSocketConnection) -> None:
self._connection = connection

self.session = RealtimeSessionResource(self)
Expand Down Expand Up @@ -470,7 +472,7 @@ def recv_bytes(self) -> bytes:
then you can call `.parse_event(data)`.
"""
message = self._connection.recv(decode=False)
log.debug(f"Received websocket message: %s", message)
log.debug(f"Received WebSocket message: %s", message)
return message

def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None:
Expand Down Expand Up @@ -523,7 +525,7 @@ def __init__(
model: str | Omit = omit,
extra_query: Query,
extra_headers: Headers,
websocket_connection_options: WebsocketConnectionOptions,
websocket_connection_options: WebSocketConnectionOptions,
) -> None:
self.__client = client
self.__call_id = call_id
Expand Down Expand Up @@ -597,7 +599,9 @@ def _prepare_url(self) -> httpx.URL:
if self.__client.websocket_base_url is not None:
base_url = httpx.URL(self.__client.websocket_base_url)
else:
base_url = self.__client._base_url.copy_with(scheme="wss")
scheme = self.__client._base_url.scheme
ws_scheme = "ws" if scheme == "http" else "wss"
base_url = self.__client._base_url.copy_with(scheme=ws_scheme)

merge_raw_path = base_url.raw_path.rstrip(b"/") + b"/realtime"
return base_url.copy_with(raw_path=merge_raw_path)
Expand Down
Loading