From 1261885972ea5f50938c7cad8a7fd88a5350d048 Mon Sep 17 00:00:00 2001 From: Dev Chiniwala Date: Mon, 26 Oct 2026 18:16:16 +0530 Subject: [PATCH] fix(responses): validate continuation fields at the sync boundary --- .../handler.py | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/litellm/responses/litellm_completion_transformation/handler.py b/litellm/responses/litellm_completion_transformation/handler.py index 2c8c8e0e29b..751ccb755f3 100644 --- a/litellm/responses/litellm_completion_transformation/handler.py +++ b/litellm/responses/litellm_completion_transformation/handler.py @@ -6,6 +6,7 @@ from collections.abc import Coroutine, Mapping from typing import Final from pydantic import TypeAdapter +from typing_extensions import ReadOnly, TypedDict import litellm from litellm.litellm_core_utils.asyncify import run_async_function @@ -29,6 +30,14 @@ from litellm.types.utils import ModelResponse _REQUEST_KWARGS_ADAPTER: Final = TypeAdapter(dict[str, object]) +class _ContinuationFields(TypedDict, total=False): + previous_response_id: ReadOnly[str | None] + instructions: ReadOnly[str | None] + + +_CONTINUATION_FIELDS_ADAPTER: Final = TypeAdapter(_ContinuationFields) + + class LiteLLMCompletionTransformationHandler: def response_api_handler( self, @@ -42,7 +51,7 @@ class LiteLLMCompletionTransformationHandler: _is_async: bool = False, stream: bool | None = None, extra_headers: Mapping[str, object] | None = None, - **kwargs, + **kwargs: object, ) -> ( ResponsesAPIResponse | BaseResponsesAPIStreamingIterator @@ -53,7 +62,7 @@ class LiteLLMCompletionTransformationHandler: bridged_request: Final[ResponsesAPIOptionalRequestParams] = ( {**responses_api_request, "tools": list(hoisted.tools)} if hoisted.hoisted else responses_api_request ) - litellm_completion_request: Final[dict] = ( + litellm_completion_request: Final = _REQUEST_KWARGS_ADAPTER.validate_python( LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request( model=model, input=bridged_input, @@ -74,15 +83,16 @@ class LiteLLMCompletionTransformationHandler: **kwargs, ) - previous_response_id: Final = bridged_request.get("previous_response_id") + continuation: Final = _CONTINUATION_FIELDS_ADAPTER.validate_python(bridged_request) + previous_response_id: Final = continuation.get("previous_response_id") sync_request: Final = ( _REQUEST_KWARGS_ADAPTER.validate_python( run_async_function( LiteLLMCompletionResponsesConfig.async_responses_api_session_handler, previous_response_id=previous_response_id, litellm_completion_request=litellm_completion_request, - instructions=bridged_request.get("instructions"), - request_kwargs=_REQUEST_KWARGS_ADAPTER.validate_python(kwargs), + instructions=continuation.get("instructions"), + request_kwargs=kwargs, previous_response_client_ids=previous_response_client_ids, input_replaces_history=holds_compaction_summary(with_compaction_items_as_params(bridged_input)), ) @@ -90,10 +100,7 @@ class LiteLLMCompletionTransformationHandler: if previous_response_id else litellm_completion_request ) - completion_args: Final = {} - completion_args.update(kwargs) - completion_args.update(sync_request) - completion_args["_skip_responses_api_bridge"] = True + completion_args: Final = {**kwargs, **sync_request, "_skip_responses_api_bridge": True} litellm_completion_response: Final[ModelResponse | litellm.CustomStreamWrapper] = litellm.completion( **completion_args, @@ -117,7 +124,7 @@ class LiteLLMCompletionTransformationHandler: request_input=bridged_input, responses_api_request=bridged_request, custom_llm_provider=custom_llm_provider, - litellm_metadata=kwargs.get("litellm_metadata", {}), + litellm_metadata=_REQUEST_KWARGS_ADAPTER.validate_python(kwargs.get("litellm_metadata") or {}), ) raise ValueError(f"Unexpected response type: {type(litellm_completion_response)}") @@ -129,7 +136,7 @@ class LiteLLMCompletionTransformationHandler: litellm_completion_request: dict, request_input: str | ResponseInputParam, responses_api_request: ResponsesAPIOptionalRequestParams, - **kwargs, + **kwargs: object, ) -> ResponsesAPIResponse | BaseResponsesAPIStreamingIterator: previous_response_id: Final[str | None] = responses_api_request.get("previous_response_id") if previous_response_id: @@ -142,10 +149,11 @@ class LiteLLMCompletionTransformationHandler: input_replaces_history=holds_compaction_summary(with_compaction_items_as_params(request_input)), ) - acompletion_args: Final = {} - acompletion_args.update(kwargs) - acompletion_args.update(litellm_completion_request) - acompletion_args["_skip_responses_api_bridge"] = True + acompletion_args: Final = { + **kwargs, + **_REQUEST_KWARGS_ADAPTER.validate_python(litellm_completion_request), + "_skip_responses_api_bridge": True, + } litellm_completion_response: Final[ModelResponse | litellm.CustomStreamWrapper] = await litellm.acompletion( **acompletion_args, @@ -169,6 +177,6 @@ class LiteLLMCompletionTransformationHandler: request_input=request_input, responses_api_request=responses_api_request, custom_llm_provider=litellm_completion_request.get("custom_llm_provider"), - litellm_metadata=kwargs.get("litellm_metadata", {}), + litellm_metadata=_REQUEST_KWARGS_ADAPTER.validate_python(kwargs.get("litellm_metadata") or {}), ) raise ValueError(f"Unexpected response type: {type(litellm_completion_response)}")