From 779d7e2f5f919b1b3f353b0094cf0e02938f1150 Mon Sep 17 00:00:00 2001 From: mateo Date: Sat, 31 Oct 2026 22:41:22 +0000 Subject: [PATCH] fix(responses): keep the spend log read-only typed so the type discipline gate holds Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/_types.py | 2 +- .../session_handler.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index e313de102f8..d4d94240ae3 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -3773,7 +3773,7 @@ class SpendLogsMetadata(TypedDict): requester_ip_address: str | None user_agent: ReadOnly[str | None] litellm_call_id: str | None - litellm_previous_response_ids: ReadOnly[NotRequired[list[str] | None]] + litellm_previous_response_ids: ReadOnly[NotRequired[Sequence[str] | None]] """Spend log request ids of the bridged /v1/responses turns this turn continues, root first.""" applied_guardrails: list[str] | None mcp_tool_call_metadata: StandardLoggingMCPToolCall | None diff --git a/litellm/responses/litellm_completion_transformation/session_handler.py b/litellm/responses/litellm_completion_transformation/session_handler.py index 3a272f7fa02..2b732315e4c 100644 --- a/litellm/responses/litellm_completion_transformation/session_handler.py +++ b/litellm/responses/litellm_completion_transformation/session_handler.py @@ -172,7 +172,7 @@ def _path_through_referenced_rows( return tuple(dict.fromkeys((*stored_path, *request_ids))) -def _in_path_order(spend_logs: Sequence["SpendLogsPayload"], path: Sequence[str]) -> list["SpendLogsPayload"]: +def _in_path_order(spend_logs: Sequence["SpendLogsPayload"], path: Sequence[str]) -> Sequence["SpendLogsPayload"]: position: Final = {request_id: index for index, request_id in enumerate(path)} return sorted(spend_logs, key=lambda spend_log: position.get(spend_log["request_id"], len(path))) @@ -280,8 +280,11 @@ _PATH_QUERY: Final = _OWNER_SCOPED_QUERY.format( ) -async def _read_spend_logs(prisma_client: "PrismaClient", query: str, *params: object) -> list["SpendLogsPayload"]: - return cast("list[SpendLogsPayload]", await prisma_client.db.query_raw(query, *params)) +async def _read_spend_logs(prisma_client: "PrismaClient", query: str, *params: object) -> Sequence["SpendLogsPayload"]: + return cast( # cast-ok: query_raw is untyped; spend log rows are read as is + "Sequence[SpendLogsPayload]", + await prisma_client.db.query_raw(query, *params), + ) class ResponsesSessionHandler: @@ -597,7 +600,7 @@ class ResponsesSessionHandler: async def get_all_spend_logs_for_previous_response_id( previous_response_id: str, client_response_ids: tuple[str, ...] = (), - ) -> "list[SpendLogsPayload]": + ) -> "Sequence[SpendLogsPayload]": """ Get all spend logs for a previous response id