Skip to content

fix(responses-bridge): decrypt proxy-managed previous_response_id for session lookup - #36360

Open
harryzhou2000 wants to merge 6 commits into
BerriAI:litellm_internal_stagingfrom
harryzhou2000:fix/responses-session-handle-encrypted-previous-response-id
Open

fix(responses-bridge): decrypt proxy-managed previous_response_id for session lookup#36360
harryzhou2000 wants to merge 6 commits into
BerriAI:litellm_internal_stagingfrom
harryzhou2000:fix/responses-session-handle-encrypted-previous-response-id

Conversation

@harryzhou2000

@harryzhou2000 harryzhou2000 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Stateful /v1/responses chaining through the chat-completions bridge loses all prior context when Responses ID security is enabled
  • The client-facing resp_... ID is encrypted, while spend logs are keyed by the internal chat-completion request ID
  • previous_response_id therefore resolves to no session and the follow-up starts a fresh conversation

How it solves it:

  • Decrypt the proxy-encrypted response ID before decoding the LiteLLM-managed ID
  • Use the resulting request ID for the spend-log lookup
  • Keep the lookup testable without importing the proxy server
  • Enforce the same ownership check on /v1/responses/compact before the shared decoder runs

User Flow

Before: a developer sends a two-turn stateful conversation and the second turn forgets the first

  1. They send POST https://litellm-domain/v1/responses with "input": "My favorite color is blue." and receive resp_... and HTTP 200
  2. They send POST https://litellm-domain/v1/responses again with previous_response_id set to that ID and "input": "What is my favorite color?"
  3. The proxy returns HTTP 200, but the model answers as if it has no memory of the first turn

After: the same two-turn flow keeps the conversation

  1. They send the same first POST https://litellm-domain/v1/responses
  2. They send the same follow-up with previous_response_id
  3. The model recalls the first turn and answers correctly, e.g. "Blue"

Relevant issues

No GitHub issue filed yet.

Linear ticket

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all CI/CD checks (lint/format/unit passed locally; CI re-running on the rebased head)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 (re-review triggered on the final head)

Screenshots / Proof of Fix

Live proof against the running proxy (real DeepSeek V4 calls).

Before (stock container, no fix):

{
  "status": "incomplete",
  "output": [
    {"type": "reasoning", "content": [{"type": "output_text", "text": "We need answer user asks favorite color, but we don't know..."}]},
    {"type": "message", "content": [{"type": "output_text", "text": ""}]}
  ],
  "usage": {"input_tokens": 95, "output_tokens": 256}
}

After (original capture at commit 73384cce13; same behavior rebased onto litellm_internal_staging as 173124f8a9 with the compaction ownership and DI fixes, spend-log row flushed before the follow-up):

{
  "status": "completed",
  "output": [
    {"type": "reasoning", "content": [{"type": "output_text", "text": "We previously said favorite color is blue. So answer just \"Blue\"."}]},
    {"type": "message", "content": [{"type": "output_text", "text": "Blue"}]}
  ],
  "usage": {"input_tokens": 135, "output_tokens": 45}
}

Note: the session handler reads the prior turn from the spend-log row, so the follow-up must be sent after the spend-log write has landed.

Security coverage: ResponsesIDSecurity.async_pre_call_hook now treats acompact_responses like aresponses, checking ownership of an encrypted previous_response_id before the shared decoder runs.

Local suites: 67 passed for the targeted responses/security tests; ruff check and ruff format --check clean on the changed files; all three repo gates pass locally.

Type

🐛 Bug Fix

Changes

  • ResponsesAPIRequestUtils.decode_previous_response_id_to_original_previous_response_id decrypts the proxy-encrypted layer before the LiteLLM-managed decode
  • ResponsesSessionHandler.get_all_spend_logs_for_previous_response_id uses the decoder so spend-log lookups receive the original request ID, with injectable Prisma client/decoder seams for tests
  • ResponsesIDSecurity.async_pre_call_hook now checks ownership on acompact_responses requests
  • Tests cover proxy decryption, the Prisma seam, the no-Prisma path, and compaction ownership

QA runbook

N/A — this PR adds mocked unit tests, not e2e tests. The live proof above was captured manually.

Final Attestation

  • The tests check the right things, including edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

@CLAassistant

CLAassistant commented Aug 9, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@harryzhou2000
harryzhou2000 marked this pull request as ready for review August 9, 2026 16:45
@greptile-apps

greptile-apps Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR restores stateful Responses API chaining when proxy-managed response-ID protection is enabled and extends the ownership check to response compaction.

  • Decrypts proxy-managed IDs before decoding the underlying request ID.
  • Uses the resolved request ID for spend-log session lookup.
  • Adds injectable seams and focused tests for decoding, database lookup, and compaction ownership.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
litellm/proxy/hooks/responses_id_security.py Extends the existing ownership-check and decryption path to compact-response requests without an eligible residual issue.
litellm/responses/litellm_completion_transformation/session_handler.py Resolves prior response IDs before spend-log lookup and introduces injectable Prisma and decoder dependencies.
litellm/responses/utils.py Adds proxy-layer decryption ahead of managed response-ID decoding while preserving fallback behavior for unencrypted IDs.
tests/test_litellm/responses/litellm_completion_transformation/test_session_handler.py Adds mocked coverage for decoded lookup keys, the Prisma accessor seam, and the unavailable-client path.
tests/test_litellm/responses/test_responses_utils.py Covers layered ID decoding, injected security-hook behavior, lazy hook resolution, and plain-ID fallback.
tests/test_litellm/test_responses_id_security.py Verifies compact-response requests apply the existing encrypted previous-response ownership path.

Reviews (3): Last reviewed commit: "fix(responses-bridge): check compaction ..." | Re-trigger Greptile

Comment thread litellm/responses/utils.py Outdated
@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/responses/utils.py 89.47% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Comment thread litellm/responses/utils.py Outdated
@veria-ai

veria-ai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

@codspeed-hq

codspeed-hq Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing harryzhou2000:fix/responses-session-handle-encrypted-previous-response-id (173124f) with litellm_internal_staging (8941f2a)

Open in CodSpeed

@harryzhou2000
harryzhou2000 force-pushed the fix/responses-session-handle-encrypted-previous-response-id branch from 63ccc40 to c9b0c9e Compare August 18, 2026 14:02
@harryzhou2000

Copy link
Copy Markdown
Contributor Author

@greptileai

@harryzhou2000
harryzhou2000 force-pushed the fix/responses-session-handle-encrypted-previous-response-id branch from c9b0c9e to 173124f Compare August 19, 2026 05:10
@harryzhou2000

Copy link
Copy Markdown
Contributor Author

@greptileai

@harryzhou2000

Copy link
Copy Markdown
Contributor Author

Note: the misc / Run tests failure is pre-existing on litellm_internal_staging (test_handle_completed_vertex_batch_computes_cost_usage_and_models expects non-batch pricing). It is unrelated to this PR; upstream draft #37443 fixes the base assertion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants