mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-29 06:33:28 +00:00
fix(ui): percent-encode request id in spend log detail URL (#48630)
* test: cover request ids with URL special characters in log detail fetches Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(ui): percent-encode request id in spend log detail URL Co-authored-by: Charlie Freeman <243496084+cf3901646@users.noreply.github.com> Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Charlie Freeman <243496084+cf3901646@users.noreply.github.com>
This commit is contained in:
parent
9e01b3c37b
commit
34c10829b1
4 changed files with 43 additions and 3 deletions
|
|
@ -15,6 +15,7 @@ from datetime import datetime, timezone
|
|||
from pathlib import Path
|
||||
from types import MappingProxyType
|
||||
from typing import Final
|
||||
from urllib.parse import quote
|
||||
|
||||
import click
|
||||
import requests
|
||||
|
|
@ -177,7 +178,7 @@ class SpendLogsFetcher:
|
|||
return DebugFailure(f"Unexpected {uri} response: {e}")
|
||||
|
||||
def payload(self, request_id: str) -> RequestResponsePayload | None | DebugFailure:
|
||||
uri: Final = f"/spend/logs/ui/{request_id}"
|
||||
uri: Final = f"/spend/logs/ui/{quote(request_id, safe='')}"
|
||||
raw: Final = self._get(uri)
|
||||
if isinstance(raw, DebugFailure):
|
||||
return raw
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from urllib.parse import unquote
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
|
@ -114,6 +116,22 @@ def test_recent_bodies_fetches_latest_turns_even_when_successful():
|
|||
assert _called_paths() == ["/spend/logs/session/ui", "/spend/logs/ui/req-ok"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("request_id", ["req#a1", "req?a=1", "req%41b", "req a&b"])
|
||||
@responses.activate
|
||||
def test_bodies_are_fetched_for_request_ids_with_url_special_characters(request_id):
|
||||
def payload_for_stored_id(request):
|
||||
stored_id = unquote(request.path_url.split("?")[0].removeprefix("/spend/logs/ui/"))
|
||||
found = stored_id == request_id
|
||||
return 200, {}, json.dumps({"response": {"id": "msg_special"}} if found else None)
|
||||
|
||||
_mock_proxy([{**OK_ROW, "request_id": request_id}], {})
|
||||
responses.add_callback(responses.GET, re.compile(f"{PROXY}/spend/logs/ui/.*"), callback=payload_for_stored_id)
|
||||
result = CliRunner().invoke(cli, ["debug", "claude", "--session-id", SESSION, "--no-save"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "msg_special" in result.output
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_bodies_are_truncated_to_max_chars():
|
||||
_mock_proxy([OK_ROW], {"req-ok": {"proxy_server_request": {"body": "a" * 5000}, "response": None}})
|
||||
|
|
|
|||
|
|
@ -602,6 +602,27 @@ describe("uiSpendLogsCall exclude_internal_health_checks serialization", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("uiSpendLogDetailsCall", () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it.each(["chatcmpl-123#a1", "chatcmpl-123?a=1", "chatcmpl-123%41b", "chatcmpl-123 a&b"])(
|
||||
"requests the details of %s with the full request id and start date",
|
||||
async (logId) => {
|
||||
const mockFetch = vi.fn().mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue({ id: logId }) });
|
||||
vi.stubGlobal("fetch", mockFetch);
|
||||
|
||||
await Networking.uiSpendLogDetailsCall("token", logId, "2026-01-01 00:00:00");
|
||||
|
||||
const url = new URL(String(mockFetch.mock.calls.at(-1)?.[0]), "http://example.com");
|
||||
expect(decodeURIComponent(url.pathname.split("/spend/logs/ui/")[1])).toBe(logId);
|
||||
expect(url.hash).toBe("");
|
||||
expect([...url.searchParams.entries()]).toEqual([["start_date", "2026-01-01 00:00:00"]]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("sessionSpendLogsCall", () => {
|
||||
const originalFetch = global.fetch;
|
||||
|
||||
|
|
|
|||
|
|
@ -4709,8 +4709,8 @@ export const uiSpendLogDetailsCall = async (accessToken: string, logId: string,
|
|||
try {
|
||||
// Construct base URL
|
||||
let url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/spend/logs/ui/${logId}?start_date=${encodeURIComponent(start_date)}`
|
||||
: `/spend/logs/ui/${logId}?start_date=${encodeURIComponent(start_date)}`;
|
||||
? `${proxyBaseUrl}/spend/logs/ui/${encodeURIComponent(logId)}?start_date=${encodeURIComponent(start_date)}`
|
||||
: `/spend/logs/ui/${encodeURIComponent(logId)}?start_date=${encodeURIComponent(start_date)}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue