diff --git a/google/genai/_gaos/_hooks/__init__.py b/google/genai/_gaos/_hooks/__init__.py index 1dde3f4ff..1bab46f52 100644 --- a/google/genai/_gaos/_hooks/__init__.py +++ b/google/genai/_gaos/_hooks/__init__.py @@ -18,4 +18,8 @@ from .sdkhooks import * from .types import * +from .asynctypes import * +from .asyncsdkhooks import * +from .adapters import * from .registration import * +from .asyncregistration import * diff --git a/google/genai/_gaos/_hooks/adapters.py b/google/genai/_gaos/_hooks/adapters.py new file mode 100644 index 000000000..4ff51f5c3 --- /dev/null +++ b/google/genai/_gaos/_hooks/adapters.py @@ -0,0 +1,218 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .asynctypes import ( + AsyncAfterErrorHook, + AsyncAfterParseErrorHook, + AsyncAfterSuccessHook, + AsyncBeforeRequestHook, +) +from .types import ( + AfterErrorContext, + AfterErrorHook, + AfterParseErrorContext, + AfterParseErrorHook, + AfterSuccessContext, + AfterSuccessHook, + BeforeRequestContext, + BeforeRequestHook, +) +import asyncio +import httpx +from typing import Optional, Tuple, Union + +# ============================================================================ +# Sync to Async Adapters +# ============================================================================ +# PERFORMANCE CONSIDERATIONS: +# - Sync-to-Async adapters use asyncio.to_thread() / run_in_executor() to run +# sync hooks in a thread pool. This adds threading overhead and context +# switching costs. +# - Async-to-Sync adapters use asyncio.run() which creates a new event loop +# for each invocation. This has significant overhead. +# - For optimal performance, register hooks that match the execution context: +# * Use native async hooks (AsyncBeforeRequestHook, etc.) for async methods +# * Use native sync hooks (BeforeRequestHook, etc.) for sync methods +# - Adapters are provided for convenience and backward compatibility, but +# native implementations are strongly recommended for production use. +# ============================================================================ + + +class SyncToAsyncBeforeRequestAdapter(AsyncBeforeRequestHook): + """Adapts a synchronous BeforeRequestHook to work in async contexts. + + PERFORMANCE WARNING: Uses asyncio.to_thread() to run the sync hook in a thread pool. + This introduces: + - Thread pool overhead for spawning/managing worker threads + - Context switching between the async event loop and worker threads + - Memory overhead for thread-local storage + - Potential thread pool exhaustion under high concurrency + + For performance-critical applications, prefer registering native AsyncBeforeRequestHook + implementations instead of relying on this adapter. + """ + + def __init__(self, sync_hook: BeforeRequestHook): + self._sync_hook = sync_hook + + async def before_request( + self, hook_ctx: BeforeRequestContext, request: httpx.Request + ) -> Union[httpx.Request, Exception]: + return await asyncio.to_thread( + self._sync_hook.before_request, hook_ctx, request + ) + + +class SyncToAsyncAfterSuccessAdapter(AsyncAfterSuccessHook): + """Adapts a synchronous AfterSuccessHook to work in async contexts. + + PERFORMANCE WARNING: Uses asyncio.to_thread() to run the sync hook in a thread pool. + This introduces threading overhead and context switching costs. Prefer native + AsyncAfterSuccessHook implementations for better performance. + """ + + def __init__(self, sync_hook: AfterSuccessHook): + self._sync_hook = sync_hook + + async def after_success( + self, hook_ctx: AfterSuccessContext, response: httpx.Response + ) -> Union[httpx.Response, Exception]: + return await asyncio.to_thread( + self._sync_hook.after_success, hook_ctx, response + ) + + +class SyncToAsyncAfterErrorAdapter(AsyncAfterErrorHook): + """Adapts a synchronous AfterErrorHook to work in async contexts. + + PERFORMANCE WARNING: Uses asyncio.to_thread() to run the sync hook in a thread pool. + This introduces threading overhead and context switching costs. Prefer native + AsyncAfterErrorHook implementations for better performance. + """ + + def __init__(self, sync_hook: AfterErrorHook): + self._sync_hook = sync_hook + + async def after_error( + self, + hook_ctx: AfterErrorContext, + response: Optional[httpx.Response], + error: Optional[Exception], + ) -> Union[Tuple[Optional[httpx.Response], Optional[Exception]], Exception]: + return await asyncio.to_thread( + self._sync_hook.after_error, hook_ctx, response, error + ) + + +class SyncToAsyncAfterParseErrorAdapter(AsyncAfterParseErrorHook): + """Adapts a synchronous AfterParseErrorHook to work in async contexts.""" + + def __init__(self, sync_hook: AfterParseErrorHook): + self._sync_hook = sync_hook + + async def after_parse_error( + self, + hook_ctx: AfterParseErrorContext, + response: httpx.Response, + error: Exception, + ) -> Exception: + return await asyncio.to_thread( + self._sync_hook.after_parse_error, hook_ctx, response, error + ) + + +# ============================================================================ +# Async to Sync Adapters +# ============================================================================ + + +class AsyncToSyncBeforeRequestAdapter(BeforeRequestHook): + """Adapts an async BeforeRequestHook to work in sync contexts. + + PERFORMANCE WARNING: Uses asyncio.run() which creates and tears down a new event + loop for each invocation. This has significant overhead: + - Event loop creation/destruction costs + - Loss of connection pooling and keep-alive benefits + - Cannot leverage existing event loop infrastructure + - Not suitable for high-frequency operations + + This adapter is primarily for compatibility in mixed sync/async codebases. + Strongly prefer native sync hooks (BeforeRequestHook) for production use. + """ + + def __init__(self, async_hook: AsyncBeforeRequestHook): + self._async_hook = async_hook + + def before_request( + self, hook_ctx: BeforeRequestContext, request: httpx.Request + ) -> Union[httpx.Request, Exception]: + return asyncio.run(self._async_hook.before_request(hook_ctx, request)) + + +class AsyncToSyncAfterSuccessAdapter(AfterSuccessHook): + """Adapts an async AfterSuccessHook to work in sync contexts. + + PERFORMANCE WARNING: Uses asyncio.run() which creates and tears down a new event + loop for each invocation. This has significant overhead. Strongly prefer native + sync hooks (AfterSuccessHook) for production use. + """ + + def __init__(self, async_hook: AsyncAfterSuccessHook): + self._async_hook = async_hook + + def after_success( + self, hook_ctx: AfterSuccessContext, response: httpx.Response + ) -> Union[httpx.Response, Exception]: + return asyncio.run(self._async_hook.after_success(hook_ctx, response)) + + +class AsyncToSyncAfterErrorAdapter(AfterErrorHook): + """Adapts an async AfterErrorHook to work in sync contexts. + + PERFORMANCE WARNING: Uses asyncio.run() which creates and tears down a new event + loop for each invocation. This has significant overhead. Strongly prefer native + sync hooks (AfterErrorHook) for production use. + """ + + def __init__(self, async_hook: AsyncAfterErrorHook): + self._async_hook = async_hook + + def after_error( + self, + hook_ctx: AfterErrorContext, + response: Optional[httpx.Response], + error: Optional[Exception], + ) -> Union[Tuple[Optional[httpx.Response], Optional[Exception]], Exception]: + return asyncio.run(self._async_hook.after_error(hook_ctx, response, error)) + + +class AsyncToSyncAfterParseErrorAdapter(AfterParseErrorHook): + """Adapts an async AfterParseErrorHook to work in sync contexts.""" + + def __init__(self, async_hook: AsyncAfterParseErrorHook): + self._async_hook = async_hook + + def after_parse_error( + self, + hook_ctx: AfterParseErrorContext, + response: httpx.Response, + error: Exception, + ) -> Exception: + return asyncio.run( + self._async_hook.after_parse_error(hook_ctx, response, error) + ) diff --git a/google/genai/_gaos/_hooks/asyncregistration.py b/google/genai/_gaos/_hooks/asyncregistration.py new file mode 100644 index 000000000..465bc6f75 --- /dev/null +++ b/google/genai/_gaos/_hooks/asyncregistration.py @@ -0,0 +1,66 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +from .asynctypes import AsyncHooks +from .adapters import ( + SyncToAsyncAfterErrorAdapter, + SyncToAsyncAfterParseErrorAdapter, + SyncToAsyncBeforeRequestAdapter, +) +from .google_genai_auth import GoogleGenAIAuthHook +from ..lib.compat_errors import CompatErrorHook +from typing import cast +from .types import AfterErrorHook, AfterParseErrorHook + + +# This file is only ever generated once on the first generation and then is free to be modified. +# Any async hooks you wish to add should be registered in the init_async_hooks function. +# Feel free to define them in this file or in separate files in the hooks folder. + + +def init_async_hooks(hooks: AsyncHooks): + # pylint: disable=unused-argument + """Add async hooks by calling hooks.register_{sdk_init/before_request/after_success/after_error}_hook + with an instance of a hook that implements that specific AsyncHook interface. + Async hooks are registered per SDK instance, and are valid for the lifetime of the SDK instance. + + Async hooks are invoked in async contexts and allow you to use async/await for non-blocking I/O operations. + + Example: + from .asynctypes import AsyncBeforeRequestHook, BeforeRequestContext + import httpx + + class MyAsyncHook(AsyncBeforeRequestHook): + async def before_request(self, hook_ctx: BeforeRequestContext, request: httpx.Request): + # Perform async operations + token = await fetch_token_from_external_service() + + # Modify request + headers = dict(request.headers) + headers["Authorization"] = f"Bearer {token}" + return httpx.Request(request.method, request.url, headers=headers, content=request.content) + + hooks.register_before_request_hook(MyAsyncHook()) + """ + hooks.register_before_request_hook( + SyncToAsyncBeforeRequestAdapter(GoogleGenAIAuthHook()) + ) + hooks.register_after_error_hook( + SyncToAsyncAfterErrorAdapter(cast(AfterErrorHook, CompatErrorHook())) + ) + hooks.register_after_parse_error_hook( + SyncToAsyncAfterParseErrorAdapter(cast(AfterParseErrorHook, CompatErrorHook())) + ) diff --git a/google/genai/_gaos/_hooks/asyncsdkhooks.py b/google/genai/_gaos/_hooks/asyncsdkhooks.py new file mode 100644 index 000000000..9b150ddee --- /dev/null +++ b/google/genai/_gaos/_hooks/asyncsdkhooks.py @@ -0,0 +1,97 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +import httpx +from .asynctypes import ( + AsyncBeforeRequestHook, + AsyncAfterSuccessHook, + AsyncAfterErrorHook, + AsyncAfterParseErrorHook, + AsyncHooks, +) +from .types import ( + BeforeRequestContext, + AfterSuccessContext, + AfterErrorContext, + AfterParseErrorContext, +) +from typing import List, Optional, Tuple + + +class AsyncSDKHooks(AsyncHooks): + def __init__(self) -> None: + self.before_request_hooks: List[AsyncBeforeRequestHook] = [] + self.after_success_hooks: List[AsyncAfterSuccessHook] = [] + self.after_error_hooks: List[AsyncAfterErrorHook] = [] + self.after_parse_error_hooks: List[AsyncAfterParseErrorHook] = [] + + def register_before_request_hook(self, hook: AsyncBeforeRequestHook) -> None: + self.before_request_hooks.append(hook) + + def register_after_success_hook(self, hook: AsyncAfterSuccessHook) -> None: + self.after_success_hooks.append(hook) + + def register_after_error_hook(self, hook: AsyncAfterErrorHook) -> None: + self.after_error_hooks.append(hook) + + def register_after_parse_error_hook(self, hook: AsyncAfterParseErrorHook) -> None: + self.after_parse_error_hooks.append(hook) + + async def before_request( + self, hook_ctx: BeforeRequestContext, request: httpx.Request + ) -> httpx.Request: + for hook in self.before_request_hooks: + out = await hook.before_request(hook_ctx, request) + if isinstance(out, Exception): + raise out + request = out + + return request + + async def after_success( + self, hook_ctx: AfterSuccessContext, response: httpx.Response + ) -> httpx.Response: + for hook in self.after_success_hooks: + out = await hook.after_success(hook_ctx, response) + if isinstance(out, Exception): + raise out + response = out + return response + + async def after_error( + self, + hook_ctx: AfterErrorContext, + response: Optional[httpx.Response], + error: Optional[Exception], + ) -> Tuple[Optional[httpx.Response], Optional[Exception]]: + for hook in self.after_error_hooks: + result = await hook.after_error(hook_ctx, response, error) + if isinstance(result, Exception): + raise result + response, error = result + return response, error + + async def after_parse_error( + self, + hook_ctx: AfterParseErrorContext, + response: httpx.Response, + error: Exception, + ) -> Exception: + for hook in self.after_parse_error_hooks: + error = await hook.after_parse_error(hook_ctx, response, error) + return error diff --git a/google/genai/_gaos/_hooks/asynctypes.py b/google/genai/_gaos/_hooks/asynctypes.py new file mode 100644 index 000000000..86f010764 --- /dev/null +++ b/google/genai/_gaos/_hooks/asynctypes.py @@ -0,0 +1,83 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .types import ( + AfterErrorContext, + AfterParseErrorContext, + AfterSuccessContext, + BeforeRequestContext, +) +from abc import ABC, abstractmethod +import httpx +from typing import Optional, Tuple, Union + + +class AsyncBeforeRequestHook(ABC): + @abstractmethod + async def before_request( + self, hook_ctx: BeforeRequestContext, request: httpx.Request + ) -> Union[httpx.Request, Exception]: + pass + + +class AsyncAfterSuccessHook(ABC): + @abstractmethod + async def after_success( + self, hook_ctx: AfterSuccessContext, response: httpx.Response + ) -> Union[httpx.Response, Exception]: + pass + + +class AsyncAfterErrorHook(ABC): + @abstractmethod + async def after_error( + self, + hook_ctx: AfterErrorContext, + response: Optional[httpx.Response], + error: Optional[Exception], + ) -> Union[Tuple[Optional[httpx.Response], Optional[Exception]], Exception]: + pass + + +class AsyncAfterParseErrorHook(ABC): + @abstractmethod + async def after_parse_error( + self, + hook_ctx: AfterParseErrorContext, + response: httpx.Response, + error: Exception, + ) -> Exception: + pass + + +class AsyncHooks(ABC): + @abstractmethod + def register_before_request_hook(self, hook: AsyncBeforeRequestHook): + pass + + @abstractmethod + def register_after_success_hook(self, hook: AsyncAfterSuccessHook): + pass + + @abstractmethod + def register_after_error_hook(self, hook: AsyncAfterErrorHook): + pass + + @abstractmethod + def register_after_parse_error_hook(self, hook: AsyncAfterParseErrorHook): + pass diff --git a/google/genai/_gaos/_hooks/sdkhooks.py b/google/genai/_gaos/_hooks/sdkhooks.py index c851f8a9d..6042df979 100644 --- a/google/genai/_gaos/_hooks/sdkhooks.py +++ b/google/genai/_gaos/_hooks/sdkhooks.py @@ -25,11 +25,14 @@ AfterSuccessHook, AfterErrorContext, AfterErrorHook, + AfterParseErrorContext, + AfterParseErrorHook, Hooks, ) from .registration import init_hooks from .google_genai_auth import GoogleGenAIAuthHook -from typing import List, Optional, Tuple +from ..lib.compat_errors import CompatErrorHook +from typing import cast, List, Optional, Tuple from ..sdkconfiguration import SDKConfiguration @@ -39,8 +42,13 @@ def __init__(self) -> None: self.before_request_hooks: List[BeforeRequestHook] = [] self.after_success_hooks: List[AfterSuccessHook] = [] self.after_error_hooks: List[AfterErrorHook] = [] + self.after_parse_error_hooks: List[AfterParseErrorHook] = [] init_hooks(self) self.register_before_request_hook(GoogleGenAIAuthHook()) + self.register_after_error_hook(cast(AfterErrorHook, CompatErrorHook())) + self.register_after_parse_error_hook( + cast(AfterParseErrorHook, CompatErrorHook()) + ) def register_sdk_init_hook(self, hook: SDKInitHook) -> None: self.sdk_init_hooks.append(hook) @@ -54,6 +62,9 @@ def register_after_success_hook(self, hook: AfterSuccessHook) -> None: def register_after_error_hook(self, hook: AfterErrorHook) -> None: self.after_error_hooks.append(hook) + def register_after_parse_error_hook(self, hook: AfterParseErrorHook) -> None: + self.after_parse_error_hooks.append(hook) + def sdk_init(self, config: SDKConfiguration) -> SDKConfiguration: for hook in self.sdk_init_hooks: config = hook.sdk_init(config) @@ -92,3 +103,13 @@ def after_error( raise result response, error = result return response, error + + def after_parse_error( + self, + hook_ctx: AfterParseErrorContext, + response: httpx.Response, + error: Exception, + ) -> Exception: + for hook in self.after_parse_error_hooks: + error = hook.after_parse_error(hook_ctx, response, error) + return error diff --git a/google/genai/_gaos/_hooks/types.py b/google/genai/_gaos/_hooks/types.py index bc32dc082..1e7f736ca 100644 --- a/google/genai/_gaos/_hooks/types.py +++ b/google/genai/_gaos/_hooks/types.py @@ -19,7 +19,20 @@ from ..sdkconfiguration import SDKConfiguration from abc import ABC, abstractmethod import httpx -from typing import Any, Callable, List, Optional, Tuple, Union +from typing import Any, Callable, List, Literal, Optional, Tuple, Union + + +class ResponseContext: + mode: Literal["parsed", "raw", "streaming"] + execution: Literal["sync", "async"] + + def __init__( + self, + mode: Literal["parsed", "raw", "streaming"] = "parsed", + execution: Literal["sync", "async"] = "sync", + ): + self.mode = mode + self.execution = execution class HookContext: @@ -28,6 +41,7 @@ class HookContext: operation_id: str oauth2_scopes: Optional[List[str]] = None security_source: Optional[Union[Any, Callable[[], Any]]] = None + response: ResponseContext def __init__( self, @@ -36,12 +50,14 @@ def __init__( operation_id: str, oauth2_scopes: Optional[List[str]], security_source: Optional[Union[Any, Callable[[], Any]]], + response: ResponseContext, ): self.config = config self.base_url = base_url self.operation_id = operation_id self.oauth2_scopes = oauth2_scopes self.security_source = security_source + self.response = response class BeforeRequestContext(HookContext): @@ -52,6 +68,7 @@ def __init__(self, hook_ctx: HookContext): hook_ctx.operation_id, hook_ctx.oauth2_scopes, hook_ctx.security_source, + response=hook_ctx.response, ) @@ -63,6 +80,7 @@ def __init__(self, hook_ctx: HookContext): hook_ctx.operation_id, hook_ctx.oauth2_scopes, hook_ctx.security_source, + response=hook_ctx.response, ) @@ -74,6 +92,23 @@ def __init__(self, hook_ctx: HookContext): hook_ctx.operation_id, hook_ctx.oauth2_scopes, hook_ctx.security_source, + response=hook_ctx.response, + ) + + +class AfterParseErrorContext(HookContext): + """Context for `AfterParseErrorHook`. Triggers when response parsing raises + an exception while decoding the body. + """ + + def __init__(self, hook_ctx: HookContext): + super().__init__( + hook_ctx.config, + hook_ctx.base_url, + hook_ctx.operation_id, + hook_ctx.oauth2_scopes, + hook_ctx.security_source, + response=hook_ctx.response, ) @@ -110,6 +145,17 @@ def after_error( pass +class AfterParseErrorHook(ABC): + @abstractmethod + def after_parse_error( + self, + hook_ctx: AfterParseErrorContext, + response: httpx.Response, + error: Exception, + ) -> Exception: + pass + + class Hooks(ABC): @abstractmethod def register_sdk_init_hook(self, hook: SDKInitHook): @@ -126,3 +172,7 @@ def register_after_success_hook(self, hook: AfterSuccessHook): @abstractmethod def register_after_error_hook(self, hook: AfterErrorHook): pass + + @abstractmethod + def register_after_parse_error_hook(self, hook: AfterParseErrorHook): + pass diff --git a/google/genai/_gaos/_version.py b/google/genai/_gaos/_version.py index 4c36a74f7..30976efce 100644 --- a/google/genai/_gaos/_version.py +++ b/google/genai/_gaos/_version.py @@ -22,8 +22,8 @@ __version__: str = "2.4.1-preview.5" __response_mode_header__: str = "x-google-genai-response-mode" __openapi_doc_version__: str = "v1beta" -__gen_version__: str = "2.904.2" -__user_agent__: str = "speakeasy-sdk/python 2.4.1-preview.5 2.904.2 v1beta google-genai" +__gen_version__: str = "2.911.0" +__user_agent__: str = "speakeasy-sdk/python 2.4.1-preview.5 2.911.0 v1beta google-genai" try: if __package__ is not None: diff --git a/google/genai/_gaos/agents.py b/google/genai/_gaos/agents.py index f5f7bc51a..d67f81997 100644 --- a/google/genai/_gaos/agents.py +++ b/google/genai/_gaos/agents.py @@ -17,7 +17,7 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" from . import errors, models, types, utils -from ._hooks import HookContext +from ._hooks import AfterParseErrorContext, HookContext, ResponseContext from .basesdk import AsyncBaseSDK, BaseSDK from .types import OptionalNullable, UNSET, agents, interactions from .types.agents import agent as agents_agent, agenttool as agents_agenttool @@ -132,7 +132,10 @@ def create( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -151,20 +154,22 @@ def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(agents.Agent, http_res) + return unmarshal_json_response(agents.Agent, http_res, validate=False) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = self.do_request( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="CreateAgent", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="CreateAgent", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="sync"), + ) + http_res = self.do_request( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -183,9 +188,19 @@ def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), ), ) - return _speakeasy_parse_response(http_res) + try: + return _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + response_helpers.raise_parse_error( + self.sdk_configuration.__dict__["_hooks"], + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) def list( self, @@ -258,7 +273,10 @@ def list( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -277,20 +295,24 @@ def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(agents.AgentListResponse, http_res) + return unmarshal_json_response( + agents.AgentListResponse, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = self.do_request( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="ListAgents", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="ListAgents", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="sync"), + ) + http_res = self.do_request( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -309,9 +331,19 @@ def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), ), ) - return _speakeasy_parse_response(http_res) + try: + return _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + response_helpers.raise_parse_error( + self.sdk_configuration.__dict__["_hooks"], + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) def get( self, @@ -378,7 +410,10 @@ def get( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -397,20 +432,22 @@ def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(agents.Agent, http_res) + return unmarshal_json_response(agents.Agent, http_res, validate=False) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = self.do_request( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="GetAgent", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="GetAgent", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="sync"), + ) + http_res = self.do_request( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -429,9 +466,19 @@ def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), ), ) - return _speakeasy_parse_response(http_res) + try: + return _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + response_helpers.raise_parse_error( + self.sdk_configuration.__dict__["_hooks"], + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) def delete( self, @@ -498,7 +545,10 @@ def delete( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -517,20 +567,24 @@ def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(interactions.Empty, http_res) + return unmarshal_json_response( + interactions.Empty, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = self.do_request( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="DeleteAgent", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="DeleteAgent", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="sync"), + ) + http_res = self.do_request( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -549,9 +603,19 @@ def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), ), ) - return _speakeasy_parse_response(http_res) + try: + return _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + response_helpers.raise_parse_error( + self.sdk_configuration.__dict__["_hooks"], + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) class AgentsWithRawResponse: @@ -689,7 +753,10 @@ async def create( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -708,20 +775,22 @@ async def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(agents.Agent, http_res) + return unmarshal_json_response(agents.Agent, http_res, validate=False) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = await self.do_request_async( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="CreateAgent", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="CreateAgent", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="async"), + ) + http_res = await self.do_request_async( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -740,9 +809,21 @@ async def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), + async_hooks=self.sdk_configuration.__dict__.get("_async_hooks"), ), ) - return await _speakeasy_parse_response(http_res) + try: + return await _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + await response_helpers.raise_parse_error_async( + self.sdk_configuration.__dict__.get("_async_hooks"), + self.sdk_configuration.__dict__.get("_hooks"), + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) async def list( self, @@ -815,7 +896,10 @@ async def list( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -834,20 +918,24 @@ async def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(agents.AgentListResponse, http_res) + return unmarshal_json_response( + agents.AgentListResponse, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = await self.do_request_async( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="ListAgents", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="ListAgents", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="async"), + ) + http_res = await self.do_request_async( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -866,9 +954,21 @@ async def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), + async_hooks=self.sdk_configuration.__dict__.get("_async_hooks"), ), ) - return await _speakeasy_parse_response(http_res) + try: + return await _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + await response_helpers.raise_parse_error_async( + self.sdk_configuration.__dict__.get("_async_hooks"), + self.sdk_configuration.__dict__.get("_hooks"), + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) async def get( self, @@ -935,7 +1035,10 @@ async def get( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -954,20 +1057,22 @@ async def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(agents.Agent, http_res) + return unmarshal_json_response(agents.Agent, http_res, validate=False) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = await self.do_request_async( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="GetAgent", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="GetAgent", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="async"), + ) + http_res = await self.do_request_async( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -986,9 +1091,21 @@ async def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), + async_hooks=self.sdk_configuration.__dict__.get("_async_hooks"), ), ) - return await _speakeasy_parse_response(http_res) + try: + return await _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + await response_helpers.raise_parse_error_async( + self.sdk_configuration.__dict__.get("_async_hooks"), + self.sdk_configuration.__dict__.get("_hooks"), + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) async def delete( self, @@ -1055,7 +1172,10 @@ async def delete( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -1074,20 +1194,24 @@ async def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(interactions.Empty, http_res) + return unmarshal_json_response( + interactions.Empty, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = await self.do_request_async( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="DeleteAgent", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="DeleteAgent", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="async"), + ) + http_res = await self.do_request_async( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -1106,9 +1230,21 @@ async def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), + async_hooks=self.sdk_configuration.__dict__.get("_async_hooks"), ), ) - return await _speakeasy_parse_response(http_res) + try: + return await _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + await response_helpers.raise_parse_error_async( + self.sdk_configuration.__dict__.get("_async_hooks"), + self.sdk_configuration.__dict__.get("_hooks"), + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) class AsyncAgentsWithRawResponse: diff --git a/google/genai/_gaos/basesdk.py b/google/genai/_gaos/basesdk.py index 3c958fc36..7c8497f24 100644 --- a/google/genai/_gaos/basesdk.py +++ b/google/genai/_gaos/basesdk.py @@ -24,12 +24,7 @@ HookContext, ) from .sdkconfiguration import SDKConfiguration -from .utils import ( - RetryConfig, - SerializedRequestBody, - get_body_content, - run_sync_in_thread, -) +from .utils import RetryConfig, SerializedRequestBody, get_body_content import httpx from typing import Any, Callable, List, Mapping, Optional, Tuple from urllib.parse import parse_qs, urlparse @@ -243,7 +238,7 @@ def do_request( hooks = self.sdk_configuration.__dict__["_hooks"] - def do(): + def do(_attempt: int = 0): http_res = None try: req = hooks.before_request(BeforeRequestContext(hook_ctx), request) @@ -508,13 +503,13 @@ async def do_request_async( client = self.sdk_configuration.async_client logger = self.sdk_configuration.debug_logger - hooks = self.sdk_configuration.__dict__["_hooks"] + async_hooks = self.sdk_configuration.__dict__["_async_hooks"] - async def do(): + async def do(_attempt: int = 0): http_res = None try: - req = await run_sync_in_thread( - hooks.before_request, BeforeRequestContext(hook_ctx), request + req = await async_hooks.before_request( + BeforeRequestContext(hook_ctx), request ) if "timeout" in request.extensions and "timeout" not in req.extensions: @@ -532,8 +527,8 @@ async def do(): http_res = await client.send(req, stream=stream) except Exception as e: - _, e = await run_sync_in_thread( - hooks.after_error, AfterErrorContext(hook_ctx), None, e + _, e = await async_hooks.after_error( + AfterErrorContext(hook_ctx), None, e ) if e is not None: @@ -562,8 +557,8 @@ async def do(): http_res = await do() if is_error_status_code(http_res.status_code): - result, err = await run_sync_in_thread( - hooks.after_error, AfterErrorContext(hook_ctx), http_res, None + result, err = await async_hooks.after_error( + AfterErrorContext(hook_ctx), http_res, None ) if err is not None: @@ -575,8 +570,8 @@ async def do(): logger.debug("Raising unexpected SDK error") raise errors.GenAiDefaultError("Unexpected error occurred", http_res) else: - http_res = await run_sync_in_thread( - hooks.after_success, AfterSuccessContext(hook_ctx), http_res + http_res = await async_hooks.after_success( + AfterSuccessContext(hook_ctx), http_res ) return http_res diff --git a/google/genai/_gaos/google_genai.py b/google/genai/_gaos/google_genai.py index f944af657..f8f85f219 100644 --- a/google/genai/_gaos/google_genai.py +++ b/google/genai/_gaos/google_genai.py @@ -31,12 +31,25 @@ GOOGLE_GENAI_API_REVISION as _GOOGLE_GENAI_API_REVISION, GoogleGenAISecurityProvider, ) +from .agents import AsyncAgents as GeneratedAsyncAgents +from .agents import Agents as GeneratedAgents from .interactions import AsyncInteractions as GeneratedAsyncInteractions from .interactions import Interactions as GeneratedInteractions +from .lib.compat_errors import ( + _AsyncRawResponseAccessorProxy, + _RawResponseAccessorProxy, + async_wrap_sdk_call, + is_stream, + wrap_async_stream_errors, + wrap_sdk_call, + wrap_stream_errors, +) from .sdk import AsyncGenAI, GenAI from .types import interactions from .types.security import Security -from .utils import eventstreaming +from .utils import BackoffStrategy, RetryConfig, eventstreaming +from .webhooks import AsyncWebhooks as GeneratedAsyncWebhooks +from .webhooks import Webhooks as GeneratedWebhooks GOOGLE_GENAI_API_REVISION = _GOOGLE_GENAI_API_REVISION @@ -104,6 +117,61 @@ def _get_google_genai_security(api_client: Any) -> Optional[Any]: return None +_DEFAULT_MAX_RETRIES = 2 +# Default backoff shape (ms): 0.5s initial, 8s cap, base 2. Individual fields +# are overridden by the matching `retry_options` value when set. +_DEFAULT_INITIAL_INTERVAL_MS = 500 +_DEFAULT_MAX_INTERVAL_MS = 8000 +_DEFAULT_EXPONENT = 2 +_MAX_ELAPSED_TIME_MS = 30000 + + +def _translate_retry_config(http_options: Any) -> RetryConfig: + """Maps parent `HttpOptions.retry_options` onto a `RetryConfig`. + + `attempts` becomes the retry count (an unset `attempts` falls back to the + default retry count). The delay-shaping options (`initial_delay`, + `max_delay`, `exp_base`, `jitter`) and `http_status_codes` are honored when + provided, each defaulting to the built-in behavior when omitted. + """ + options = getattr(http_options, 'retry_options', None) + + attempts = getattr(options, 'attempts', None) if options is not None else None + max_retries = attempts if attempts is not None else _DEFAULT_MAX_RETRIES + 1 + + initial_interval = _DEFAULT_INITIAL_INTERVAL_MS + max_interval = _DEFAULT_MAX_INTERVAL_MS + exponent = _DEFAULT_EXPONENT + jitter_ms = None + status_codes_override = None + + if options is not None: + if options.initial_delay is not None: + initial_interval = int(options.initial_delay * 1000) + if options.max_delay is not None: + max_interval = int(options.max_delay * 1000) + if options.exp_base is not None: + exponent = options.exp_base + if options.jitter is not None: + jitter_ms = int(options.jitter * 1000) + if options.http_status_codes: + status_codes_override = [str(code) for code in options.http_status_codes] + + return RetryConfig( + 'attempt-count-backoff', + BackoffStrategy( + initial_interval, + max_interval, + exponent, + _MAX_ELAPSED_TIME_MS, + jitter_ms=jitter_ms, + ), + True, + max_retries=max_retries, + status_codes_override=status_codes_override, + ) + + def build_google_genai_client( api_client: Any, api_version: Optional[str] = None ) -> GenAI: @@ -116,6 +184,7 @@ def build_google_genai_client( server_url=get_google_genai_server_url(api_client), client=getattr(api_client, '_httpx_client', None), timeout_ms=http_options.timeout, + retry_config=_translate_retry_config(http_options), ) return sdk @@ -123,7 +192,7 @@ def build_google_genai_client( def build_google_genai_async_client( api_client: Any, api_version: Optional[str] = None ) -> AsyncGenAI: - """Builds an async generated NextGen client from the parent GenAI client.""" + """Builds a generated NextGen client from the parent GenAI client.""" http_options = api_client._http_options sdk = AsyncGenAI( security=_get_google_genai_security(api_client), @@ -132,6 +201,7 @@ def build_google_genai_async_client( server_url=get_google_genai_server_url(api_client), async_client=getattr(api_client, '_async_httpx_client', None), timeout_ms=http_options.timeout, + retry_config=_translate_retry_config(http_options), ) return sdk @@ -142,19 +212,31 @@ class GeminiNextGenInteractions(GeneratedInteractions): Subclasses the generated resource so newly generated methods (and the raw/streaming response wrappers) are exposed automatically. Only the methods that need legacy input/output normalization are overridden. + + Each override sits inside `if not TYPE_CHECKING:` so static type checkers + see the inherited generated signature (full overload sets) rather than the + runtime stub here. """ def __init__(self, api_client: Any): sdk = build_google_genai_client(api_client) super().__init__(sdk.sdk_configuration, parent_ref=sdk) + if not TYPE_CHECKING: + @property + def with_raw_response(self): + return _RawResponseAccessorProxy(super().with_raw_response) + + @property + def with_streaming_response(self): + return _RawResponseAccessorProxy(super().with_streaming_response) + - # Runtime-only overrides: type checkers see the inherited generated - # signatures (full overload sets) instead of hand-maintained stubs. if not TYPE_CHECKING: def create( self, *, + request: Any = None, api_version: Optional[str] = None, extra_headers: Optional[Mapping[str, str]] = None, extra_query: Optional[Mapping[str, Any]] = None, @@ -165,9 +247,29 @@ def create( interactions.Interaction, eventstreaming.Stream[interactions.InteractionSSEEvent], ]: + if request is not None: + if body: + raise TypeError(_REQUEST_AND_BODY_ERROR) + stream = _request_stream(request) + response = wrap_sdk_call( + super().create, + request=request, + api_version=api_version, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if stream: + if is_stream(response): + return wrap_stream_errors(response) + return response + return _add_output_properties_if_interaction(response) + stream = _optional_bool(body.get('stream'), default=False) body = _normalize_create_body(body) - response = super().create( + response = wrap_sdk_call( + super().create, api_version=api_version, **cast(Any, body), extra_headers=extra_headers, @@ -176,12 +278,12 @@ def create( timeout=timeout, ) if stream: + if is_stream(response): + return wrap_stream_errors(response) return response return _add_output_properties_if_interaction(response) - # Runtime-only overrides: type checkers see the inherited generated - # signatures (full overload sets) instead of hand-maintained stubs. if not TYPE_CHECKING: def get( self, @@ -199,7 +301,8 @@ def get( eventstreaming.Stream[interactions.InteractionSSEEvent], ]: stream_bool = bool(_optional_bool(stream, default=False)) - response = super().get( + response = wrap_sdk_call( + super().get, id=id, api_version=api_version, include_input=_optional_bool(include_input), @@ -210,13 +313,16 @@ def get( timeout=timeout, ) if stream_bool: - return cast(eventstreaming.Stream[interactions.InteractionSSEEvent], response) + if not is_stream(response): + return response + return cast( + eventstreaming.Stream[interactions.InteractionSSEEvent], + wrap_stream_errors(response), + ) return cast( interactions.Interaction, _add_output_properties_if_interaction(response) ) - # Runtime-only overrides: type checkers see the inherited generated - # signatures (full overload sets) instead of hand-maintained stubs. if not TYPE_CHECKING: def cancel( self, @@ -230,7 +336,8 @@ def cancel( return cast( interactions.Interaction, _add_output_properties_if_interaction( - super().cancel( + wrap_sdk_call( + super().cancel, id=id, api_version=api_version, extra_headers=extra_headers, @@ -240,26 +347,48 @@ def cancel( ), ) + if not TYPE_CHECKING: + def delete( + self, + id: str, + *, + api_version: Optional[str] = None, + extra_headers: Optional[Mapping[str, str]] = None, + extra_query: Optional[Mapping[str, Any]] = None, + timeout: Optional[Union[float, httpx.Timeout]] = None, + ) -> Any: + return wrap_sdk_call( + super().delete, + id=id, + api_version=api_version, + extra_headers=extra_headers, + extra_query=extra_query, + timeout=timeout, + ) -class AsyncGeminiNextGenInteractions(GeneratedAsyncInteractions): - """Async public interactions resource backed by the NextGen client. - Subclasses the generated resource so newly generated methods (and the - raw/streaming response wrappers) are exposed automatically. Only the - methods that need legacy input/output normalization are overridden. - """ +class AsyncGeminiNextGenInteractions(GeneratedAsyncInteractions): + """Async public interactions resource backed by the NextGen client.""" def __init__(self, api_client: Any): sdk = build_google_genai_async_client(api_client) super().__init__(sdk.sdk_configuration, parent_ref=sdk) + if not TYPE_CHECKING: + @property + def with_raw_response(self): + return _AsyncRawResponseAccessorProxy(super().with_raw_response) + + @property + def with_streaming_response(self): + return _AsyncRawResponseAccessorProxy(super().with_streaming_response) + - # Runtime-only overrides: type checkers see the inherited generated - # signatures (full overload sets) instead of hand-maintained stubs. if not TYPE_CHECKING: async def create( self, *, + request: Any = None, api_version: Optional[str] = None, extra_headers: Optional[Mapping[str, str]] = None, extra_query: Optional[Mapping[str, Any]] = None, @@ -270,9 +399,29 @@ async def create( interactions.Interaction, eventstreaming.AsyncStream[interactions.InteractionSSEEvent], ]: + if request is not None: + if body: + raise TypeError(_REQUEST_AND_BODY_ERROR) + stream = _request_stream(request) + response = await async_wrap_sdk_call( + super().create, + request=request, + api_version=api_version, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if stream: + if is_stream(response): + return wrap_async_stream_errors(response) + return response + return _add_output_properties_if_interaction(response) + stream = _optional_bool(body.get('stream'), default=False) body = _normalize_create_body(body) - response = await super().create( + response = await async_wrap_sdk_call( + super().create, api_version=api_version, **cast(Any, body), extra_headers=extra_headers, @@ -281,12 +430,12 @@ async def create( timeout=timeout, ) if stream: + if is_stream(response): + return wrap_async_stream_errors(response) return response return _add_output_properties_if_interaction(response) - # Runtime-only overrides: type checkers see the inherited generated - # signatures (full overload sets) instead of hand-maintained stubs. if not TYPE_CHECKING: async def get( self, @@ -304,7 +453,8 @@ async def get( eventstreaming.AsyncStream[interactions.InteractionSSEEvent], ]: stream_bool = bool(_optional_bool(stream, default=False)) - response = await super().get( + response = await async_wrap_sdk_call( + super().get, id=id, api_version=api_version, include_input=_optional_bool(include_input), @@ -315,13 +465,16 @@ async def get( timeout=timeout, ) if stream_bool: - return cast(eventstreaming.AsyncStream[interactions.InteractionSSEEvent], response) + if not is_stream(response): + return response + return cast( + eventstreaming.AsyncStream[interactions.InteractionSSEEvent], + wrap_async_stream_errors(response), + ) return cast( interactions.Interaction, _add_output_properties_if_interaction(response) ) - # Runtime-only overrides: type checkers see the inherited generated - # signatures (full overload sets) instead of hand-maintained stubs. if not TYPE_CHECKING: async def cancel( self, @@ -335,7 +488,8 @@ async def cancel( return cast( interactions.Interaction, _add_output_properties_if_interaction( - await super().cancel( + await async_wrap_sdk_call( + super().cancel, id=id, api_version=api_version, extra_headers=extra_headers, @@ -345,6 +499,173 @@ async def cancel( ), ) + if not TYPE_CHECKING: + async def delete( + self, + id: str, + *, + api_version: Optional[str] = None, + extra_headers: Optional[Mapping[str, str]] = None, + extra_query: Optional[Mapping[str, Any]] = None, + timeout: Optional[Union[float, httpx.Timeout]] = None, + ) -> Any: + return await async_wrap_sdk_call( + super().delete, + id=id, + api_version=api_version, + extra_headers=extra_headers, + extra_query=extra_query, + timeout=timeout, + ) + + +class GeminiNextGenWebhooks(GeneratedWebhooks): + """Public webhooks resource backed by the NextGen client. + + Subclasses the generated resource so every public method is wrapped in + `wrap_sdk_call`, translating per-operation `GenAiError` raises into the + status-code `APIError` hierarchy exposed at the + `google.genai._interactions` import surface. + """ + + def __init__(self, api_client: Any): + sdk = build_google_genai_client(api_client) + super().__init__(sdk.sdk_configuration, parent_ref=sdk) + + if not TYPE_CHECKING: + @property + def with_raw_response(self): + return _RawResponseAccessorProxy(super().with_raw_response) + + @property + def with_streaming_response(self): + return _RawResponseAccessorProxy(super().with_streaming_response) + + def create(self, *args: Any, **kwargs: Any) -> Any: + return wrap_sdk_call(super().create, *args, **kwargs) + + def list(self, *args: Any, **kwargs: Any) -> Any: + return wrap_sdk_call(super().list, *args, **kwargs) + + def get(self, *args: Any, **kwargs: Any) -> Any: + return wrap_sdk_call(super().get, *args, **kwargs) + + def update(self, *args: Any, **kwargs: Any) -> Any: + return wrap_sdk_call(super().update, *args, **kwargs) + + def delete(self, *args: Any, **kwargs: Any) -> Any: + return wrap_sdk_call(super().delete, *args, **kwargs) + + def rotate_signing_secret(self, *args: Any, **kwargs: Any) -> Any: + return wrap_sdk_call(super().rotate_signing_secret, *args, **kwargs) + + def ping(self, *args: Any, **kwargs: Any) -> Any: + return wrap_sdk_call(super().ping, *args, **kwargs) + + +class AsyncGeminiNextGenWebhooks(GeneratedAsyncWebhooks): + """Async public webhooks resource backed by the NextGen client.""" + + def __init__(self, api_client: Any): + sdk = build_google_genai_async_client(api_client) + super().__init__(sdk.sdk_configuration, parent_ref=sdk) + + if not TYPE_CHECKING: + @property + def with_raw_response(self): + return _AsyncRawResponseAccessorProxy(super().with_raw_response) + + @property + def with_streaming_response(self): + return _AsyncRawResponseAccessorProxy(super().with_streaming_response) + + async def create(self, *args: Any, **kwargs: Any) -> Any: + return await async_wrap_sdk_call(super().create, *args, **kwargs) + + async def list(self, *args: Any, **kwargs: Any) -> Any: + return await async_wrap_sdk_call(super().list, *args, **kwargs) + + async def get(self, *args: Any, **kwargs: Any) -> Any: + return await async_wrap_sdk_call(super().get, *args, **kwargs) + + async def update(self, *args: Any, **kwargs: Any) -> Any: + return await async_wrap_sdk_call(super().update, *args, **kwargs) + + async def delete(self, *args: Any, **kwargs: Any) -> Any: + return await async_wrap_sdk_call(super().delete, *args, **kwargs) + + async def rotate_signing_secret(self, *args: Any, **kwargs: Any) -> Any: + return await async_wrap_sdk_call( + super().rotate_signing_secret, *args, **kwargs + ) + + async def ping(self, *args: Any, **kwargs: Any) -> Any: + return await async_wrap_sdk_call(super().ping, *args, **kwargs) + + +class GeminiNextGenAgents(GeneratedAgents): + """Public agents resource backed by the NextGen client. + + Subclasses the generated resource so every public method is wrapped in + `wrap_sdk_call`, translating per-operation `GenAiError` raises into the + status-code `APIError` hierarchy exposed at the + `google.genai._interactions` import surface. + """ + + def __init__(self, api_client: Any): + sdk = build_google_genai_client(api_client) + super().__init__(sdk.sdk_configuration, parent_ref=sdk) + + if not TYPE_CHECKING: + @property + def with_raw_response(self): + return _RawResponseAccessorProxy(super().with_raw_response) + + @property + def with_streaming_response(self): + return _RawResponseAccessorProxy(super().with_streaming_response) + + def create(self, *args: Any, **kwargs: Any) -> Any: + return wrap_sdk_call(super().create, *args, **kwargs) + + def list(self, *args: Any, **kwargs: Any) -> Any: + return wrap_sdk_call(super().list, *args, **kwargs) + + def get(self, *args: Any, **kwargs: Any) -> Any: + return wrap_sdk_call(super().get, *args, **kwargs) + + def delete(self, *args: Any, **kwargs: Any) -> Any: + return wrap_sdk_call(super().delete, *args, **kwargs) + + +class AsyncGeminiNextGenAgents(GeneratedAsyncAgents): + """Async public agents resource backed by the NextGen client.""" + + def __init__(self, api_client: Any): + sdk = build_google_genai_async_client(api_client) + super().__init__(sdk.sdk_configuration, parent_ref=sdk) + + if not TYPE_CHECKING: + @property + def with_raw_response(self): + return _AsyncRawResponseAccessorProxy(super().with_raw_response) + + @property + def with_streaming_response(self): + return _AsyncRawResponseAccessorProxy(super().with_streaming_response) + + async def create(self, *args: Any, **kwargs: Any) -> Any: + return await async_wrap_sdk_call(super().create, *args, **kwargs) + + async def list(self, *args: Any, **kwargs: Any) -> Any: + return await async_wrap_sdk_call(super().list, *args, **kwargs) + + async def get(self, *args: Any, **kwargs: Any) -> Any: + return await async_wrap_sdk_call(super().get, *args, **kwargs) + + async def delete(self, *args: Any, **kwargs: Any) -> Any: + return await async_wrap_sdk_call(super().delete, *args, **kwargs) + def _add_output_properties_if_interaction(value: Any) -> Any: normalized = _normalize_interaction_shape(value) @@ -467,7 +788,33 @@ def _get_value(value: Any, name: str) -> Any: return getattr(value, name, None) +# Allowed create() body keys, derived from the generated request models so the +# set tracks the schema; output-only fields are excluded. +_CREATE_BODY_KEYS = frozenset( + field.alias or name + for model in ( + interactions.CreateModelInteraction, + interactions.CreateAgentInteraction, + ) + for name, field in model.model_fields.items() +) + + +_REQUEST_AND_BODY_ERROR = ( + 'create() accepts either request=... or individual body keyword arguments, ' + 'not both.' +) + + def _normalize_create_body(body: dict[str, Any]) -> dict[str, Any]: + unknown = set(body) - _CREATE_BODY_KEYS + if unknown: + raise TypeError( + 'create() got unexpected keyword argument(s): ' + + ', '.join(sorted(unknown)) + + '. Use extra_body=... to send additional request body fields.' + ) + input_value = body.get('input') if not _is_content_list(input_value): return body @@ -519,3 +866,17 @@ def _optional_str(value: Any) -> Optional[str]: if isinstance(value, str): return value return None + + +def _request_stream(request: Any) -> bool: + body = ( + request.get('body') + if isinstance(request, Mapping) + else getattr(request, 'body', None) + ) + stream = ( + body.get('stream') + if isinstance(body, Mapping) + else getattr(body, 'stream', None) + ) + return bool(_optional_bool(stream, default=False)) diff --git a/google/genai/_gaos/interactions.py b/google/genai/_gaos/interactions.py index 43881605d..79746f8a8 100644 --- a/google/genai/_gaos/interactions.py +++ b/google/genai/_gaos/interactions.py @@ -17,7 +17,7 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" from . import errors, models, types, utils -from ._hooks import HookContext +from ._hooks import AfterParseErrorContext, HookContext, ResponseContext from .basesdk import AsyncBaseSDK, BaseSDK from .types import BaseModel, OptionalNullable, UNSET, interactions from .utils import get_security_from_env, response_helpers @@ -71,7 +71,6 @@ def create( background: bool = ..., system_instruction: str = ..., tools: List[interactions.ToolParam] = ..., - usage: interactions.UsageParam = ..., response_modalities: List[interactions.ResponseModality] = ..., response_mime_type: str = ..., previous_interaction_id: str = ..., @@ -98,7 +97,6 @@ def create( :param background: Input only. Whether to run the model interaction in the background. :param system_instruction: System instruction for the interaction. :param tools: A list of tool declarations the model may call during interaction. - :param usage: Statistics on the interaction request's token usage. :param response_modalities: The requested modalities of the response (TEXT, IMAGE, AUDIO). :param response_mime_type: The mime type of the response. This is required if response_format is set. :param previous_interaction_id: The ID of the previous interaction, if any. @@ -130,7 +128,6 @@ def create( background: bool = ..., system_instruction: str = ..., tools: List[interactions.ToolParam] = ..., - usage: interactions.UsageParam = ..., response_modalities: List[interactions.ResponseModality] = ..., response_mime_type: str = ..., previous_interaction_id: str = ..., @@ -157,7 +154,6 @@ def create( :param background: Input only. Whether to run the model interaction in the background. :param system_instruction: System instruction for the interaction. :param tools: A list of tool declarations the model may call during interaction. - :param usage: Statistics on the interaction request's token usage. :param response_modalities: The requested modalities of the response (TEXT, IMAGE, AUDIO). :param response_mime_type: The mime type of the response. This is required if response_format is set. :param previous_interaction_id: The ID of the previous interaction, if any. @@ -189,7 +185,6 @@ def create( background: bool = ..., system_instruction: str = ..., tools: List[interactions.ToolParam] = ..., - usage: interactions.UsageParam = ..., response_modalities: List[interactions.ResponseModality] = ..., response_mime_type: str = ..., previous_interaction_id: str = ..., @@ -215,7 +210,6 @@ def create( :param background: Input only. Whether to run the model interaction in the background. :param system_instruction: System instruction for the interaction. :param tools: A list of tool declarations the model may call during interaction. - :param usage: Statistics on the interaction request's token usage. :param response_modalities: The requested modalities of the response (TEXT, IMAGE, AUDIO). :param response_mime_type: The mime type of the response. This is required if response_format is set. :param previous_interaction_id: The ID of the previous interaction, if any. @@ -242,7 +236,6 @@ def create( background: bool = ..., system_instruction: str = ..., tools: List[interactions.ToolParam] = ..., - usage: interactions.UsageParam = ..., response_modalities: List[interactions.ResponseModality] = ..., response_mime_type: str = ..., previous_interaction_id: str = ..., @@ -268,7 +261,6 @@ def create( :param background: Input only. Whether to run the model interaction in the background. :param system_instruction: System instruction for the interaction. :param tools: A list of tool declarations the model may call during interaction. - :param usage: Statistics on the interaction request's token usage. :param response_modalities: The requested modalities of the response (TEXT, IMAGE, AUDIO). :param response_mime_type: The mime type of the response. This is required if response_format is set. :param previous_interaction_id: The ID of the previous interaction, if any. @@ -309,7 +301,6 @@ def create( :param background: Input only. Whether to run the model interaction in the background. :param system_instruction: System instruction for the interaction. :param tools: A list of tool declarations the model may call during interaction. - :param usage: Statistics on the interaction request's token usage. :param response_modalities: The requested modalities of the response (TEXT, IMAGE, AUDIO). :param response_mime_type: The mime type of the response. This is required if response_format is set. :param previous_interaction_id: The ID of the previous interaction, if any. @@ -356,7 +347,6 @@ def create( :param background: Input only. Whether to run the model interaction in the background. :param system_instruction: System instruction for the interaction. :param tools: A list of tool declarations the model may call during interaction. - :param usage: Statistics on the interaction request's token usage. :param response_modalities: The requested modalities of the response (TEXT, IMAGE, AUDIO). :param response_mime_type: The mime type of the response. This is required if response_format is set. :param previous_interaction_id: The ID of the previous interaction, if any. @@ -467,7 +457,10 @@ def create( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -479,7 +472,7 @@ def _speakeasy_parse_response(http_res): if utils.match_response(http_res, "200", "application/json"): http_res_text = utils.stream_to_text(http_res) return unmarshal_json_response( - interactions.Interaction, http_res, http_res_text + interactions.Interaction, http_res, http_res_text, validate=False ) if utils.match_response(http_res, "200", "text/event-stream"): return Stream( @@ -492,36 +485,52 @@ def _speakeasy_parse_response(http_res): ) if utils.match_response(http_res, "4XX", "application/json"): http_res_text = utils.stream_to_text(http_res) - response_data = unmarshal_json_response( - errors.CreateInteractionClientErrorData, http_res, http_res_text - ) - raise errors.CreateInteractionClientError( - response_data, http_res, http_res_text - ) + try: + response_data = unmarshal_json_response( + errors.CreateInteractionClientErrorData, http_res, http_res_text + ) + raise errors.CreateInteractionClientError( + response_data, http_res, http_res_text + ) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res_text, + ) from e if utils.match_response(http_res, "5XX", "application/json"): http_res_text = utils.stream_to_text(http_res) - response_data = unmarshal_json_response( - errors.CreateInteractionServerErrorData, http_res, http_res_text - ) - raise errors.CreateInteractionServerError( - response_data, http_res, http_res_text - ) + try: + response_data = unmarshal_json_response( + errors.CreateInteractionServerErrorData, http_res, http_res_text + ) + raise errors.CreateInteractionServerError( + response_data, http_res, http_res_text + ) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res_text, + ) from e http_res_text = utils.stream_to_text(http_res) raise errors.GenAiDefaultError( "Unexpected response received", http_res, http_res_text ) - http_res = self.do_request( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="CreateInteraction", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="CreateInteraction", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="sync"), + ) + http_res = self.do_request( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=getattr(getattr(request, "body", None), "stream", False) is True @@ -546,9 +555,19 @@ def _speakeasy_parse_response(http_res): else "buffered" ), client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), ), ) - return _speakeasy_parse_response(http_res) + try: + return _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + response_helpers.raise_parse_error( + self.sdk_configuration.__dict__["_hooks"], + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) @overload def get( @@ -709,7 +728,10 @@ def get( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -721,7 +743,7 @@ def _speakeasy_parse_response(http_res): if utils.match_response(http_res, "200", "application/json"): http_res_text = utils.stream_to_text(http_res) return unmarshal_json_response( - interactions.Interaction, http_res, http_res_text + interactions.Interaction, http_res, http_res_text, validate=False ) if utils.match_response(http_res, "200", "text/event-stream"): return Stream( @@ -734,36 +756,56 @@ def _speakeasy_parse_response(http_res): ) if utils.match_response(http_res, "4XX", "application/json"): http_res_text = utils.stream_to_text(http_res) - response_data = unmarshal_json_response( - errors.GetInteractionByIDClientErrorData, http_res, http_res_text - ) - raise errors.GetInteractionByIDClientError( - response_data, http_res, http_res_text - ) + try: + response_data = unmarshal_json_response( + errors.GetInteractionByIDClientErrorData, + http_res, + http_res_text, + ) + raise errors.GetInteractionByIDClientError( + response_data, http_res, http_res_text + ) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res_text, + ) from e if utils.match_response(http_res, "5XX", "application/json"): http_res_text = utils.stream_to_text(http_res) - response_data = unmarshal_json_response( - errors.GetInteractionByIDServerErrorData, http_res, http_res_text - ) - raise errors.GetInteractionByIDServerError( - response_data, http_res, http_res_text - ) + try: + response_data = unmarshal_json_response( + errors.GetInteractionByIDServerErrorData, + http_res, + http_res_text, + ) + raise errors.GetInteractionByIDServerError( + response_data, http_res, http_res_text + ) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res_text, + ) from e http_res_text = utils.stream_to_text(http_res) raise errors.GenAiDefaultError( "Unexpected response received", http_res, http_res_text ) - http_res = self.do_request( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="getInteractionById", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getInteractionById", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="sync"), + ) + http_res = self.do_request( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=stream is True or _speakeasy_response_mode == "streaming", @@ -782,9 +824,19 @@ def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode=("event_stream" if stream is True else "buffered"), client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), ), ) - return _speakeasy_parse_response(http_res) + try: + return _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + response_helpers.raise_parse_error( + self.sdk_configuration.__dict__["_hooks"], + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) def delete( self, @@ -853,7 +905,10 @@ def delete( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -865,28 +920,44 @@ def _speakeasy_parse_response(http_res): if utils.match_response(http_res, "200", "*"): return if utils.match_response(http_res, "4XX", "application/json"): - response_data = unmarshal_json_response( - errors.DeleteInteractionClientErrorData, http_res - ) - raise errors.DeleteInteractionClientError(response_data, http_res) + try: + response_data = unmarshal_json_response( + errors.DeleteInteractionClientErrorData, http_res + ) + raise errors.DeleteInteractionClientError(response_data, http_res) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res.text, + ) from e if utils.match_response(http_res, "5XX", "application/json"): - response_data = unmarshal_json_response( - errors.DeleteInteractionServerErrorData, http_res - ) - raise errors.DeleteInteractionServerError(response_data, http_res) + try: + response_data = unmarshal_json_response( + errors.DeleteInteractionServerErrorData, http_res + ) + raise errors.DeleteInteractionServerError(response_data, http_res) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res.text, + ) from e raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = self.do_request( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="deleteInteraction", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="deleteInteraction", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="sync"), + ) + http_res = self.do_request( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -905,9 +976,19 @@ def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), ), ) - return _speakeasy_parse_response(http_res) + try: + return _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + response_helpers.raise_parse_error( + self.sdk_configuration.__dict__["_hooks"], + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) def cancel( self, @@ -976,7 +1057,10 @@ def cancel( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -986,30 +1070,52 @@ def cancel( def _speakeasy_parse_response(http_res): response_data: Any = None if utils.match_response(http_res, "200", "application/json"): - return unmarshal_json_response(interactions.Interaction, http_res) - if utils.match_response(http_res, "4XX", "application/json"): - response_data = unmarshal_json_response( - errors.CancelInteractionByIDClientErrorData, http_res + return unmarshal_json_response( + interactions.Interaction, http_res, validate=False ) - raise errors.CancelInteractionByIDClientError(response_data, http_res) + if utils.match_response(http_res, "4XX", "application/json"): + try: + response_data = unmarshal_json_response( + errors.CancelInteractionByIDClientErrorData, http_res + ) + raise errors.CancelInteractionByIDClientError( + response_data, http_res + ) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res.text, + ) from e if utils.match_response(http_res, "5XX", "application/json"): - response_data = unmarshal_json_response( - errors.CancelInteractionByIDServerErrorData, http_res - ) - raise errors.CancelInteractionByIDServerError(response_data, http_res) + try: + response_data = unmarshal_json_response( + errors.CancelInteractionByIDServerErrorData, http_res + ) + raise errors.CancelInteractionByIDServerError( + response_data, http_res + ) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res.text, + ) from e raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = self.do_request( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="cancelInteractionById", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="cancelInteractionById", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="sync"), + ) + http_res = self.do_request( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -1028,9 +1134,19 @@ def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), ), ) - return _speakeasy_parse_response(http_res) + try: + return _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + response_helpers.raise_parse_error( + self.sdk_configuration.__dict__["_hooks"], + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) class InteractionsWithRawResponse: @@ -1109,7 +1225,6 @@ async def create( background: bool = ..., system_instruction: str = ..., tools: List[interactions.ToolParam] = ..., - usage: interactions.UsageParam = ..., response_modalities: List[interactions.ResponseModality] = ..., response_mime_type: str = ..., previous_interaction_id: str = ..., @@ -1136,7 +1251,6 @@ async def create( :param background: Input only. Whether to run the model interaction in the background. :param system_instruction: System instruction for the interaction. :param tools: A list of tool declarations the model may call during interaction. - :param usage: Statistics on the interaction request's token usage. :param response_modalities: The requested modalities of the response (TEXT, IMAGE, AUDIO). :param response_mime_type: The mime type of the response. This is required if response_format is set. :param previous_interaction_id: The ID of the previous interaction, if any. @@ -1168,7 +1282,6 @@ async def create( background: bool = ..., system_instruction: str = ..., tools: List[interactions.ToolParam] = ..., - usage: interactions.UsageParam = ..., response_modalities: List[interactions.ResponseModality] = ..., response_mime_type: str = ..., previous_interaction_id: str = ..., @@ -1195,7 +1308,6 @@ async def create( :param background: Input only. Whether to run the model interaction in the background. :param system_instruction: System instruction for the interaction. :param tools: A list of tool declarations the model may call during interaction. - :param usage: Statistics on the interaction request's token usage. :param response_modalities: The requested modalities of the response (TEXT, IMAGE, AUDIO). :param response_mime_type: The mime type of the response. This is required if response_format is set. :param previous_interaction_id: The ID of the previous interaction, if any. @@ -1227,7 +1339,6 @@ async def create( background: bool = ..., system_instruction: str = ..., tools: List[interactions.ToolParam] = ..., - usage: interactions.UsageParam = ..., response_modalities: List[interactions.ResponseModality] = ..., response_mime_type: str = ..., previous_interaction_id: str = ..., @@ -1253,7 +1364,6 @@ async def create( :param background: Input only. Whether to run the model interaction in the background. :param system_instruction: System instruction for the interaction. :param tools: A list of tool declarations the model may call during interaction. - :param usage: Statistics on the interaction request's token usage. :param response_modalities: The requested modalities of the response (TEXT, IMAGE, AUDIO). :param response_mime_type: The mime type of the response. This is required if response_format is set. :param previous_interaction_id: The ID of the previous interaction, if any. @@ -1280,7 +1390,6 @@ async def create( background: bool = ..., system_instruction: str = ..., tools: List[interactions.ToolParam] = ..., - usage: interactions.UsageParam = ..., response_modalities: List[interactions.ResponseModality] = ..., response_mime_type: str = ..., previous_interaction_id: str = ..., @@ -1306,7 +1415,6 @@ async def create( :param background: Input only. Whether to run the model interaction in the background. :param system_instruction: System instruction for the interaction. :param tools: A list of tool declarations the model may call during interaction. - :param usage: Statistics on the interaction request's token usage. :param response_modalities: The requested modalities of the response (TEXT, IMAGE, AUDIO). :param response_mime_type: The mime type of the response. This is required if response_format is set. :param previous_interaction_id: The ID of the previous interaction, if any. @@ -1347,7 +1455,6 @@ async def create( :param background: Input only. Whether to run the model interaction in the background. :param system_instruction: System instruction for the interaction. :param tools: A list of tool declarations the model may call during interaction. - :param usage: Statistics on the interaction request's token usage. :param response_modalities: The requested modalities of the response (TEXT, IMAGE, AUDIO). :param response_mime_type: The mime type of the response. This is required if response_format is set. :param previous_interaction_id: The ID of the previous interaction, if any. @@ -1394,7 +1501,6 @@ async def create( :param background: Input only. Whether to run the model interaction in the background. :param system_instruction: System instruction for the interaction. :param tools: A list of tool declarations the model may call during interaction. - :param usage: Statistics on the interaction request's token usage. :param response_modalities: The requested modalities of the response (TEXT, IMAGE, AUDIO). :param response_mime_type: The mime type of the response. This is required if response_format is set. :param previous_interaction_id: The ID of the previous interaction, if any. @@ -1505,7 +1611,10 @@ async def create( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -1517,7 +1626,7 @@ async def _speakeasy_parse_response(http_res): if utils.match_response(http_res, "200", "application/json"): http_res_text = await utils.stream_to_text_async(http_res) return unmarshal_json_response( - interactions.Interaction, http_res, http_res_text + interactions.Interaction, http_res, http_res_text, validate=False ) if utils.match_response(http_res, "200", "text/event-stream"): return AsyncStream( @@ -1530,36 +1639,52 @@ async def _speakeasy_parse_response(http_res): ) if utils.match_response(http_res, "4XX", "application/json"): http_res_text = await utils.stream_to_text_async(http_res) - response_data = unmarshal_json_response( - errors.CreateInteractionClientErrorData, http_res, http_res_text - ) - raise errors.CreateInteractionClientError( - response_data, http_res, http_res_text - ) + try: + response_data = unmarshal_json_response( + errors.CreateInteractionClientErrorData, http_res, http_res_text + ) + raise errors.CreateInteractionClientError( + response_data, http_res, http_res_text + ) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res_text, + ) from e if utils.match_response(http_res, "5XX", "application/json"): http_res_text = await utils.stream_to_text_async(http_res) - response_data = unmarshal_json_response( - errors.CreateInteractionServerErrorData, http_res, http_res_text - ) - raise errors.CreateInteractionServerError( - response_data, http_res, http_res_text - ) + try: + response_data = unmarshal_json_response( + errors.CreateInteractionServerErrorData, http_res, http_res_text + ) + raise errors.CreateInteractionServerError( + response_data, http_res, http_res_text + ) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res_text, + ) from e http_res_text = await utils.stream_to_text_async(http_res) raise errors.GenAiDefaultError( "Unexpected response received", http_res, http_res_text ) - http_res = await self.do_request_async( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="CreateInteraction", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="CreateInteraction", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="async"), + ) + http_res = await self.do_request_async( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=getattr(getattr(request, "body", None), "stream", False) is True @@ -1584,9 +1709,21 @@ async def _speakeasy_parse_response(http_res): else "buffered" ), client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), + async_hooks=self.sdk_configuration.__dict__.get("_async_hooks"), ), ) - return await _speakeasy_parse_response(http_res) + try: + return await _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + await response_helpers.raise_parse_error_async( + self.sdk_configuration.__dict__.get("_async_hooks"), + self.sdk_configuration.__dict__.get("_hooks"), + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) @overload async def get( @@ -1747,7 +1884,10 @@ async def get( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -1759,7 +1899,7 @@ async def _speakeasy_parse_response(http_res): if utils.match_response(http_res, "200", "application/json"): http_res_text = await utils.stream_to_text_async(http_res) return unmarshal_json_response( - interactions.Interaction, http_res, http_res_text + interactions.Interaction, http_res, http_res_text, validate=False ) if utils.match_response(http_res, "200", "text/event-stream"): return AsyncStream( @@ -1772,36 +1912,56 @@ async def _speakeasy_parse_response(http_res): ) if utils.match_response(http_res, "4XX", "application/json"): http_res_text = await utils.stream_to_text_async(http_res) - response_data = unmarshal_json_response( - errors.GetInteractionByIDClientErrorData, http_res, http_res_text - ) - raise errors.GetInteractionByIDClientError( - response_data, http_res, http_res_text - ) + try: + response_data = unmarshal_json_response( + errors.GetInteractionByIDClientErrorData, + http_res, + http_res_text, + ) + raise errors.GetInteractionByIDClientError( + response_data, http_res, http_res_text + ) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res_text, + ) from e if utils.match_response(http_res, "5XX", "application/json"): http_res_text = await utils.stream_to_text_async(http_res) - response_data = unmarshal_json_response( - errors.GetInteractionByIDServerErrorData, http_res, http_res_text - ) - raise errors.GetInteractionByIDServerError( - response_data, http_res, http_res_text - ) + try: + response_data = unmarshal_json_response( + errors.GetInteractionByIDServerErrorData, + http_res, + http_res_text, + ) + raise errors.GetInteractionByIDServerError( + response_data, http_res, http_res_text + ) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res_text, + ) from e http_res_text = await utils.stream_to_text_async(http_res) raise errors.GenAiDefaultError( "Unexpected response received", http_res, http_res_text ) - http_res = await self.do_request_async( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="getInteractionById", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="getInteractionById", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="async"), + ) + http_res = await self.do_request_async( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=stream is True or _speakeasy_response_mode == "streaming", @@ -1820,9 +1980,21 @@ async def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode=("event_stream" if stream is True else "buffered"), client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), + async_hooks=self.sdk_configuration.__dict__.get("_async_hooks"), ), ) - return await _speakeasy_parse_response(http_res) + try: + return await _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + await response_helpers.raise_parse_error_async( + self.sdk_configuration.__dict__.get("_async_hooks"), + self.sdk_configuration.__dict__.get("_hooks"), + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) async def delete( self, @@ -1891,7 +2063,10 @@ async def delete( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -1903,28 +2078,44 @@ async def _speakeasy_parse_response(http_res): if utils.match_response(http_res, "200", "*"): return if utils.match_response(http_res, "4XX", "application/json"): - response_data = unmarshal_json_response( - errors.DeleteInteractionClientErrorData, http_res - ) - raise errors.DeleteInteractionClientError(response_data, http_res) + try: + response_data = unmarshal_json_response( + errors.DeleteInteractionClientErrorData, http_res + ) + raise errors.DeleteInteractionClientError(response_data, http_res) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res.text, + ) from e if utils.match_response(http_res, "5XX", "application/json"): - response_data = unmarshal_json_response( - errors.DeleteInteractionServerErrorData, http_res - ) - raise errors.DeleteInteractionServerError(response_data, http_res) + try: + response_data = unmarshal_json_response( + errors.DeleteInteractionServerErrorData, http_res + ) + raise errors.DeleteInteractionServerError(response_data, http_res) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res.text, + ) from e raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = await self.do_request_async( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="deleteInteraction", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="deleteInteraction", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="async"), + ) + http_res = await self.do_request_async( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -1943,9 +2134,21 @@ async def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), + async_hooks=self.sdk_configuration.__dict__.get("_async_hooks"), ), ) - return await _speakeasy_parse_response(http_res) + try: + return await _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + await response_helpers.raise_parse_error_async( + self.sdk_configuration.__dict__.get("_async_hooks"), + self.sdk_configuration.__dict__.get("_hooks"), + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) async def cancel( self, @@ -2014,7 +2217,10 @@ async def cancel( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -2024,30 +2230,52 @@ async def cancel( async def _speakeasy_parse_response(http_res): response_data: Any = None if utils.match_response(http_res, "200", "application/json"): - return unmarshal_json_response(interactions.Interaction, http_res) - if utils.match_response(http_res, "4XX", "application/json"): - response_data = unmarshal_json_response( - errors.CancelInteractionByIDClientErrorData, http_res + return unmarshal_json_response( + interactions.Interaction, http_res, validate=False ) - raise errors.CancelInteractionByIDClientError(response_data, http_res) + if utils.match_response(http_res, "4XX", "application/json"): + try: + response_data = unmarshal_json_response( + errors.CancelInteractionByIDClientErrorData, http_res + ) + raise errors.CancelInteractionByIDClientError( + response_data, http_res + ) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res.text, + ) from e if utils.match_response(http_res, "5XX", "application/json"): - response_data = unmarshal_json_response( - errors.CancelInteractionByIDServerErrorData, http_res - ) - raise errors.CancelInteractionByIDServerError(response_data, http_res) + try: + response_data = unmarshal_json_response( + errors.CancelInteractionByIDServerErrorData, http_res + ) + raise errors.CancelInteractionByIDServerError( + response_data, http_res + ) + except errors.ResponseValidationError as e: + raise errors.GenAiDefaultError( + "Error response body did not match expected schema", + http_res, + http_res.text, + ) from e raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = await self.do_request_async( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="cancelInteractionById", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="cancelInteractionById", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="async"), + ) + http_res = await self.do_request_async( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -2066,9 +2294,21 @@ async def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), + async_hooks=self.sdk_configuration.__dict__.get("_async_hooks"), ), ) - return await _speakeasy_parse_response(http_res) + try: + return await _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + await response_helpers.raise_parse_error_async( + self.sdk_configuration.__dict__.get("_async_hooks"), + self.sdk_configuration.__dict__.get("_hooks"), + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) class AsyncInteractionsWithRawResponse: diff --git a/google/genai/_gaos/lib/compat_errors.py b/google/genai/_gaos/lib/compat_errors.py new file mode 100644 index 000000000..a3e7eb9b4 --- /dev/null +++ b/google/genai/_gaos/lib/compat_errors.py @@ -0,0 +1,589 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Error-class compatibility shim. + +Backs `google.genai._interactions` imports of `BadRequestError`, +`NotFoundError`, `RateLimitError`, etc. + +The generated SDK raises `GenAiError` (and per-operation subclasses thereof). +The bridge layer in `google_genai.py` translates those into the status-code +classes defined here via `wrap_sdk_error` / `wrap_sdk_call`. +""" + +import inspect +import json +from typing import Any, Awaitable, Callable, Optional, TypeVar, cast + +import httpx + +from ..errors.genaierror import GenAiError +from ..errors.no_response_error import NoResponseError +from ..errors.responsevalidationerror import ResponseValidationError +from ..utils.eventstreaming import AsyncStream, Stream + + +T = TypeVar("T") + + +class GeminiNextGenAPIClientError(Exception): + """Root of the GenAI error hierarchy.""" + + +class APIError(GeminiNextGenAPIClientError): + """General errors raised by the GenAI API. + + Carries `message`, `request`, `body`. Status-code subclasses add + `response` and override `status_code` to a concrete value. + """ + + message: str + request: httpx.Request + body: object + status_code: Optional[int] = None + + def __init__( + self, + message: str, + request: httpx.Request, + *, + body: object, + ) -> None: + super().__init__(message) + self.request = request + self.message = message + self.body = body + + def __str__(self) -> str: + return self.message + + @classmethod + def generate( + cls, + status_code: Optional[int], + body: object, + message: Optional[str], + response: Optional[httpx.Response], + ) -> "APIError": + """Return the most specific APIError subclass for the given status.""" + target_cls = _status_class(status_code) + msg = message if message is not None else _compose_message(status_code, body) + if issubclass(target_cls, APIStatusError): + if response is None: + raise ValueError( + "APIStatusError subclasses require a response object." + ) + status_cls = cast(type[APIStatusError], target_cls) + return status_cls(msg, response=response, body=body) + # Plain APIError fallback (status_code None or unexpected success + # status like 201). Attach status_code to the instance for callers + # that need to inspect it; not part of the Stainless contract for + # APIError but covers the unexpected-status path. + request = response.request if response is not None else None + err = target_cls(msg, request, body=body) # type: ignore[arg-type] + if status_code is not None: + err.status_code = status_code + return err + + +class APIResponseValidationError(APIError): + response: httpx.Response + + def __init__( + self, + response: httpx.Response, + body: object, + *, + message: Optional[str] = None, + ) -> None: + super().__init__( + message or "Data returned by API invalid for expected schema.", + response.request, + body=body, + ) + self.response = response + self.status_code = response.status_code + + +class APIStatusError(APIError): + """Raised when an API response has a 4xx or 5xx status code.""" + + response: httpx.Response + + def __init__( + self, + message: str, + *, + response: httpx.Response, + body: object, + ) -> None: + super().__init__(message, response.request, body=body) + self.response = response + self.status_code = response.status_code + + +class APIConnectionError(APIError): + def __init__( + self, + *, + message: str = "Connection error.", + request: httpx.Request, + ) -> None: + super().__init__(message, request, body=None) + + +class APITimeoutError(APIConnectionError): + def __init__(self, request: httpx.Request) -> None: + super().__init__( + message=( + "Request timed out. This is a client-side timeout. You can " + "increase the timeout by setting the `timeout` argument on " + "your request or in the client http options." + ), + request=request, + ) + + +class BadRequestError(APIStatusError): + status_code: int = 400 # type: ignore[assignment] + + +class AuthenticationError(APIStatusError): + status_code: int = 401 # type: ignore[assignment] + + +class PermissionDeniedError(APIStatusError): + status_code: int = 403 # type: ignore[assignment] + + +class NotFoundError(APIStatusError): + status_code: int = 404 # type: ignore[assignment] + + +class ConflictError(APIStatusError): + status_code: int = 409 # type: ignore[assignment] + + +class UnprocessableEntityError(APIStatusError): + status_code: int = 422 # type: ignore[assignment] + + +class RateLimitError(APIStatusError): + status_code: int = 429 # type: ignore[assignment] + + +class InternalServerError(APIStatusError): + pass + + +_STATUS_MAP = { + 400: BadRequestError, + 401: AuthenticationError, + 403: PermissionDeniedError, + 404: NotFoundError, + 409: ConflictError, + 422: UnprocessableEntityError, + 429: RateLimitError, +} + + +def _status_class(status_code: Optional[int]) -> type[APIError]: + if status_code is None: + return APIError + cls = _STATUS_MAP.get(status_code) + if cls is not None: + return cls + if 500 <= status_code < 600: + return InternalServerError + if 400 <= status_code < 500: + return APIStatusError + return APIError + + +def _parse_body(body: Any) -> Any: + """Return parsed JSON if body is a JSON string, else the original value.""" + if isinstance(body, (dict, list)): + return body + if not isinstance(body, str): + return body + text = body.strip() + if not text: + return None + try: + return json.loads(text) + except (ValueError, TypeError): + return text + + +def _compose_message(status_code: Optional[int], body: Any) -> str: + """Build an error message in `Error code: {status} - {body}` form. + + JSON body → `Error code: {status} - {body}`. + Text body → raw text. + Empty body → `Error code: {status}`. + """ + if isinstance(body, (dict, list)): + return f"Error code: {status_code} - {body}" + if isinstance(body, str) and body.strip(): + return body.strip() + if status_code is not None: + return f"Error code: {status_code}" + return "An error occurred" + + +def _wrap_validation_error(error: ResponseValidationError) -> APIResponseValidationError: + response = error.raw_response + body = _parse_body(error.body) + wrapped = APIResponseValidationError(response, body, message=error.message) + wrapped.__cause__ = error + return wrapped + + +def _wrap_no_response_error(error: NoResponseError) -> APIConnectionError: + wrapped = APIConnectionError( + message=error.message or "No response received.", + request=None, # type: ignore[arg-type] + ) + wrapped.__cause__ = error + return wrapped + + +def _wrap_httpx_error(error: BaseException) -> APIConnectionError: + """Translate an `httpx.HTTPError` into the compat connection-error class. + + `httpx.HTTPError.request` is a descriptor that raises `RuntimeError` when + the request was never attached. Reads the underlying `_request` attribute + directly. May pass `None` if the error was raised before request + construction — type hint stays strict (mirrors Stainless) but runtime + tolerates the edge case rather than fabricating a misleading stand-in. + """ + request = getattr(error, "_request", None) + if isinstance(error, httpx.TimeoutException): + wrapped: APIConnectionError = APITimeoutError(request=request) # type: ignore[arg-type] + else: + wrapped = APIConnectionError( + message=str(error) or "Connection error.", + request=request, # type: ignore[arg-type] + ) + wrapped.__cause__ = error + return wrapped + + +def wrap_sdk_error(error: BaseException) -> BaseException: + """Translate a generated SDK exception into an `APIError` subclass. + + Covers: + - `ResponseValidationError` → `APIResponseValidationError`. + - `NoResponseError` → `APIConnectionError`. + - `httpx.TimeoutException` → `APITimeoutError`. + - `httpx.HTTPError` → `APIConnectionError`. + - `GenAiError` → status-code class via `APIError.generate`. + + Already-wrapped or unrelated exceptions pass through. + """ + if isinstance(error, APIError): + return error + if isinstance(error, ResponseValidationError): + return _wrap_validation_error(error) + if isinstance(error, NoResponseError): + return _wrap_no_response_error(error) + if isinstance(error, httpx.HTTPError): + return _wrap_httpx_error(error) + if not isinstance(error, GenAiError): + return error + + response = error.raw_response + body = _parse_body(error.body) + wrapped = APIError.generate( + status_code=error.status_code, + body=body, + message=_compose_message(error.status_code, body), + response=response, + ) + wrapped.__cause__ = error + return wrapped + + +_WRAP_EXCEPTIONS = (GenAiError, NoResponseError, httpx.HTTPError) + + +class CompatErrorHook: + """Hook adapter that maps generated SDK exceptions to compat errors.""" + + def after_error( + self, + hook_ctx: Any, + response: Optional[httpx.Response], + error: Optional[Exception], + ) -> tuple[Optional[httpx.Response], Optional[Exception]]: + if error is None: + return response, error + return response, wrap_sdk_error(error) # type: ignore[return-value] + + def after_parse_error( + self, + hook_ctx: Any, + response: httpx.Response, + error: Exception, + ) -> Exception: + return wrap_sdk_error(error) # type: ignore[return-value] + + +def wrap_sdk_call(fn: Callable[..., T], *args: Any, **kwargs: Any) -> T: + """Execute `fn(*args, **kwargs)`, translating known SDK exceptions.""" + try: + return fn(*args, **kwargs) + except _WRAP_EXCEPTIONS as exc: + raise wrap_sdk_error(exc) from exc + + +async def async_wrap_sdk_call( + fn: Callable[..., Awaitable[T]], *args: Any, **kwargs: Any +) -> T: + """Async variant of `wrap_sdk_call`.""" + try: + return await fn(*args, **kwargs) + except _WRAP_EXCEPTIONS as exc: + raise wrap_sdk_error(exc) from exc + + +def is_stream(obj: Any) -> bool: + """True if `obj` is a live SSE `Stream` / `AsyncStream`. + + Distinguishes the streaming iterator — which carries a `.generator` ready + for `wrap_stream_errors` to wrap — from a `StreamedAPIResponse`, whose + underlying stream only materializes once `.parse()` is called. Subclasses + produced by `parse(to=MyStream)` are covered by `isinstance`. + """ + return isinstance(obj, (Stream, AsyncStream)) + + +def wrap_stream_errors(stream: Stream[Any]) -> Stream[Any]: + """Wrap a `Stream` so iteration-time `GenAiError`s become `APIError`s. + + Mutates `stream.generator` in place and returns the same stream object so + the caller's typing and identity are preserved. + + Precondition: caller ensures `is_stream(stream)` (see `_wrap_response_parse` + and the `google_genai` bridge). A `StreamedAPIResponse` has no `.generator`. + """ + original = stream.generator + + def _wrapped_gen(): + try: + for chunk in original: + yield chunk + except _WRAP_EXCEPTIONS as exc: + raise wrap_sdk_error(exc) from exc + + stream.generator = _wrapped_gen() + return stream + + +def wrap_async_stream_errors(stream: AsyncStream[Any]) -> AsyncStream[Any]: + """Async variant of `wrap_stream_errors`.""" + original = stream.generator + + async def _wrapped_agen(): + try: + async for chunk in original: + yield chunk + except _WRAP_EXCEPTIONS as exc: + raise wrap_sdk_error(exc) from exc + + stream.generator = _wrapped_agen() + return stream + + +def _wrap_response_parse(response: Any) -> Any: + """Shadow `response.parse` so SDK errors raised at parse-time and iteration-time become compat errors.""" + original_parse = response.parse + + def _parse(*args: Any, **kwargs: Any) -> Any: + result = wrap_sdk_call(original_parse, *args, **kwargs) + if is_stream(result): + return wrap_stream_errors(result) + return result + + response.parse = _parse # type: ignore[method-assign] + return response + + +def _wrap_async_response_parse(response: Any) -> Any: + """Async variant of `_wrap_response_parse`.""" + original_parse = response.parse + + async def _parse(*args: Any, **kwargs: Any) -> Any: + result = await async_wrap_sdk_call(original_parse, *args, **kwargs) + if is_stream(result): + return wrap_async_stream_errors(result) + return result + + response.parse = _parse # type: ignore[method-assign] + return response + + +class _StreamingContextManagerProxy: + """Proxy a sync `ResponseContextManager` so the entered response wraps parse.""" + + __slots__ = ("_inner",) + + def __init__(self, inner: Any) -> None: + self._inner = inner + + def __enter__(self) -> Any: + try: + response = self._inner.__enter__() + except _WRAP_EXCEPTIONS as exc: + raise wrap_sdk_error(exc) from exc + if hasattr(response, "parse"): + response = _wrap_response_parse(response) + return response + + def __exit__(self, *exc_info: Any) -> Any: + return self._inner.__exit__(*exc_info) + + +class _AsyncStreamingContextManagerProxy: + """Async variant of `_StreamingContextManagerProxy`.""" + + __slots__ = ("_inner",) + + def __init__(self, inner: Any) -> None: + self._inner = inner + + async def __aenter__(self) -> Any: + try: + response = await self._inner.__aenter__() + except _WRAP_EXCEPTIONS as exc: + raise wrap_sdk_error(exc) from exc + if hasattr(response, "parse"): + response = _wrap_async_response_parse(response) + return response + + async def __aexit__(self, *exc_info: Any) -> Any: + return await self._inner.__aexit__(*exc_info) + + +class _RawResponseAccessorProxy: + """Sync proxy around the value of `with_raw_response` / `with_streaming_response`. + + Delegates attribute access to the wrapped instance. Methods are wrapped so + that exceptions are translated via `wrap_sdk_error`, and the returned value + is decorated based on shape: + + - `APIResponse` / `StreamedAPIResponse`: `.parse` is shadowed so parse-time + errors are translated. + - `ResponseContextManager` (from `with_streaming_response`): proxied so the + response yielded by `__enter__` has `.parse` shadowed. + """ + + __slots__ = ("_inner",) + + def __init__(self, inner: Any) -> None: + object.__setattr__(self, "_inner", inner) + + def __getattr__(self, name: str) -> Any: + inner = object.__getattribute__(self, "_inner") + attr = getattr(inner, name) + if name.startswith("_") or not callable(attr): + return attr + + def _wrapped(*args: Any, **kwargs: Any) -> Any: + try: + result = attr(*args, **kwargs) + except _WRAP_EXCEPTIONS as exc: + raise wrap_sdk_error(exc) from exc + if hasattr(result, "parse"): + return _wrap_response_parse(result) + if hasattr(result, "__enter__"): + return _StreamingContextManagerProxy(result) + return result + + return _wrapped + + +class _AsyncRawResponseAccessorProxy: + """Async variant of `_RawResponseAccessorProxy`. + + The accessor's methods may return: + - `Awaitable[AsyncAPIResponse]` (raw): await, then shadow `.parse`. + - `AsyncResponseContextManager` (streaming): proxy so `__aenter__` yields a + response with `.parse` shadowed. + """ + + __slots__ = ("_inner",) + + def __init__(self, inner: Any) -> None: + object.__setattr__(self, "_inner", inner) + + def __getattr__(self, name: str) -> Any: + inner = object.__getattribute__(self, "_inner") + attr = getattr(inner, name) + if name.startswith("_") or not callable(attr): + return attr + + def _wrapped(*args: Any, **kwargs: Any) -> Any: + try: + result = attr(*args, **kwargs) + except _WRAP_EXCEPTIONS as exc: + raise wrap_sdk_error(exc) from exc + if hasattr(result, "__aenter__"): + return _AsyncStreamingContextManagerProxy(result) + if inspect.isawaitable(result): + + async def _await_and_wrap() -> Any: + try: + response = await result + except _WRAP_EXCEPTIONS as exc: + raise wrap_sdk_error(exc) from exc + if hasattr(response, "parse"): + return _wrap_async_response_parse(response) + return response + + return _await_and_wrap() + if hasattr(result, "parse"): + return _wrap_async_response_parse(result) + return result + + return _wrapped + + +__all__ = [ + "APIConnectionError", + "APIError", + "APIResponseValidationError", + "APIStatusError", + "APITimeoutError", + "AuthenticationError", + "BadRequestError", + "ConflictError", + "CompatErrorHook", + "GeminiNextGenAPIClientError", + "InternalServerError", + "NotFoundError", + "PermissionDeniedError", + "RateLimitError", + "UnprocessableEntityError", + "_AsyncRawResponseAccessorProxy", + "_RawResponseAccessorProxy", + "async_wrap_sdk_call", + "is_stream", + "wrap_async_stream_errors", + "wrap_sdk_call", + "wrap_sdk_error", + "wrap_stream_errors", +] diff --git a/google/genai/_gaos/resources/interactions/__init__.py b/google/genai/_gaos/resources/interactions/__init__.py index 1a58f6bca..7159a5bb9 100644 --- a/google/genai/_gaos/resources/interactions/__init__.py +++ b/google/genai/_gaos/resources/interactions/__init__.py @@ -16,6 +16,9 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" +from ...models.cancelinteractionbyid import ( + CancelInteractionByIDRequestParam as InteractionCancelParams, +) from ...models.createinteraction import ( CreateInteractionRequestParam as InteractionCreateParams, ) @@ -25,310 +28,186 @@ from ...models.getinteractionbyid import ( GetInteractionByIDRequestParam as InteractionGetParams, ) -from ...models.listagents import ListAgentsRequestParam as AgentListParams -from ...models.listwebhooks import ListWebhooksRequestParam as WebhookListParams -from ...types.agents.agent import Agent, AgentParam as AgentCreateParams -from ...types.agents.agentlistresponse import AgentListResponse -from ...types.interactions.agentoption import AgentOption from ...types.interactions.allowedtools import AllowedTools -from ...types.interactions.allowlistentry import AllowlistEntry from ...types.interactions.annotation import Annotation -from ...types.interactions.argumentsdelta import ArgumentsDelta -from ...types.interactions.audiocontent import AudioContent, AudioContentParam -from ...types.interactions.audiodelta import AudioDelta +from ...types.interactions.audiocontent import AudioContent from ...types.interactions.audioresponseformat import AudioResponseFormat -from ...types.interactions.codeexecution import CodeExecution from ...types.interactions.codeexecutioncallarguments import CodeExecutionCallArguments -from ...types.interactions.codeexecutioncalldelta import CodeExecutionCallDelta from ...types.interactions.codeexecutioncallstep import CodeExecutionCallStep -from ...types.interactions.codeexecutionresultdelta import CodeExecutionResultDelta from ...types.interactions.codeexecutionresultstep import CodeExecutionResultStep -from ...types.interactions.computeruse import ComputerUse from ...types.interactions.content import Content -from ...types.interactions.createagentinteraction import CreateAgentInteraction -from ...types.interactions.createmodelinteraction import CreateModelInteraction +from ...types.interactions.createagentinteraction import ( + CreateAgentInteractionParam as CreateAgentInteractionParamsNonStreaming, +) +from ...types.interactions.createmodelinteraction import ( + CreateModelInteractionParam as CreateModelInteractionParamsNonStreaming, +) from ...types.interactions.deepresearchagentconfig import DeepResearchAgentConfig from ...types.interactions.documentcontent import DocumentContent -from ...types.interactions.documentdelta import DocumentDelta from ...types.interactions.dynamicagentconfig import DynamicAgentConfig -from ...types.interactions.empty import Empty +from ...types.interactions.empty import Empty as InteractionDeleteResponse from ...types.interactions.environment import Environment -from ...types.interactions.environmentnetworkegressallowlist import ( - EnvironmentNetworkEgressAllowlist, -) -from ...types.interactions.error import Error from ...types.interactions.errorevent import ErrorEvent -from ...types.interactions.exaaisearchconfig import ExaAISearchConfig from ...types.interactions.filecitation import FileCitation -from ...types.interactions.filesearch import FileSearch -from ...types.interactions.filesearchcalldelta import FileSearchCallDelta from ...types.interactions.filesearchcallstep import FileSearchCallStep -from ...types.interactions.filesearchresult import FileSearchResult -from ...types.interactions.filesearchresultdelta import FileSearchResultDelta from ...types.interactions.filesearchresultstep import FileSearchResultStep -from ...types.interactions.filter_ import Filter from ...types.interactions.function import Function from ...types.interactions.functioncallstep import FunctionCallStep -from ...types.interactions.functionresultdelta import FunctionResultDelta from ...types.interactions.functionresultstep import FunctionResultStep -from ...types.interactions.functionresultsubcontent import FunctionResultSubcontent from ...types.interactions.generationconfig import GenerationConfig -from ...types.interactions.googlemaps import GoogleMaps from ...types.interactions.googlemapscallarguments import GoogleMapsCallArguments -from ...types.interactions.googlemapscalldelta import GoogleMapsCallDelta from ...types.interactions.googlemapscallstep import GoogleMapsCallStep from ...types.interactions.googlemapsresult import GoogleMapsResult -from ...types.interactions.googlemapsresultdelta import GoogleMapsResultDelta -from ...types.interactions.googlemapsresultplaces import GoogleMapsResultPlaces from ...types.interactions.googlemapsresultstep import GoogleMapsResultStep -from ...types.interactions.googlesearch import GoogleSearch from ...types.interactions.googlesearchcallarguments import GoogleSearchCallArguments -from ...types.interactions.googlesearchcalldelta import GoogleSearchCallDelta from ...types.interactions.googlesearchcallstep import GoogleSearchCallStep from ...types.interactions.googlesearchresult import GoogleSearchResult -from ...types.interactions.googlesearchresultdelta import GoogleSearchResultDelta from ...types.interactions.googlesearchresultstep import GoogleSearchResultStep -from ...types.interactions.groundingtoolcount import GroundingToolCount -from ...types.interactions.hybridsearch import HybridSearch from ...types.interactions.imageconfig import ImageConfig from ...types.interactions.imagecontent import ImageContent -from ...types.interactions.imagedelta import ImageDelta from ...types.interactions.imageresponseformat import ImageResponseFormat from ...types.interactions.interaction import Interaction from ...types.interactions.interactioncompletedevent import InteractionCompletedEvent from ...types.interactions.interactioncreatedevent import InteractionCreatedEvent -from ...types.interactions.interactionsinput import InteractionsInput from ...types.interactions.interactionsseevent import InteractionSSEEvent -from ...types.interactions.interactionsseeventinteraction import ( - InteractionSseEventInteraction, -) -from ...types.interactions.interactionssestreamevent import InteractionSSEStreamEvent from ...types.interactions.interactionstatusupdate import InteractionStatusUpdate -from ...types.interactions.mcpserver import MCPServer -from ...types.interactions.mcpservertoolcalldelta import MCPServerToolCallDelta from ...types.interactions.mcpservertoolcallstep import MCPServerToolCallStep -from ...types.interactions.mcpservertoolresultdelta import MCPServerToolResultDelta from ...types.interactions.mcpservertoolresultstep import MCPServerToolResultStep -from ...types.interactions.mediaresolution import MediaResolution -from ...types.interactions.modalitytokens import ModalityTokens from ...types.interactions.model import Model from ...types.interactions.modeloutputstep import ModelOutputStep -from ...types.interactions.parallelaisearchconfig import ParallelAISearchConfig from ...types.interactions.placecitation import PlaceCitation -from ...types.interactions.ragresource import RagResource -from ...types.interactions.ragretrievalconfig import RagRetrievalConfig -from ...types.interactions.ragstoreconfig import RagStoreConfig -from ...types.interactions.ranking import Ranking -from ...types.interactions.responseformat import ResponseFormat -from ...types.interactions.responsemodality import ResponseModality -from ...types.interactions.retrieval import Retrieval -from ...types.interactions.reviewsnippet import ReviewSnippet -from ...types.interactions.servicetier import ServiceTier -from ...types.interactions.source import Source from ...types.interactions.speechconfig import SpeechConfig from ...types.interactions.step import Step from ...types.interactions.stepdelta import StepDelta -from ...types.interactions.stepdeltadata import StepDeltaData -from ...types.interactions.stepdeltametadata import StepDeltaMetadata from ...types.interactions.stepstart import StepStart from ...types.interactions.stepstop import StepStop -from ...types.interactions.streammetadata import StreamMetadata -from ...types.interactions.textannotationdelta import TextAnnotationDelta from ...types.interactions.textcontent import TextContent -from ...types.interactions.textdelta import TextDelta from ...types.interactions.textresponseformat import TextResponseFormat from ...types.interactions.thinkinglevel import ThinkingLevel -from ...types.interactions.thinkingsummaries import ThinkingSummaries -from ...types.interactions.thoughtsignaturedelta import ThoughtSignatureDelta from ...types.interactions.thoughtstep import ThoughtStep -from ...types.interactions.thoughtsummarycontent import ThoughtSummaryContent -from ...types.interactions.thoughtsummarydelta import ThoughtSummaryDelta from ...types.interactions.tool import Tool from ...types.interactions.toolchoiceconfig import ToolChoiceConfig from ...types.interactions.toolchoicetype import ToolChoiceType -from ...types.interactions.turn import Turn from ...types.interactions.urlcitation import URLCitation -from ...types.interactions.urlcontext import URLContext from ...types.interactions.urlcontextcallarguments import URLContextCallArguments -from ...types.interactions.urlcontextcalldelta import URLContextCallDelta -from ...types.interactions.urlcontextcallstep import Arguments, URLContextCallStep +from ...types.interactions.urlcontextcallstep import URLContextCallStep from ...types.interactions.urlcontextresult import URLContextResult -from ...types.interactions.urlcontextresultdelta import URLContextResultDelta from ...types.interactions.urlcontextresultstep import URLContextResultStep from ...types.interactions.usage import Usage from ...types.interactions.userinputstep import UserInputStep -from ...types.interactions.vertexaisearchconfig import VertexAISearchConfig -from ...types.interactions.videocontent import VideoContent, VideoContentParam -from ...types.interactions.videodelta import VideoDelta +from ...types.interactions.videocontent import VideoContent from ...types.interactions.webhookconfig import WebhookConfig -from ...types.webhooks.pingwebhookrequest import ( - PingWebhookRequestParam as WebhookPingParams, -) -from ...types.webhooks.rotatesigningsecretrequest import ( - RotateSigningSecretRequestParam as WebhookRotateSigningSecretParams, -) -from ...types.webhooks.signingsecret import SigningSecret -from ...types.webhooks.webhook import Webhook, WebhookInputParam as WebhookCreateParams -from ...types.webhooks.webhooklistresponse import WebhookListResponse -from ...types.webhooks.webhookpingresponse import WebhookPingResponse -from ...types.webhooks.webhookrotatesigningsecretresponse import ( - WebhookRotateSigningSecretResponse, -) -from ...types.webhooks.webhookupdate import WebhookUpdateParam as WebhookUpdateParams +from . import codeexecutioncallstep +from . import environment +from . import errorevent +from . import googlemapscallstep +from . import googlemapsresult +from . import googlemapsresultstep +from . import googlesearchcallstep +from . import googlesearchresultstep +from . import interactioncompletedevent +from . import interactioncreatedevent +from . import interactionstatusupdate +from . import placecitation +from . import stepdelta +from . import stepstart +from . import stepstop +from . import tool +from . import urlcontextcallstep +from . import urlcontextresultstep +from . import usage -AgentDeleteResponse = Empty -ModelParam = Model -WebhookDeleteResponse = Empty +CreateAgentInteractionParamsStreaming = CreateAgentInteractionParamsNonStreaming +CreateModelInteractionParamsStreaming = CreateModelInteractionParamsNonStreaming __all__ = [ - "Agent", - "AgentCreateParams", - "AgentDeleteResponse", - "AgentListParams", - "AgentListResponse", - "AgentOption", "AllowedTools", - "AllowlistEntry", "Annotation", - "Arguments", - "ArgumentsDelta", "AudioContent", - "AudioContentParam", - "AudioDelta", "AudioResponseFormat", - "CodeExecution", "CodeExecutionCallArguments", - "CodeExecutionCallDelta", "CodeExecutionCallStep", - "CodeExecutionResultDelta", "CodeExecutionResultStep", - "ComputerUse", "Content", - "CreateAgentInteraction", - "CreateModelInteraction", + "CreateAgentInteractionParamsNonStreaming", + "CreateAgentInteractionParamsStreaming", + "CreateModelInteractionParamsNonStreaming", + "CreateModelInteractionParamsStreaming", "DeepResearchAgentConfig", "DocumentContent", - "DocumentDelta", "DynamicAgentConfig", - "Empty", "Environment", - "EnvironmentNetworkEgressAllowlist", - "Error", "ErrorEvent", - "ExaAISearchConfig", "FileCitation", - "FileSearch", - "FileSearchCallDelta", "FileSearchCallStep", - "FileSearchResult", - "FileSearchResultDelta", "FileSearchResultStep", - "Filter", "Function", "FunctionCallStep", - "FunctionResultDelta", "FunctionResultStep", - "FunctionResultSubcontent", "GenerationConfig", - "GoogleMaps", "GoogleMapsCallArguments", - "GoogleMapsCallDelta", "GoogleMapsCallStep", "GoogleMapsResult", - "GoogleMapsResultDelta", - "GoogleMapsResultPlaces", "GoogleMapsResultStep", - "GoogleSearch", "GoogleSearchCallArguments", - "GoogleSearchCallDelta", "GoogleSearchCallStep", "GoogleSearchResult", - "GoogleSearchResultDelta", "GoogleSearchResultStep", - "GroundingToolCount", - "HybridSearch", "ImageConfig", "ImageContent", - "ImageDelta", "ImageResponseFormat", "Interaction", + "InteractionCancelParams", "InteractionCompletedEvent", "InteractionCreateParams", "InteractionCreatedEvent", "InteractionDeleteParams", + "InteractionDeleteResponse", "InteractionGetParams", "InteractionSSEEvent", - "InteractionSSEStreamEvent", - "InteractionSseEventInteraction", "InteractionStatusUpdate", - "InteractionsInput", - "MCPServer", - "MCPServerToolCallDelta", "MCPServerToolCallStep", - "MCPServerToolResultDelta", "MCPServerToolResultStep", - "MediaResolution", - "ModalityTokens", "Model", "ModelOutputStep", - "ModelParam", - "ParallelAISearchConfig", "PlaceCitation", - "RagResource", - "RagRetrievalConfig", - "RagStoreConfig", - "Ranking", - "ResponseFormat", - "ResponseModality", - "Retrieval", - "ReviewSnippet", - "ServiceTier", - "SigningSecret", - "Source", "SpeechConfig", "Step", "StepDelta", - "StepDeltaData", - "StepDeltaMetadata", "StepStart", "StepStop", - "StreamMetadata", - "TextAnnotationDelta", "TextContent", - "TextDelta", "TextResponseFormat", "ThinkingLevel", - "ThinkingSummaries", - "ThoughtSignatureDelta", "ThoughtStep", - "ThoughtSummaryContent", - "ThoughtSummaryDelta", "Tool", "ToolChoiceConfig", "ToolChoiceType", - "Turn", "URLCitation", - "URLContext", "URLContextCallArguments", - "URLContextCallDelta", "URLContextCallStep", "URLContextResult", - "URLContextResultDelta", "URLContextResultStep", "Usage", "UserInputStep", - "VertexAISearchConfig", "VideoContent", - "VideoContentParam", - "VideoDelta", - "Webhook", "WebhookConfig", - "WebhookCreateParams", - "WebhookDeleteResponse", - "WebhookListParams", - "WebhookListResponse", - "WebhookPingParams", - "WebhookPingResponse", - "WebhookRotateSigningSecretParams", - "WebhookRotateSigningSecretResponse", - "WebhookUpdateParams", + "codeexecutioncallstep", + "environment", + "errorevent", + "googlemapscallstep", + "googlemapsresult", + "googlemapsresultstep", + "googlesearchcallstep", + "googlesearchresultstep", + "interactioncompletedevent", + "interactioncreatedevent", + "interactionstatusupdate", + "placecitation", + "stepdelta", + "stepstart", + "stepstop", + "tool", + "urlcontextcallstep", + "urlcontextresultstep", + "usage", ] diff --git a/google/genai/_gaos/resources/interactions/codeexecutioncallstep/__init__.py b/google/genai/_gaos/resources/interactions/codeexecutioncallstep/__init__.py new file mode 100644 index 000000000..03da56c4f --- /dev/null +++ b/google/genai/_gaos/resources/interactions/codeexecutioncallstep/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.codeexecutioncallarguments import ( + CodeExecutionCallArguments as Arguments, +) + +__all__ = ["Arguments"] diff --git a/google/genai/_gaos/resources/interactions/environment/__init__.py b/google/genai/_gaos/resources/interactions/environment/__init__.py new file mode 100644 index 000000000..06a67cbf3 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/environment/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.environmentnetworkegressallowlist import Allowlist +from ....types.interactions.source import Source +from . import allowlist + +__all__ = ["Allowlist", "Source", "allowlist"] diff --git a/google/genai/_gaos/resources/interactions/environment/allowlist/__init__.py b/google/genai/_gaos/resources/interactions/environment/allowlist/__init__.py new file mode 100644 index 000000000..63b9597f9 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/environment/allowlist/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .....types.interactions.allowlistentry import AllowlistEntry as Allowlist + +__all__ = ["Allowlist"] diff --git a/google/genai/_gaos/resources/interactions/errorevent/__init__.py b/google/genai/_gaos/resources/interactions/errorevent/__init__.py new file mode 100644 index 000000000..e0585bd3b --- /dev/null +++ b/google/genai/_gaos/resources/interactions/errorevent/__init__.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.error import Error +from ....types.interactions.streammetadata import StreamMetadata as Metadata + +__all__ = ["Error", "Metadata"] diff --git a/google/genai/_gaos/resources/interactions/googlemapscallstep/__init__.py b/google/genai/_gaos/resources/interactions/googlemapscallstep/__init__.py new file mode 100644 index 000000000..94583493b --- /dev/null +++ b/google/genai/_gaos/resources/interactions/googlemapscallstep/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.googlemapscallarguments import ( + GoogleMapsCallArguments as Arguments, +) + +__all__ = ["Arguments"] diff --git a/google/genai/_gaos/resources/interactions/googlemapsresult/__init__.py b/google/genai/_gaos/resources/interactions/googlemapsresult/__init__.py new file mode 100644 index 000000000..41bca67e5 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/googlemapsresult/__init__.py @@ -0,0 +1,24 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.googlemapsresultplaces import ( + GoogleMapsResultPlaces as Place, +) +from . import place + +__all__ = ["Place", "place"] diff --git a/google/genai/_gaos/resources/interactions/googlemapsresult/place/__init__.py b/google/genai/_gaos/resources/interactions/googlemapsresult/place/__init__.py new file mode 100644 index 000000000..8e040e599 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/googlemapsresult/place/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .....types.interactions.reviewsnippet import ReviewSnippet + +__all__ = ["ReviewSnippet"] diff --git a/google/genai/_gaos/resources/interactions/googlemapsresultstep/__init__.py b/google/genai/_gaos/resources/interactions/googlemapsresultstep/__init__.py new file mode 100644 index 000000000..b144b8cfb --- /dev/null +++ b/google/genai/_gaos/resources/interactions/googlemapsresultstep/__init__.py @@ -0,0 +1,22 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.googlemapsresult import GoogleMapsResult as Result +from . import result + +__all__ = ["Result", "result"] diff --git a/google/genai/_gaos/resources/interactions/googlemapsresultstep/result/__init__.py b/google/genai/_gaos/resources/interactions/googlemapsresultstep/result/__init__.py new file mode 100644 index 000000000..e949e8259 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/googlemapsresultstep/result/__init__.py @@ -0,0 +1,24 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .....types.interactions.googlemapsresultplaces import ( + GoogleMapsResultPlaces as Place, +) +from . import place + +__all__ = ["Place", "place"] diff --git a/google/genai/_gaos/resources/interactions/googlemapsresultstep/result/place/__init__.py b/google/genai/_gaos/resources/interactions/googlemapsresultstep/result/place/__init__.py new file mode 100644 index 000000000..3f30752de --- /dev/null +++ b/google/genai/_gaos/resources/interactions/googlemapsresultstep/result/place/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ......types.interactions.reviewsnippet import ReviewSnippet + +__all__ = ["ReviewSnippet"] diff --git a/google/genai/_gaos/resources/interactions/googlesearchcallstep/__init__.py b/google/genai/_gaos/resources/interactions/googlesearchcallstep/__init__.py new file mode 100644 index 000000000..e84cc28f0 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/googlesearchcallstep/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.googlesearchcallarguments import ( + GoogleSearchCallArguments as Arguments, +) + +__all__ = ["Arguments"] diff --git a/google/genai/_gaos/resources/interactions/googlesearchresultstep/__init__.py b/google/genai/_gaos/resources/interactions/googlesearchresultstep/__init__.py new file mode 100644 index 000000000..c9893c70f --- /dev/null +++ b/google/genai/_gaos/resources/interactions/googlesearchresultstep/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.googlesearchresult import GoogleSearchResult as Result + +__all__ = ["Result"] diff --git a/google/genai/_gaos/resources/interactions/interactioncompletedevent/__init__.py b/google/genai/_gaos/resources/interactions/interactioncompletedevent/__init__.py new file mode 100644 index 000000000..cefc54863 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/interactioncompletedevent/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.streammetadata import StreamMetadata as Metadata + +__all__ = ["Metadata"] diff --git a/google/genai/_gaos/resources/interactions/interactioncreatedevent/__init__.py b/google/genai/_gaos/resources/interactions/interactioncreatedevent/__init__.py new file mode 100644 index 000000000..cefc54863 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/interactioncreatedevent/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.streammetadata import StreamMetadata as Metadata + +__all__ = ["Metadata"] diff --git a/google/genai/_gaos/resources/interactions/interactionstatusupdate/__init__.py b/google/genai/_gaos/resources/interactions/interactionstatusupdate/__init__.py new file mode 100644 index 000000000..cefc54863 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/interactionstatusupdate/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.streammetadata import StreamMetadata as Metadata + +__all__ = ["Metadata"] diff --git a/google/genai/_gaos/resources/interactions/placecitation/__init__.py b/google/genai/_gaos/resources/interactions/placecitation/__init__.py new file mode 100644 index 000000000..8a52eac14 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/placecitation/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.reviewsnippet import ReviewSnippet + +__all__ = ["ReviewSnippet"] diff --git a/google/genai/_gaos/resources/interactions/stepdelta/__init__.py b/google/genai/_gaos/resources/interactions/stepdelta/__init__.py new file mode 100644 index 000000000..e45245eb5 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/stepdelta/__init__.py @@ -0,0 +1,97 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.argumentsdelta import ArgumentsDelta +from ....types.interactions.audiodelta import AudioDelta as Audio +from ....types.interactions.codeexecutioncalldelta import ( + CodeExecutionCallDelta as CodeExecutionCall, +) +from ....types.interactions.codeexecutionresultdelta import ( + CodeExecutionResultDelta as CodeExecutionResult, +) +from ....types.interactions.documentdelta import DocumentDelta as Document +from ....types.interactions.filesearchcalldelta import ( + FileSearchCallDelta as FileSearchCall, +) +from ....types.interactions.filesearchresultdelta import ( + FileSearchResultDelta as FileSearchResult, +) +from ....types.interactions.functionresultdelta import ( + FunctionResultDelta as FunctionResult, +) +from ....types.interactions.googlemapscalldelta import ( + GoogleMapsCallDelta as GoogleMapsCall, +) +from ....types.interactions.googlemapsresultdelta import ( + GoogleMapsResultDelta as GoogleMapsResult, +) +from ....types.interactions.googlesearchcalldelta import ( + GoogleSearchCallDelta as GoogleSearchCall, +) +from ....types.interactions.googlesearchresultdelta import ( + GoogleSearchResultDelta as GoogleSearchResult, +) +from ....types.interactions.imagedelta import ImageDelta as Image +from ....types.interactions.mcpservertoolcalldelta import ( + MCPServerToolCallDelta as MCPServerToolCall, +) +from ....types.interactions.mcpservertoolresultdelta import ( + MCPServerToolResultDelta as MCPServerToolResult, +) +from ....types.interactions.stepdeltametadata import StepDeltaMetadata as Metadata +from ....types.interactions.textannotationdelta import TextAnnotationDelta +from ....types.interactions.textdelta import TextDelta as Text +from ....types.interactions.thoughtsignaturedelta import ( + ThoughtSignatureDelta as ThoughtSignature, +) +from ....types.interactions.thoughtsummarydelta import ( + ThoughtSummaryDelta as ThoughtSummary, +) +from ....types.interactions.urlcontextcalldelta import ( + URLContextCallDelta as URLContextCall, +) +from ....types.interactions.urlcontextresultdelta import ( + URLContextResultDelta as URLContextResult, +) +from ....types.interactions.videodelta import VideoDelta as Video + +__all__ = [ + "ArgumentsDelta", + "Audio", + "CodeExecutionCall", + "CodeExecutionResult", + "Document", + "FileSearchCall", + "FileSearchResult", + "FunctionResult", + "GoogleMapsCall", + "GoogleMapsResult", + "GoogleSearchCall", + "GoogleSearchResult", + "Image", + "MCPServerToolCall", + "MCPServerToolResult", + "Metadata", + "Text", + "TextAnnotationDelta", + "ThoughtSignature", + "ThoughtSummary", + "URLContextCall", + "URLContextResult", + "Video", +] diff --git a/google/genai/_gaos/resources/interactions/stepstart/__init__.py b/google/genai/_gaos/resources/interactions/stepstart/__init__.py new file mode 100644 index 000000000..cefc54863 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/stepstart/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.streammetadata import StreamMetadata as Metadata + +__all__ = ["Metadata"] diff --git a/google/genai/_gaos/resources/interactions/stepstop/__init__.py b/google/genai/_gaos/resources/interactions/stepstop/__init__.py new file mode 100644 index 000000000..cefc54863 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/stepstop/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.streammetadata import StreamMetadata as Metadata + +__all__ = ["Metadata"] diff --git a/google/genai/_gaos/resources/interactions/tool/__init__.py b/google/genai/_gaos/resources/interactions/tool/__init__.py new file mode 100644 index 000000000..4f63f2c5c --- /dev/null +++ b/google/genai/_gaos/resources/interactions/tool/__init__.py @@ -0,0 +1,39 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.codeexecution import CodeExecution +from ....types.interactions.computeruse import ComputerUse +from ....types.interactions.filesearch import FileSearch +from ....types.interactions.googlemaps import GoogleMaps +from ....types.interactions.googlesearch import GoogleSearch +from ....types.interactions.mcpserver import MCPServer +from ....types.interactions.retrieval import Retrieval +from ....types.interactions.urlcontext import URLContext +from . import retrieval + +__all__ = [ + "CodeExecution", + "ComputerUse", + "FileSearch", + "GoogleMaps", + "GoogleSearch", + "MCPServer", + "Retrieval", + "URLContext", + "retrieval", +] diff --git a/google/genai/_gaos/resources/interactions/tool/retrieval/__init__.py b/google/genai/_gaos/resources/interactions/tool/retrieval/__init__.py new file mode 100644 index 000000000..ae18fd508 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/tool/retrieval/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from .....types.interactions.vertexaisearchconfig import VertexAISearchConfig + +__all__ = ["VertexAISearchConfig"] diff --git a/google/genai/_gaos/resources/interactions/urlcontextcallstep/__init__.py b/google/genai/_gaos/resources/interactions/urlcontextcallstep/__init__.py new file mode 100644 index 000000000..e393f77e6 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/urlcontextcallstep/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.urlcontextcallstep import Arguments + +__all__ = ["Arguments"] diff --git a/google/genai/_gaos/resources/interactions/urlcontextresultstep/__init__.py b/google/genai/_gaos/resources/interactions/urlcontextresultstep/__init__.py new file mode 100644 index 000000000..1b6e9711a --- /dev/null +++ b/google/genai/_gaos/resources/interactions/urlcontextresultstep/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.urlcontextresult import URLContextResult as Result + +__all__ = ["Result"] diff --git a/google/genai/_gaos/resources/interactions/usage/__init__.py b/google/genai/_gaos/resources/interactions/usage/__init__.py new file mode 100644 index 000000000..a296949c9 --- /dev/null +++ b/google/genai/_gaos/resources/interactions/usage/__init__.py @@ -0,0 +1,33 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# pyformat: disable + +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from ....types.interactions.groundingtoolcount import GroundingToolCount +from ....types.interactions.modalitytokens import ( + ModalityTokens as CachedTokensByModality, +) + +InputTokensByModality = CachedTokensByModality +OutputTokensByModality = CachedTokensByModality +ToolUseTokensByModality = CachedTokensByModality +__all__ = [ + "CachedTokensByModality", + "GroundingToolCount", + "InputTokensByModality", + "OutputTokensByModality", + "ToolUseTokensByModality", +] diff --git a/google/genai/_gaos/resources/webhooks/__init__.py b/google/genai/_gaos/resources/webhooks/__init__.py index ae9b071ca..a029efd6d 100644 --- a/google/genai/_gaos/resources/webhooks/__init__.py +++ b/google/genai/_gaos/resources/webhooks/__init__.py @@ -20,22 +20,26 @@ from ...models.deletewebhook import DeleteWebhookRequestParam as WebhookDeleteParams from ...models.getwebhook import GetWebhookRequestParam as WebhookGetParams from ...models.listwebhooks import ListWebhooksRequestParam as WebhookListParams -from ...models.pingwebhook import PingWebhookRequestParam as WebhookPingParams -from ...models.rotatesigningsecret import ( +from ...types.interactions.empty import Empty as WebhookDeleteResponse +from ...types.webhooks.pingwebhookrequest import ( + PingWebhookRequest, + PingWebhookRequestParam as WebhookPingParams, +) +from ...types.webhooks.rotatesigningsecretrequest import ( + RotateSigningSecretRequest, RotateSigningSecretRequestParam as WebhookRotateSigningSecretParams, ) -from ...models.updatewebhook import UpdateWebhookRequestParam as WebhookUpdateParams -from ...types.interactions.empty import Empty as WebhookDeleteResponse -from ...types.webhooks.pingwebhookrequest import PingWebhookRequest -from ...types.webhooks.rotatesigningsecretrequest import RotateSigningSecretRequest from ...types.webhooks.signingsecret import SigningSecret -from ...types.webhooks.webhook import Webhook, WebhookInput +from ...types.webhooks.webhook import Webhook, WebhookInputParam as WebhookInput from ...types.webhooks.webhooklistresponse import WebhookListResponse from ...types.webhooks.webhookpingresponse import WebhookPingResponse from ...types.webhooks.webhookrotatesigningsecretresponse import ( WebhookRotateSigningSecretResponse, ) -from ...types.webhooks.webhookupdate import WebhookUpdate +from ...types.webhooks.webhookupdate import ( + WebhookUpdate, + WebhookUpdateParam as WebhookUpdateParams, +) __all__ = [ "PingWebhookRequest", diff --git a/google/genai/_gaos/sdk.py b/google/genai/_gaos/sdk.py index 1bbe7706d..6dca740b1 100644 --- a/google/genai/_gaos/sdk.py +++ b/google/genai/_gaos/sdk.py @@ -17,7 +17,7 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" from . import models, types, utils -from ._hooks import SDKHooks +from ._hooks import AsyncSDKHooks, SDKHooks, init_async_hooks from .basesdk import AsyncBaseSDK, BaseSDK from .httpclient import ( AsyncHttpClient, @@ -137,9 +137,12 @@ def __init__( ) hooks = SDKHooks() + async_hooks = AsyncSDKHooks() # pylint: disable=protected-access self.sdk_configuration.__dict__["_hooks"] = hooks + self.sdk_configuration.__dict__["_async_hooks"] = async_hooks + init_async_hooks(async_hooks) self.sdk_configuration = hooks.sdk_init(self.sdk_configuration) @@ -328,9 +331,12 @@ def __init__( ) hooks = SDKHooks() + async_hooks = AsyncSDKHooks() # pylint: disable=protected-access self.sdk_configuration.__dict__["_hooks"] = hooks + self.sdk_configuration.__dict__["_async_hooks"] = async_hooks + init_async_hooks(async_hooks) self.sdk_configuration = hooks.sdk_init(self.sdk_configuration) diff --git a/google/genai/_gaos/types/agents/agenttool.py b/google/genai/_gaos/types/agents/agenttool.py index e25cdb23d..553b31870 100644 --- a/google/genai/_gaos/types/agents/agenttool.py +++ b/google/genai/_gaos/types/agents/agenttool.py @@ -77,6 +77,7 @@ class UnknownAgentTool(BaseModel): variants=_AGENT_TOOL_VARIANTS, unknown_cls=UnknownAgentTool, union_name="AgentTool", + lenient=True, ) ), ] diff --git a/google/genai/_gaos/types/basemodel.py b/google/genai/_gaos/types/basemodel.py index cfac7a3e0..5996a49f9 100644 --- a/google/genai/_gaos/types/basemodel.py +++ b/google/genai/_gaos/types/basemodel.py @@ -190,7 +190,7 @@ def validate_lax(v: Any) -> 'UnrecognizedStr': ]), strict_schema=core_schema.none_schema(), # Always fails in strict mode serialization=core_schema.plain_serializer_function_ser_schema( - lambda v: str(v) + str ), ) @@ -211,6 +211,6 @@ def validate_lax(v: Any) -> 'UnrecognizedInt': ]), strict_schema=core_schema.none_schema(), # Always fails in strict mode serialization=core_schema.plain_serializer_function_ser_schema( - lambda v: int(v) + int ), ) diff --git a/google/genai/_gaos/types/interactions/__init__.py b/google/genai/_gaos/types/interactions/__init__.py index f4a711f23..051fa095f 100644 --- a/google/genai/_gaos/types/interactions/__init__.py +++ b/google/genai/_gaos/types/interactions/__init__.py @@ -189,8 +189,8 @@ ) from .groundingtoolcount import ( GroundingToolCount, - GroundingToolCountParam, GroundingToolCountType, + GroundingToolCountTypedDict, ) from .hybridsearch import HybridSearch, HybridSearchParam from .imageconfig import ( @@ -272,7 +272,7 @@ MCPServerToolResultStepResultUnionParam, ) from .mediaresolution import MediaResolution - from .modalitytokens import ModalityTokens, ModalityTokensParam + from .modalitytokens import ModalityTokens, ModalityTokensTypedDict from .model import Model from .modeloutputstep import ModelOutputStep, ModelOutputStepParam from .parallelaisearchconfig import ( @@ -350,7 +350,7 @@ URLContextResultDeltaTypedDict, ) from .urlcontextresultstep import URLContextResultStep, URLContextResultStepParam - from .usage import Usage, UsageParam + from .usage import Usage, UsageTypedDict from .userinputstep import UserInputStep, UserInputStepParam from .vertexaisearchconfig import VertexAISearchConfig, VertexAISearchConfigParam from .videocontent import VideoContent, VideoContentMimeType, VideoContentParam @@ -504,8 +504,8 @@ "GoogleSearchResultStepParam", "GoogleSearchSearchType", "GroundingToolCount", - "GroundingToolCountParam", "GroundingToolCountType", + "GroundingToolCountTypedDict", "HybridSearch", "HybridSearchParam", "ImageConfig", @@ -570,7 +570,7 @@ "MCPServerToolResultStepResultUnionParam", "MediaResolution", "ModalityTokens", - "ModalityTokensParam", + "ModalityTokensTypedDict", "Model", "ModelOutputStep", "ModelOutputStepParam", @@ -674,7 +674,7 @@ "UnknownThoughtSummaryContent", "UnknownTool", "Usage", - "UsageParam", + "UsageTypedDict", "UserInputStep", "UserInputStepParam", "VertexAISearchConfig", @@ -845,8 +845,8 @@ "GoogleSearchResultStep": ".googlesearchresultstep", "GoogleSearchResultStepParam": ".googlesearchresultstep", "GroundingToolCount": ".groundingtoolcount", - "GroundingToolCountParam": ".groundingtoolcount", "GroundingToolCountType": ".groundingtoolcount", + "GroundingToolCountTypedDict": ".groundingtoolcount", "HybridSearch": ".hybridsearch", "HybridSearchParam": ".hybridsearch", "ImageConfig": ".imageconfig", @@ -912,7 +912,7 @@ "MCPServerToolResultStepResultUnionParam": ".mcpservertoolresultstep", "MediaResolution": ".mediaresolution", "ModalityTokens": ".modalitytokens", - "ModalityTokensParam": ".modalitytokens", + "ModalityTokensTypedDict": ".modalitytokens", "Model": ".model", "ModelOutputStep": ".modeloutputstep", "ModelOutputStepParam": ".modeloutputstep", @@ -1008,7 +1008,7 @@ "URLContextResultStep": ".urlcontextresultstep", "URLContextResultStepParam": ".urlcontextresultstep", "Usage": ".usage", - "UsageParam": ".usage", + "UsageTypedDict": ".usage", "UserInputStep": ".userinputstep", "UserInputStepParam": ".userinputstep", "VertexAISearchConfig": ".vertexaisearchconfig", diff --git a/google/genai/_gaos/types/interactions/agentoption.py b/google/genai/_gaos/types/interactions/agentoption.py index 270727eb3..00618642b 100644 --- a/google/genai/_gaos/types/interactions/agentoption.py +++ b/google/genai/_gaos/types/interactions/agentoption.py @@ -29,6 +29,8 @@ "deep-research-preview-04-2026", # Gemini Deep Research Max Agent "deep-research-max-preview-04-2026", + # Use the Antigravity managed agent to perform multi-step tasks that require reasoning, file operations, and tool use. + "antigravity-preview-05-2026", ], UnrecognizedStr, ] diff --git a/google/genai/_gaos/types/interactions/annotation.py b/google/genai/_gaos/types/interactions/annotation.py index 4176b47bc..481e76053 100644 --- a/google/genai/_gaos/types/interactions/annotation.py +++ b/google/genai/_gaos/types/interactions/annotation.py @@ -61,6 +61,7 @@ class UnknownAnnotation(BaseModel): variants=_ANNOTATION_VARIANTS, unknown_cls=UnknownAnnotation, union_name="Annotation", + lenient=True, ) ), ] diff --git a/google/genai/_gaos/types/interactions/codeexecutioncallstep.py b/google/genai/_gaos/types/interactions/codeexecutioncallstep.py index a04653132..6cbd4f59e 100644 --- a/google/genai/_gaos/types/interactions/codeexecutioncallstep.py +++ b/google/genai/_gaos/types/interactions/codeexecutioncallstep.py @@ -33,11 +33,11 @@ class CodeExecutionCallStepParam(TypedDict): r"""Code execution call step.""" + arguments: CodeExecutionCallArgumentsParam + r"""The arguments to pass to the code execution.""" id: str r"""Required. A unique ID for this specific tool call.""" type: Literal["code_execution_call"] - arguments: NotRequired[CodeExecutionCallArgumentsParam] - r"""The arguments to pass to the code execution.""" signature: NotRequired[Union[str, Base64FileInput]] r"""A signature hash for backend validation.""" @@ -45,6 +45,9 @@ class CodeExecutionCallStepParam(TypedDict): class CodeExecutionCallStep(BaseModel): r"""Code execution call step.""" + arguments: CodeExecutionCallArguments + r"""The arguments to pass to the code execution.""" + id: str r"""Required. A unique ID for this specific tool call.""" @@ -56,15 +59,12 @@ class CodeExecutionCallStep(BaseModel): pydantic.Field(alias="type"), ] = "code_execution_call" - arguments: Optional[CodeExecutionCallArguments] = None - r"""The arguments to pass to the code execution.""" - signature: Optional[Base64EncodedString] = None r"""A signature hash for backend validation.""" @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["arguments", "signature"]) + optional_fields = set(["signature"]) serialized = handler(self) m = {} diff --git a/google/genai/_gaos/types/interactions/codeexecutionresultstep.py b/google/genai/_gaos/types/interactions/codeexecutionresultstep.py index c91656ed9..d78bfbebc 100644 --- a/google/genai/_gaos/types/interactions/codeexecutionresultstep.py +++ b/google/genai/_gaos/types/interactions/codeexecutionresultstep.py @@ -29,11 +29,11 @@ class CodeExecutionResultStepParam(TypedDict): r"""Code execution result step.""" + result: str + r"""Required. The output of the code execution.""" call_id: str r"""Required. ID to match the ID from the function call block.""" type: Literal["code_execution_result"] - result: NotRequired[str] - r"""The output of the code execution.""" is_error: NotRequired[bool] r"""Whether the code execution resulted in an error.""" signature: NotRequired[Union[str, Base64FileInput]] @@ -43,6 +43,9 @@ class CodeExecutionResultStepParam(TypedDict): class CodeExecutionResultStep(BaseModel): r"""Code execution result step.""" + result: str + r"""Required. The output of the code execution.""" + call_id: str r"""Required. ID to match the ID from the function call block.""" @@ -54,9 +57,6 @@ class CodeExecutionResultStep(BaseModel): pydantic.Field(alias="type"), ] = "code_execution_result" - result: Optional[str] = None - r"""The output of the code execution.""" - is_error: Optional[bool] = None r"""Whether the code execution resulted in an error.""" @@ -65,7 +65,7 @@ class CodeExecutionResultStep(BaseModel): @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["result", "is_error", "signature"]) + optional_fields = set(["is_error", "signature"]) serialized = handler(self) m = {} diff --git a/google/genai/_gaos/types/interactions/content.py b/google/genai/_gaos/types/interactions/content.py index 1d42ebf87..6da27ffe8 100644 --- a/google/genai/_gaos/types/interactions/content.py +++ b/google/genai/_gaos/types/interactions/content.py @@ -79,6 +79,7 @@ class UnknownContent(BaseModel): variants=_CONTENT_VARIANTS, unknown_cls=UnknownContent, union_name="Content", + lenient=True, ) ), ] diff --git a/google/genai/_gaos/types/interactions/createagentinteraction.py b/google/genai/_gaos/types/interactions/createagentinteraction.py index 8f15f833a..18736d09e 100644 --- a/google/genai/_gaos/types/interactions/createagentinteraction.py +++ b/google/genai/_gaos/types/interactions/createagentinteraction.py @@ -30,7 +30,6 @@ from .responsemodality import ResponseModality from .servicetier import ServiceTier from .tool import Tool, ToolParam -from .usage import Usage, UsageParam from .webhookconfig import WebhookConfig, WebhookConfigParam import pydantic from pydantic import Field, model_serializer @@ -93,8 +92,6 @@ class CreateAgentInteractionParam(TypedDict): r"""System instruction for the interaction.""" tools: NotRequired[List[ToolParam]] r"""A list of tool declarations the model may call during interaction.""" - usage: NotRequired[UsageParam] - r"""Statistics on the interaction request's token usage.""" response_modalities: NotRequired[List[ResponseModality]] r"""The requested modalities of the response (TEXT, IMAGE, AUDIO).""" response_mime_type: NotRequired[str] @@ -136,9 +133,6 @@ class CreateAgentInteraction(BaseModel): tools: Optional[List[Tool]] = None r"""A list of tool declarations the model may call during interaction.""" - usage: Optional[Usage] = None - r"""Statistics on the interaction request's token usage.""" - response_modalities: Optional[List[ResponseModality]] = None r"""The requested modalities of the response (TEXT, IMAGE, AUDIO).""" @@ -176,7 +170,6 @@ def serialize_model(self, handler): "background", "system_instruction", "tools", - "usage", "response_modalities", "response_mime_type", "previous_interaction_id", diff --git a/google/genai/_gaos/types/interactions/createmodelinteraction.py b/google/genai/_gaos/types/interactions/createmodelinteraction.py index c0606ead4..571fc1f97 100644 --- a/google/genai/_gaos/types/interactions/createmodelinteraction.py +++ b/google/genai/_gaos/types/interactions/createmodelinteraction.py @@ -26,7 +26,6 @@ from .responsemodality import ResponseModality from .servicetier import ServiceTier from .tool import Tool, ToolParam -from .usage import Usage, UsageParam from .webhookconfig import WebhookConfig, WebhookConfigParam import pydantic from pydantic import model_serializer @@ -76,8 +75,6 @@ class CreateModelInteractionParam(TypedDict): r"""System instruction for the interaction.""" tools: NotRequired[List[ToolParam]] r"""A list of tool declarations the model may call during interaction.""" - usage: NotRequired[UsageParam] - r"""Statistics on the interaction request's token usage.""" response_modalities: NotRequired[List[ResponseModality]] r"""The requested modalities of the response (TEXT, IMAGE, AUDIO).""" response_mime_type: NotRequired[str] @@ -126,9 +123,6 @@ class CreateModelInteraction(BaseModel): tools: Optional[List[Tool]] = None r"""A list of tool declarations the model may call during interaction.""" - usage: Optional[Usage] = None - r"""Statistics on the interaction request's token usage.""" - response_modalities: Optional[List[ResponseModality]] = None r"""The requested modalities of the response (TEXT, IMAGE, AUDIO).""" @@ -174,7 +168,6 @@ def _serialize_model(self, handler): "background", "system_instruction", "tools", - "usage", "response_modalities", "response_mime_type", "previous_interaction_id", diff --git a/google/genai/_gaos/types/interactions/functionresultsubcontent.py b/google/genai/_gaos/types/interactions/functionresultsubcontent.py index c194e0383..15d130f4e 100644 --- a/google/genai/_gaos/types/interactions/functionresultsubcontent.py +++ b/google/genai/_gaos/types/interactions/functionresultsubcontent.py @@ -58,6 +58,7 @@ class UnknownFunctionResultSubcontent(BaseModel): variants=_FUNCTION_RESULT_SUBCONTENT_VARIANTS, unknown_cls=UnknownFunctionResultSubcontent, union_name="FunctionResultSubcontent", + lenient=True, ) ), ] diff --git a/google/genai/_gaos/types/interactions/generationconfig.py b/google/genai/_gaos/types/interactions/generationconfig.py index 570bda226..ebcbe995d 100644 --- a/google/genai/_gaos/types/interactions/generationconfig.py +++ b/google/genai/_gaos/types/interactions/generationconfig.py @@ -24,9 +24,10 @@ from .thinkingsummaries import ThinkingSummaries from .toolchoiceconfig import ToolChoiceConfig, ToolChoiceConfigParam from .toolchoicetype import ToolChoiceType +import pydantic from pydantic import model_serializer from typing import List, Optional, Union -from typing_extensions import NotRequired, TypeAliasType, TypedDict +from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict ToolChoiceParam = TypeAliasType( @@ -97,7 +98,12 @@ class GenerationConfig(BaseModel): speech_config: Optional[List[SpeechConfig]] = None r"""Configuration for speech interaction.""" - image_config: Optional[ImageConfig] = None + image_config: Annotated[ + Optional[ImageConfig], + pydantic.Field( + deprecated="warning: ** DEPRECATED ** - This will be removed in a future release, please migrate away from it as soon as possible." + ), + ] = None r"""The configuration for image interaction.""" presence_penalty: Optional[float] = None diff --git a/google/genai/_gaos/types/interactions/googlemapsresultstep.py b/google/genai/_gaos/types/interactions/googlemapsresultstep.py index 71956db47..1617a82c0 100644 --- a/google/genai/_gaos/types/interactions/googlemapsresultstep.py +++ b/google/genai/_gaos/types/interactions/googlemapsresultstep.py @@ -30,10 +30,10 @@ class GoogleMapsResultStepParam(TypedDict): r"""Google Maps result step.""" + result: List[GoogleMapsResultParam] call_id: str r"""Required. ID to match the ID from the function call block.""" type: Literal["google_maps_result"] - result: NotRequired[List[GoogleMapsResultParam]] signature: NotRequired[Union[str, Base64FileInput]] r"""A signature hash for backend validation.""" @@ -41,6 +41,8 @@ class GoogleMapsResultStepParam(TypedDict): class GoogleMapsResultStep(BaseModel): r"""Google Maps result step.""" + result: List[GoogleMapsResult] + call_id: str r"""Required. ID to match the ID from the function call block.""" @@ -52,14 +54,12 @@ class GoogleMapsResultStep(BaseModel): pydantic.Field(alias="type"), ] = "google_maps_result" - result: Optional[List[GoogleMapsResult]] = None - signature: Optional[Base64EncodedString] = None r"""A signature hash for backend validation.""" @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["result", "signature"]) + optional_fields = set(["signature"]) serialized = handler(self) m = {} diff --git a/google/genai/_gaos/types/interactions/googlesearchcallstep.py b/google/genai/_gaos/types/interactions/googlesearchcallstep.py index cb8e169db..98a3c7348 100644 --- a/google/genai/_gaos/types/interactions/googlesearchcallstep.py +++ b/google/genai/_gaos/types/interactions/googlesearchcallstep.py @@ -50,11 +50,11 @@ class GoogleSearchCallStepParam(TypedDict): r"""Google Search call step.""" + arguments: GoogleSearchCallArgumentsParam + r"""The arguments to pass to Google Search.""" id: str r"""Required. A unique ID for this specific tool call.""" type: Literal["google_search_call"] - arguments: NotRequired[GoogleSearchCallArgumentsParam] - r"""The arguments to pass to Google Search.""" search_type: NotRequired[GoogleSearchCallStepSearchType] r"""The type of search grounding enabled.""" signature: NotRequired[Union[str, Base64FileInput]] @@ -64,6 +64,9 @@ class GoogleSearchCallStepParam(TypedDict): class GoogleSearchCallStep(BaseModel): r"""Google Search call step.""" + arguments: GoogleSearchCallArguments + r"""The arguments to pass to Google Search.""" + id: str r"""Required. A unique ID for this specific tool call.""" @@ -75,9 +78,6 @@ class GoogleSearchCallStep(BaseModel): pydantic.Field(alias="type"), ] = "google_search_call" - arguments: Optional[GoogleSearchCallArguments] = None - r"""The arguments to pass to Google Search.""" - search_type: Optional[GoogleSearchCallStepSearchType] = None r"""The type of search grounding enabled.""" @@ -86,7 +86,7 @@ class GoogleSearchCallStep(BaseModel): @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["arguments", "search_type", "signature"]) + optional_fields = set(["search_type", "signature"]) serialized = handler(self) m = {} diff --git a/google/genai/_gaos/types/interactions/googlesearchresultstep.py b/google/genai/_gaos/types/interactions/googlesearchresultstep.py index fdc81d7f7..37d9526e2 100644 --- a/google/genai/_gaos/types/interactions/googlesearchresultstep.py +++ b/google/genai/_gaos/types/interactions/googlesearchresultstep.py @@ -30,11 +30,11 @@ class GoogleSearchResultStepParam(TypedDict): r"""Google Search result step.""" + result: List[GoogleSearchResultParam] + r"""Required. The results of the Google Search.""" call_id: str r"""Required. ID to match the ID from the function call block.""" type: Literal["google_search_result"] - result: NotRequired[List[GoogleSearchResultParam]] - r"""The results of the Google Search.""" is_error: NotRequired[bool] r"""Whether the Google Search resulted in an error.""" signature: NotRequired[Union[str, Base64FileInput]] @@ -44,6 +44,9 @@ class GoogleSearchResultStepParam(TypedDict): class GoogleSearchResultStep(BaseModel): r"""Google Search result step.""" + result: List[GoogleSearchResult] + r"""Required. The results of the Google Search.""" + call_id: str r"""Required. ID to match the ID from the function call block.""" @@ -55,9 +58,6 @@ class GoogleSearchResultStep(BaseModel): pydantic.Field(alias="type"), ] = "google_search_result" - result: Optional[List[GoogleSearchResult]] = None - r"""The results of the Google Search.""" - is_error: Optional[bool] = None r"""Whether the Google Search resulted in an error.""" @@ -66,7 +66,7 @@ class GoogleSearchResultStep(BaseModel): @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["result", "is_error", "signature"]) + optional_fields = set(["is_error", "signature"]) serialized = handler(self) m = {} diff --git a/google/genai/_gaos/types/interactions/groundingtoolcount.py b/google/genai/_gaos/types/interactions/groundingtoolcount.py index 613071b67..98a03347d 100644 --- a/google/genai/_gaos/types/interactions/groundingtoolcount.py +++ b/google/genai/_gaos/types/interactions/groundingtoolcount.py @@ -34,7 +34,7 @@ r"""The grounding tool type associated with the count.""" -class GroundingToolCountParam(TypedDict): +class GroundingToolCountTypedDict(TypedDict): r"""The number of grounding tool counts.""" type: NotRequired[GroundingToolCountType] diff --git a/google/genai/_gaos/types/interactions/imageconfig.py b/google/genai/_gaos/types/interactions/imageconfig.py index c121f30d8..172448f6f 100644 --- a/google/genai/_gaos/types/interactions/imageconfig.py +++ b/google/genai/_gaos/types/interactions/imageconfig.py @@ -20,7 +20,7 @@ from .. import BaseModel, UNSET_SENTINEL, UnrecognizedStr from pydantic import model_serializer from typing import Literal, Optional, Union -from typing_extensions import NotRequired, TypedDict +from typing_extensions import NotRequired, TypedDict, deprecated ImageConfigAspectRatio = Union[ @@ -55,6 +55,9 @@ ] +@deprecated( + "warning: ** DEPRECATED ** - This will be removed in a future release, please migrate away from it as soon as possible." +) class ImageConfigParam(TypedDict): r"""The configuration for image interaction.""" @@ -62,6 +65,9 @@ class ImageConfigParam(TypedDict): image_size: NotRequired[ImageConfigImageSize] +@deprecated( + "warning: ** DEPRECATED ** - This will be removed in a future release, please migrate away from it as soon as possible." +) class ImageConfig(BaseModel): r"""The configuration for image interaction.""" diff --git a/google/genai/_gaos/types/interactions/interaction.py b/google/genai/_gaos/types/interactions/interaction.py index 6943b0ecc..07f593ec5 100644 --- a/google/genai/_gaos/types/interactions/interaction.py +++ b/google/genai/_gaos/types/interactions/interaction.py @@ -36,7 +36,7 @@ from .servicetier import ServiceTier from .step import Step, StepParam from .tool import Tool, ToolParam -from .usage import Usage, UsageParam +from .usage import Usage, UsageTypedDict from .videocontent import VideoContent, VideoContentParam from .webhookconfig import WebhookConfig, WebhookConfigParam from functools import partial @@ -122,6 +122,7 @@ class UnknownInteractionAgentConfig(BaseModel): variants=_INTERACTION_AGENT_CONFIG_VARIANTS, unknown_cls=UnknownInteractionAgentConfig, union_name="InteractionAgentConfig", + lenient=True, ) ), ] @@ -151,7 +152,7 @@ class InteractionTypedDict(TypedDict): r"""System instruction for the interaction.""" tools: NotRequired[List[ToolParam]] r"""A list of tool declarations the model may call during interaction.""" - usage: NotRequired[UsageParam] + usage: NotRequired[UsageTypedDict] r"""Statistics on the interaction request's token usage.""" response_modalities: NotRequired[List[ResponseModality]] r"""The requested modalities of the response (TEXT, IMAGE, AUDIO).""" diff --git a/google/genai/_gaos/types/interactions/interactionsseevent.py b/google/genai/_gaos/types/interactions/interactionsseevent.py index a70cb9cf7..b02db3b51 100644 --- a/google/genai/_gaos/types/interactions/interactionsseevent.py +++ b/google/genai/_gaos/types/interactions/interactionsseevent.py @@ -95,6 +95,7 @@ class UnknownInteractionSSEEvent(BaseModel): variants=_INTERACTION_SSE_EVENT_VARIANTS, unknown_cls=UnknownInteractionSSEEvent, union_name="InteractionSSEEvent", + lenient=True, ) ), ] diff --git a/google/genai/_gaos/types/interactions/interactionsseeventinteraction.py b/google/genai/_gaos/types/interactions/interactionsseeventinteraction.py index 38cd6352f..5754c6f8d 100644 --- a/google/genai/_gaos/types/interactions/interactionsseeventinteraction.py +++ b/google/genai/_gaos/types/interactions/interactionsseeventinteraction.py @@ -20,7 +20,7 @@ from .. import BaseModel, UNSET_SENTINEL, UnrecognizedStr from .servicetier import ServiceTier from .step import Step, StepParam -from .usage import Usage, UsageParam +from .usage import Usage, UsageTypedDict from pydantic import model_serializer from typing import List, Literal, Optional, Union from typing_extensions import NotRequired, TypedDict @@ -62,7 +62,7 @@ class InteractionSseEventInteractionTypedDict(TypedDict): updated: NotRequired[str] r"""Output only. The time at which the response was last updated in ISO 8601 format.""" service_tier: NotRequired[ServiceTier] - usage: NotRequired[UsageParam] + usage: NotRequired[UsageTypedDict] r"""Statistics on the interaction request's token usage.""" steps: NotRequired[List[StepParam]] r"""Output only. The steps that make up the interaction, if included in this event.""" diff --git a/google/genai/_gaos/types/interactions/mcpservertoolresultstep.py b/google/genai/_gaos/types/interactions/mcpservertoolresultstep.py index c16181ad4..3eec383bc 100644 --- a/google/genai/_gaos/types/interactions/mcpservertoolresultstep.py +++ b/google/genai/_gaos/types/interactions/mcpservertoolresultstep.py @@ -57,13 +57,13 @@ class MCPServerToolResultStepParam(TypedDict): call_id: str r"""Required. ID to match the ID from the function call block.""" + result: MCPServerToolResultStepResultUnionParam + r"""The output from the MCP server call. Can be simple text or rich content.""" type: Literal["mcp_server_tool_result"] name: NotRequired[str] r"""Name of the tool which is called for this specific tool call.""" server_name: NotRequired[str] r"""The name of the used MCP server.""" - result: NotRequired[MCPServerToolResultStepResultUnionParam] - r"""The output from the MCP server call. Can be simple text or rich content.""" class MCPServerToolResultStep(BaseModel): @@ -72,6 +72,9 @@ class MCPServerToolResultStep(BaseModel): call_id: str r"""Required. ID to match the ID from the function call block.""" + result: MCPServerToolResultStepResultUnion + r"""The output from the MCP server call. Can be simple text or rich content.""" + type: Annotated[ Annotated[ Literal["mcp_server_tool_result"], @@ -86,12 +89,9 @@ class MCPServerToolResultStep(BaseModel): server_name: Optional[str] = None r"""The name of the used MCP server.""" - result: Optional[MCPServerToolResultStepResultUnion] = None - r"""The output from the MCP server call. Can be simple text or rich content.""" - @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["name", "server_name", "result"]) + optional_fields = set(["name", "server_name"]) serialized = handler(self) m = {} diff --git a/google/genai/_gaos/types/interactions/modalitytokens.py b/google/genai/_gaos/types/interactions/modalitytokens.py index ebb14ff57..558e8de19 100644 --- a/google/genai/_gaos/types/interactions/modalitytokens.py +++ b/google/genai/_gaos/types/interactions/modalitytokens.py @@ -24,7 +24,7 @@ from typing_extensions import NotRequired, TypedDict -class ModalityTokensParam(TypedDict): +class ModalityTokensTypedDict(TypedDict): r"""The token count for a single response modality.""" modality: NotRequired[ResponseModality] diff --git a/google/genai/_gaos/types/interactions/model.py b/google/genai/_gaos/types/interactions/model.py index 7273d7f2b..1201e3eef 100644 --- a/google/genai/_gaos/types/interactions/model.py +++ b/google/genai/_gaos/types/interactions/model.py @@ -59,6 +59,8 @@ "gemini-3.1-flash-lite-preview", # Gemini 3.1 Flash TTS: Powerful, low-latency speech generation. Enjoy natural outputs, steerable prompts, and new expressive audio tags for precise narration control. "gemini-3.1-flash-tts-preview", + # Our most intelligent model for sustained frontier performance in agentic and coding tasks. + "gemini-3.5-flash", # Our low-latency, music generation model optimized for high-fidelity audio clips and precise rhythmic control. "lyria-3-clip-preview", # Our advanced, full-song generative model with deep compositional understanding, optimized for precise structural control and complex transitions across diverse musical styles. diff --git a/google/genai/_gaos/types/interactions/step.py b/google/genai/_gaos/types/interactions/step.py index d8b1bf02e..721d31969 100644 --- a/google/genai/_gaos/types/interactions/step.py +++ b/google/genai/_gaos/types/interactions/step.py @@ -133,6 +133,7 @@ class UnknownStep(BaseModel): variants=_STEP_VARIANTS, unknown_cls=UnknownStep, union_name="Step", + lenient=True, ) ), ] diff --git a/google/genai/_gaos/types/interactions/stepdeltadata.py b/google/genai/_gaos/types/interactions/stepdeltadata.py index 884327cfa..6f64d7062 100644 --- a/google/genai/_gaos/types/interactions/stepdeltadata.py +++ b/google/genai/_gaos/types/interactions/stepdeltadata.py @@ -161,6 +161,7 @@ class UnknownStepDeltaData(BaseModel): variants=_STEP_DELTA_DATA_VARIANTS, unknown_cls=UnknownStepDeltaData, union_name="StepDeltaData", + lenient=True, ) ), ] diff --git a/google/genai/_gaos/types/interactions/stepdeltametadata.py b/google/genai/_gaos/types/interactions/stepdeltametadata.py index 44ae3bf0e..166d586b2 100644 --- a/google/genai/_gaos/types/interactions/stepdeltametadata.py +++ b/google/genai/_gaos/types/interactions/stepdeltametadata.py @@ -18,7 +18,7 @@ from __future__ import annotations from .. import BaseModel, UNSET_SENTINEL -from .usage import Usage, UsageParam +from .usage import Usage, UsageTypedDict from pydantic import model_serializer from typing import Optional from typing_extensions import NotRequired, TypedDict @@ -27,7 +27,7 @@ class StepDeltaMetadataTypedDict(TypedDict): r"""Optional metadata accompanying ANY streamed event.""" - total_usage: NotRequired[UsageParam] + total_usage: NotRequired[UsageTypedDict] r"""Statistics on the interaction request's token usage.""" diff --git a/google/genai/_gaos/types/interactions/streammetadata.py b/google/genai/_gaos/types/interactions/streammetadata.py index 8e89b0ba1..5b31e30a6 100644 --- a/google/genai/_gaos/types/interactions/streammetadata.py +++ b/google/genai/_gaos/types/interactions/streammetadata.py @@ -18,14 +18,14 @@ from __future__ import annotations from .. import BaseModel, UNSET_SENTINEL -from .usage import Usage, UsageParam +from .usage import Usage, UsageTypedDict from pydantic import model_serializer from typing import Optional from typing_extensions import NotRequired, TypedDict class StreamMetadataTypedDict(TypedDict): - total_usage: NotRequired[UsageParam] + total_usage: NotRequired[UsageTypedDict] r"""Statistics on the interaction request's token usage.""" diff --git a/google/genai/_gaos/types/interactions/thoughtsummarycontent.py b/google/genai/_gaos/types/interactions/thoughtsummarycontent.py index 91c911038..4bc68abe0 100644 --- a/google/genai/_gaos/types/interactions/thoughtsummarycontent.py +++ b/google/genai/_gaos/types/interactions/thoughtsummarycontent.py @@ -58,6 +58,7 @@ class UnknownThoughtSummaryContent(BaseModel): variants=_THOUGHT_SUMMARY_CONTENT_VARIANTS, unknown_cls=UnknownThoughtSummaryContent, union_name="ThoughtSummaryContent", + lenient=True, ) ), ] diff --git a/google/genai/_gaos/types/interactions/tool.py b/google/genai/_gaos/types/interactions/tool.py index d23322bfb..f6c369066 100644 --- a/google/genai/_gaos/types/interactions/tool.py +++ b/google/genai/_gaos/types/interactions/tool.py @@ -95,6 +95,7 @@ class UnknownTool(BaseModel): variants=_TOOL_VARIANTS, unknown_cls=UnknownTool, union_name="Tool", + lenient=True, ) ), ] diff --git a/google/genai/_gaos/types/interactions/urlcontextcallstep.py b/google/genai/_gaos/types/interactions/urlcontextcallstep.py index c563f233f..5c6a68648 100644 --- a/google/genai/_gaos/types/interactions/urlcontextcallstep.py +++ b/google/genai/_gaos/types/interactions/urlcontextcallstep.py @@ -61,11 +61,11 @@ class URLContextCallStepParam(TypedDict): id: str r"""Required. A unique ID for this specific tool call.""" + arguments: ArgumentsParam + r"""The arguments to pass to the URL context.""" type: Literal["url_context_call"] signature: NotRequired[Union[str, Base64FileInput]] r"""A signature hash for backend validation.""" - arguments: NotRequired[ArgumentsParam] - r"""The arguments to pass to the URL context.""" class URLContextCallStep(BaseModel): @@ -74,6 +74,9 @@ class URLContextCallStep(BaseModel): id: str r"""Required. A unique ID for this specific tool call.""" + arguments: Arguments + r"""The arguments to pass to the URL context.""" + type: Annotated[ Annotated[ Literal["url_context_call"], @@ -85,12 +88,9 @@ class URLContextCallStep(BaseModel): signature: Optional[Base64EncodedString] = None r"""A signature hash for backend validation.""" - arguments: Optional[Arguments] = None - r"""The arguments to pass to the URL context.""" - @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["signature", "arguments"]) + optional_fields = set(["signature"]) serialized = handler(self) m = {} diff --git a/google/genai/_gaos/types/interactions/urlcontextresultstep.py b/google/genai/_gaos/types/interactions/urlcontextresultstep.py index 4ce765fa2..af91ce439 100644 --- a/google/genai/_gaos/types/interactions/urlcontextresultstep.py +++ b/google/genai/_gaos/types/interactions/urlcontextresultstep.py @@ -30,11 +30,11 @@ class URLContextResultStepParam(TypedDict): r"""URL context result step.""" + result: List[URLContextResultParam] + r"""Required. The results of the URL context.""" call_id: str r"""Required. ID to match the ID from the function call block.""" type: Literal["url_context_result"] - result: NotRequired[List[URLContextResultParam]] - r"""The results of the URL context.""" is_error: NotRequired[bool] r"""Whether the URL context resulted in an error.""" signature: NotRequired[Union[str, Base64FileInput]] @@ -44,6 +44,9 @@ class URLContextResultStepParam(TypedDict): class URLContextResultStep(BaseModel): r"""URL context result step.""" + result: List[URLContextResult] + r"""Required. The results of the URL context.""" + call_id: str r"""Required. ID to match the ID from the function call block.""" @@ -55,9 +58,6 @@ class URLContextResultStep(BaseModel): pydantic.Field(alias="type"), ] = "url_context_result" - result: Optional[List[URLContextResult]] = None - r"""The results of the URL context.""" - is_error: Optional[bool] = None r"""Whether the URL context resulted in an error.""" @@ -66,7 +66,7 @@ class URLContextResultStep(BaseModel): @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["result", "is_error", "signature"]) + optional_fields = set(["is_error", "signature"]) serialized = handler(self) m = {} diff --git a/google/genai/_gaos/types/interactions/usage.py b/google/genai/_gaos/types/interactions/usage.py index e72499830..342409486 100644 --- a/google/genai/_gaos/types/interactions/usage.py +++ b/google/genai/_gaos/types/interactions/usage.py @@ -18,31 +18,31 @@ from __future__ import annotations from .. import BaseModel, UNSET_SENTINEL -from .groundingtoolcount import GroundingToolCount, GroundingToolCountParam -from .modalitytokens import ModalityTokens, ModalityTokensParam +from .groundingtoolcount import GroundingToolCount, GroundingToolCountTypedDict +from .modalitytokens import ModalityTokens, ModalityTokensTypedDict from pydantic import model_serializer from typing import List, Optional from typing_extensions import NotRequired, TypedDict -class UsageParam(TypedDict): +class UsageTypedDict(TypedDict): r"""Statistics on the interaction request's token usage.""" total_input_tokens: NotRequired[int] r"""Number of tokens in the prompt (context).""" - input_tokens_by_modality: NotRequired[List[ModalityTokensParam]] + input_tokens_by_modality: NotRequired[List[ModalityTokensTypedDict]] r"""A breakdown of input token usage by modality.""" total_cached_tokens: NotRequired[int] r"""Number of tokens in the cached part of the prompt (the cached content).""" - cached_tokens_by_modality: NotRequired[List[ModalityTokensParam]] + cached_tokens_by_modality: NotRequired[List[ModalityTokensTypedDict]] r"""A breakdown of cached token usage by modality.""" total_output_tokens: NotRequired[int] r"""Total number of tokens across all the generated responses.""" - output_tokens_by_modality: NotRequired[List[ModalityTokensParam]] + output_tokens_by_modality: NotRequired[List[ModalityTokensTypedDict]] r"""A breakdown of output token usage by modality.""" total_tool_use_tokens: NotRequired[int] r"""Number of tokens present in tool-use prompt(s).""" - tool_use_tokens_by_modality: NotRequired[List[ModalityTokensParam]] + tool_use_tokens_by_modality: NotRequired[List[ModalityTokensTypedDict]] r"""A breakdown of tool-use token usage by modality.""" total_thought_tokens: NotRequired[int] r"""Number of tokens of thoughts for thinking models.""" @@ -50,7 +50,7 @@ class UsageParam(TypedDict): r"""Total token count for the interaction request (prompt + responses + other internal tokens). """ - grounding_tool_count: NotRequired[List[GroundingToolCountParam]] + grounding_tool_count: NotRequired[List[GroundingToolCountTypedDict]] r"""Grounding tool count.""" diff --git a/google/genai/_gaos/utils/response_helpers.py b/google/genai/_gaos/utils/response_helpers.py index b7837cbbe..4f1298b99 100644 --- a/google/genai/_gaos/utils/response_helpers.py +++ b/google/genai/_gaos/utils/response_helpers.py @@ -32,6 +32,7 @@ Iterator, Literal, Mapping, + NoReturn, Optional, ParamSpec, Tuple, @@ -49,6 +50,7 @@ from typing_extensions import TypeAliasType from .._version import __response_mode_header__ +from .._hooks.types import AfterParseErrorContext from .eventstreaming import Stream, AsyncStream from .unmarshal_json_response import unmarshal_json_response @@ -59,17 +61,55 @@ AsyncResponseT = TypeVar("AsyncResponseT", bound="_AsyncAPIResponseBase[Any]") ResponseMode = Literal["buffered", "raw_stream", "event_stream"] +ResponseModeHeader = Literal["parsed", "raw", "streaming"] _DEFAULT_PARSE_KEY = object() -_PARSED_RESPONSE_MODE = "parsed" -_RAW_RESPONSE_MODE = "raw" -_STREAMING_RESPONSE_MODE = "streaming" +_PARSED_RESPONSE_MODE: ResponseModeHeader = "parsed" +_RAW_RESPONSE_MODE: ResponseModeHeader = "raw" +_STREAMING_RESPONSE_MODE: ResponseModeHeader = "streaming" # Internal marker header set by the raw/streaming response helpers and removed # before the request is sent, so it never reaches the wire. _RESPONSE_MODE_HEADER = __response_mode_header__.lower() +def raise_parse_error( + hooks: Optional[Any], + hook_ctx: Optional[AfterParseErrorContext], + response: httpx.Response, + exc: Exception, +) -> NoReturn: + """Raise the hook-substituted parse error. + + When hooks return the same exception instance, preserve its existing cause + chain instead of overwriting it with `raise exc from exc`. + """ + parse_error = exc + if hooks is not None and hook_ctx is not None: + parse_error = hooks.after_parse_error(hook_ctx, response, exc) + if parse_error is exc: + raise exc.with_traceback(exc.__traceback__) + raise parse_error from exc + + +async def raise_parse_error_async( + async_hooks: Optional[Any], + hooks: Optional[Any], + hook_ctx: Optional[AfterParseErrorContext], + response: httpx.Response, + exc: Exception, +) -> NoReturn: + """Raise the async hook-substituted parse error.""" + parse_error = exc + if async_hooks is not None and hook_ctx is not None: + parse_error = await async_hooks.after_parse_error(hook_ctx, response, exc) + elif hooks is not None and hook_ctx is not None: + parse_error = hooks.after_parse_error(hook_ctx, response, exc) + if parse_error is exc: + raise exc.with_traceback(exc.__traceback__) + raise parse_error from exc + + def _unwrap_type(t: Any) -> Any: """Strip ``TypeAliasType('Name', T)`` and ``Annotated[T, ...]`` wrappers.""" if isinstance(t, TypeAliasType): @@ -186,13 +226,22 @@ def __init__( parser: Callable[[httpx.Response], T], mode: ResponseMode = "buffered", client_ref: Optional[object] = None, + hook_ctx: Optional[Any] = None, + hooks: Optional[Any] = None, ) -> None: self.http_response = raw self._parser = parser self._mode = mode self._client_ref = client_ref + self._hook_ctx = hook_ctx + self._hooks = hooks self._parsed_by_type: dict[Any, Any] = {} + def _raise_parse_error(self, exc: Exception) -> NoReturn: + if self._hook_ctx is None: + raise_parse_error(None, None, self.http_response, exc) + raise_parse_error(self._hooks, self._hook_ctx, self.http_response, exc) + @property def headers(self) -> httpx.Headers: return self.http_response.headers @@ -255,15 +304,18 @@ def _parse( # raw_stream / event_stream wrappers leave the body unread so the # caller can iter_bytes/iter_lines; buffered mode eagerly reads. body = None - if to is None: - if mode == "buffered": - self.read() - self._parsed_by_type[parse_key] = self._parser(self.http_response) - else: - body = self.text() - self._parsed_by_type[parse_key] = unmarshal_json_response( - to, self.http_response, body - ) + try: + if to is None: + if mode == "buffered": + self.read() + self._parsed_by_type[parse_key] = self._parser(self.http_response) + else: + body = self.text() + self._parsed_by_type[parse_key] = unmarshal_json_response( + to, self.http_response, body, validate=False + ) + except Exception as exc: + self._raise_parse_error(exc) return self._parsed_by_type[parse_key] def read(self) -> bytes: @@ -395,13 +447,26 @@ def __init__( parser: Callable[[httpx.Response], Awaitable[T]], mode: ResponseMode = "buffered", client_ref: Optional[object] = None, + hook_ctx: Optional[Any] = None, + hooks: Optional[Any] = None, + async_hooks: Optional[Any] = None, ) -> None: self.http_response = raw self._parser = parser self._mode = mode self._client_ref = client_ref + self._hook_ctx = hook_ctx + self._hooks = hooks + self._async_hooks = async_hooks self._parsed_by_type: dict[Any, Any] = {} + async def _raise_parse_error(self, exc: Exception) -> NoReturn: + if self._hook_ctx is None: + raise_parse_error(None, None, self.http_response, exc) + await raise_parse_error_async( + self._async_hooks, self._hooks, self._hook_ctx, self.http_response, exc + ) + @property def headers(self) -> httpx.Headers: return self.http_response.headers @@ -464,15 +529,20 @@ async def _parse( # raw_stream / event_stream wrappers leave the body unread so the # caller can iter_bytes/iter_lines; buffered mode eagerly reads. body = None - if to is None: - if mode == "buffered": - await self.read() - self._parsed_by_type[parse_key] = await self._parser(self.http_response) - else: - body = await self.text() - self._parsed_by_type[parse_key] = unmarshal_json_response( - to, self.http_response, body - ) + try: + if to is None: + if mode == "buffered": + await self.read() + self._parsed_by_type[parse_key] = await self._parser( + self.http_response + ) + else: + body = await self.text() + self._parsed_by_type[parse_key] = unmarshal_json_response( + to, self.http_response, body, validate=False + ) + except Exception as exc: + await self._raise_parse_error(exc) return self._parsed_by_type[parse_key] async def read(self) -> bytes: @@ -616,17 +686,17 @@ def _set_response_mode(kwargs: Dict[str, Any], mode: str, header_kwarg: str) -> def consume_response_mode( http_headers: Optional[Mapping[str, str]], -) -> Tuple[str, Optional[Mapping[str, str]]]: +) -> Tuple[ResponseModeHeader, Optional[Mapping[str, str]]]: """Read and remove the marker header, returning the response mode and the remaining headers.""" if http_headers is None: return _PARSED_RESPONSE_MODE, None - mode = _PARSED_RESPONSE_MODE + mode: ResponseModeHeader = _PARSED_RESPONSE_MODE remaining: Dict[str, str] = {} for name, value in http_headers.items(): if name.lower() == _RESPONSE_MODE_HEADER: if value in (_RAW_RESPONSE_MODE, _STREAMING_RESPONSE_MODE): - mode = value + mode = cast(ResponseModeHeader, value) else: remaining[name] = value return mode, (remaining or None) diff --git a/google/genai/_gaos/utils/retries.py b/google/genai/_gaos/utils/retries.py index a07bb7955..152733c87 100644 --- a/google/genai/_gaos/utils/retries.py +++ b/google/genai/_gaos/utils/retries.py @@ -27,10 +27,13 @@ class BackoffStrategy: + """Exponential backoff strategy configuration.""" + initial_interval: int max_interval: int exponent: float max_elapsed_time: int + jitter_ms: Optional[int] def __init__( self, @@ -38,24 +41,68 @@ def __init__( max_interval: int, exponent: float, max_elapsed_time: int, + jitter_ms: Optional[int] = None, ): + """Initialize a backoff strategy. + + Args: + initial_interval: Initial retry interval in milliseconds. + max_interval: Maximum retry interval in milliseconds. + exponent: Base of the exponential backoff; the interval grows as + ``initial_interval * exponent ** retries``. + max_elapsed_time: Maximum total elapsed time in milliseconds. Only used + by the ``"backoff"`` strategy; ignored by ``"attempt-count-backoff"``. + jitter_ms: Additive jitter bound in milliseconds. When set, adds a random + value in ``[0, jitter_ms]`` to each computed backoff interval (default -25% + for attempt-count, +[0, 1s] for backoff). + + Note: + When a response carries a ``Retry-After`` or ``retry-after-ms`` header, + that delay is used as-is and the sleep-shaping parameters + (``initial_interval``, ``max_interval``, ``exponent``, ``jitter_ms``) are + ignored for that attempt. + """ + if jitter_ms is not None and jitter_ms < 0: + raise ValueError("jitter_ms must be >= 0") self.initial_interval = initial_interval self.max_interval = max_interval self.exponent = exponent self.max_elapsed_time = max_elapsed_time + self.jitter_ms = jitter_ms class RetryConfig: + """Runtime retry configuration.""" + strategy: str - backoff: BackoffStrategy + backoff: Optional[BackoffStrategy] retry_connection_errors: bool + max_retries: Optional[int] + status_codes_override: Optional[List[str]] def __init__( - self, strategy: str, backoff: BackoffStrategy, retry_connection_errors: bool + self, + strategy: str, + backoff: Optional[BackoffStrategy], + retry_connection_errors: bool, + max_retries: Optional[int] = None, + status_codes_override: Optional[List[str]] = None, ): + """Initialize a retry configuration. + + Args: + strategy: Retry strategy: ``"none"``, ``"backoff"`` or ``"attempt-count-backoff"``. + backoff: Backoff parameters; falls back to a built-in default when ``None``. + retry_connection_errors: Whether to also retry transport-level connection errors. + max_retries: Maximum number of retries for the ``"attempt-count-backoff"`` strategy. + status_codes_override: Retryable HTTP status codes that take precedence over the + per-operation defaults when non-empty. + """ self.strategy = strategy self.backoff = backoff self.retry_connection_errors = retry_connection_errors + self.max_retries = max_retries + self.status_codes_override = status_codes_override class Retries: @@ -64,7 +111,7 @@ class Retries: def __init__(self, config: RetryConfig, status_codes: List[str]): self.config = config - self.status_codes = status_codes + self.status_codes = config.status_codes_override or status_codes class TemporaryError(Exception): @@ -109,12 +156,29 @@ def _parse_retry_after_header(response: httpx.Response) -> Optional[int]: return None +def _parse_retry_after_ms_header(response: httpx.Response) -> Optional[int]: + retry_after_ms_header = response.headers.get("retry-after-ms") + if not retry_after_ms_header: + return None + + try: + milliseconds = float(retry_after_ms_header) + if milliseconds >= 0: + return round(milliseconds) + except (OverflowError, ValueError): + pass + + return None + + def _get_sleep_interval( exception: Exception, initial_interval: int, max_interval: int, exponent: float, retries: int, + attempt_count_backoff: bool = False, + jitter_ms: Optional[int] = None, ) -> float: """Get sleep interval for retry with exponential backoff. @@ -124,6 +188,8 @@ def _get_sleep_interval( max_interval: Maximum retry interval in milliseconds. exponent: Base for exponential backoff calculation. retries: Current retry attempt count. + attempt_count_backoff: Use multiplicative jitter when ``jitter_ms`` is unset. + jitter_ms: Additive jitter bound in ms; see ``BackoffStrategy.jitter_ms``. Returns: Sleep interval in seconds. @@ -135,18 +201,25 @@ def _get_sleep_interval( ): return exception.retry_after / 1000 - sleep = (initial_interval / 1000) * exponent**retries + random.uniform(0, 1) + sleep = (initial_interval / 1000) * exponent**retries + if jitter_ms is not None: + sleep += random.uniform(0, jitter_ms / 1000) + elif attempt_count_backoff: + sleep = sleep * (1 - random.random() * 0.25) + else: + sleep += random.uniform(0, 1) return min(sleep, max_interval / 1000) def retry(func, retries: Retries): - if retries.config.strategy == "backoff": + if retries.config.strategy in ("backoff", "attempt-count-backoff"): - def do_request() -> httpx.Response: + def do_request(attempt: int) -> httpx.Response: res: httpx.Response try: - res = func() + res = func(attempt) + should_retry = False for code in retries.status_codes: if "X" in code.upper(): code_range = int(code[0]) @@ -154,12 +227,15 @@ def do_request() -> httpx.Response: status_major = res.status_code / 100 if code_range <= status_major < code_range + 1: - raise TemporaryError(res) + should_retry = True else: parsed_code = int(code) if res.status_code == parsed_code: - raise TemporaryError(res) + should_retry = True + + if should_retry: + raise TemporaryError(res) except (httpx.NetworkError, httpx.TimeoutException) as exception: if retries.config.retry_connection_errors: raise @@ -172,25 +248,38 @@ def do_request() -> httpx.Response: return res + backoff = retries.config.backoff or BackoffStrategy(500, 60000, 1.5, 3600000) + if retries.config.strategy == "attempt-count-backoff": + return retry_with_attempt_count_backoff( + do_request, + backoff.initial_interval, + backoff.max_interval, + backoff.exponent, + retries.config.max_retries or 0, + backoff.jitter_ms, + ) + return retry_with_backoff( do_request, - retries.config.backoff.initial_interval, - retries.config.backoff.max_interval, - retries.config.backoff.exponent, - retries.config.backoff.max_elapsed_time, + backoff.initial_interval, + backoff.max_interval, + backoff.exponent, + backoff.max_elapsed_time, + backoff.jitter_ms, ) - return func() + return func(0) async def retry_async(func, retries: Retries): - if retries.config.strategy == "backoff": + if retries.config.strategy in ("backoff", "attempt-count-backoff"): - async def do_request() -> httpx.Response: + async def do_request(attempt: int) -> httpx.Response: res: httpx.Response try: - res = await func() + res = await func(attempt) + should_retry = False for code in retries.status_codes: if "X" in code.upper(): code_range = int(code[0]) @@ -198,12 +287,15 @@ async def do_request() -> httpx.Response: status_major = res.status_code / 100 if code_range <= status_major < code_range + 1: - raise TemporaryError(res) + should_retry = True else: parsed_code = int(code) if res.status_code == parsed_code: - raise TemporaryError(res) + should_retry = True + + if should_retry: + raise TemporaryError(res) except (httpx.NetworkError, httpx.TimeoutException) as exception: if retries.config.retry_connection_errors: raise @@ -216,15 +308,27 @@ async def do_request() -> httpx.Response: return res + backoff = retries.config.backoff or BackoffStrategy(500, 60000, 1.5, 3600000) + if retries.config.strategy == "attempt-count-backoff": + return await retry_with_attempt_count_backoff_async( + do_request, + backoff.initial_interval, + backoff.max_interval, + backoff.exponent, + retries.config.max_retries or 0, + backoff.jitter_ms, + ) + return await retry_with_backoff_async( do_request, - retries.config.backoff.initial_interval, - retries.config.backoff.max_interval, - retries.config.backoff.exponent, - retries.config.backoff.max_elapsed_time, + backoff.initial_interval, + backoff.max_interval, + backoff.exponent, + backoff.max_elapsed_time, + backoff.jitter_ms, ) - return await func() + return await func(0) def retry_with_backoff( @@ -233,13 +337,14 @@ def retry_with_backoff( max_interval=60000, exponent=1.5, max_elapsed_time=3600000, + jitter_ms=None, ): start = round(time.time() * 1000) retries = 0 while True: try: - return func() + return func(retries) except PermanentError as exception: raise exception.inner except Exception as exception: # pylint: disable=broad-exception-caught @@ -250,8 +355,56 @@ def retry_with_backoff( raise + if isinstance(exception, TemporaryError): + retry_after_ms = _parse_retry_after_ms_header(exception.response) + if retry_after_ms is not None: + exception.retry_after = retry_after_ms sleep = _get_sleep_interval( - exception, initial_interval, max_interval, exponent, retries + exception, + initial_interval, + max_interval, + exponent, + retries, + jitter_ms=jitter_ms, + ) + time.sleep(sleep) + retries += 1 + + +def retry_with_attempt_count_backoff( + func, + initial_interval=500, + max_interval=60000, + exponent=1.5, + max_retries=0, + jitter_ms=None, +): + retries = 0 + + while True: + try: + return func(retries) + except PermanentError as exception: + raise exception.inner + except Exception as exception: # pylint: disable=broad-exception-caught + if retries >= max_retries: + if isinstance(exception, TemporaryError): + return exception.response + + raise + + if isinstance(exception, TemporaryError): + retry_after_ms = _parse_retry_after_ms_header(exception.response) + if retry_after_ms is not None: + exception.retry_after = retry_after_ms + sleep = _get_sleep_interval( + exception, + initial_interval, + max_interval, + exponent, + retries, + attempt_count_backoff=True, + jitter_ms=jitter_ms, ) time.sleep(sleep) retries += 1 @@ -263,13 +416,14 @@ async def retry_with_backoff_async( max_interval=60000, exponent=1.5, max_elapsed_time=3600000, + jitter_ms=None, ): start = round(time.time() * 1000) retries = 0 while True: try: - return await func() + return await func(retries) except PermanentError as exception: raise exception.inner except Exception as exception: # pylint: disable=broad-exception-caught @@ -280,8 +434,56 @@ async def retry_with_backoff_async( raise + if isinstance(exception, TemporaryError): + retry_after_ms = _parse_retry_after_ms_header(exception.response) + if retry_after_ms is not None: + exception.retry_after = retry_after_ms + sleep = _get_sleep_interval( + exception, + initial_interval, + max_interval, + exponent, + retries, + jitter_ms=jitter_ms, + ) + await asyncio.sleep(sleep) + retries += 1 + + +async def retry_with_attempt_count_backoff_async( + func, + initial_interval=500, + max_interval=60000, + exponent=1.5, + max_retries=0, + jitter_ms=None, +): + retries = 0 + + while True: + try: + return await func(retries) + except PermanentError as exception: + raise exception.inner + except Exception as exception: # pylint: disable=broad-exception-caught + if retries >= max_retries: + if isinstance(exception, TemporaryError): + return exception.response + + raise + + if isinstance(exception, TemporaryError): + retry_after_ms = _parse_retry_after_ms_header(exception.response) + if retry_after_ms is not None: + exception.retry_after = retry_after_ms sleep = _get_sleep_interval( - exception, initial_interval, max_interval, exponent, retries + exception, + initial_interval, + max_interval, + exponent, + retries, + attempt_count_backoff=True, + jitter_ms=jitter_ms, ) await asyncio.sleep(sleep) retries += 1 diff --git a/google/genai/_gaos/utils/serializers.py b/google/genai/_gaos/utils/serializers.py index 3a7b81c5b..5946bd907 100644 --- a/google/genai/_gaos/utils/serializers.py +++ b/google/genai/_gaos/utils/serializers.py @@ -147,6 +147,121 @@ def unmarshal(val, typ: Any, coerce_iterables: bool = True) -> Any: return m.body # type: ignore +_CONSTRUCT_UNVALIDATED_MAX_DEPTH = 64 + + +def construct_unvalidated(value: Any, typ: Any, _depth: int = 0) -> Any: + try: + return unmarshal(value, typ, coerce_iterables=True) + except Exception: + pass + return _construct_lenient(value, typ, _depth) + + +def _construct_lenient(value: Any, typ: Any, depth: int) -> Any: + if depth > _CONSTRUCT_UNVALIDATED_MAX_DEPTH: + return value + + typ = _resolve_type_alias(typ) + origin = get_origin(typ) + + if _is_annotated_type(origin): + args = get_args(typ) + return _construct_lenient(value, args[0], depth) if args else value + + if is_union(origin): + variants = [arg for arg in get_args(typ) if arg is not type(None)] + for variant in variants: + try: + return unmarshal(value, variant, coerce_iterables=True) + except Exception: + continue + last_err = None + for variant in variants: + try: + return _construct_lenient(value, variant, depth) + except Exception as e: # noqa: BLE001 + last_err = e + continue + if value is None and type(None) in get_args(typ): + return None + if last_err is not None: + raise last_err + return value + + if _is_list_type(typ): + if not isinstance(value, list): + raise TypeError(f"expected list for {typ!r}, got {type(value)!r}") + item_type = get_args(typ)[0] if get_args(typ) else Any + return [construct_unvalidated(item, item_type, depth + 1) for item in value] + + if _is_mapping_type(typ): + if not isinstance(value, Mapping): + raise TypeError(f"expected mapping for {typ!r}, got {type(value)!r}") + value_type = get_args(typ)[1] if len(get_args(typ)) > 1 else Any + return { + key: construct_unvalidated(item, value_type, depth + 1) + for key, item in value.items() + } + + if _is_pydantic_model_type(typ): + if not isinstance(value, Mapping): + raise TypeError(f"expected mapping for {typ!r}, got {type(value)!r}") + return _construct_model_lenient(value, typ, depth) + + return value + + +def _construct_model_lenient(value: Mapping, typ: Any, depth: int) -> Any: + fields: Dict[str, Any] = {} + consumed = set() + for name, field in typ.model_fields.items(): + wire_key = None + if field.alias is not None and field.alias in value: + wire_key = field.alias + elif name in value: + wire_key = name + + annotation = field.rebuild_annotation() + if wire_key is not None: + consumed.add(wire_key) + fields[name] = construct_unvalidated(value[wire_key], annotation, depth + 1) + elif field.is_required(): + fields[name] = _default_for_required(annotation, depth) + else: + fields[name] = field.get_default(call_default_factory=True) + + for key, item in value.items(): + if key not in consumed and key not in fields: + fields[key] = item + + return typ.model_construct(**fields) + + +def _default_for_required(typ: Any, depth: int) -> Any: + typ = _resolve_type_alias(typ) + origin = get_origin(typ) + + if _is_annotated_type(origin): + args = get_args(typ) + return _default_for_required(args[0], depth) if args else None + + if is_union(origin): + if type(None) in get_args(typ): + return None + for variant in get_args(typ): + try: + return _default_for_required(variant, depth) + except Exception: # noqa: BLE001 + continue + return None + + if depth <= _CONSTRUCT_UNVALIDATED_MAX_DEPTH and _is_pydantic_model_type(typ): + return _construct_model_lenient({}, typ, depth + 1) + + return None + + def marshal_json(val, typ): if is_nullable(typ) and val is None: return "null" diff --git a/google/genai/_gaos/utils/unions.py b/google/genai/_gaos/utils/unions.py index e42cf3f97..32f982ccf 100644 --- a/google/genai/_gaos/utils/unions.py +++ b/google/genai/_gaos/utils/unions.py @@ -19,6 +19,7 @@ from typing import Any from pydantic import BaseModel, TypeAdapter, ValidationError +from .serializers import construct_unvalidated def parse_open_union( @@ -28,6 +29,7 @@ def parse_open_union( variants: dict[str, Any], unknown_cls: type, union_name: str, + lenient: bool = False, ) -> Any: """Parse an open discriminated union value with forward-compatibility. @@ -53,5 +55,10 @@ def parse_open_union( return variant_cls.model_validate(v) return TypeAdapter(variant_cls).validate_python(v) except ValidationError: + if lenient: + try: + return construct_unvalidated(v, variant_cls) + except Exception: + pass return unknown_cls(raw=v) return unknown_cls(raw=v) diff --git a/google/genai/_gaos/utils/unmarshal_json_response.py b/google/genai/_gaos/utils/unmarshal_json_response.py index b3653f685..9c133c36e 100644 --- a/google/genai/_gaos/utils/unmarshal_json_response.py +++ b/google/genai/_gaos/utils/unmarshal_json_response.py @@ -20,32 +20,44 @@ import httpx -from .serializers import unmarshal_json +from .serializers import unmarshal_json, construct_unvalidated from .. import errors +import json T = TypeVar("T") @overload def unmarshal_json_response( - typ: Type[T], http_res: httpx.Response, body: Optional[str] = None + typ: Type[T], + http_res: httpx.Response, + body: Optional[str] = None, + validate: bool = True, ) -> T: ... @overload def unmarshal_json_response( - typ: Any, http_res: httpx.Response, body: Optional[str] = None + typ: Any, + http_res: httpx.Response, + body: Optional[str] = None, + validate: bool = True, ) -> Any: ... def unmarshal_json_response( - typ: Any, http_res: httpx.Response, body: Optional[str] = None + typ: Any, + http_res: httpx.Response, + body: Optional[str] = None, + validate: bool = True, ) -> Any: if body is None: body = http_res.text try: return unmarshal_json(body, typ) except Exception as e: + if not validate: + return construct_unvalidated(json.loads(body), typ) raise errors.ResponseValidationError( "Response validation failed", http_res, diff --git a/google/genai/_gaos/webhooks.py b/google/genai/_gaos/webhooks.py index 64aa559b8..7931a6e03 100644 --- a/google/genai/_gaos/webhooks.py +++ b/google/genai/_gaos/webhooks.py @@ -17,7 +17,7 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" from . import errors, models, types, utils -from ._hooks import HookContext +from ._hooks import AfterParseErrorContext, HookContext, ResponseContext from .basesdk import AsyncBaseSDK, BaseSDK from .types import OptionalNullable, UNSET, interactions, webhooks from .types.webhooks import ( @@ -134,7 +134,10 @@ def create( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -153,20 +156,24 @@ def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(webhooks.Webhook, http_res) + return unmarshal_json_response( + webhooks.Webhook, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = self.do_request( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="CreateWebhook", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="CreateWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="sync"), + ) + http_res = self.do_request( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -185,9 +192,19 @@ def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), ), ) - return _speakeasy_parse_response(http_res) + try: + return _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + response_helpers.raise_parse_error( + self.sdk_configuration.__dict__["_hooks"], + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) def list( self, @@ -260,7 +277,10 @@ def list( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -279,20 +299,24 @@ def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(webhooks.WebhookListResponse, http_res) + return unmarshal_json_response( + webhooks.WebhookListResponse, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = self.do_request( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="ListWebhooks", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="ListWebhooks", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="sync"), + ) + http_res = self.do_request( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -311,9 +335,19 @@ def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), ), ) - return _speakeasy_parse_response(http_res) + try: + return _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + response_helpers.raise_parse_error( + self.sdk_configuration.__dict__["_hooks"], + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) def get( self, @@ -380,7 +414,10 @@ def get( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -399,20 +436,24 @@ def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(webhooks.Webhook, http_res) + return unmarshal_json_response( + webhooks.Webhook, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = self.do_request( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="GetWebhook", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="GetWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="sync"), + ) + http_res = self.do_request( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -431,9 +472,19 @@ def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), ), ) - return _speakeasy_parse_response(http_res) + try: + return _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + response_helpers.raise_parse_error( + self.sdk_configuration.__dict__["_hooks"], + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) def update( self, @@ -540,7 +591,10 @@ def update( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -559,20 +613,24 @@ def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(webhooks.Webhook, http_res) + return unmarshal_json_response( + webhooks.Webhook, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = self.do_request( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="UpdateWebhook", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="UpdateWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="sync"), + ) + http_res = self.do_request( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -591,9 +649,19 @@ def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), ), ) - return _speakeasy_parse_response(http_res) + try: + return _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + response_helpers.raise_parse_error( + self.sdk_configuration.__dict__["_hooks"], + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) def delete( self, @@ -661,7 +729,10 @@ def delete( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -680,20 +751,24 @@ def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(interactions.Empty, http_res) + return unmarshal_json_response( + interactions.Empty, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = self.do_request( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="DeleteWebhook", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="DeleteWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="sync"), + ) + http_res = self.do_request( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -712,9 +787,19 @@ def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), ), ) - return _speakeasy_parse_response(http_res) + try: + return _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + response_helpers.raise_parse_error( + self.sdk_configuration.__dict__["_hooks"], + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) def rotate_signing_secret( self, @@ -799,7 +884,10 @@ def rotate_signing_secret( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -819,21 +907,25 @@ def _speakeasy_parse_response(http_res): ) if utils.match_response(http_res, "default", "application/json"): return unmarshal_json_response( - webhooks.WebhookRotateSigningSecretResponse, http_res + webhooks.WebhookRotateSigningSecretResponse, + http_res, + validate=False, ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = self.do_request( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="RotateSigningSecret", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="RotateSigningSecret", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="sync"), + ) + http_res = self.do_request( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -852,9 +944,19 @@ def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), ), ) - return _speakeasy_parse_response(http_res) + try: + return _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + response_helpers.raise_parse_error( + self.sdk_configuration.__dict__["_hooks"], + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) def ping( self, @@ -940,7 +1042,10 @@ def ping( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -959,20 +1064,24 @@ def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(webhooks.WebhookPingResponse, http_res) + return unmarshal_json_response( + webhooks.WebhookPingResponse, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = self.do_request( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="PingWebhook", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="PingWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="sync"), + ) + http_res = self.do_request( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -991,9 +1100,19 @@ def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), ), ) - return _speakeasy_parse_response(http_res) + try: + return _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + response_helpers.raise_parse_error( + self.sdk_configuration.__dict__["_hooks"], + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) class WebhooksWithRawResponse: @@ -1144,7 +1263,10 @@ async def create( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -1163,20 +1285,24 @@ async def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(webhooks.Webhook, http_res) + return unmarshal_json_response( + webhooks.Webhook, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = await self.do_request_async( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="CreateWebhook", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="CreateWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="async"), + ) + http_res = await self.do_request_async( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -1195,9 +1321,21 @@ async def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), + async_hooks=self.sdk_configuration.__dict__.get("_async_hooks"), ), ) - return await _speakeasy_parse_response(http_res) + try: + return await _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + await response_helpers.raise_parse_error_async( + self.sdk_configuration.__dict__.get("_async_hooks"), + self.sdk_configuration.__dict__.get("_hooks"), + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) async def list( self, @@ -1270,7 +1408,10 @@ async def list( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -1289,20 +1430,24 @@ async def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(webhooks.WebhookListResponse, http_res) + return unmarshal_json_response( + webhooks.WebhookListResponse, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = await self.do_request_async( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="ListWebhooks", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="ListWebhooks", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="async"), + ) + http_res = await self.do_request_async( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -1321,9 +1466,21 @@ async def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), + async_hooks=self.sdk_configuration.__dict__.get("_async_hooks"), ), ) - return await _speakeasy_parse_response(http_res) + try: + return await _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + await response_helpers.raise_parse_error_async( + self.sdk_configuration.__dict__.get("_async_hooks"), + self.sdk_configuration.__dict__.get("_hooks"), + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) async def get( self, @@ -1390,7 +1547,10 @@ async def get( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -1409,20 +1569,24 @@ async def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(webhooks.Webhook, http_res) + return unmarshal_json_response( + webhooks.Webhook, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = await self.do_request_async( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="GetWebhook", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="GetWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="async"), + ) + http_res = await self.do_request_async( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -1441,9 +1605,21 @@ async def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), + async_hooks=self.sdk_configuration.__dict__.get("_async_hooks"), ), ) - return await _speakeasy_parse_response(http_res) + try: + return await _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + await response_helpers.raise_parse_error_async( + self.sdk_configuration.__dict__.get("_async_hooks"), + self.sdk_configuration.__dict__.get("_hooks"), + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) async def update( self, @@ -1550,7 +1726,10 @@ async def update( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -1569,20 +1748,24 @@ async def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(webhooks.Webhook, http_res) + return unmarshal_json_response( + webhooks.Webhook, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = await self.do_request_async( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="UpdateWebhook", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="UpdateWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="async"), + ) + http_res = await self.do_request_async( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -1601,9 +1784,21 @@ async def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), + async_hooks=self.sdk_configuration.__dict__.get("_async_hooks"), ), ) - return await _speakeasy_parse_response(http_res) + try: + return await _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + await response_helpers.raise_parse_error_async( + self.sdk_configuration.__dict__.get("_async_hooks"), + self.sdk_configuration.__dict__.get("_hooks"), + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) async def delete( self, @@ -1671,7 +1866,10 @@ async def delete( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -1690,20 +1888,24 @@ async def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(interactions.Empty, http_res) + return unmarshal_json_response( + interactions.Empty, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = await self.do_request_async( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="DeleteWebhook", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="DeleteWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="async"), + ) + http_res = await self.do_request_async( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -1722,9 +1924,21 @@ async def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), + async_hooks=self.sdk_configuration.__dict__.get("_async_hooks"), ), ) - return await _speakeasy_parse_response(http_res) + try: + return await _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + await response_helpers.raise_parse_error_async( + self.sdk_configuration.__dict__.get("_async_hooks"), + self.sdk_configuration.__dict__.get("_hooks"), + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) async def rotate_signing_secret( self, @@ -1809,7 +2023,10 @@ async def rotate_signing_secret( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -1829,21 +2046,25 @@ async def _speakeasy_parse_response(http_res): ) if utils.match_response(http_res, "default", "application/json"): return unmarshal_json_response( - webhooks.WebhookRotateSigningSecretResponse, http_res + webhooks.WebhookRotateSigningSecretResponse, + http_res, + validate=False, ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = await self.do_request_async( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="RotateSigningSecret", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="RotateSigningSecret", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="async"), + ) + http_res = await self.do_request_async( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -1862,9 +2083,21 @@ async def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), + async_hooks=self.sdk_configuration.__dict__.get("_async_hooks"), ), ) - return await _speakeasy_parse_response(http_res) + try: + return await _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + await response_helpers.raise_parse_error_async( + self.sdk_configuration.__dict__.get("_async_hooks"), + self.sdk_configuration.__dict__.get("_hooks"), + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) async def ping( self, @@ -1950,7 +2183,10 @@ async def ping( retries = self.sdk_configuration.retry_config else: retries = utils.RetryConfig( - "backoff", utils.BackoffStrategy(500, 8000, 2, 30000), True + "attempt-count-backoff", + utils.BackoffStrategy(500, 8000, 2, 30000), + True, + max_retries=4, ) retry_config = None @@ -1969,20 +2205,24 @@ async def _speakeasy_parse_response(http_res): "API error occurred", http_res, http_res_text ) if utils.match_response(http_res, "default", "application/json"): - return unmarshal_json_response(webhooks.WebhookPingResponse, http_res) + return unmarshal_json_response( + webhooks.WebhookPingResponse, http_res, validate=False + ) raise errors.GenAiDefaultError("Unexpected response received", http_res) - http_res = await self.do_request_async( - hook_ctx=HookContext( - config=self.sdk_configuration, - base_url=base_url or "", - operation_id="PingWebhook", - oauth2_scopes=None, - security_source=get_security_from_env( - self.sdk_configuration.security, types.Security - ), + _speakeasy_hook_ctx = HookContext( + config=self.sdk_configuration, + base_url=base_url or "", + operation_id="PingWebhook", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, types.Security ), + response=ResponseContext(mode=_speakeasy_response_mode, execution="async"), + ) + http_res = await self.do_request_async( + hook_ctx=_speakeasy_hook_ctx, request=req, is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=_speakeasy_response_mode == "streaming", @@ -2001,9 +2241,21 @@ async def _speakeasy_parse_response(http_res): parser=_speakeasy_parse_response, mode="buffered", client_ref=self, + hook_ctx=AfterParseErrorContext(_speakeasy_hook_ctx), + hooks=self.sdk_configuration.__dict__.get("_hooks"), + async_hooks=self.sdk_configuration.__dict__.get("_async_hooks"), ), ) - return await _speakeasy_parse_response(http_res) + try: + return await _speakeasy_parse_response(http_res) + except Exception as parse_exc_: + await response_helpers.raise_parse_error_async( + self.sdk_configuration.__dict__.get("_async_hooks"), + self.sdk_configuration.__dict__.get("_hooks"), + AfterParseErrorContext(_speakeasy_hook_ctx), + http_res, + parse_exc_, + ) class AsyncWebhooksWithRawResponse: diff --git a/google/genai/client.py b/google/genai/client.py index dbea09db0..dc4e01b16 100644 --- a/google/genai/client.py +++ b/google/genai/client.py @@ -39,18 +39,18 @@ from . import _common -from ._gaos.agents import Agents as GeminiNextGenAgents -from ._gaos.agents import AsyncAgents as AsyncGeminiNextGenAgents from ._gaos.google_genai import ( + AsyncGeminiNextGenAgents, AsyncGeminiNextGenInteractions, + AsyncGeminiNextGenWebhooks, + GeminiNextGenAgents, GeminiNextGenInteractions, + GeminiNextGenWebhooks, build_google_genai_async_client, build_google_genai_client, ) from ._gaos.sdk import AsyncGenAI as AsyncGeminiNextGenAPI from ._gaos.sdk import GenAI as GeminiNextGenAPI -from ._gaos.webhooks import AsyncWebhooks as AsyncGeminiNextGenWebhooks -from ._gaos.webhooks import Webhooks as GeminiNextGenWebhooks _interactions_experimental_warned = False _agent_experimental_warned = False @@ -100,7 +100,7 @@ def interactions(self) -> AsyncGeminiNextGenInteractions: @property def webhooks(self) -> AsyncGeminiNextGenWebhooks: if self._webhooks is None: - self._webhooks = self._nextgen_client.webhooks + self._webhooks = AsyncGeminiNextGenWebhooks(self._api_client) return self._webhooks @property @@ -114,7 +114,7 @@ def agents(self) -> AsyncGeminiNextGenAgents: stacklevel=1, ) if self._agents is None: - self._agents = self._nextgen_client.agents + self._agents = AsyncGeminiNextGenAgents(self._api_client) return self._agents @property @@ -431,7 +431,7 @@ def interactions(self) -> GeminiNextGenInteractions: @property def webhooks(self) -> GeminiNextGenWebhooks: if self._webhooks is None: - self._webhooks = self._nextgen_client.webhooks + self._webhooks = GeminiNextGenWebhooks(self._api_client) return self._webhooks @property @@ -445,7 +445,7 @@ def agents(self) -> GeminiNextGenAgents: stacklevel=2, ) if self._agents is None: - self._agents = self._nextgen_client.agents + self._agents = GeminiNextGenAgents(self._api_client) return self._agents @property @@ -532,4 +532,3 @@ def __del__(self) -> None: self.close() except Exception: pass - diff --git a/google/genai/interactions.py b/google/genai/interactions.py index 70c39495e..f3026ab84 100644 --- a/google/genai/interactions.py +++ b/google/genai/interactions.py @@ -22,12 +22,31 @@ from ._gaos.types.interactions import * # noqa: F401,F403 from ._gaos.types.interactions import __all__ as _interactions_all +from ._gaos.models.listagents import ListAgentsRequestParam as AgentListParams +from ._gaos.models.listwebhooks import ListWebhooksRequestParam as WebhookListParams from ._gaos.resources.interactions import * # noqa: F401,F403 from ._gaos.resources.interactions import __all__ as _resources_all +from ._gaos.types.agents.agent import Agent, AgentParam as AgentCreateParams +from ._gaos.types.agents.agentlistresponse import AgentListResponse +from ._gaos.types.interactions.empty import Empty as AgentDeleteResponse from ._gaos.types.interactions import ( CreateAgentInteractionParam, CreateModelInteractionParam, ) +from ._gaos.types.interactions.model import Model as ModelParam +from ._gaos.types.webhooks.pingwebhookrequest import ( + PingWebhookRequestParam as WebhookPingParams, +) +from ._gaos.types.webhooks.rotatesigningsecretrequest import ( + RotateSigningSecretRequestParam as WebhookRotateSigningSecretParams, +) +from ._gaos.types.webhooks.signingsecret import SigningSecret +from ._gaos.types.webhooks.webhook import Webhook, WebhookInputParam as WebhookCreateParams +from ._gaos.types.webhooks.webhooklistresponse import WebhookListResponse +from ._gaos.types.webhooks.webhookpingresponse import WebhookPingResponse +from ._gaos.types.webhooks.webhookupdate import WebhookUpdateParam as WebhookUpdateParams + +WebhookDeleteResponse = AgentDeleteResponse # Legacy flat operation parameter typed dicts that generation cannot express # yet: the get-params dicts exclude the positional `id` path parameter, and @@ -84,6 +103,11 @@ class InteractionGetParamsStreaming(InteractionGetParamsBase): __all__ = [ + "Agent", + "AgentCreateParams", + "AgentDeleteResponse", + "AgentListParams", + "AgentListResponse", "CreateAgentInteractionParamsNonStreaming", "CreateAgentInteractionParamsStreaming", "CreateModelInteractionParamsNonStreaming", @@ -93,5 +117,16 @@ class InteractionGetParamsStreaming(InteractionGetParamsBase): "InteractionGetParamsBase", "InteractionGetParamsNonStreaming", "InteractionGetParamsStreaming", + "ModelParam", + "SigningSecret", + "Webhook", + "WebhookCreateParams", + "WebhookDeleteResponse", + "WebhookListParams", + "WebhookListResponse", + "WebhookPingParams", + "WebhookPingResponse", + "WebhookRotateSigningSecretParams", + "WebhookUpdateParams", ] __all__ = __all__ + list(_interactions_all) + list(_resources_all) diff --git a/google/genai/tests/interactions/test_integration.py b/google/genai/tests/interactions/test_integration.py index 36fdecc1e..85fc76320 100644 --- a/google/genai/tests/interactions/test_integration.py +++ b/google/genai/tests/interactions/test_integration.py @@ -61,6 +61,7 @@ def test_client_timeout(): server_url=mock.ANY, client=mock.ANY, timeout_ms=5000, + retry_config=mock.ANY, ) @@ -87,4 +88,27 @@ async def test_async_client_timeout(): server_url=mock.ANY, async_client=mock.ANY, timeout_ms=5000, + retry_config=mock.ANY, ) + + +@pytest.mark.filterwarnings("error") +def test_unrecognized_model_serialization(): + from ..._gaos.types.interactions.createmodelinteraction import CreateModelInteraction + # This shouldn't raise a Pydantic serialization error due to UnrecognizedStr + obj = CreateModelInteraction(model="gemini-3.5-flash", input="hello") + dumped = obj.model_dump() + assert dumped["model"] == "gemini-3.5-flash" + + +@pytest.mark.filterwarnings("error") +def test_unrecognized_model_request_serialization(): + from ..._gaos.models.createinteraction import CreateInteractionRequest + from ..._gaos.types.interactions.createmodelinteraction import CreateModelInteraction + body = CreateModelInteraction(model="gemini-3.5-flash", input="hello") + req = CreateInteractionRequest(body=body) + dumped = req.model_dump() + assert dumped["body"]["model"] == "gemini-3.5-flash" + + + diff --git a/pyproject.toml b/pyproject.toml index b0fba1904..c4f0186fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,6 +93,8 @@ module = "google.genai.interactions" disable_error_code = [ "misc", "assignment", + # Intentionally overriding imported alias, runtime safe. + "no-redef", ] # Remove after the switch.