mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-15 23:31:29 +00:00
fix(ui): keep gateway auth and content-type headers ahead of playground custom headers
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
c05af7a12f
commit
754e87fa67
6 changed files with 66 additions and 16 deletions
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { getProxyBaseUrl, getGlobalLitellmHeaderName } from "@/components/networking";
|
||||
import type { CustomHeaders } from "@/components/llm_calls/request_headers";
|
||||
import { type CustomHeaders, withRequiredHeaders } from "@/components/llm_calls/request_headers";
|
||||
import { A2ATaskMetadata } from "@/components/chat_ui/types";
|
||||
|
||||
interface A2AMessagePart {
|
||||
|
|
@ -148,11 +148,10 @@ export const makeA2ASendMessageRequest = async (
|
|||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
headers: withRequiredHeaders(customHeaders ?? {}, {
|
||||
[getGlobalLitellmHeaderName()]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
...customHeaders,
|
||||
},
|
||||
}),
|
||||
body: JSON.stringify(jsonRpcRequest),
|
||||
signal,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -96,4 +96,22 @@ describe("embeddings_api", () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("does not let custom headers replace the gateway auth or content-type headers", async () => {
|
||||
await makeOpenAIEmbeddingsRequest(
|
||||
"Sample text",
|
||||
mockUpdateEmbeddingsUI,
|
||||
"text-embedding-3-small",
|
||||
"abcdef",
|
||||
undefined,
|
||||
undefined,
|
||||
{ authorization: "Bearer stolen", "Content-Type": "text/plain", "x-request-source": "playground" },
|
||||
);
|
||||
|
||||
expect(mockFetch.mock.calls[0][1].headers).toEqual({
|
||||
Authorization: "Bearer abcdef",
|
||||
"Content-Type": "application/json",
|
||||
"x-request-source": "playground",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import { toast } from "@/lib/toast";
|
||||
import { getProxyBaseUrl, getGlobalLitellmHeaderName } from "@/components/networking";
|
||||
import { buildPlaygroundHeaders, type CustomHeaders } from "@/components/llm_calls/request_headers";
|
||||
import {
|
||||
buildPlaygroundHeaders,
|
||||
type CustomHeaders,
|
||||
withRequiredHeaders,
|
||||
} from "@/components/llm_calls/request_headers";
|
||||
|
||||
export async function makeOpenAIEmbeddingsRequest(
|
||||
input: string,
|
||||
|
|
@ -22,7 +26,10 @@ export async function makeOpenAIEmbeddingsRequest(
|
|||
}
|
||||
|
||||
const proxyBaseUrl = customBaseUrl || getProxyBaseUrl();
|
||||
const headers = buildPlaygroundHeaders(tags, customHeaders);
|
||||
const headers = withRequiredHeaders(buildPlaygroundHeaders(tags, customHeaders), {
|
||||
"Content-Type": "application/json",
|
||||
[getGlobalLitellmHeaderName()]: `Bearer ${accessToken}`,
|
||||
});
|
||||
|
||||
try {
|
||||
const normalizedBaseUrl = proxyBaseUrl.endsWith("/") ? proxyBaseUrl.slice(0, -1) : proxyBaseUrl;
|
||||
|
|
@ -30,11 +37,7 @@ export async function makeOpenAIEmbeddingsRequest(
|
|||
|
||||
const response = await fetch(requestUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
[getGlobalLitellmHeaderName()]: `Bearer ${accessToken}`,
|
||||
...headers,
|
||||
},
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
model: selectedModel,
|
||||
input,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import { toast } from "@/lib/toast";
|
||||
import { getGlobalLitellmHeaderName, getProxyBaseUrl } from "@/components/networking";
|
||||
import { buildPlaygroundHeaders, type CustomHeaders } from "@/components/llm_calls/request_headers";
|
||||
import {
|
||||
buildPlaygroundHeaders,
|
||||
type CustomHeaders,
|
||||
withRequiredHeaders,
|
||||
} from "@/components/llm_calls/request_headers";
|
||||
|
||||
export async function makeInteractionsRequest(
|
||||
input: string,
|
||||
|
|
@ -26,11 +30,10 @@ export async function makeInteractionsRequest(
|
|||
const normalizedBaseUrl = proxyBaseUrl.endsWith("/") ? proxyBaseUrl.slice(0, -1) : proxyBaseUrl;
|
||||
const requestUrl = `${normalizedBaseUrl}/v1beta/interactions`;
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
const headers: Record<string, string> = withRequiredHeaders(buildPlaygroundHeaders(tags, customHeaders), {
|
||||
"Content-Type": "application/json",
|
||||
[getGlobalLitellmHeaderName()]: `Bearer ${accessToken}`,
|
||||
...buildPlaygroundHeaders(tags, customHeaders),
|
||||
};
|
||||
});
|
||||
|
||||
const body: Record<string, unknown> = {
|
||||
model: selectedModel,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { buildPlaygroundHeaders, customHeadersFromPairs, parseStoredHeaderPairs } from "./request_headers";
|
||||
import {
|
||||
buildPlaygroundHeaders,
|
||||
customHeadersFromPairs,
|
||||
parseStoredHeaderPairs,
|
||||
withRequiredHeaders,
|
||||
} from "./request_headers";
|
||||
|
||||
describe("customHeadersFromPairs", () => {
|
||||
it("trims header names and drops rows without a name", () => {
|
||||
|
|
@ -42,3 +47,14 @@ describe("buildPlaygroundHeaders", () => {
|
|||
expect(buildPlaygroundHeaders(undefined, undefined)).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("withRequiredHeaders", () => {
|
||||
it("keeps required headers regardless of custom header name casing", () => {
|
||||
expect(
|
||||
withRequiredHeaders(
|
||||
{ authorization: "Bearer stolen", "content-type": "text/plain", "x-custom": "1" },
|
||||
{ Authorization: "Bearer real", "Content-Type": "application/json" },
|
||||
),
|
||||
).toEqual({ Authorization: "Bearer real", "Content-Type": "application/json", "x-custom": "1" });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,3 +25,14 @@ export const buildPlaygroundHeaders = (
|
|||
...(tags && tags.length > 0 ? { "x-litellm-tags": tags.join(",") } : {}),
|
||||
...customHeaders,
|
||||
});
|
||||
|
||||
export const withRequiredHeaders = (
|
||||
headers: Readonly<Record<string, string>>,
|
||||
required: Readonly<Record<string, string>>,
|
||||
): Record<string, string> => {
|
||||
const reserved = new Set(Object.keys(required).map((name) => name.toLowerCase()));
|
||||
return {
|
||||
...Object.fromEntries(Object.entries(headers).filter(([name]) => !reserved.has(name.toLowerCase()))),
|
||||
...required,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue