mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-29 01:42:19 +00:00
143 lines
6.6 KiB
Swift
143 lines
6.6 KiB
Swift
import Foundation
|
|
|
|
struct GatewayClient: Sendable {
|
|
let baseURL: URL
|
|
|
|
static let defaultBaseURL = URL(string: "https://api.litellm.ai")!
|
|
|
|
static func baseURL(from input: String) -> URL? {
|
|
let trimmed = input.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard var components = URLComponents(string: trimmed),
|
|
let scheme = components.scheme?.lowercased(),
|
|
["http", "https"].contains(scheme),
|
|
let host = components.host,
|
|
!host.isEmpty,
|
|
components.user == nil,
|
|
components.password == nil,
|
|
components.query == nil,
|
|
components.fragment == nil else { return nil }
|
|
while components.path.count > 1, components.path.hasSuffix("/") {
|
|
components.path.removeLast()
|
|
}
|
|
return components.url
|
|
}
|
|
|
|
func login(username: String, password: String) async throws -> AuthSession {
|
|
let url = baseURL.appending(path: "/v2/login")
|
|
var request = URLRequest(url: url)
|
|
request.httpMethod = "POST"
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
request.httpBody = try JSONSerialization.data(withJSONObject: ["username": username, "password": password])
|
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
guard let httpResponse = response as? HTTPURLResponse else { throw GatewayError.invalidResponse }
|
|
guard httpResponse.statusCode == 200 else {
|
|
if httpResponse.statusCode == 401 { throw GatewayError.unauthorized }
|
|
throw GatewayError.server(httpResponse.statusCode)
|
|
}
|
|
let payload = try JSONDecoder().decode(LoginResponse.self, from: data)
|
|
guard let token = payload.token, let session = AuthSession(token: token) else {
|
|
throw GatewayError.invalidLoginResponse
|
|
}
|
|
return session
|
|
}
|
|
|
|
func fetchUserInfo(apiKey: String) async throws -> UserInfoResponse {
|
|
let url = baseURL.appending(path: "/v2/user/info")
|
|
var request = URLRequest(url: url)
|
|
request.httpMethod = "GET"
|
|
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
|
|
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
|
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
guard let httpResponse = response as? HTTPURLResponse else { throw GatewayError.invalidResponse }
|
|
switch httpResponse.statusCode {
|
|
case 200...299: break
|
|
case 401: throw GatewayError.unauthorized
|
|
case 403: throw GatewayError.forbidden
|
|
case 404: throw GatewayError.notFound
|
|
default: throw GatewayError.server(httpResponse.statusCode)
|
|
}
|
|
|
|
return try decoder.decode(UserInfoResponse.self, from: data)
|
|
}
|
|
|
|
func fetchTeamInfo(apiKey: String, teamID: String) async throws -> TeamBudget {
|
|
var components = URLComponents(url: baseURL.appending(path: "/team/info"), resolvingAgainstBaseURL: false)
|
|
components?.queryItems = [URLQueryItem(name: "team_id", value: teamID)]
|
|
guard let url = components?.url else { throw GatewayError.invalidResponse }
|
|
var request = URLRequest(url: url)
|
|
request.httpMethod = "GET"
|
|
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
|
|
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
|
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
guard let httpResponse = response as? HTTPURLResponse else { throw GatewayError.invalidResponse }
|
|
switch httpResponse.statusCode {
|
|
case 200...299: break
|
|
case 401: throw GatewayError.unauthorized
|
|
case 403: throw GatewayError.forbidden
|
|
case 404: throw GatewayError.notFound
|
|
default: throw GatewayError.server(httpResponse.statusCode)
|
|
}
|
|
return try decoder.decode(TeamInfoResponse.self, from: data).teamInfo
|
|
}
|
|
|
|
func fetchKeyList(apiKey: String) async throws -> [String] {
|
|
let url = baseURL.appending(path: "/key/list")
|
|
var request = URLRequest(url: url)
|
|
request.httpMethod = "GET"
|
|
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
|
|
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
|
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
guard let httpResponse = response as? HTTPURLResponse else { throw GatewayError.invalidResponse }
|
|
switch httpResponse.statusCode {
|
|
case 200...299: break
|
|
case 401: throw GatewayError.unauthorized
|
|
case 403: throw GatewayError.forbidden
|
|
case 404: throw GatewayError.notFound
|
|
default: throw GatewayError.server(httpResponse.statusCode)
|
|
}
|
|
return try decoder.decode(KeyListResponse.self, from: data).keys
|
|
}
|
|
|
|
func fetchKeyInfo(apiKey: String, key: String) async throws -> KeyBudget {
|
|
var components = URLComponents(url: baseURL.appending(path: "/key/info"), resolvingAgainstBaseURL: false)
|
|
components?.queryItems = [URLQueryItem(name: "key", value: key)]
|
|
guard let url = components?.url else { throw GatewayError.invalidResponse }
|
|
var request = URLRequest(url: url)
|
|
request.httpMethod = "GET"
|
|
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
|
|
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
|
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
guard let httpResponse = response as? HTTPURLResponse else { throw GatewayError.invalidResponse }
|
|
switch httpResponse.statusCode {
|
|
case 200...299: break
|
|
case 401: throw GatewayError.unauthorized
|
|
case 403: throw GatewayError.forbidden
|
|
case 404: throw GatewayError.notFound
|
|
default: throw GatewayError.server(httpResponse.statusCode)
|
|
}
|
|
return try decoder.decode(KeyInfoResponse.self, from: data).info
|
|
}
|
|
|
|
private var decoder: JSONDecoder {
|
|
let decoder = JSONDecoder()
|
|
decoder.dateDecodingStrategy = .custom { decoder in
|
|
let value = try decoder.singleValueContainer().decode(String.self)
|
|
if let date = ISO8601DateFormatter().date(from: value) { return date }
|
|
let formatter = DateFormatter()
|
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
|
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX"
|
|
guard let date = formatter.date(from: value) else { throw GatewayError.invalidResponse }
|
|
return date
|
|
}
|
|
return decoder
|
|
}
|
|
|
|
}
|
|
|
|
private struct LoginResponse: Decodable {
|
|
let token: String?
|
|
}
|