Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions ui/litellm-dashboard/eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -4142,11 +4142,6 @@
"count": 1
}
},
"src/components/view_logs/LogDetailsDrawer/prettyMessagesUtils.ts": {
"no-nested-ternary": {
"count": 1
}
},
"src/components/view_logs/LogDetailsDrawer/useKeyboardNavigation.ts": {
"react-hooks/immutability": {
"count": 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,126 @@ describe("PrettyMessagesView", () => {
expect(modelElements.length).toBeGreaterThanOrEqual(1);
});

it("renders a Responses API log, whose body uses input/output instead of messages/choices", () => {
const request = {
model: "gpt-5.6",
input: [{ role: "user", content: "Reply with exactly: hello from responses api" }],
};
const response = {
output: [
{
id: "msg_070989277645d4ae",
role: "assistant",
type: "message",
status: "completed",
content: [{ text: "hello from responses api", type: "output_text", annotations: [] }],
},
],
};

render(<PrettyMessagesView request={request} response={response} />);
expect(screen.getByText("Reply with exactly: hello from responses api")).toBeInTheDocument();
expect(screen.getByText("hello from responses api")).toBeInTheDocument();
expect(screen.queryByText("No response data available")).not.toBeInTheDocument();
});

it("renders a Responses API tool call, whose output item is a function_call", () => {
const request = {
model: "gpt-5.6",
input: [{ role: "user", content: "What is the weather in San Francisco? Use the tool." }],
};
const response = {
output: [
{
id: "fc_08edf6c2312f1485",
name: "get_weather",
type: "function_call",
status: "completed",
call_id: "call_AtO0J9eNy5jgECXzBicMJM8W",
arguments: '{"city":"San Francisco"}',
},
],
};

render(<PrettyMessagesView request={request} response={response} />);
expect(screen.getByText("What is the weather in San Francisco? Use the tool.")).toBeInTheDocument();
expect(screen.getByText("get_weather")).toBeInTheDocument();
expect(screen.queryByText("No response data available")).not.toBeInTheDocument();
});

it("renders instructions as the system turn and a bare string input", () => {
const request = { model: "gpt-5.6", instructions: "You are terse.", input: "Say A" };
const response = {
output: [{ type: "message", role: "assistant", content: [{ type: "output_text", text: "A" }] }],
};

render(<PrettyMessagesView request={request} response={response} />);
expect(screen.getByText("You are terse.")).toBeInTheDocument();
expect(screen.getByText("Say A")).toBeInTheDocument();
expect(screen.getByText("A")).toBeInTheDocument();
});

it("skips reasoning output items rather than rendering them as empty turns", () => {
const request = { input: [{ role: "user", content: "Think then answer" }] };
const response = {
output: [
{ type: "reasoning", id: "rs_1", summary: [] },
{ type: "message", role: "assistant", content: [{ type: "output_text", text: "answered" }] },
],
};

render(<PrettyMessagesView request={request} response={response} />);
expect(screen.getByText("answered")).toBeInTheDocument();
expect(screen.queryByText("No response data available")).not.toBeInTheDocument();
});

it("renders a Responses API follow-up turn carrying a prior function_call and its output", () => {
const request = {
input: [
{ role: "user", content: "What is the weather in San Francisco? Use the tool." },
{
type: "function_call",
name: "get_weather",
call_id: "call_AtO0J9eNy5jgECXzBicMJM8W",
arguments: '{"city":"San Francisco"}',
},
{ type: "function_call_output", call_id: "call_AtO0J9eNy5jgECXzBicMJM8W", output: '{"temp":18}' },
],
};
const response = {
output: [{ type: "message", role: "assistant", content: [{ type: "output_text", text: "It is 18 degrees." }] }],
};

render(<PrettyMessagesView request={request} response={response} />);
expect(screen.getByText("It is 18 degrees.")).toBeInTheDocument();
expect(screen.getByText('{"temp":18}')).toBeInTheDocument();
expect(screen.getByText("TOOL")).toBeInTheDocument();
});

it("maps the developer and legacy function roles onto the roles the drawer renders", () => {
const request = {
messages: [
{ role: "developer", content: "Stay terse." },
{ role: "user", content: "Weather?" },
{ role: "function", name: "get_weather", content: '{"temp":18}' },
],
};
const response = { choices: [{ message: { role: "assistant", content: "18 degrees." } }] };

render(<PrettyMessagesView request={request} response={response} />);
expect(screen.getByText("Stay terse.")).toBeInTheDocument();
expect(screen.getByText("TOOL")).toBeInTheDocument();
expect(screen.queryByText("FUNCTION")).not.toBeInTheDocument();
});

it("still reports missing output when a Responses API log has an empty output array", () => {
const request = { input: [{ role: "user", content: "Hello" }] };

render(<PrettyMessagesView request={request} response={{ output: [] }} />);
expect(screen.getByText("Hello")).toBeInTheDocument();
expect(screen.getByText("No response data available")).toBeInTheDocument();
});

it("should render standard view when response has results but no realtime events", () => {
const request = {
messages: [{ role: "user", content: "Test" }],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,29 @@
* Type definitions for pretty messages view
*/

export type MessageRole = "system" | "user" | "assistant" | "tool";

export interface ParsedMessage {
role: "system" | "user" | "assistant" | "tool";
role: MessageRole;
content: string;
toolCalls?: ToolCall[];
toolCallId?: string;
}

export type RequestPayload =
| { kind: "chat"; messages: readonly unknown[] }
| { kind: "responses"; instructions: string; input: string | readonly unknown[] }
| { kind: "unknown" };

export type ResponsePayload =
| { kind: "chat"; choices: readonly unknown[] }
| { kind: "responses"; output: readonly unknown[] }
| { kind: "unknown" };

export interface ToolCall {
id: string;
name: string;
arguments: Record<string, any>;
arguments: Record<string, unknown>;
}

export interface ParsedMessages {
Expand Down
Loading
Loading