mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-12 03:48:56 +00:00
fix(ui): send the Decisions cURL under the configured auth header and preview the validated payload
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
306cf9e654
commit
985cadfcaf
4 changed files with 72 additions and 8 deletions
|
|
@ -8,7 +8,7 @@ import { SimpleTooltip } from "@/components/ui/tooltip";
|
|||
import { SearchSelect } from "@/components/shared/SearchSelect";
|
||||
import { cn } from "@/lib/cva.config";
|
||||
import { DECISIONS_DOCS_URL } from "@/lib/decisionModels";
|
||||
import { getProxyBaseUrl } from "@/components/networking";
|
||||
import { getGlobalLitellmHeaderName, getProxyBaseUrl } from "@/components/networking";
|
||||
import { uiHref } from "@/utils/uiHref";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { BookOpen, Eraser, LoaderCircle, Send } from "lucide-react";
|
||||
|
|
@ -118,6 +118,10 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation =
|
|||
systemOne.reset();
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
handlePayloadChange(presetPayload(DECISION_PRESETS[0], endpoint, draftModel ?? decisionModels[0]));
|
||||
}
|
||||
|
||||
function handlePayloadChange(value: string) {
|
||||
if (value !== rawPayload) {
|
||||
clearRequestState();
|
||||
|
|
@ -260,14 +264,14 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation =
|
|||
value={rawPayload}
|
||||
onChange={handlePayloadChange}
|
||||
validation={validated.validation}
|
||||
onReset={() => handlePayloadChange(presetPayload(DECISION_PRESETS[0], endpoint, draftModel))}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
) : (
|
||||
<SystemOneForm
|
||||
value={rawPayload}
|
||||
onChange={handlePayloadChange}
|
||||
validation={validated.validation}
|
||||
onReset={() => handlePayloadChange(presetPayload(DECISION_PRESETS[0], endpoint, draftModel))}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
|
@ -275,7 +279,8 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation =
|
|||
<CurlPreview
|
||||
baseUrl={getCustomProxyBaseUrl() ?? getProxyBaseUrl()}
|
||||
endpoint={endpoint}
|
||||
payload={rawPayload}
|
||||
payload={validation.payload === undefined ? rawPayload : JSON.stringify(validation.payload)}
|
||||
authHeaderName={getGlobalLitellmHeaderName()}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { IssueList, ValidationStatus } from "./ValidationFeedback";
|
||||
import type { PayloadValidation } from "./lib/validatePayload";
|
||||
|
||||
const validation = (issues: PayloadValidation<unknown>["issues"]): PayloadValidation<unknown> => ({
|
||||
isValid: issues.every((issue) => issue.severity !== "error"),
|
||||
issues,
|
||||
});
|
||||
|
||||
describe("ValidationStatus", () => {
|
||||
it("counts only errors as issues", () => {
|
||||
render(
|
||||
<ValidationStatus
|
||||
validation={validation([
|
||||
{ path: "state", message: "Required", severity: "error" },
|
||||
{ path: "model", message: "Required", severity: "error" },
|
||||
{ path: "questions.a", message: "Long question", severity: "warning" },
|
||||
])}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("2 issues")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("reports a valid payload when only warnings remain", () => {
|
||||
render(<ValidationStatus validation={validation([{ path: "q", message: "Long", severity: "warning" }])} />);
|
||||
expect(screen.getByText("Valid payload")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("IssueList", () => {
|
||||
it("lists every issue with its path and says ready when there are none", () => {
|
||||
const { rerender } = render(
|
||||
<IssueList id="issues" validation={validation([{ path: "state", message: "Required", severity: "error" }])} />,
|
||||
);
|
||||
expect(screen.getByRole("list", { name: "Payload validation issues" })).toHaveTextContent("stateRequired");
|
||||
rerender(<IssueList id="issues" validation={validation([])} />);
|
||||
expect(screen.getByRole("status")).toHaveTextContent("Ready to send");
|
||||
});
|
||||
});
|
||||
|
|
@ -7,10 +7,11 @@ describe("buildCurlCommand", () => {
|
|||
baseUrl: "http://localhost:4000/",
|
||||
endpoint: "/v1/systemone",
|
||||
payload: JSON.stringify({ model: "jev-latest", state: "it's broken" }),
|
||||
authHeaderName: "Authorization",
|
||||
});
|
||||
|
||||
expect(command).toContain("curl -X POST 'http://localhost:4000/v1/systemone'");
|
||||
expect(command).toContain('-H "Authorization: Bearer $LITELLM_API_KEY"');
|
||||
expect(command).toContain("-H 'Authorization: Bearer $LITELLM_API_KEY'");
|
||||
expect(command).not.toContain("sk-");
|
||||
expect(command).toContain(' "state": "it\'\\\'\'s broken"');
|
||||
expect(JSON.parse(curlPayload(command) ?? "")).toEqual({ model: "jev-latest", state: "it's broken" });
|
||||
|
|
@ -21,12 +22,29 @@ describe("buildCurlCommand", () => {
|
|||
baseUrl: "http://proxy.example/$(touch pwned)'; echo x",
|
||||
endpoint: "/v1/decisions",
|
||||
payload: "{}",
|
||||
authHeaderName: "Authorization",
|
||||
});
|
||||
expect(command).toContain(`curl -X POST 'http://proxy.example/$(touch pwned)'\\''; echo x/v1/decisions'`);
|
||||
});
|
||||
|
||||
it("sends the key under the proxy's configured auth header name", () => {
|
||||
const command = buildCurlCommand({
|
||||
baseUrl: "https://proxy.example",
|
||||
endpoint: "/v1/systemone",
|
||||
payload: "{}",
|
||||
authHeaderName: "X-Litellm-Key",
|
||||
});
|
||||
expect(command).toContain("-H 'X-Litellm-Key: Bearer $LITELLM_API_KEY'");
|
||||
expect(command).not.toContain("Authorization");
|
||||
});
|
||||
|
||||
it("passes a payload that is not JSON through untouched", () => {
|
||||
const command = buildCurlCommand({ baseUrl: "https://proxy.example", endpoint: "/v1/decisions", payload: "{" });
|
||||
const command = buildCurlCommand({
|
||||
baseUrl: "https://proxy.example",
|
||||
endpoint: "/v1/decisions",
|
||||
payload: "{",
|
||||
authHeaderName: "Authorization",
|
||||
});
|
||||
expect(command).toContain("'https://proxy.example/v1/decisions'");
|
||||
expect(curlPayload(command)).toBe("{");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export interface CurlCommandInput {
|
|||
baseUrl: string;
|
||||
endpoint: "/v1/decisions" | "/v1/systemone";
|
||||
payload: string;
|
||||
authHeaderName: string;
|
||||
}
|
||||
|
||||
export const CURL_KEY_PLACEHOLDER = "$LITELLM_API_KEY";
|
||||
|
|
@ -16,10 +17,10 @@ const prettyPayload = (payload: string): string => {
|
|||
}
|
||||
};
|
||||
|
||||
export const buildCurlCommand = ({ baseUrl, endpoint, payload }: CurlCommandInput): string =>
|
||||
export const buildCurlCommand = ({ baseUrl, endpoint, payload, authHeaderName }: CurlCommandInput): string =>
|
||||
[
|
||||
`curl -X POST ${shellSingleQuote(`${baseUrl.replace(/\/+$/, "")}${endpoint}`)}`,
|
||||
` -H "Authorization: Bearer ${CURL_KEY_PLACEHOLDER}"`,
|
||||
` -H ${shellSingleQuote(`${authHeaderName}: Bearer ${CURL_KEY_PLACEHOLDER}`)}`,
|
||||
` -H "Content-Type: application/json"`,
|
||||
` -d ${shellSingleQuote(prettyPayload(payload))}`,
|
||||
].join(" \\\n");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue