diff --git a/ui/litellm-dashboard/src/components/organisms/createKeyPayload.test.ts b/ui/litellm-dashboard/src/components/organisms/createKeyPayload.test.ts index 7c6d5def8da..43f0d1ea711 100644 --- a/ui/litellm-dashboard/src/components/organisms/createKeyPayload.test.ts +++ b/ui/litellm-dashboard/src/components/organisms/createKeyPayload.test.ts @@ -167,6 +167,25 @@ describe("key ownership", () => { }); }); +describe("tags", () => { + it("stores the Tags field inside metadata and leaves it off the top level", () => { + const values = { + key_alias: "my-key", + metadata: '{"team":"core","tags":["from-json"]}', + tags: ["production", "pilot"], + }; + const before = structuredClone(values); + const payload = payloadOf(build(values)); + + expect(values).toStrictEqual(before); + expect(payload).not.toHaveProperty("tags"); + expect(JSON.parse(String(payload.metadata))).toEqual({ + team: "core", + tags: ["production", "pilot"], + }); + }); +}); + describe("metadata", () => { it("re-serialises the parsed form value", () => { expect(payloadOf(build({ key_alias: "my-key", metadata: '{"team":"core"}' }))).toStrictEqual( diff --git a/ui/litellm-dashboard/src/components/organisms/createKeyPayload.ts b/ui/litellm-dashboard/src/components/organisms/createKeyPayload.ts index 311e9357825..45f478643d5 100644 --- a/ui/litellm-dashboard/src/components/organisms/createKeyPayload.ts +++ b/ui/litellm-dashboard/src/components/organisms/createKeyPayload.ts @@ -90,6 +90,17 @@ const parseMetadata = (raw: unknown): unknown => { } }; +const selectedTags = (raw: unknown): unknown[] | undefined => (Array.isArray(raw) && raw.length > 0 ? raw : undefined); + +const metadataWithSelectedTags = (parsed: unknown, rawTags: unknown): unknown => { + const tags = selectedTags(rawTags); + if (!tags) return parsed; + if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) { + return { ...parsed, tags }; + } + return { tags }; +}; + const buildMetadataJson = (values: Record, input: KeyCreateInput): string => { const parsed = parseMetadata(values.metadata); if (input.keyOwner === "service_account") { @@ -103,7 +114,7 @@ const buildMetadataJson = (values: Record, input: KeyCreateInpu input.disabledCallbacks.length > 0 ? { ...(logged as object), litellm_disabled_callbacks: mapDisplayToInternalNames(input.disabledCallbacks) } : logged; - return JSON.stringify(disabled); + return JSON.stringify(metadataWithSelectedTags(disabled, values.tags)); }; interface PermissionSources { @@ -183,7 +194,10 @@ export const buildKeyCreatePayload = (input: KeyCreateInput): KeyPayloadResult = const sources = readPermissionSources(values); const objectPermission = buildObjectPermission(sources); - const dropped = consumedSourceKeys(values, sources); + const dropped = new Set([ + ...consumedSourceKeys(values, sources), + ...(selectedTags(values.tags) ? ["tags"] : []), + ]); const duration = values.duration; const validWindows = input.budgetLimits.filter( diff --git a/ui/litellm-dashboard/src/components/organisms/create_key_button.integration.test.tsx b/ui/litellm-dashboard/src/components/organisms/create_key_button.integration.test.tsx index 0fbe043c0d1..dacfc4e33e4 100644 --- a/ui/litellm-dashboard/src/components/organisms/create_key_button.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/organisms/create_key_button.integration.test.tsx @@ -473,7 +473,7 @@ describe("CreateKey", () => { expect(payload.budget_duration).toBeNull(); }); - it("routes typed tags into the tags key", async () => { + it("routes typed tags into metadata, the same place key edit sends them", async () => { state.tags = { production: { name: "production" } }; await openModal(); await nameTheKey(); @@ -481,7 +481,9 @@ describe("CreateKey", () => { await userEvent.type(await screen.findByLabelText("Tags"), "production{Enter}"); await submit(); - expect((await createdPayload()).tags).toStrictEqual(["production"]); + const payload = await createdPayload(); + expect(payload).not.toHaveProperty("tags"); + expect(JSON.parse(String(payload.metadata))).toMatchObject({ tags: ["production"] }); }); it("routes a chosen access group into access_group_ids", async () => {