diff --git a/.castiron.stats.yml b/.castiron.stats.yml index 07cc92d5db..f5c5c0a576 100644 --- a/.castiron.stats.yml +++ b/.castiron.stats.yml @@ -1,8 +1,8 @@ schema_version: 1 -generation_id: bb452d19-8d5f-4013-bfd3-3edecb25c37e -openapi_spec_hash: 72d34e76f62e434dc0b71e0b4ac0ce8a -openapi_transformed_spec_hash: bca4c6179df519f310841c4730b72c18 +generation_id: b1641964-c2c6-4bee-bbf0-94ce27e69bbe +openapi_spec_hash: e856004bcb1faf4b5b91d43290188237 +openapi_transformed_spec_hash: 7f2f62b14c75d5cd32be2baa653486ed config_hash: 8a49d8603252fe24c2dc96f50eb0d05b -codegen_sha: 016ee2b2da744118cf209752f57c8616ad20b842 -codegen_hash: 82cc4b7aca374db5d389b1ac06369a1ffa5946795a9ab86c87f57a5f5ec00390 -public_codegen_sha: e23db4894b46c4da1d4d9625eb0876d0ab512e78 +codegen_sha: 782e497532d06845ad69cd009bff71251296d2c7 +codegen_hash: 2427f947d2dc5d6321110dc0749b2bf4888e2817b4be8818115de07d4097cffc +public_codegen_sha: 50008a1abc1c26e8e7fb404f4322430e099986d3 diff --git a/.github/workflows/castiron-custom-code-comment.yml b/.github/workflows/castiron-custom-code-comment.yml index 43f3a1da2a..11588b3147 100644 --- a/.github/workflows/castiron-custom-code-comment.yml +++ b/.github/workflows/castiron-custom-code-comment.yml @@ -168,9 +168,13 @@ jobs: if (pr.state === 'open' && pr.head.sha === head && ['main', 'next'].includes(pr.base.ref) && pr.base.repo.full_name === `${context.repo.owner}/${context.repo.repo}`) current.push(pr); } - if (current.length !== 1) return; - branch = current[0].base.ref; - number = current[0].number; + // Match compute's preference for required main checks when + // fallback discovery finds an advisory next PR for the same head. + const main = current.filter(pr => pr.base.ref === 'main'); + const selected = main.length ? main : current; + if (selected.length !== 1) return; + branch = selected[0].base.ref; + number = selected[0].number; } else { const match = run.head_branch.match(/^gh-readonly-queue\/(main|next)\//); if (run.event !== 'merge_group' || !match) return; diff --git a/.github/workflows/castiron-custom-code.yml b/.github/workflows/castiron-custom-code.yml index 0fb4c3c759..be0288f8f0 100644 --- a/.github/workflows/castiron-custom-code.yml +++ b/.github/workflows/castiron-custom-code.yml @@ -16,7 +16,7 @@ concurrency: cancel-in-progress: false env: - REPORTER_SHA256: 157ed30ac9d54d20fe5f7e625dc8ee5958beb91cc7e30aaa97cfba8fb3e06e21 + REPORTER_SHA256: 4dc056b460d688cbe3035bd9649820f77e489424d0c9c48bd3f19ed4dfb7e9b4 jobs: queue-signal: diff --git a/api_reference/openapi.transformed.yml b/api_reference/openapi.transformed.yml index 7eea64cdcb..7f380efb93 100644 --- a/api_reference/openapi.transformed.yml +++ b/api_reference/openapi.transformed.yml @@ -78486,6 +78486,11 @@ components: - AE_STORAGE - AE_STORAGE_PROCESSING UpdateProjectApiKeyBody: + anyOf: + - required: + - scopes + - required: + - expires_in_seconds properties: expires_in_seconds: type: integer diff --git a/scripts/castiron/custom_code_report.py b/scripts/castiron/custom_code_report.py index 21906f5a52..02054ac86b 100755 --- a/scripts/castiron/custom_code_report.py +++ b/scripts/castiron/custom_code_report.py @@ -452,18 +452,31 @@ def resolve_checkpoint( if not isinstance(decoded, dict): raise ReportError("invalid codegen checkpoint metadata") metadata = cast(dict[str, Any], decoded) + namespace = "" + if "schema_version" in metadata: + if ( + set(metadata) != {"schema_version", "release_target", "record"} + or metadata["schema_version"] != "castiron-checkpoint/v2" + or metadata["release_target"] != "next" + or not isinstance(metadata["record"], dict) + ): + raise ReportError("invalid versioned checkpoint envelope") + metadata = cast(dict[str, Any], metadata["record"]) + namespace = "castiron/next/" + if {"release_target", "schema_version", "record"}.intersection(metadata): + raise ReportError("checkpoint target metadata must use the versioned envelope") if metadata.get("generation_id") != stats["generation_id"]: raise ReportError("codegen checkpoint generation ID does not match stats") branch = metadata.get("source_branch") if not isinstance(branch, str) or not branch: raise ReportError("codegen checkpoint has no source branch") - ref = f"refs/heads/codegen/{branch}" + ref = f"refs/heads/codegen/{namespace}{branch}" git(repo, "check-ref-format", ref) tracking = f"refs/castiron/report/{hashlib.sha256(ref.encode()).hexdigest()}" if fetch: git(repo, "fetch", "--quiet", "--no-tags", "origin", f"+{ref}:{tracking}") else: - tracking = f"refs/remotes/origin/codegen/{branch}" + tracking = f"refs/remotes/origin/codegen/{namespace}{branch}" git(repo, "merge-base", "--is-ancestor", commit, tracking) actual = hash_codegen_commit(repo, commit) expected = stats.get("codegen_hash") @@ -807,6 +820,10 @@ def current_pull(repository: str, run: dict[str, Any]) -> tuple[int, str] | None and pull["base"]["ref"] in TARGET_BRANCHES ): current.append((number, pull["base"]["ref"])) + # Fallback discovery can find both targets for one head. Main's required + # checks take precedence over next's advisory report; keep same-target + # ambiguity fail-closed. Explicit run associations still limit discovery. + current = [pull for pull in current if pull[1] == "main"] or current if len(current) > 1: raise ReportError("workflow run has multiple current pull requests") return current[0] if current else None diff --git a/scripts/castiron/fixtures/github_publisher.cjs b/scripts/castiron/fixtures/github_publisher.cjs index 9f16565bcc..8c549a65bc 100644 --- a/scripts/castiron/fixtures/github_publisher.cjs +++ b/scripts/castiron/fixtures/github_publisher.cjs @@ -6,7 +6,7 @@ const writes = []; const github = { rest: { actions: { getWorkflowRun: async () => ({ data: data.run }) }, - pulls: { get: async () => ({ data: data.current_sequence?.shift() || data.current }), list: 'pulls' }, + pulls: { get: async ({ pull_number }) => ({ data: data.current_sequence?.shift() || data.pulls_by_number?.[pull_number] || data.current }), list: 'pulls' }, git: { getRef: async ({ ref }) => ({ data: { diff --git a/scripts/castiron/generated-files.json b/scripts/castiron/generated-files.json index 1233e098ff..ac49a478b5 100644 --- a/scripts/castiron/generated-files.json +++ b/scripts/castiron/generated-files.json @@ -3,11 +3,11 @@ "files": { ".github/workflows/castiron-custom-code-comment.yml": { "mode": "100644", - "sha256": "02b1c497461fcaf604e1389e7dcab06cfcca01635a8cb2e92f8769245a2aaead" + "sha256": "f0a54911a96ae55a6a9c71ed7e6b259b0da84642dd1716b68e75a7123519728c" }, ".github/workflows/castiron-custom-code.yml": { "mode": "100644", - "sha256": "ccd67760a8cfcc17ebdf5c8df7e330ee7be5199cb62232afdf9035fea68bbc2f" + "sha256": "867db0c0cb67312a7682236b2ff04f2ccf611ad9e307a2ee650b3a997191f289" }, "api.md": { "mode": "100644", @@ -27,7 +27,7 @@ }, "scripts/castiron/custom_code_report.py": { "mode": "100755", - "sha256": "157ed30ac9d54d20fe5f7e625dc8ee5958beb91cc7e30aaa97cfba8fb3e06e21" + "sha256": "4dc056b460d688cbe3035bd9649820f77e489424d0c9c48bd3f19ed4dfb7e9b4" }, "scripts/castiron/custom_code_test_support.py": { "mode": "100644", @@ -35,7 +35,7 @@ }, "scripts/castiron/fixtures/github_publisher.cjs": { "mode": "100644", - "sha256": "fc80b1aff054f8a2ea16fc6bd9cff5ed4e4a48eebc7f3af8427947796fb8d865" + "sha256": "4f5475e905818875e37d686bf03553b55219ac05e30957628d4aaa834fced202" }, "scripts/castiron/ruff.toml": { "mode": "100644", @@ -47,15 +47,15 @@ }, "scripts/castiron/test_custom_code_github.py": { "mode": "100644", - "sha256": "aa170458b1b58523df78073a96ba8c58b75fce03206ff7d62d99b29791c6ca39" + "sha256": "338d54bf6b53a44b555dc7baa778c76af499b06959caf0339142f1b4293edd92" }, "scripts/castiron/test_custom_code_publication.py": { "mode": "100644", - "sha256": "99b266761f427d77990e68ce2fdea4b5f1a5b175612b3a9f4355c094939130ca" + "sha256": "1d93757664a6ffd26f21df2a4c07ac635393f5da1ab0283df75bad66faa2ef2e" }, "scripts/castiron/test_custom_code_report.py": { "mode": "100644", - "sha256": "56a748c56ccf137aee0e5997dd29484157d5566bf844cc4969270dd26fa38cbd" + "sha256": "8cb4add28b7e6acb4f8b3844073d33e7a95d8a554c4caf47bd1e09da86a3bd04" }, "src/openai/__init__.py": { "mode": "100644", @@ -411,7 +411,7 @@ }, "src/openai/resources/beta/responses/responses.py": { "mode": "100644", - "sha256": "e631b84ca7b00615a9e2f232d45747ec39f284ffa1ceb71517032e969bb386bb" + "sha256": "c7e705185228ea5a1662dc0fe569e0c16bb7eadf38beff925f6067643d98feb6" }, "src/openai/resources/beta/threads/__init__.py": { "mode": "100644", @@ -591,11 +591,11 @@ }, "src/openai/resources/live/forks.py": { "mode": "100644", - "sha256": "f3395e5c09d1e690b3c4fd0e2adf04990362e217df3b1ab2a046657d6e1b54b2" + "sha256": "ff834c298a4566869e81e769be92bcbed216510557ff4eb344fd840bab2f2cd5" }, "src/openai/resources/live/live.py": { "mode": "100644", - "sha256": "472c140e576cb42c4965e1f623fc7b29e50b01dae6b474c2e9db2f0190289f40" + "sha256": "a0ae2ae3eaaeccf152edca7999656be8cf10e5da49fc6335d479d71ba259b30d" }, "src/openai/resources/live/sessions.py": { "mode": "100644", @@ -603,7 +603,7 @@ }, "src/openai/resources/live/sideband.py": { "mode": "100644", - "sha256": "073e10b3edc936de7d35776069354c4272a9d7636070cb3ce4804dca978915f5" + "sha256": "37028100a5301b90b58cb6ed4ea2e20e0277fa476796d5d932cf848ad2a294c1" }, "src/openai/resources/models.py": { "mode": "100644", @@ -631,7 +631,7 @@ }, "src/openai/resources/realtime/realtime.py": { "mode": "100644", - "sha256": "a1af3c8791a4f29b66e3401348b6c6dea6dc658a8a6bb88843cdb30b4626119c" + "sha256": "29cf5fe34140d2f985151b264d3438897ed1579d7db1ef523a5831dd89ac7936" }, "src/openai/resources/realtime/translations/__init__.py": { "mode": "100644", @@ -643,7 +643,7 @@ }, "src/openai/resources/realtime/translations/translations.py": { "mode": "100644", - "sha256": "0da275b10d6f42a82cc83dde4ed659efc7b75ce1d8191365c22a3914fbb840e4" + "sha256": "2ed29f4fa975a92d0067c7c28e30fefad8070fc4bd6a7b35ff124b0bbaeb94b5" }, "src/openai/resources/responses/__init__.py": { "mode": "100644", @@ -663,7 +663,7 @@ }, "src/openai/resources/responses/responses.py": { "mode": "100644", - "sha256": "2239f2e9fe19aa019d2580a0f74a5d2efcedc2c284650ee1c2567af334a045e6" + "sha256": "77b3bd59e28c76c8c54d21d556718d30d6be437f1b19507e40e3708a24986e77" }, "src/openai/resources/safety/__init__.py": { "mode": "100644", @@ -755,7 +755,7 @@ }, "src/openai/types/__init__.py": { "mode": "100644", - "sha256": "5caaa29a79510a0a3e4a92f7b59d3ccb3f8f6651706f658738cb5e2a22b91481" + "sha256": "47c98e4ffd0eb3a28a9d60b78facf0bdd165d6d35b24ebb6bea5834a0165eeea" }, "src/openai/types/admin/__init__.py": { "mode": "100644", @@ -7271,7 +7271,7 @@ }, "tests/api_resources/admin/organization/projects/test_api_keys.py": { "mode": "100644", - "sha256": "1fa9769ccdc65d47c8c17c1e525a69e70ebb6d9248a017eb5fe4de7a337ec8ee" + "sha256": "49c21791da11c49c01c26b40da8e826b37a496d8e209c4b76911be04435d5048" }, "tests/api_resources/admin/organization/projects/test_certificates.py": { "mode": "100644", diff --git a/scripts/castiron/test_custom_code_github.py b/scripts/castiron/test_custom_code_github.py index 3c04d2075c..a96162fdf9 100644 --- a/scripts/castiron/test_custom_code_github.py +++ b/scripts/castiron/test_custom_code_github.py @@ -62,6 +62,35 @@ def __call__(self, method: str, path: str, payload: dict[str, Any] | None = None class GitHubReportTests(GitTestCase): + def test_same_head_targets_preserve_required_main_discovery(self) -> None: + head = "b" * 40 + run = source_run(head, "pull_request") + run["pull_requests"] = [] + main: dict[str, Any] = { + "state": "open", + "head": {"sha": head}, + "base": {"ref": "main", "repo": {"full_name": REPOSITORY}}, + } + next_pull: dict[str, Any] = {**main, "base": {**main["base"], "ref": "next"}} + for numbers in ([3, 4], [4, 3]): + def api_response(_method: str, path: str, numbers: list[int] = numbers) -> Any: + if "/commits/" in path: + return [{"number": number} for number in numbers] + return {"3": main, "4": next_pull}[path.rsplit("/", 1)[1]] + + with ( + self.subTest(numbers=numbers), + mock.patch.object(report, "api", side_effect=api_response), + ): + self.assertEqual(report.current_pull(REPOSITORY, run), (3, "main")) + run["pull_requests"] = [{"number": 4}] + self.assertEqual(report.current_pull(REPOSITORY, run), (4, "next")) + run["pull_requests"] = [] + next_pull["base"]["ref"] = "main" + with self.assertRaisesRegex(report.ReportError, "multiple current"): + report.current_pull(REPOSITORY, run) + next_pull["base"]["ref"] = "next" + def test_trusted_report_recomputes_pr_output_in_a_bare_repository(self) -> None: generated, _ = self.baseline() content_hash = report.hash_codegen_commit(self.repo, generated) diff --git a/scripts/castiron/test_custom_code_publication.py b/scripts/castiron/test_custom_code_publication.py index f8137165ae..475cc7dc7d 100644 --- a/scripts/castiron/test_custom_code_publication.py +++ b/scripts/castiron/test_custom_code_publication.py @@ -326,6 +326,7 @@ def publish( queue_absent: bool = False, queue_pages: list[dict[str, Any]] | None = None, retarget_during_publication: bool = False, + same_head_next_pr: bool = False, ) -> list[dict[str, Any]]: base, head = "a" * 40, "b" * 40 run = source_run(head, event_name) @@ -369,6 +370,12 @@ def publish( "PUBLISH_ATTEMPT": "1", }, } + if same_head_next_pr: + current = payload["current"] + payload["pulls_by_number"] = { + 3: current, + 4: {**current, "number": 4, "base": {**current["base"], "ref": "next"}}, + } if retarget_during_publication: current = payload["current"] payload["current_sequence"] = [ @@ -397,6 +404,22 @@ def test_next_cannot_overwrite_main_results(self) -> None: result = self.publish(event_name=event, branch="next", previous_statuses=main) self.assertEqual(result, []) + def test_same_head_next_pr_does_not_block_required_main_statuses(self) -> None: + for numbers in ([3, 4], [4, 3]): + with self.subTest(numbers=numbers): + results = self.publish( + run_overrides={"pull_requests": []}, + fallback_pulls=[{"number": number} for number in numbers], + same_head_next_pr=True, + failed_budget=True, + ) + self.assertEqual([r["state"] for r in results], ["success", "failure"]) + self.assertTrue(all(r["sha"] == "b" * 40 for r in results)) + self.assertEqual( + self.publish(run_overrides={"pull_requests": [{"number": 4}]}, same_head_next_pr=True), + [], + ) + def test_next_queue_never_authorizes_against_a_stale_main_policy(self) -> None: for changed in (False, True): with self.subTest(policy_changed=changed): @@ -413,7 +436,9 @@ def test_main_statuses_bind_target_policy_and_queue_membership(self) -> None: for policy_changed, queue_absent in ((True, False), (False, True)): with self.subTest(policy_changed=policy_changed, queue_absent=queue_absent): result = self.publish( - event_name="merge_group", policy_changed=policy_changed, queue_absent=queue_absent + event_name="merge_group", + policy_changed=policy_changed, + queue_absent=queue_absent, ) self.assertEqual([r["state"] for r in result], ["failure", "failure"]) self.assertEqual(self.publish(retarget_during_publication=True), []) diff --git a/scripts/castiron/test_custom_code_report.py b/scripts/castiron/test_custom_code_report.py index d483c31c82..6317d00c55 100644 --- a/scripts/castiron/test_custom_code_report.py +++ b/scripts/castiron/test_custom_code_report.py @@ -166,6 +166,51 @@ def test_stats_symlink_is_rejected(self) -> None: with self.assertRaisesRegex(report.ReportError, "regular Git file"): report.read_stats(self.repo, self.commit()) + def test_next_checkpoint_uses_its_namespaced_ref(self) -> None: + generated, base = self.baseline() + self.git("checkout", "--detach", generated) + metadata = { + "schema_version": "castiron-checkpoint/v2", + "release_target": "next", + "record": {"generation_id": GENERATION, "source_branch": "test"}, + } + encoded = base64.b64encode(json.dumps(metadata).encode()).decode() + checkpoint = self.commit(f"codegen\n\nGeneration metadata: {encoded}") + ref = "refs/remotes/origin/codegen/castiron/next/test" + self.git("update-ref", ref, checkpoint) + stats = report.read_stats(self.repo, base) | { + "codegen_sha": checkpoint, + "codegen_hash": report.hash_codegen_commit(self.repo, checkpoint), + } + result = report.resolve_checkpoint(self.repo, stats, fetch=False, require_hash=True) + self.assertEqual(result["commit"], checkpoint) + self.git("update-ref", "refs/remotes/origin/codegen/test", checkpoint) + self.git("update-ref", "-d", ref) + with self.assertRaises(report.ReportError): + report.resolve_checkpoint(self.repo, stats, fetch=False, require_hash=True) + + def test_invalid_checkpoint_envelopes_are_rejected(self) -> None: + _, base = self.baseline() + record = {"generation_id": GENERATION, "source_branch": "test"} + envelope = { + "schema_version": "castiron-checkpoint/v2", + "release_target": "next", + "record": record, + } + for metadata in ( + envelope | {"schema_version": "unknown"}, + envelope | {"release_target": "main"}, + envelope | {"extra": True}, + envelope | {"record": record | {"release_target": "next"}}, + record | {"release_target": "next"}, + ): + with self.subTest(metadata=metadata): + encoded = base64.b64encode(json.dumps(metadata).encode()).decode() + checkpoint = self.commit(f"codegen\n\nGeneration metadata: {encoded}") + stats = report.read_stats(self.repo, base) | {"codegen_sha": checkpoint} + with self.assertRaisesRegex(report.ReportError, "envelope"): + report.resolve_checkpoint(self.repo, stats, fetch=False, require_hash=True) + def test_public_snapshot_rejects_private_governance_and_unpublished_paths(self) -> None: entry = report.Entry(b"100644", "a" * 40) blocked = [ diff --git a/src/openai/resources/beta/responses/responses.py b/src/openai/resources/beta/responses/responses.py index 0199b766ae..6e61b51ebc 100644 --- a/src/openai/resources/beta/responses/responses.py +++ b/src/openai/resources/beta/responses/responses.py @@ -52,7 +52,7 @@ from ....types.beta.beta_service_tier import BetaServiceTier from ....types.websocket_reconnection import ReconnectingEvent, ReconnectingOverrides from ....types.beta.beta_compacted_response import BetaCompactedResponse -from ....types.websocket_connection_options import WebSocketConnectionOptions +from ....types.websocket_connection_options import WebSocketConnectionOptions, SyncWebSocketConnectionOptions from ....types.beta.beta_response_includable import BetaResponseIncludable from ....types.beta.beta_response_input_param import BetaResponseInputParam from ....types.beta.beta_response_prompt_param import BetaResponsePromptParam @@ -2069,7 +2069,7 @@ def connect( self, extra_query: Query = {}, extra_headers: Headers = {}, - websocket_connection_options: WebSocketConnectionOptions = {}, + websocket_connection_options: SyncWebSocketConnectionOptions = {}, on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, max_retries: int = 5, initial_delay: float = 0.5, @@ -4545,20 +4545,29 @@ async def __aenter__(self) -> AsyncResponsesConnection: """ ws = await self._connect_ws(self.__extra_query, self.__extra_headers) - self.__connection = AsyncResponsesConnection( - ws, - make_ws=self._connect_ws if self.__on_reconnecting is not None else None, - on_reconnecting=self.__on_reconnecting, - max_retries=self.__max_retries, - initial_delay=self.__initial_delay, - max_delay=self.__max_delay, - extra_query=self.__extra_query, - extra_headers=self.__extra_headers, - send_queue=self.__send_queue, - ) + try: + self.__connection = AsyncResponsesConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) - self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) - await self.__connection._flush_send_queue() + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + await self.__connection._flush_send_queue() + except BaseException: + self.__connection = None + try: + await ws.close() + except BaseException: + # Preserve the exception that prevented successful entry. + pass + raise return self.__connection @@ -4603,11 +4612,16 @@ async def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> Async if self.__websocket_connection_options: log.debug("Custom WebSocket connection options provided") + websocket_options = self.__websocket_connection_options.copy() + websocket_options.setdefault("max_size", None) + if websocket_options.get("max_size") is None: + websocket_options.setdefault("compression", None) + return await connect( str(url), user_agent_header=None, additional_headers=_merge_mappings(dict(headers.values()), {}), - **self.__websocket_connection_options, + **websocket_options, ) def _prepare_url(self) -> httpx2.URL: @@ -4851,7 +4865,7 @@ def __init__( client: OpenAI, extra_query: Query, extra_headers: Headers, - websocket_connection_options: WebSocketConnectionOptions, + websocket_connection_options: SyncWebSocketConnectionOptions, on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, max_retries: int = 5, initial_delay: float = 0.5, @@ -4935,20 +4949,29 @@ def __enter__(self) -> ResponsesConnection: """ ws = self._connect_ws(self.__extra_query, self.__extra_headers) - self.__connection = ResponsesConnection( - ws, - make_ws=self._connect_ws if self.__on_reconnecting is not None else None, - on_reconnecting=self.__on_reconnecting, - max_retries=self.__max_retries, - initial_delay=self.__initial_delay, - max_delay=self.__max_delay, - extra_query=self.__extra_query, - extra_headers=self.__extra_headers, - send_queue=self.__send_queue, - ) + try: + self.__connection = ResponsesConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) - self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) - self.__connection._flush_send_queue() + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + self.__connection._flush_send_queue() + except BaseException: + self.__connection = None + try: + ws.close() + except BaseException: + # Preserve the exception that prevented successful entry. + pass + raise return self.__connection @@ -4993,11 +5016,16 @@ def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> WebSocketCo if self.__websocket_connection_options: log.debug("Custom WebSocket connection options provided") + websocket_options = self.__websocket_connection_options.copy() + websocket_options.setdefault("max_size", None) + if websocket_options.get("max_size") is None: + websocket_options.setdefault("compression", None) + return connect( str(url), user_agent_header=None, additional_headers=_merge_mappings(dict(headers.values()), {}), - **self.__websocket_connection_options, + **websocket_options, ) def _prepare_url(self) -> httpx2.URL: diff --git a/src/openai/resources/live/forks.py b/src/openai/resources/live/forks.py index b792516d39..1cece08732 100644 --- a/src/openai/resources/live/forks.py +++ b/src/openai/resources/live/forks.py @@ -28,7 +28,7 @@ from ...types.live.fork_server_event import ForkServerEvent from ...types.websocket_reconnection import ReconnectingEvent, ReconnectingOverrides from ...types.live.fork_client_event_param import ForkClientEventParam -from ...types.websocket_connection_options import WebSocketConnectionOptions +from ...types.websocket_connection_options import WebSocketConnectionOptions, SyncWebSocketConnectionOptions from ...types.live.fork_session_config_param import ForkSessionConfigParam from ...types.live.session_update_config_param import SessionUpdateConfigParam from ...types.responses.response_input_item_param import ResponseInputItemParam @@ -51,7 +51,7 @@ def connect( session_id: str, extra_query: Query = {}, extra_headers: Headers = {}, - websocket_connection_options: WebSocketConnectionOptions = {}, + websocket_connection_options: SyncWebSocketConnectionOptions = {}, on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, max_retries: int = 5, initial_delay: float = 0.5, @@ -423,20 +423,29 @@ async def __aenter__(self) -> AsyncForksConnection: """ ws = await self._connect_ws(self.__extra_query, self.__extra_headers) - self.__connection = AsyncForksConnection( - ws, - make_ws=self._connect_ws if self.__on_reconnecting is not None else None, - on_reconnecting=self.__on_reconnecting, - max_retries=self.__max_retries, - initial_delay=self.__initial_delay, - max_delay=self.__max_delay, - extra_query=self.__extra_query, - extra_headers=self.__extra_headers, - send_queue=self.__send_queue, - ) + try: + self.__connection = AsyncForksConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) - self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) - await self.__connection._flush_send_queue() + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + await self.__connection._flush_send_queue() + except BaseException: + self.__connection = None + try: + await ws.close() + except BaseException: + # Preserve the exception that prevented successful entry. + pass + raise return self.__connection @@ -481,11 +490,16 @@ async def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> Async if self.__websocket_connection_options: log.debug("Custom WebSocket connection options provided") + websocket_options = self.__websocket_connection_options.copy() + websocket_options.setdefault("max_size", None) + if websocket_options.get("max_size") is None: + websocket_options.setdefault("compression", None) + return await connect( str(url), user_agent_header=None, additional_headers=_merge_mappings(dict(headers.values()), {}), - **self.__websocket_connection_options, + **websocket_options, ) def _prepare_url(self) -> httpx2.URL: @@ -734,7 +748,7 @@ def __init__( session_id: str, extra_query: Query, extra_headers: Headers, - websocket_connection_options: WebSocketConnectionOptions, + websocket_connection_options: SyncWebSocketConnectionOptions, on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, max_retries: int = 5, initial_delay: float = 0.5, @@ -819,20 +833,29 @@ def __enter__(self) -> ForksConnection: """ ws = self._connect_ws(self.__extra_query, self.__extra_headers) - self.__connection = ForksConnection( - ws, - make_ws=self._connect_ws if self.__on_reconnecting is not None else None, - on_reconnecting=self.__on_reconnecting, - max_retries=self.__max_retries, - initial_delay=self.__initial_delay, - max_delay=self.__max_delay, - extra_query=self.__extra_query, - extra_headers=self.__extra_headers, - send_queue=self.__send_queue, - ) + try: + self.__connection = ForksConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) - self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) - self.__connection._flush_send_queue() + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + self.__connection._flush_send_queue() + except BaseException: + self.__connection = None + try: + ws.close() + except BaseException: + # Preserve the exception that prevented successful entry. + pass + raise return self.__connection @@ -877,11 +900,16 @@ def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> WebSocketCo if self.__websocket_connection_options: log.debug("Custom WebSocket connection options provided") + websocket_options = self.__websocket_connection_options.copy() + websocket_options.setdefault("max_size", None) + if websocket_options.get("max_size") is None: + websocket_options.setdefault("compression", None) + return connect( str(url), user_agent_header=None, additional_headers=_merge_mappings(dict(headers.values()), {}), - **self.__websocket_connection_options, + **websocket_options, ) def _prepare_url(self) -> httpx2.URL: diff --git a/src/openai/resources/live/live.py b/src/openai/resources/live/live.py index b93468b96c..422057288e 100644 --- a/src/openai/resources/live/live.py +++ b/src/openai/resources/live/live.py @@ -42,7 +42,7 @@ from ...types.live.client_event_param import ClientEventParam from ...types.live.live_create_response import LiveCreateResponse from ...types.live.session_config_param import SessionConfigParam -from ...types.websocket_connection_options import WebSocketConnectionOptions +from ...types.websocket_connection_options import WebSocketConnectionOptions, SyncWebSocketConnectionOptions from ...types.live.media_session_config_param import MediaSessionConfigParam from ...types.live.session_update_config_param import SessionUpdateConfigParam from ...types.responses.response_input_item_param import ResponseInputItemParam @@ -143,7 +143,7 @@ def connect( self, extra_query: Query = {}, extra_headers: Headers = {}, - websocket_connection_options: WebSocketConnectionOptions = {}, + websocket_connection_options: SyncWebSocketConnectionOptions = {}, on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, max_retries: int = 5, initial_delay: float = 0.5, @@ -641,20 +641,29 @@ async def __aenter__(self) -> AsyncLiveConnection: """ ws = await self._connect_ws(self.__extra_query, self.__extra_headers) - self.__connection = AsyncLiveConnection( - ws, - make_ws=self._connect_ws if self.__on_reconnecting is not None else None, - on_reconnecting=self.__on_reconnecting, - max_retries=self.__max_retries, - initial_delay=self.__initial_delay, - max_delay=self.__max_delay, - extra_query=self.__extra_query, - extra_headers=self.__extra_headers, - send_queue=self.__send_queue, - ) + try: + self.__connection = AsyncLiveConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) - self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) - await self.__connection._flush_send_queue() + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + await self.__connection._flush_send_queue() + except BaseException: + self.__connection = None + try: + await ws.close() + except BaseException: + # Preserve the exception that prevented successful entry. + pass + raise return self.__connection @@ -699,11 +708,16 @@ async def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> Async if self.__websocket_connection_options: log.debug("Custom WebSocket connection options provided") + websocket_options = self.__websocket_connection_options.copy() + websocket_options.setdefault("max_size", None) + if websocket_options.get("max_size") is None: + websocket_options.setdefault("compression", None) + return await connect( str(url), user_agent_header=None, additional_headers=_merge_mappings(dict(headers.values()), {}), - **self.__websocket_connection_options, + **websocket_options, ) def _prepare_url(self) -> httpx2.URL: @@ -949,7 +963,7 @@ def __init__( client: OpenAI, extra_query: Query, extra_headers: Headers, - websocket_connection_options: WebSocketConnectionOptions, + websocket_connection_options: SyncWebSocketConnectionOptions, on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, max_retries: int = 5, initial_delay: float = 0.5, @@ -1033,20 +1047,29 @@ def __enter__(self) -> LiveConnection: """ ws = self._connect_ws(self.__extra_query, self.__extra_headers) - self.__connection = LiveConnection( - ws, - make_ws=self._connect_ws if self.__on_reconnecting is not None else None, - on_reconnecting=self.__on_reconnecting, - max_retries=self.__max_retries, - initial_delay=self.__initial_delay, - max_delay=self.__max_delay, - extra_query=self.__extra_query, - extra_headers=self.__extra_headers, - send_queue=self.__send_queue, - ) + try: + self.__connection = LiveConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) - self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) - self.__connection._flush_send_queue() + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + self.__connection._flush_send_queue() + except BaseException: + self.__connection = None + try: + ws.close() + except BaseException: + # Preserve the exception that prevented successful entry. + pass + raise return self.__connection @@ -1091,11 +1114,16 @@ def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> WebSocketCo if self.__websocket_connection_options: log.debug("Custom WebSocket connection options provided") + websocket_options = self.__websocket_connection_options.copy() + websocket_options.setdefault("max_size", None) + if websocket_options.get("max_size") is None: + websocket_options.setdefault("compression", None) + return connect( str(url), user_agent_header=None, additional_headers=_merge_mappings(dict(headers.values()), {}), - **self.__websocket_connection_options, + **websocket_options, ) def _prepare_url(self) -> httpx2.URL: diff --git a/src/openai/resources/live/sideband.py b/src/openai/resources/live/sideband.py index 329ce7a13a..a3fb2ebc73 100644 --- a/src/openai/resources/live/sideband.py +++ b/src/openai/resources/live/sideband.py @@ -27,7 +27,7 @@ from ...types.websocket_reconnection import ReconnectingEvent, ReconnectingOverrides from ...types.live.connect_client_event import ConnectClientEvent from ...types.live.connect_server_event import ConnectServerEvent -from ...types.websocket_connection_options import WebSocketConnectionOptions +from ...types.websocket_connection_options import WebSocketConnectionOptions, SyncWebSocketConnectionOptions from ...types.live.connect_client_event_param import ConnectClientEventParam from ...types.live.session_update_config_param import SessionUpdateConfigParam from ...types.responses.response_input_item_param import ResponseInputItemParam @@ -51,7 +51,7 @@ def connect( graceful_close: bool | Omit = omit, extra_query: Query = {}, extra_headers: Headers = {}, - websocket_connection_options: WebSocketConnectionOptions = {}, + websocket_connection_options: SyncWebSocketConnectionOptions = {}, on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, max_retries: int = 5, initial_delay: float = 0.5, @@ -430,20 +430,29 @@ async def __aenter__(self) -> AsyncSidebandConnection: """ ws = await self._connect_ws(self.__extra_query, self.__extra_headers) - self.__connection = AsyncSidebandConnection( - ws, - make_ws=self._connect_ws if self.__on_reconnecting is not None else None, - on_reconnecting=self.__on_reconnecting, - max_retries=self.__max_retries, - initial_delay=self.__initial_delay, - max_delay=self.__max_delay, - extra_query=self.__extra_query, - extra_headers=self.__extra_headers, - send_queue=self.__send_queue, - ) + try: + self.__connection = AsyncSidebandConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) - self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) - await self.__connection._flush_send_queue() + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + await self.__connection._flush_send_queue() + except BaseException: + self.__connection = None + try: + await ws.close() + except BaseException: + # Preserve the exception that prevented successful entry. + pass + raise return self.__connection @@ -496,11 +505,16 @@ async def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> Async if self.__websocket_connection_options: log.debug("Custom WebSocket connection options provided") + websocket_options = self.__websocket_connection_options.copy() + websocket_options.setdefault("max_size", None) + if websocket_options.get("max_size") is None: + websocket_options.setdefault("compression", None) + return await connect( str(url), user_agent_header=None, additional_headers=_merge_mappings(dict(headers.values()), {}), - **self.__websocket_connection_options, + **websocket_options, ) def _prepare_url(self) -> httpx2.URL: @@ -752,7 +766,7 @@ def __init__( graceful_close: bool | Omit = omit, extra_query: Query, extra_headers: Headers, - websocket_connection_options: WebSocketConnectionOptions, + websocket_connection_options: SyncWebSocketConnectionOptions, on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, max_retries: int = 5, initial_delay: float = 0.5, @@ -838,20 +852,29 @@ def __enter__(self) -> SidebandConnection: """ ws = self._connect_ws(self.__extra_query, self.__extra_headers) - self.__connection = SidebandConnection( - ws, - make_ws=self._connect_ws if self.__on_reconnecting is not None else None, - on_reconnecting=self.__on_reconnecting, - max_retries=self.__max_retries, - initial_delay=self.__initial_delay, - max_delay=self.__max_delay, - extra_query=self.__extra_query, - extra_headers=self.__extra_headers, - send_queue=self.__send_queue, - ) + try: + self.__connection = SidebandConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) - self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) - self.__connection._flush_send_queue() + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + self.__connection._flush_send_queue() + except BaseException: + self.__connection = None + try: + ws.close() + except BaseException: + # Preserve the exception that prevented successful entry. + pass + raise return self.__connection @@ -904,11 +927,16 @@ def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> WebSocketCo if self.__websocket_connection_options: log.debug("Custom WebSocket connection options provided") + websocket_options = self.__websocket_connection_options.copy() + websocket_options.setdefault("max_size", None) + if websocket_options.get("max_size") is None: + websocket_options.setdefault("compression", None) + return connect( str(url), user_agent_header=None, additional_headers=_merge_mappings(dict(headers.values()), {}), - **self.__websocket_connection_options, + **websocket_options, ) def _prepare_url(self) -> httpx2.URL: diff --git a/src/openai/resources/realtime/realtime.py b/src/openai/resources/realtime/realtime.py index 1575803283..2ab8ced9f3 100644 --- a/src/openai/resources/realtime/realtime.py +++ b/src/openai/resources/realtime/realtime.py @@ -55,7 +55,7 @@ AsyncTranslationsWithStreamingResponse, ) from ...types.websocket_reconnection import ReconnectingEvent, ReconnectingOverrides -from ...types.websocket_connection_options import WebSocketConnectionOptions +from ...types.websocket_connection_options import WebSocketConnectionOptions, SyncWebSocketConnectionOptions from ...types.realtime.realtime_error_event import RealtimeErrorEvent from ...types.realtime.realtime_client_event import RealtimeClientEvent from ...types.realtime.realtime_server_event import RealtimeServerEvent @@ -115,7 +115,7 @@ def connect( model: str | Omit = omit, extra_query: Query = {}, extra_headers: Headers = {}, - websocket_connection_options: WebSocketConnectionOptions = {}, + websocket_connection_options: SyncWebSocketConnectionOptions = {}, on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, max_retries: int = 5, initial_delay: float = 0.5, @@ -611,20 +611,29 @@ async def __aenter__(self) -> AsyncRealtimeConnection: """ ws = await self._connect_ws(self.__extra_query, self.__extra_headers) - self.__connection = AsyncRealtimeConnection( - ws, - make_ws=self._connect_ws if self.__on_reconnecting is not None else None, - on_reconnecting=self.__on_reconnecting, - max_retries=self.__max_retries, - initial_delay=self.__initial_delay, - max_delay=self.__max_delay, - extra_query=self.__extra_query, - extra_headers=self.__extra_headers, - send_queue=self.__send_queue, - ) + try: + self.__connection = AsyncRealtimeConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) - self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) - await self.__connection._flush_send_queue() + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + await self.__connection._flush_send_queue() + except BaseException: + self.__connection = None + try: + await ws.close() + except BaseException: + # Preserve the exception that prevented successful entry. + pass + raise return self.__connection @@ -692,11 +701,16 @@ async def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> Async if self.__websocket_connection_options: log.debug("Custom WebSocket connection options provided") + websocket_options = self.__websocket_connection_options.copy() + websocket_options.setdefault("max_size", None) + if websocket_options.get("max_size") is None: + websocket_options.setdefault("compression", None) + return await connect( str(url), user_agent_header=None, additional_headers=_merge_mappings(dict(headers.values()), {}), - **self.__websocket_connection_options, + **websocket_options, ) def _prepare_url(self) -> httpx2.URL: @@ -949,7 +963,7 @@ def __init__( model: str | Omit = omit, extra_query: Query, extra_headers: Headers, - websocket_connection_options: WebSocketConnectionOptions, + websocket_connection_options: SyncWebSocketConnectionOptions, on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, max_retries: int = 5, initial_delay: float = 0.5, @@ -1035,20 +1049,29 @@ def __enter__(self) -> RealtimeConnection: """ ws = self._connect_ws(self.__extra_query, self.__extra_headers) - self.__connection = RealtimeConnection( - ws, - make_ws=self._connect_ws if self.__on_reconnecting is not None else None, - on_reconnecting=self.__on_reconnecting, - max_retries=self.__max_retries, - initial_delay=self.__initial_delay, - max_delay=self.__max_delay, - extra_query=self.__extra_query, - extra_headers=self.__extra_headers, - send_queue=self.__send_queue, - ) + try: + self.__connection = RealtimeConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) - self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) - self.__connection._flush_send_queue() + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + self.__connection._flush_send_queue() + except BaseException: + self.__connection = None + try: + ws.close() + except BaseException: + # Preserve the exception that prevented successful entry. + pass + raise return self.__connection @@ -1114,11 +1137,16 @@ def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> WebSocketCo if self.__websocket_connection_options: log.debug("Custom WebSocket connection options provided") + websocket_options = self.__websocket_connection_options.copy() + websocket_options.setdefault("max_size", None) + if websocket_options.get("max_size") is None: + websocket_options.setdefault("compression", None) + return connect( str(url), user_agent_header=None, additional_headers=_merge_mappings(dict(headers.values()), {}), - **self.__websocket_connection_options, + **websocket_options, ) def _prepare_url(self) -> httpx2.URL: diff --git a/src/openai/resources/realtime/translations/translations.py b/src/openai/resources/realtime/translations/translations.py index ec50d97eda..8fb860f720 100644 --- a/src/openai/resources/realtime/translations/translations.py +++ b/src/openai/resources/realtime/translations/translations.py @@ -30,7 +30,7 @@ from ...._event_handler import EventHandlerRegistry from ...._websocket_connection import WebSocketConnectionBase, AsyncWebSocketConnectionBase from ....types.websocket_reconnection import ReconnectingEvent, ReconnectingOverrides -from ....types.websocket_connection_options import WebSocketConnectionOptions +from ....types.websocket_connection_options import WebSocketConnectionOptions, SyncWebSocketConnectionOptions from ....types.realtime.realtime_error_event import RealtimeErrorEvent from ....types.realtime.realtime_translation_client_event import RealtimeTranslationClientEvent from ....types.realtime.realtime_translation_server_event import RealtimeTranslationServerEvent @@ -79,7 +79,7 @@ def connect( model: str, extra_query: Query = {}, extra_headers: Headers = {}, - websocket_connection_options: WebSocketConnectionOptions = {}, + websocket_connection_options: SyncWebSocketConnectionOptions = {}, on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, max_retries: int = 5, initial_delay: float = 0.5, @@ -503,20 +503,29 @@ async def __aenter__(self) -> AsyncTranslationsConnection: """ ws = await self._connect_ws(self.__extra_query, self.__extra_headers) - self.__connection = AsyncTranslationsConnection( - ws, - make_ws=self._connect_ws if self.__on_reconnecting is not None else None, - on_reconnecting=self.__on_reconnecting, - max_retries=self.__max_retries, - initial_delay=self.__initial_delay, - max_delay=self.__max_delay, - extra_query=self.__extra_query, - extra_headers=self.__extra_headers, - send_queue=self.__send_queue, - ) + try: + self.__connection = AsyncTranslationsConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) - self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) - await self.__connection._flush_send_queue() + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + await self.__connection._flush_send_queue() + except BaseException: + self.__connection = None + try: + await ws.close() + except BaseException: + # Preserve the exception that prevented successful entry. + pass + raise return self.__connection @@ -562,11 +571,16 @@ async def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> Async if self.__websocket_connection_options: log.debug("Custom WebSocket connection options provided") + websocket_options = self.__websocket_connection_options.copy() + websocket_options.setdefault("max_size", None) + if websocket_options.get("max_size") is None: + websocket_options.setdefault("compression", None) + return await connect( str(url), user_agent_header=None, additional_headers=_merge_mappings(dict(headers.values()), {}), - **self.__websocket_connection_options, + **websocket_options, ) def _prepare_url(self) -> httpx2.URL: @@ -811,7 +825,7 @@ def __init__( model: str, extra_query: Query, extra_headers: Headers, - websocket_connection_options: WebSocketConnectionOptions, + websocket_connection_options: SyncWebSocketConnectionOptions, on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, max_retries: int = 5, initial_delay: float = 0.5, @@ -899,20 +913,29 @@ def __enter__(self) -> TranslationsConnection: """ ws = self._connect_ws(self.__extra_query, self.__extra_headers) - self.__connection = TranslationsConnection( - ws, - make_ws=self._connect_ws if self.__on_reconnecting is not None else None, - on_reconnecting=self.__on_reconnecting, - max_retries=self.__max_retries, - initial_delay=self.__initial_delay, - max_delay=self.__max_delay, - extra_query=self.__extra_query, - extra_headers=self.__extra_headers, - send_queue=self.__send_queue, - ) + try: + self.__connection = TranslationsConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) - self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) - self.__connection._flush_send_queue() + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + self.__connection._flush_send_queue() + except BaseException: + self.__connection = None + try: + ws.close() + except BaseException: + # Preserve the exception that prevented successful entry. + pass + raise return self.__connection @@ -958,11 +981,16 @@ def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> WebSocketCo if self.__websocket_connection_options: log.debug("Custom WebSocket connection options provided") + websocket_options = self.__websocket_connection_options.copy() + websocket_options.setdefault("max_size", None) + if websocket_options.get("max_size") is None: + websocket_options.setdefault("compression", None) + return connect( str(url), user_agent_header=None, additional_headers=_merge_mappings(dict(headers.values()), {}), - **self.__websocket_connection_options, + **websocket_options, ) def _prepare_url(self) -> httpx2.URL: diff --git a/src/openai/resources/responses/responses.py b/src/openai/resources/responses/responses.py index 5c3bdc1252..54300192eb 100644 --- a/src/openai/resources/responses/responses.py +++ b/src/openai/resources/responses/responses.py @@ -77,7 +77,7 @@ from ...types.responses.parsed_response import ParsedResponse from ...lib.streaming.responses._responses import ResponseStreamManager, AsyncResponseStreamManager from ...types.responses.compacted_response import CompactedResponse -from ...types.websocket_connection_options import WebSocketConnectionOptions +from ...types.websocket_connection_options import WebSocketConnectionOptions, SyncWebSocketConnectionOptions from ...types.responses.response_includable import ResponseIncludable from ...types.shared_params.responses_model import ResponsesModel from ...types.responses.response_input_param import ResponseInputParam @@ -1986,7 +1986,7 @@ def connect( self, extra_query: Query = {}, extra_headers: Headers = {}, - websocket_connection_options: WebSocketConnectionOptions = {}, + websocket_connection_options: SyncWebSocketConnectionOptions = {}, on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, max_retries: int = 5, initial_delay: float = 0.5, @@ -4398,20 +4398,29 @@ async def __aenter__(self) -> AsyncResponsesConnection: """ ws = await self._connect_ws(self.__extra_query, self.__extra_headers) - self.__connection = AsyncResponsesConnection( - ws, - make_ws=self._connect_ws if self.__on_reconnecting is not None else None, - on_reconnecting=self.__on_reconnecting, - max_retries=self.__max_retries, - initial_delay=self.__initial_delay, - max_delay=self.__max_delay, - extra_query=self.__extra_query, - extra_headers=self.__extra_headers, - send_queue=self.__send_queue, - ) + try: + self.__connection = AsyncResponsesConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) - self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) - await self.__connection._flush_send_queue() + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + await self.__connection._flush_send_queue() + except BaseException: + self.__connection = None + try: + await ws.close() + except BaseException: + # Preserve the exception that prevented successful entry. + pass + raise return self.__connection @@ -4456,11 +4465,16 @@ async def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> Async if self.__websocket_connection_options: log.debug("Custom WebSocket connection options provided") + websocket_options = self.__websocket_connection_options.copy() + websocket_options.setdefault("max_size", None) + if websocket_options.get("max_size") is None: + websocket_options.setdefault("compression", None) + return await connect( str(url), user_agent_header=None, additional_headers=_merge_mappings(dict(headers.values()), {}), - **self.__websocket_connection_options, + **websocket_options, ) def _prepare_url(self) -> httpx2.URL: @@ -4705,7 +4719,7 @@ def __init__( client: OpenAI, extra_query: Query, extra_headers: Headers, - websocket_connection_options: WebSocketConnectionOptions, + websocket_connection_options: SyncWebSocketConnectionOptions, on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, max_retries: int = 5, initial_delay: float = 0.5, @@ -4789,20 +4803,29 @@ def __enter__(self) -> ResponsesConnection: """ ws = self._connect_ws(self.__extra_query, self.__extra_headers) - self.__connection = ResponsesConnection( - ws, - make_ws=self._connect_ws if self.__on_reconnecting is not None else None, - on_reconnecting=self.__on_reconnecting, - max_retries=self.__max_retries, - initial_delay=self.__initial_delay, - max_delay=self.__max_delay, - extra_query=self.__extra_query, - extra_headers=self.__extra_headers, - send_queue=self.__send_queue, - ) + try: + self.__connection = ResponsesConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) - self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) - self.__connection._flush_send_queue() + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + self.__connection._flush_send_queue() + except BaseException: + self.__connection = None + try: + ws.close() + except BaseException: + # Preserve the exception that prevented successful entry. + pass + raise return self.__connection @@ -4847,11 +4870,16 @@ def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> WebSocketCo if self.__websocket_connection_options: log.debug("Custom WebSocket connection options provided") + websocket_options = self.__websocket_connection_options.copy() + websocket_options.setdefault("max_size", None) + if websocket_options.get("max_size") is None: + websocket_options.setdefault("compression", None) + return connect( str(url), user_agent_header=None, additional_headers=_merge_mappings(dict(headers.values()), {}), - **self.__websocket_connection_options, + **websocket_options, ) def _prepare_url(self) -> httpx2.URL: diff --git a/src/openai/types/__init__.py b/src/openai/types/__init__.py index 24bc681ca5..65ef9dbda1 100644 --- a/src/openai/types/__init__.py +++ b/src/openai/types/__init__.py @@ -119,6 +119,7 @@ from .websocket_connection_options import ( WebSocketConnectionOptions as WebSocketConnectionOptions, WebsocketConnectionOptions as WebsocketConnectionOptions, + SyncWebSocketConnectionOptions as SyncWebSocketConnectionOptions, ) from .image_create_variation_params import ImageCreateVariationParams as ImageCreateVariationParams from .image_gen_partial_image_event import ImageGenPartialImageEvent as ImageGenPartialImageEvent diff --git a/tests/api_resources/admin/organization/projects/test_api_keys.py b/tests/api_resources/admin/organization/projects/test_api_keys.py index 4ff49e68ed..6fe5d95f2d 100644 --- a/tests/api_resources/admin/organization/projects/test_api_keys.py +++ b/tests/api_resources/admin/organization/projects/test_api_keys.py @@ -74,6 +74,7 @@ def test_method_update(self, client: OpenAI) -> None: api_key = client.admin.organization.projects.api_keys.update( api_key_id="api_key_id", project_id="project_id", + scopes=["api.all.read"], ) assert_matches_type(ProjectAPIKey, api_key, path=["response"]) @@ -92,6 +93,7 @@ def test_raw_response_update(self, client: OpenAI) -> None: response = client.admin.organization.projects.api_keys.with_raw_response.update( api_key_id="api_key_id", project_id="project_id", + scopes=["api.all.read"], ) assert response.is_closed is True @@ -104,6 +106,7 @@ def test_streaming_response_update(self, client: OpenAI) -> None: with client.admin.organization.projects.api_keys.with_streaming_response.update( api_key_id="api_key_id", project_id="project_id", + scopes=["api.all.read"], ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -119,12 +122,14 @@ def test_path_params_update(self, client: OpenAI) -> None: client.admin.organization.projects.api_keys.with_raw_response.update( api_key_id="api_key_id", project_id="", + scopes=["api.all.read"], ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `api_key_id` but received ''"): client.admin.organization.projects.api_keys.with_raw_response.update( api_key_id="", project_id="project_id", + scopes=["api.all.read"], ) @parametrize @@ -282,6 +287,7 @@ async def test_method_update(self, async_client: AsyncOpenAI) -> None: api_key = await async_client.admin.organization.projects.api_keys.update( api_key_id="api_key_id", project_id="project_id", + scopes=["api.all.read"], ) assert_matches_type(ProjectAPIKey, api_key, path=["response"]) @@ -300,6 +306,7 @@ async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: response = await async_client.admin.organization.projects.api_keys.with_raw_response.update( api_key_id="api_key_id", project_id="project_id", + scopes=["api.all.read"], ) assert response.is_closed is True @@ -312,6 +319,7 @@ async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> Non async with async_client.admin.organization.projects.api_keys.with_streaming_response.update( api_key_id="api_key_id", project_id="project_id", + scopes=["api.all.read"], ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -327,12 +335,14 @@ async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: await async_client.admin.organization.projects.api_keys.with_raw_response.update( api_key_id="api_key_id", project_id="", + scopes=["api.all.read"], ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `api_key_id` but received ''"): await async_client.admin.organization.projects.api_keys.with_raw_response.update( api_key_id="", project_id="project_id", + scopes=["api.all.read"], ) @parametrize