mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
fix(proxy): recognize opencode's bare x-session-id header for session affinity (#39802)
Co-authored-by: yassin <yassin@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
b3c867c7b2
commit
c373645e21
2 changed files with 73 additions and 0 deletions
|
|
@ -669,6 +669,21 @@ def _extract_codex_session_id_from_headers(
|
|||
)
|
||||
|
||||
|
||||
def _extract_bare_session_id_from_headers(
|
||||
normalized: Mapping[str, str],
|
||||
) -> str | None:
|
||||
"""
|
||||
Read a vendor-less ``x-session-id`` header (opencode sends ``X-Session-Id``
|
||||
alongside ``x-session-affinity`` on every turn of a session). Checked after
|
||||
the ``x-<vendor>-session-id`` scan so a more specific header such as
|
||||
opencode's ``x-parent-session-id`` on subagent calls keeps winning.
|
||||
"""
|
||||
value: Final = normalized.get("x-session-id")
|
||||
if isinstance(value, str) and _SESSION_ID_VALUE_RE.match(value):
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def get_chain_id_from_headers(headers: dict[str, str] | None) -> str | None:
|
||||
"""
|
||||
Extract chain id for call chaining from request headers.
|
||||
|
|
@ -679,6 +694,7 @@ def get_chain_id_from_headers(headers: dict[str, str] | None) -> str | None:
|
|||
3. Any ``x-<vendor>-session-id`` header whose value looks like a session id
|
||||
(alphanumeric / UUID, at least 8 chars). E.g. ``x-claude-code-session-id``.
|
||||
4. Codex's unprefixed ``session-id`` / ``thread-id``, for Codex callers only.
|
||||
5. A vendor-less ``x-session-id`` header (e.g. opencode), same value rules.
|
||||
|
||||
Header keys are matched case-insensitively so this works with raw header
|
||||
dicts from any transport.
|
||||
|
|
@ -694,6 +710,7 @@ def get_chain_id_from_headers(headers: dict[str, str] | None) -> str | None:
|
|||
or normalized.get("x-litellm-session-id")
|
||||
or _extract_generic_session_id_from_headers(normalized)
|
||||
or _extract_codex_session_id_from_headers(normalized)
|
||||
or _extract_bare_session_id_from_headers(normalized)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3343,6 +3343,62 @@ def test_add_litellm_metadata_groups_codex_turns_into_one_session():
|
|||
assert turn["litellm_metadata"]["session_id"] == CODEX_SESSION_UUID
|
||||
|
||||
|
||||
OPENCODE_SESSION_ID = "ses_f91e6e825ffeuhlu5EbglxjAN2"
|
||||
OPENCODE_HEADERS = {
|
||||
"x-session-affinity": OPENCODE_SESSION_ID,
|
||||
"X-Session-Id": OPENCODE_SESSION_ID,
|
||||
"User-Agent": "opencode/1.18.28",
|
||||
}
|
||||
|
||||
|
||||
def test_add_litellm_metadata_groups_opencode_turns_into_one_session():
|
||||
"""Every turn of an opencode session must land on metadata.session_id, which is what
|
||||
DeploymentAffinityCheck reads for session pinning, instead of a fresh per-call id."""
|
||||
turns = [{"metadata": {}}, {"metadata": {}}]
|
||||
for turn in turns:
|
||||
LiteLLMProxyRequestSetup.add_litellm_metadata_from_request_headers(
|
||||
headers=OPENCODE_HEADERS, data=turn, _metadata_variable_name="metadata"
|
||||
)
|
||||
|
||||
for turn in turns:
|
||||
assert turn["metadata"]["session_id"] == OPENCODE_SESSION_ID
|
||||
assert turn["metadata"]["trace_id"] == OPENCODE_SESSION_ID
|
||||
assert turn["litellm_session_id"] == OPENCODE_SESSION_ID
|
||||
assert turn["litellm_trace_id"] == OPENCODE_SESSION_ID
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["short", "has spaces!!", ""])
|
||||
def test_get_chain_id_from_headers_bare_session_id_ignores_implausible_value(value: str):
|
||||
from litellm.proxy.litellm_pre_call_utils import get_chain_id_from_headers
|
||||
|
||||
assert get_chain_id_from_headers({"x-session-id": value}) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"other_header",
|
||||
[
|
||||
"x-litellm-trace-id",
|
||||
"x-litellm-session-id",
|
||||
"x-claude-code-session-id",
|
||||
"x-parent-session-id",
|
||||
],
|
||||
)
|
||||
def test_get_chain_id_from_headers_bare_session_id_loses_to_more_specific_header(other_header: str):
|
||||
"""opencode subagent calls carry x-parent-session-id next to X-Session-Id; explicit and
|
||||
vendor-scoped headers must keep winning over the bare header."""
|
||||
from litellm.proxy.litellm_pre_call_utils import get_chain_id_from_headers
|
||||
|
||||
assert (
|
||||
get_chain_id_from_headers(
|
||||
{
|
||||
"x-session-id": OPENCODE_SESSION_ID,
|
||||
other_header: "e96634a3-fa28-4083-b354-55542e2dca01",
|
||||
}
|
||||
)
|
||||
== "e96634a3-fa28-4083-b354-55542e2dca01"
|
||||
)
|
||||
|
||||
|
||||
def test_trace_id_from_traceparent_valid():
|
||||
from litellm.proxy.litellm_pre_call_utils import _trace_id_from_traceparent
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue