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
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: c8545f55-7807-4037-9c86-3784b96f3960
openapi_spec_hash: 0f07f47154153d68e717ae594b07dc4c
openapi_transformed_spec_hash: a1a351d4356d65a15a857d0aca5f7b09
generation_id: 7c85e3b8-1fd9-4422-9e62-0b845e4d0dd6
openapi_spec_hash: 4e1ab2560f0c0f65641ea0a2d769a0d5
openapi_transformed_spec_hash: fd14e90569d03122e5fdcc35e52153b4
config_hash: dfe8a20c64cc5d852e69c441cbd28ae7
codegen_sha: 1d8024b3eb37e3e329104029ed81fd2085ff1218
codegen_hash: e1ee819da91a6c8ae8c8af8c4e3054f52eaec918912e9be3b2bd6bc692608d5e
public_codegen_sha: 67cbc9398ec6c0feb5bad24421b648b42bbc405f
codegen_sha: 1a2564e113780db175aecce7b826099172da9171
codegen_hash: aee79621a90e3602ea790e7bc34cfc58368c4217f4d77921bc8960d65b4a8845
public_codegen_sha: 5d5101490afb27fea944f63bc21b52140409bf4a
17 changes: 17 additions & 0 deletions api_reference/openapi.transformed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34039,6 +34039,8 @@ components:
description: |
Whether to store the generated model response for later retrieval via
API.
Defaults to true when omitted.
If set to true, response data will be stored for at least 30 days, subject to the [data retention exceptions](/api/docs/guides/your-data#v1responses).
default: true
- type: 'null'
instructions:
Expand Down Expand Up @@ -66300,6 +66302,13 @@ components:
description: API key scope.
type: array
description: API key scopes.
expires_in_seconds:
anyOf:
- type: integer
maximum: 31536000
minimum: 1
description: Number of seconds until the API key expires.
- type: 'null'
type: object
required: []
description: The service account API key create request payload.
Expand All @@ -66322,6 +66331,12 @@ components:
type: integer
format: unixtime
description: The Unix timestamp (in seconds) when the API key was created.
expires_at:
anyOf:
- type: integer
format: unixtime
description: The Unix timestamp (in seconds) when the API key expires, or null if it does not expire.
- type: 'null'
id:
type: string
description: The identifier of the API key.
Expand Down Expand Up @@ -80449,6 +80464,8 @@ components:
description: |
Whether to store the generated model response for later retrieval via
API.
Defaults to true when omitted.
If set to true, response data will be stored for at least 30 days, subject to the [data retention exceptions](/api/docs/guides/your-data#v1responses).
default: true
- type: 'null'
instructions:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from __future__ import annotations

from typing import Optional

import httpx2

from ...... import _legacy_response
Expand Down Expand Up @@ -42,6 +44,7 @@ def create(
service_account_id: str,
*,
project_id: str,
expires_in_seconds: Optional[int] | Omit = omit,
name: str | Omit = omit,
scopes: SequenceNotStr[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
Expand All @@ -59,6 +62,8 @@ def create(

service_account_id: The ID of the service account.

expires_in_seconds: Number of seconds until the API key expires.

name: API key name.

scopes: API key scopes.
Expand All @@ -83,6 +88,7 @@ def create(
),
body=maybe_transform(
{
"expires_in_seconds": expires_in_seconds,
"name": name,
"scopes": scopes,
},
Expand Down Expand Up @@ -124,6 +130,7 @@ async def create(
service_account_id: str,
*,
project_id: str,
expires_in_seconds: Optional[int] | Omit = omit,
name: str | Omit = omit,
scopes: SequenceNotStr[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
Expand All @@ -141,6 +148,8 @@ async def create(

service_account_id: The ID of the service account.

expires_in_seconds: Number of seconds until the API key expires.

name: API key name.

scopes: API key scopes.
Expand All @@ -165,6 +174,7 @@ async def create(
),
body=await async_maybe_transform(
{
"expires_in_seconds": expires_in_seconds,
"name": name,
"scopes": scopes,
},
Expand Down
50 changes: 46 additions & 4 deletions src/openai/resources/beta/responses/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,9 @@ def create(
parameter.

store: Whether to store the generated model response for later retrieval via API.
Defaults to true when omitted. If set to true, response data will be stored for
at least 30 days, subject to the
[data retention exceptions](/api/docs/guides/your-data#v1responses).

stream: If set to true, the model response data will be streamed to the client as it is
generated using
Expand Down Expand Up @@ -822,6 +825,9 @@ def create(
parameter.

store: Whether to store the generated model response for later retrieval via API.
Defaults to true when omitted. If set to true, response data will be stored for
at least 30 days, subject to the
[data retention exceptions](/api/docs/guides/your-data#v1responses).

stream_options: Options for streaming responses. Only set this when you set `stream: true`.

Expand Down Expand Up @@ -1217,6 +1223,9 @@ def create(
parameter.

store: Whether to store the generated model response for later retrieval via API.
Defaults to true when omitted. If set to true, response data will be stored for
at least 30 days, subject to the
[data retention exceptions](/api/docs/guides/your-data#v1responses).

stream_options: Options for streaming responses. Only set this when you set `stream: true`.

Expand Down Expand Up @@ -2375,6 +2384,9 @@ async def create(
parameter.

store: Whether to store the generated model response for later retrieval via API.
Defaults to true when omitted. If set to true, response data will be stored for
at least 30 days, subject to the
[data retention exceptions](/api/docs/guides/your-data#v1responses).

stream: If set to true, the model response data will be streamed to the client as it is
generated using
Expand Down Expand Up @@ -2777,6 +2789,9 @@ async def create(
parameter.

store: Whether to store the generated model response for later retrieval via API.
Defaults to true when omitted. If set to true, response data will be stored for
at least 30 days, subject to the
[data retention exceptions](/api/docs/guides/your-data#v1responses).

stream_options: Options for streaming responses. Only set this when you set `stream: true`.

Expand Down Expand Up @@ -3172,6 +3187,9 @@ async def create(
parameter.

store: Whether to store the generated model response for later retrieval via API.
Defaults to true when omitted. If set to true, response data will be stored for
at least 30 days, subject to the
[data retention exceptions](/api/docs/guides/your-data#v1responses).

stream_options: Options for streaming responses. Only set this when you set `stream: true`.

Expand Down Expand Up @@ -4126,6 +4144,7 @@ def __init__(
self._make_ws = make_ws
self._on_reconnecting = on_reconnecting
self._max_retries = max_retries
self._reconnect_attempt = 0
self._initial_delay = initial_delay
self._max_delay = max_delay
self._extra_query = extra_query
Expand Down Expand Up @@ -4165,7 +4184,17 @@ async def recv(self) -> BetaResponsesServerEvent:

Canceling this method is safe. There's no risk of losing data.
"""
return self.parse_event(await self.recv_bytes())
event = self.parse_event(await self.recv_bytes())
event_type = (
cast("dict[str, object]", event).get("type")
if isinstance(cast(object, event), dict)
else getattr(event, "type", None)
)
# A successful upgrade can still be followed by an admission error.
# Reset the budget only after receiving a non-error application event.
if isinstance(event_type, str) and event_type and event_type != "error":
self._reconnect_attempt = 0
return event

async def recv_bytes(self) -> bytes:
"""Receive the next message from the connection as raw bytes.
Expand Down Expand Up @@ -4238,7 +4267,8 @@ async def _reconnect(self, exc: Exception) -> bool:

self._is_reconnecting = True

for attempt in range(1, self._max_retries + 1):
for attempt in range(self._reconnect_attempt + 1, self._max_retries + 1):
self._reconnect_attempt = attempt
base_delay = min(self._initial_delay * (2 ** (attempt - 1)), self._max_delay)
jitter = 0.75 + random.random() * 0.25
delay = base_delay * jitter
Expand Down Expand Up @@ -4583,6 +4613,7 @@ def __init__(
self._make_ws = make_ws
self._on_reconnecting = on_reconnecting
self._max_retries = max_retries
self._reconnect_attempt = 0
self._initial_delay = initial_delay
self._max_delay = max_delay
self._extra_query = extra_query
Expand Down Expand Up @@ -4622,7 +4653,17 @@ def recv(self) -> BetaResponsesServerEvent:

Canceling this method is safe. There's no risk of losing data.
"""
return self.parse_event(self.recv_bytes())
event = self.parse_event(self.recv_bytes())
event_type = (
cast("dict[str, object]", event).get("type")
if isinstance(cast(object, event), dict)
else getattr(event, "type", None)
)
# A successful upgrade can still be followed by an admission error.
# Reset the budget only after receiving a non-error application event.
if isinstance(event_type, str) and event_type and event_type != "error":
self._reconnect_attempt = 0
return event

def recv_bytes(self) -> bytes:
"""Receive the next message from the connection as raw bytes.
Expand Down Expand Up @@ -4693,7 +4734,8 @@ def _reconnect(self, exc: Exception) -> bool:

self._is_reconnecting = True

for attempt in range(1, self._max_retries + 1):
for attempt in range(self._reconnect_attempt + 1, self._max_retries + 1):
self._reconnect_attempt = attempt
base_delay = min(self._initial_delay * (2 ** (attempt - 1)), self._max_delay)
jitter = 0.75 + random.random() * 0.25
delay = base_delay * jitter
Expand Down
32 changes: 28 additions & 4 deletions src/openai/resources/realtime/realtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ def __init__(
self._make_ws = make_ws
self._on_reconnecting = on_reconnecting
self._max_retries = max_retries
self._reconnect_attempt = 0
self._initial_delay = initial_delay
self._max_delay = max_delay
self._extra_query = extra_query
Expand Down Expand Up @@ -329,7 +330,17 @@ async def recv(self) -> RealtimeServerEvent:

Canceling this method is safe. There's no risk of losing data.
"""
return self.parse_event(await self.recv_bytes())
event = self.parse_event(await self.recv_bytes())
event_type = (
cast("dict[str, object]", event).get("type")
if isinstance(cast(object, event), dict)
else getattr(event, "type", None)
)
# A successful upgrade can still be followed by an admission error.
# Reset the budget only after receiving a non-error application event.
if isinstance(event_type, str) and event_type and event_type != "error":
self._reconnect_attempt = 0
Comment on lines +341 to +342

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset retries after recoverable error events

When a successful reconnect is followed by a normal request-level error event, this condition leaves the previous attempt count active even though RealtimeErrorEvent explicitly documents that most errors are recoverable and the session remains open. If that healthy session later experiences an unrelated network disconnect before producing a non-error event, reconnection starts from the stale count and can eventually be refused after several independent incidents. Distinguish admission failures from recoverable application errors instead of treating every type == "error" event as a failed reconnect; the same condition is duplicated in the sync and Responses connection implementations.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the actual sync/async Realtime, Responses, and beta Responses connection classes. For Realtime, session.created is the first event on an established connection; it resets the count before later recoverable errors. A probe starting at the retry limit, receiving that event and then a request error, reconnects at attempt 1 after a later network close.

For a Responses connection that has received only errors since reconnecting, your observation about retaining the count is correct. This guard currently requires a non-error application event (such as response.created) to replenish the budget. Error events remain available to the caller and do not themselves close the connection or consume another retry. Resetting on a subset of errors needs a reliable distinction from errors followed by admission failure/closure, so I am preserving the current behavior pending maintainer agreement on that policy rather than adding an unverified classification. Leaving this thread open for that decision.

return event

async def recv_bytes(self) -> bytes:
"""Receive the next message from the connection as raw bytes.
Expand Down Expand Up @@ -401,7 +412,8 @@ async def _reconnect(self, exc: Exception) -> bool:

self._is_reconnecting = True

for attempt in range(1, self._max_retries + 1):
for attempt in range(self._reconnect_attempt + 1, self._max_retries + 1):
self._reconnect_attempt = attempt
base_delay = min(self._initial_delay * (2 ** (attempt - 1)), self._max_delay)
jitter = 0.75 + random.random() * 0.25
delay = base_delay * jitter
Expand Down Expand Up @@ -768,6 +780,7 @@ def __init__(
self._make_ws = make_ws
self._on_reconnecting = on_reconnecting
self._max_retries = max_retries
self._reconnect_attempt = 0
self._initial_delay = initial_delay
self._max_delay = max_delay
self._extra_query = extra_query
Expand Down Expand Up @@ -811,7 +824,17 @@ def recv(self) -> RealtimeServerEvent:

Canceling this method is safe. There's no risk of losing data.
"""
return self.parse_event(self.recv_bytes())
event = self.parse_event(self.recv_bytes())
event_type = (
cast("dict[str, object]", event).get("type")
if isinstance(cast(object, event), dict)
else getattr(event, "type", None)
)
# A successful upgrade can still be followed by an admission error.
# Reset the budget only after receiving a non-error application event.
if isinstance(event_type, str) and event_type and event_type != "error":
self._reconnect_attempt = 0
return event

def recv_bytes(self) -> bytes:
"""Receive the next message from the connection as raw bytes.
Expand Down Expand Up @@ -881,7 +904,8 @@ def _reconnect(self, exc: Exception) -> bool:

self._is_reconnecting = True

for attempt in range(1, self._max_retries + 1):
for attempt in range(self._reconnect_attempt + 1, self._max_retries + 1):
self._reconnect_attempt = attempt
base_delay = min(self._initial_delay * (2 ** (attempt - 1)), self._max_delay)
jitter = 0.75 + random.random() * 0.25
delay = base_delay * jitter
Expand Down
Loading
Loading