import { LiteLLMConfig, UserInfo, VirtualKey, ModelInfo, UsageMetrics, TeamInfo, GenerateKeyRequest, GenerateKeyResponse, UpdateKeyRequest, DeleteKeyRequest, CreateUserRequest, CreateUserResponse, CreateTeamRequest, CreateTeamResponse, UpdateTeamRequest, AuditLogsParams, PaginatedAuditLogs, SpendLogEntry, SpendLogsParams } from './types'; /** * LiteLLM versions differ on `request_tags`: an array of `k:v` strings in * current builds, but historically an object map. Normalise both to a flat * string array so callers can `includes('session:...')` uniformly. */ export declare function normalizeRequestTags(tags: SpendLogEntry['request_tags']): string[]; /** * Typed error for failed upstream LiteLLM responses. Preserves the HTTP * status and the structured `param` (e.g. `key_alias`) from the upstream * body so the router can surface a 400 instead of collapsing everything * into a 500. The message is the upstream `error.message` when present, * otherwise the raw body text. */ export declare class LiteLLMUpstreamError extends Error { status: number; param?: string; constructor(status: number, statusText: string, body: string); } export declare class LiteLLMClient { private baseUrl; private masterKey; private timeout; constructor(config: LiteLLMConfig, timeout?: number); private request; /** * Returns null when the user is not found in LiteLLM (404). * Throws on all other errors so callers know something went wrong. * * LiteLLM's `/user/info` wraps the user row inside `user_info` and returns * `teams` as an array of full team objects, not team_id strings. We flatten * `user_info` onto the top level and reduce `teams` to a string[] of ids so * the rest of the code can rely on the UserInfo contract. */ getUserInfo(userId?: string): Promise; createUser(payload: CreateUserRequest): Promise; /** * Updates an existing LiteLLM user record. Used as a defensive follow-up * after /user/new because the upsert path of /user/new has been observed * to silently drop fields like user_role under concurrent inserts. */ updateUser(payload: Partial & { user_id: string; }): Promise; /** * Returns the keys belonging to a user. * * Implementation note: LiteLLM's `/key/info` endpoint requires a `key` * hash and returns 404 when only `user_id` is passed. The correct way * to enumerate a user's keys is `/user/info?user_id=X`, which embeds * a `keys` array with per-key metadata. We unwrap that array and * normalise field names to match the frontend VirtualKey shape * (LiteLLM exposes `key_name` for the masked display value and * `expires` instead of `expires_at`). */ listKeys(userId?: string): Promise; private toVirtualKey; /** * Creates a new virtual key on the LiteLLM proxy. * * Implementation notes — both required to avoid silently-empty keys: * 1. The body must be the plain payload. An earlier version wrapped * it as `{ json: request }`; LiteLLM doesn't unwrap that envelope * and treats the request as having no fields, returning a key * with null alias / models / budget / limits. * 2. LiteLLM expects `key_alias`, not `alias`. Without the rename, * the alias the user typed is dropped on the floor. */ generateKey(request: GenerateKeyRequest): Promise; updateKey(request: UpdateKeyRequest): Promise; deleteKeys(request: DeleteKeyRequest): Promise<{ success: boolean; }>; blockKey(key: string): Promise; unblockKey(key: string): Promise; resetKeySpend(key: string): Promise; getAuditLogs(params: AuditLogsParams): Promise; /** * Returns the proxy's model catalogue normalised to the ModelInfo shape. * * Prefers `/model/info` which exposes `model_name`, `mode`, capability flags * and per-token costs. Falls back to OpenAI-compatible `/models` (which only * returns `{id}`) so the dropdown still works on installs where `/model/info` * isn't reachable. Without this normalisation the UI saw blank labels and * a single "other" group because `/models` doesn't populate `model_name` * or `mode`. */ listModels(): Promise; /** * LiteLLM's `/team/info` wraps the team row inside `team_info` (alongside * sibling `keys` / `team_memberships` arrays), the same shape as * `/user/info` wrapping the user row inside `user_info`. Without unwrapping, * `team_alias`, `members_with_roles`, `models`, budgets and limits are all * undefined, so the UI fell back to displaying the raw team_id / "Untitled * team". */ getTeamInfo(teamId: string): Promise; /** * Normalises a single team row into the TeamInfo contract. Tolerates both * the `/team/info` shape (row wrapped in a `team_info` envelope alongside * sibling arrays) and a bare row as returned per-item by `/team/list`. * `metadata` is always surfaced so callers can read `owning_group`. */ private toTeamInfo; /** * Lists every team known to the LiteLLM proxy. Tolerates the observed * response shapes — a bare array, `{ teams: [...] }`, or `{ data: [...] }` — * and normalises each row through the same unwrap as `getTeamInfo`. * * This returns the GLOBAL team list and must never be exposed directly to * end users; callers are responsible for scoping the result (e.g. to teams * whose `metadata.owning_group` matches the caller's admin group). */ listTeams(): Promise; createTeam(payload: CreateTeamRequest): Promise; updateTeam(payload: UpdateTeamRequest): Promise; deleteTeam(teamId: string): Promise; blockTeam(teamId: string): Promise; unblockTeam(teamId: string): Promise; /** * Adds a member to a team. LiteLLM's `/team/member_add` nests the member * under a `member` object; `role` is 'user' or 'admin' (we only ever send * 'user' from Backstage). `max_budget_in_team` optionally caps that member's * spend within the team. */ teamMemberAdd(payload: { team_id: string; user_id: string; role?: 'user'; max_budget_in_team?: number; }): Promise; /** Removes a member from a team via LiteLLM's `/team/member_delete`. */ teamMemberDelete(payload: { team_id: string; user_id: string; }): Promise; /** * Lists the vector stores (knowledge bases) registered on the LiteLLM proxy. * Tolerates a bare array, `{ data: [...] }`, or `{ vector_stores: [...] }`. * Each entry is normalised to `{ id, name? }` — `id` prefers * `vector_store_id` then `id` then `name`. */ listVectorStores(): Promise>; /** * Lists the MCP servers registered on the LiteLLM proxy. Path is * `/mcp/server/list`; tolerates a bare array, `{ data: [...] }` or * `{ servers: [...] }`. Normalised to `{ id, name?, url? }` — `id` prefers * `server_id` then `id` then `alias`/`name`. */ listMcpServers(): Promise>; /** * Reads per-request spend logs from LiteLLM (`GET /spend/logs`), filtered * by date range and optionally by key/user/team. Rows are normalised so * `request_tags` is always a `string[]` (LiteLLM returns either an array * or an object depending on version) — callers group by tag to attribute * spend to a conversation or caller. * * This is the admin/DB-backed endpoint: it can return a large number of * rows, so callers should keep the date window tight and cap `page_size`. */ getSpendLogs(params: SpendLogsParams): Promise; private emptyUsage; /** * Transforms LiteLLM's SpendAnalyticsPaginatedResponse into the flatter * UsageMetrics shape consumed by the frontend charts. * * Source shape (per result row): * { date, metrics, breakdown: { models: { [name]: { metrics, api_key_breakdown: { [keyHash]: { metrics, metadata } } } } } } * * We fan that out into three views the UI consumes: * - daily_usage → spend + request trends over time * - usage_by_model → which models drove cost / traffic * - usage_by_key → which keys drove cost / traffic (with key_alias + team_id from metadata) */ private transformDailyActivity; getUsage(startDate: string, endDate: string, userId?: string): Promise; getTeamUsage(teamId: string, startDate: string, endDate: string): Promise; }