feat(ui): send logging_exporters as a typed field; picker trusts backend scoping

Assignments now live on a typed logging_exporters column, so the key/team/org
create and update forms send logging_exporters as a top-level field instead of
packing it into the metadata JSON blob, and the info views read it off the object
(via loggingExportersOf, column-first with a metadata fallback for rows written
before the column existed).

The destination picker no longer filters options by the caller's role client-side.
GET /credentials is already scoped server-side by the same predicate the assignment
gate and the resolver use, so the picker renders exactly what the backend returns;
the previous isAdminRole short-circuit treated org-admins and admin-viewers as
'see every destination', which was broader than the backend's proxy-admin-only
'see all'. Regenerated schema.d.ts for the new field.
This commit is contained in:
yucheng-berriai 2026-07-02 00:22:33 -07:00
parent a86e333832
commit 9733ce5809
11 changed files with 126 additions and 162 deletions

View file

@ -470,21 +470,12 @@ const Teams: React.FC<TeamProps> = ({ accessToken, userID, userRole, premiumUser
formValues.metadata = JSON.stringify(metadata);
}
// Merge admin-owned logging_exporters into metadata at create time so the
// user can assign destinations from the new-team form (instead of create-then-edit).
if (Array.isArray(formValues.logging_exporters) && formValues.logging_exporters.length > 0) {
let metadata: Record<string, unknown> = {};
if (typeof formValues.metadata === "string" && formValues.metadata.trim().length > 0) {
try {
metadata = JSON.parse(formValues.metadata);
} catch (e) {
console.warn("Invalid JSON in metadata field, starting with empty object");
}
}
metadata = { ...metadata, logging_exporters: formValues.logging_exporters };
formValues.metadata = JSON.stringify(metadata);
// logging_exporters is a top-level typed field on the team (its own column),
// not part of the free-form metadata blob; send it as-is when set so the user
// can assign destinations from the new-team form (instead of create-then-edit).
if (!Array.isArray(formValues.logging_exporters) || formValues.logging_exporters.length === 0) {
delete formValues.logging_exporters;
}
delete formValues.logging_exporters;
if (formValues.secret_manager_settings) {
if (typeof formValues.secret_manager_settings === "string") {

View file

@ -4,22 +4,10 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import LoggingExportersSelect from "./LoggingExportersSelect";
const mockUseCredentials = vi.fn();
const mockUseAuthorized = vi.fn();
const mockUseTeams = vi.fn();
const mockUseOrganizations = vi.fn();
vi.mock("@/app/(dashboard)/hooks/credentials/useCredentials", () => ({
useCredentials: () => mockUseCredentials(),
}));
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
default: () => mockUseAuthorized(),
}));
vi.mock("@/app/(dashboard)/hooks/teams/useTeams", () => ({
useTeams: () => mockUseTeams(),
}));
vi.mock("@/app/(dashboard)/hooks/organizations/useOrganizations", () => ({
useOrganizations: () => mockUseOrganizations(),
}));
vi.mock("antd", async () => {
const React = await import("react");
@ -48,10 +36,7 @@ vi.mock("antd", async () => {
});
beforeEach(() => {
// Default: a proxy admin (formatted role "Admin"), no team/org membership needed.
mockUseAuthorized.mockReturnValue({ userRole: "Admin" });
mockUseTeams.mockReturnValue({ data: [] });
mockUseOrganizations.mockReturnValue({ data: [] });
mockUseCredentials.mockReset();
});
describe("LoggingExportersSelect", () => {
@ -99,32 +84,21 @@ describe("LoggingExportersSelect", () => {
expect(screen.getByTestId("empty").textContent).toMatch(/proxy admin/i);
});
it("scopes a non-admin caller to destinations granted to their team/org (plus global/auto_enable)", () => {
// An internal_user who is a member of team-a. They must see only what they could
// actually assign: the team-a destination, the global one, and the auto_enable
// default -- never the team-b destination or the foreign-org one. This mirrors the
// backend assignment gate; the backend stays the authoritative check.
mockUseAuthorized.mockReturnValue({ userRole: "Internal User" });
mockUseTeams.mockReturnValue({ data: [{ team_id: "team-a" }] });
mockUseOrganizations.mockReturnValue({ data: [{ organization_id: "org-a" }] });
it("shows exactly the logging destinations the backend returned, without any client-side scope filtering", () => {
// GET /credentials is already scoped server-side (proxy admin -> all; team/org
// admin -> only in-scope destinations) by the same predicate the assignment gate
// and the resolver use. The picker must therefore render every logging-typed
// destination in the response verbatim; re-filtering here by role/scope on the
// client would risk disagreeing with the authoritative backend in either
// direction. This response mixes access shapes to prove none are dropped locally.
mockUseCredentials.mockReturnValue({
data: {
credentials: [
{
credential_name: "mine-team",
credential_info: { credential_type: "logging", access: { teams: ["team-a"] } },
},
{
credential_name: "foreign-team",
credential_info: { credential_type: "logging", access: { teams: ["team-b"] } },
},
{ credential_name: "mine-org", credential_info: { credential_type: "logging", access: { orgs: ["org-a"] } } },
{
credential_name: "foreign-org",
credential_info: { credential_type: "logging", access: { orgs: ["org-z"] } },
},
{ credential_name: "team-scoped", credential_info: { credential_type: "logging", access: { teams: ["t"] } } },
{ credential_name: "org-scoped", credential_info: { credential_type: "logging", access: { orgs: ["o"] } } },
{ credential_name: "everyone", credential_info: { credential_type: "logging", access: { global: true } } },
{ credential_name: "always-on", credential_info: { credential_type: "logging", auto_enable: true } },
{ credential_name: "provider", credential_info: { custom_llm_provider: "openai" } },
],
},
});
@ -132,27 +106,7 @@ describe("LoggingExportersSelect", () => {
render(<LoggingExportersSelect value={[]} onChange={() => {}} />);
const options = screen.getAllByTestId("option").map((el) => el.textContent);
expect(options).toEqual(["mine-team", "mine-org", "everyone", "always-on"]);
});
it("shows every logging destination to a proxy admin regardless of access scope", () => {
mockUseAuthorized.mockReturnValue({ userRole: "Admin" });
mockUseTeams.mockReturnValue({ data: [] });
mockUseCredentials.mockReturnValue({
data: {
credentials: [
{
credential_name: "team-b-only",
credential_info: { credential_type: "logging", access: { teams: ["team-b"] } },
},
{ credential_name: "no-access", credential_info: { credential_type: "logging" } },
],
},
});
render(<LoggingExportersSelect value={[]} onChange={() => {}} />);
const options = screen.getAllByTestId("option").map((el) => el.textContent);
expect(options).toEqual(["team-b-only", "no-access"]);
// every logging destination the backend returned, and only those (provider dropped)
expect(options).toEqual(["team-scoped", "org-scoped", "everyone", "always-on"]);
});
});

View file

@ -2,10 +2,6 @@ import { Select } from "antd";
import React from "react";
import { useCredentials } from "@/app/(dashboard)/hooks/credentials/useCredentials";
import { useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { isAdminRole } from "@/utils/roles";
interface LoggingExportersSelectProps {
value?: string[];
@ -14,39 +10,22 @@ interface LoggingExportersSelectProps {
/**
* Multi-select of admin-owned logging destinations (credential_type=logging) that an
* identity (key / team / org) exports its traces to. The selected names are stored in
* metadata.logging_exporters; the proxy unions them across the identity chain and fans
* out.
* identity (key / team / org) exports its traces to. The selected names are persisted to
* the identity's logging_exporters column; the proxy unions them across the identity
* chain and fans out.
*
* Options are scoped to what the caller can actually assign: a proxy admin sees every
* destination; everyone else sees only the ones visible to a team or org they belong to
* (plus global / auto_enable destinations). This mirrors the backend assignment gate so
* a team admin is not offered another tenant's destination only to have the save
* rejected, and it avoids surfacing other tenants' destination names. The backend stays
* the authoritative check -- this filter is UX, not a security boundary.
* The options are exactly what GET /credentials returns for the caller, which the backend
* already scopes: a proxy admin receives every destination, while a team or org admin
* receives only the destinations granted to a scope they administer. Visibility is
* enforced server-side by the same predicate the assignment gate and the request-time
* resolver use, so this component does no role-based filtering of its own; doing so would
* risk disagreeing with the backend in either direction.
*/
const LoggingExportersSelect: React.FC<LoggingExportersSelectProps> = ({ value, onChange }) => {
const { data } = useCredentials();
const { userRole } = useAuthorized();
const { data: teams } = useTeams();
const { data: orgs } = useOrganizations();
const seesEveryDestination = isAdminRole(userRole ?? "");
const myTeamIds = new Set((teams ?? []).map((t) => t.team_id));
const myOrgIds = new Set((orgs ?? []).map((o) => o.organization_id));
const assignable = (info: {
auto_enable?: boolean;
access?: { global?: boolean; teams?: string[]; orgs?: string[] };
}) => {
if (info.auto_enable === true || info.access?.global === true) return true;
if ((info.access?.teams ?? []).some((id) => myTeamIds.has(id))) return true;
return (info.access?.orgs ?? []).some((id) => myOrgIds.has(id));
};
const options = (data?.credentials ?? [])
.filter((credential) => credential.credential_info?.credential_type === "logging")
.filter((credential) => seesEveryDestination || assignable(credential.credential_info))
.map((credential) => ({
value: credential.credential_name,
label: credential.credential_info?.host

View file

@ -0,0 +1,18 @@
/**
* The admin-owned logging destinations assigned to an identity (key / team / org).
*
* Assignments live on a typed logging_exporters column, surfaced at the top level of
* the API object. A metadata.logging_exporters fallback is kept only so a row written
* before the column existed still renders; the column is the source of truth.
*/
export const loggingExportersOf = (obj: unknown): string[] => {
const record = obj as {
logging_exporters?: unknown;
metadata?: { logging_exporters?: unknown } | null;
} | null;
if (Array.isArray(record?.logging_exporters)) {
return record.logging_exporters as string[];
}
const fromMetadata = record?.metadata?.logging_exporters;
return Array.isArray(fromMetadata) ? (fromMetadata as string[]) : [];
};

View file

@ -13,6 +13,7 @@ import MemberTable from "../common_components/MemberTable";
import UserSearchModal from "../common_components/user_search_modal";
import MCPServerSelector from "../mcp_server_management/MCPServerSelector";
import LoggingExportersSelect from "../logging_credentials/LoggingExportersSelect";
import { loggingExportersOf } from "../logging_credentials/loggingExportersOf";
import { useCredentials } from "@/app/(dashboard)/hooks/credentials/useCredentials";
import { ModelSelect } from "../ModelSelect/ModelSelect";
import NotificationsManager from "../molecules/notifications_manager";
@ -155,10 +156,9 @@ const OrganizationInfoView: React.FC<OrganizationInfoProps> = ({
max_budget: values.max_budget,
budget_duration: values.budget_duration,
},
metadata: {
...(values.metadata ? JSON.parse(values.metadata) : {}),
...(values.logging_exporters !== undefined ? { logging_exporters: values.logging_exporters } : {}),
},
metadata: values.metadata ? JSON.parse(values.metadata) : {},
// logging_exporters is a top-level typed column on the org, not metadata.
...(values.logging_exporters !== undefined ? { logging_exporters: values.logging_exporters } : {}),
};
// Handle object_permission updates
@ -336,9 +336,7 @@ const OrganizationInfoView: React.FC<OrganizationInfoProps> = ({
<Text>Logging Exporters</Text>
<div className="mt-2 flex flex-wrap gap-2">
{(() => {
const own = Array.isArray(orgData.metadata?.logging_exporters)
? (orgData.metadata.logging_exporters as string[])
: [];
const own = loggingExportersOf(orgData);
const ownSet = new Set(own);
const scopedOnly = scopedExportersForOrg.filter((n) => !ownSet.has(n));
const all = [
@ -416,7 +414,7 @@ const OrganizationInfoView: React.FC<OrganizationInfoProps> = ({
max_budget: orgData.litellm_budget_table.max_budget,
budget_duration: orgData.litellm_budget_table.budget_duration,
metadata: orgData.metadata ? JSON.stringify(orgData.metadata, null, 2) : "",
logging_exporters: orgData.metadata?.logging_exporters || [],
logging_exporters: loggingExportersOf(orgData),
vector_stores: orgData.object_permission?.vector_stores || [],
mcp_servers_and_groups: {
servers: orgData.object_permission?.mcp_servers || [],
@ -553,9 +551,7 @@ const OrganizationInfoView: React.FC<OrganizationInfoProps> = ({
<div>
<Text className="font-medium">Logging Exporters</Text>
{(() => {
const own = Array.isArray(orgData.metadata?.logging_exporters)
? (orgData.metadata.logging_exporters as string[])
: [];
const own = loggingExportersOf(orgData);
const ownSet = new Set(own);
const scopedOnly = scopedExportersForOrg.filter((n) => !ownSet.has(n));
const all = [

View file

@ -159,16 +159,11 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
}
}
if (Array.isArray(values.logging_exporters) && values.logging_exporters.length > 0) {
let existingMetadata: Record<string, unknown> = {};
if (typeof values.metadata === "string" && values.metadata.trim().length > 0) {
existingMetadata = JSON.parse(values.metadata);
} else if (values.metadata && typeof values.metadata === "object") {
existingMetadata = values.metadata;
}
values.metadata = { ...existingMetadata, logging_exporters: values.logging_exporters };
// logging_exporters is a top-level typed field on the org (its own column),
// not part of the free-form metadata blob; send it as-is when set.
if (!Array.isArray(values.logging_exporters) || values.logging_exporters.length === 0) {
delete values.logging_exporters;
}
delete values.logging_exporters;
await organizationCreateCall(accessToken, values);
NotificationsManager.success("Organization created successfully");

View file

@ -46,6 +46,7 @@ import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
import { useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
import EditLoggingCredentialModal from "./logging_credentials/EditLoggingCredentialModal";
import AccessControlFields from "./logging_credentials/AccessControlFields";
import { loggingExportersOf } from "./logging_credentials/loggingExportersOf";
import {
backendLabel,
createLoggingCredential,
@ -295,15 +296,14 @@ const Settings: React.FC<SettingsPageProps> = ({ accessToken, userRole, userID,
for (const teamId of access?.teams ?? []) teams.add(teamAlias(teamId));
for (const orgId of access?.orgs ?? []) orgs.add(orgAlias(orgId));
for (const team of teamsData ?? []) {
const teamMetadata = (team as { metadata?: Record<string, unknown> | null }).metadata;
const exporters = teamMetadata?.logging_exporters;
if (Array.isArray(exporters) && exporters.includes(destinationName)) {
const exporters = loggingExportersOf(team);
if (exporters.includes(destinationName)) {
teams.add(team.team_alias || team.team_id);
}
}
for (const org of orgsData ?? []) {
const exporters = (org.metadata as Record<string, unknown> | null | undefined)?.logging_exporters;
if (Array.isArray(exporters) && exporters.includes(destinationName)) {
const exporters = loggingExportersOf(org);
if (exporters.includes(destinationName)) {
orgs.add(org.organization_alias || org.organization_id);
}
}

View file

@ -53,6 +53,7 @@ import VectorStoreSelector from "../vector_store_management/VectorStoreSelector"
import SearchToolSelector from "../SearchTools/SearchToolSelector";
import EditLoggingSettings from "./EditLoggingSettings";
import LoggingExportersSelect from "../logging_credentials/LoggingExportersSelect";
import { loggingExportersOf } from "../logging_credentials/loggingExportersOf";
import RouterSettingsAccordion, { RouterSettingsAccordionRef } from "../common_components/RouterSettingsAccordion";
import MemberModal from "./EditMembership";
import MemberPermissions from "./member_permissions";
@ -545,13 +546,14 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
max_budget: values.max_budget,
soft_budget: sanitizeNumeric(values.soft_budget),
budget_duration: values.budget_duration,
// logging_exporters is a top-level typed column on the team, not metadata.
...(values.logging_exporters !== undefined ? { logging_exporters: values.logging_exporters } : {}),
metadata: {
...parsedMetadata,
...passthroughRoutesMetadata,
guardrails: (values.guardrails || []).filter((n: string) => !globalGuardrailNames.has(n)),
opted_out_global_guardrails: optedOutGlobalGuardrails,
...(values.logging_settings?.length > 0 ? { logging: values.logging_settings } : {}),
...(values.logging_exporters !== undefined ? { logging_exporters: values.logging_exporters } : {}),
disable_global_guardrails: killSwitchOnAtSave,
soft_budget_alerting_emails:
typeof values.soft_budget_alerting_emails === "string"
@ -888,9 +890,7 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
<LoggingSettingsView
loggingConfigs={info.metadata?.logging || []}
loggingExporters={
Array.isArray(info.metadata?.logging_exporters) ? info.metadata.logging_exporters : []
}
loggingExporters={loggingExportersOf(info)}
scopedExporters={scopedExportersForTeam}
disabledCallbacks={[]}
variant="card"
@ -1004,7 +1004,7 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
)
: "",
logging_settings: info.metadata?.logging || [],
logging_exporters: info.metadata?.logging_exporters || [],
logging_exporters: loggingExportersOf(info),
secret_manager_settings: info.metadata?.secret_manager_settings
? JSON.stringify(info.metadata.secret_manager_settings, null, 2)
: "",
@ -1678,9 +1678,7 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
<LoggingSettingsView
loggingConfigs={info.metadata?.logging || []}
loggingExporters={
Array.isArray(info.metadata?.logging_exporters) ? info.metadata.logging_exporters : []
}
loggingExporters={loggingExportersOf(info)}
scopedExporters={scopedExportersForTeam}
disabledCallbacks={[]}
variant="inline"

View file

@ -28,6 +28,7 @@ import NumericalInput from "../shared/numerical_input";
import { Tag } from "../tag_management/types";
import EditLoggingSettings from "../team/EditLoggingSettings";
import LoggingExportersSelect from "../logging_credentials/LoggingExportersSelect";
import { loggingExportersOf } from "../logging_credentials/loggingExportersOf";
import VectorStoreSelector from "../vector_store_management/VectorStoreSelector";
interface KeyEditViewProps {
@ -191,7 +192,7 @@ export function KeyEditView({
accessGroups: keyData.object_permission?.agent_access_groups || [],
},
logging_settings: extractLoggingSettings(keyData.metadata),
logging_exporters: keyData.metadata?.logging_exporters || [],
logging_exporters: loggingExportersOf(keyData),
disabled_callbacks: Array.isArray(keyData.metadata?.litellm_disabled_callbacks)
? mapInternalToDisplayNames(keyData.metadata.litellm_disabled_callbacks)
: [],
@ -221,7 +222,7 @@ export function KeyEditView({
},
mcp_tool_permissions: keyData.object_permission?.mcp_tool_permissions || {},
logging_settings: extractLoggingSettings(keyData.metadata),
logging_exporters: keyData.metadata?.logging_exporters || [],
logging_exporters: loggingExportersOf(keyData),
disabled_callbacks: Array.isArray(keyData.metadata?.litellm_disabled_callbacks)
? mapInternalToDisplayNames(keyData.metadata.litellm_disabled_callbacks)
: [],

View file

@ -16,6 +16,7 @@ import DeleteResourceModal from "../common_components/DeleteResourceModal";
import { extractLoggingSettings, formatMetadataForDisplay, stripTagsFromMetadata } from "../key_info_utils";
import { KeyResponse } from "../key_team_helpers/key_list";
import LoggingSettingsView from "../logging_settings_view";
import { loggingExportersOf } from "../logging_credentials/loggingExportersOf";
import { useCredentials } from "@/app/(dashboard)/hooks/credentials/useCredentials";
import { useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
import NotificationManager from "../molecules/notifications_manager";
@ -278,7 +279,6 @@ export default function KeyInfoView({
...(Array.isArray(formValues.logging_settings) && formValues.logging_settings.length > 0
? { logging: formValues.logging_settings }
: {}),
...(formValues.logging_exporters !== undefined ? { logging_exporters: formValues.logging_exporters } : {}),
...(formValues.disabled_callbacks?.length > 0
? {
litellm_disabled_callbacks: mapDisplayToInternalNames(formValues.disabled_callbacks),
@ -300,7 +300,6 @@ export default function KeyInfoView({
...(Array.isArray(formValues.logging_settings) && formValues.logging_settings.length > 0
? { logging: formValues.logging_settings }
: {}),
...(formValues.logging_exporters !== undefined ? { logging_exporters: formValues.logging_exporters } : {}),
...(formValues.disabled_callbacks?.length > 0
? {
litellm_disabled_callbacks: mapDisplayToInternalNames(formValues.disabled_callbacks),
@ -643,11 +642,7 @@ export default function KeyInfoView({
<LoggingSettingsView
loggingConfigs={extractLoggingSettings(currentKeyData.metadata)}
loggingExporters={
Array.isArray(currentKeyData.metadata?.logging_exporters)
? currentKeyData.metadata.logging_exporters
: []
}
loggingExporters={loggingExportersOf(currentKeyData)}
scopedExporters={scopedExportersForKey}
disabledCallbacks={
Array.isArray(currentKeyData.metadata?.litellm_disabled_callbacks)
@ -897,11 +892,7 @@ export default function KeyInfoView({
<LoggingSettingsView
loggingConfigs={extractLoggingSettings(currentKeyData.metadata)}
loggingExporters={
Array.isArray(currentKeyData.metadata?.logging_exporters)
? currentKeyData.metadata.logging_exporters
: []
}
loggingExporters={loggingExportersOf(currentKeyData)}
scopedExporters={scopedExportersForKey}
disabledCallbacks={
Array.isArray(currentKeyData.metadata?.litellm_disabled_callbacks)

View file

@ -2424,12 +2424,13 @@ export interface paths {
* Get Credentials
* @description [BETA] endpoint. This might change unexpectedly.
*
* Proxy admins see every credential (values masked). Team-admins and
* org-admins see only logging-typed destinations so they can self-assign
* them; provider credentials stay invisible to non-PROXY_ADMINs. Plain
* internal users with no team-admin or org-admin status get 403 they
* have no use for the list and shouldn't see destination names, hosts,
* or scope metadata (Veria F2).
* Proxy admins see every credential (values masked). A non-proxy-admin sees
* only the logging destinations actually visible to a scope they administer:
* the same ``is_destination_visible`` predicate the assignment validator and
* the request-time resolver use, so the list can never show a destination a
* caller could neither assign nor route to. Provider credentials, and logging
* destinations scoped to other tenants, stay invisible. A caller who
* administers nothing gets 403 (Veria F2).
*/
get: operations["get_credentials_credentials_get"];
put?: never;
@ -23530,6 +23531,8 @@ export interface components {
* @default default
*/
key_type: components["schemas"]["LiteLLMKeyType"] | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -23675,6 +23678,8 @@ export interface components {
key_name?: string | null;
/** Litellm Budget Table */
litellm_budget_table?: unknown | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -24523,6 +24528,8 @@ export interface components {
/** Litellm Changed By */
litellm_changed_by?: string | null;
litellm_model_table?: components["schemas"]["LiteLLM_ModelTable"] | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -24670,6 +24677,8 @@ export interface components {
} | null;
/** Litellm Changed By */
litellm_changed_by?: string | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -25140,6 +25149,8 @@ export interface components {
/** Created By */
created_by: string;
litellm_budget_table?: components["schemas"]["LiteLLM_BudgetTable"] | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/**
* Members
* @default []
@ -25683,6 +25694,8 @@ export interface components {
/** Default Team Member Models */
default_team_member_models?: string[] | null;
litellm_model_table?: components["schemas"]["LiteLLM_ModelTable"] | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -26039,6 +26052,8 @@ export interface components {
litellm_budget_table?: {
[key: string]: unknown;
} | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -27455,6 +27470,8 @@ export interface components {
budget_duration?: string | null;
/** Budget Id */
budget_id?: string | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -27504,6 +27521,8 @@ export interface components {
/** Created By */
created_by: string;
litellm_budget_table?: components["schemas"]["LiteLLM_BudgetTable"] | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Metadata */
metadata?: {
[key: string]: unknown;
@ -27706,6 +27725,8 @@ export interface components {
} | null;
/** Guardrails */
guardrails?: string[] | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Mcp Rpm Limit */
@ -27827,6 +27848,8 @@ export interface components {
guardrails?: string[] | null;
/** Key Alias */
key_alias?: string | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -27979,6 +28002,8 @@ export interface components {
key_name?: string | null;
/** Litellm Budget Table */
litellm_budget_table?: unknown | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -29594,6 +29619,8 @@ export interface components {
* @default default
*/
key_type: components["schemas"]["LiteLLMKeyType"] | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -30636,6 +30663,8 @@ export interface components {
/** Default Team Member Models */
default_team_member_models?: string[] | null;
litellm_model_table?: components["schemas"]["LiteLLM_ModelTable"] | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -30751,6 +30780,8 @@ export interface components {
*/
keys_count: number;
litellm_model_table?: components["schemas"]["LiteLLM_ModelTable"] | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -31559,6 +31590,8 @@ export interface components {
key: string;
/** Key Alias */
key_alias?: string | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -31908,6 +31941,8 @@ export interface components {
} | null;
/** Guardrails */
guardrails?: string[] | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Mcp Rpm Limit */
@ -32014,6 +32049,8 @@ export interface components {
guardrails?: string[] | null;
/** Key Alias */
key_alias?: string | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -32112,6 +32149,8 @@ export interface components {
guardrails?: string[] | null;
/** Key Alias */
key_alias?: string | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -32385,6 +32424,8 @@ export interface components {
litellm_budget_table?: {
[key: string]: unknown;
} | null;
/** Logging Exporters */
logging_exporters?: string[] | null;
/** Max Budget */
max_budget?: number | null;
/** Max Parallel Requests */
@ -43825,13 +43866,13 @@ export interface operations {
/**
* @description Unified rate-limit error.
*
* Every rate-limit condition surfaced by litellm whether it originated from
* an upstream LLM provider, a vendor batch endpoint, or one of litellm's own
* proxy-side limiters (parallel-requests, dynamic-rate, batch-rate, budget,
* max-iterations, etc.) is raised as an instance of this class.
* Every rate-limit condition surfaced by litellm whether it originated from
* an upstream LLM provider, a vendor batch endpoint, or one of litellm's own
* proxy-side limiters (parallel-requests, dynamic-rate, batch-rate, budget,
* max-iterations, etc.) is raised as an instance of this class.
*
* The :attr:`category` attribute lets callers distinguish the source. See
* :class:`RateLimitErrorCategory` for the available values.
* The :attr:`category` attribute lets callers distinguish the source. See
* :class:`RateLimitErrorCategory` for the available values.
*/
429: {
headers: {