diff --git a/README.md b/README.md index e15341a..a15ec31 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,36 @@ Import the framework in your project: [Create an OpenAI API key](https://platform.openai.com/account/api-keys) and add it to your configuration: -`let openAI = OpenAISwift(authToken: "TOKEN")` +`let openAI: OpenAISwift = OpenAISwift(config: OpenAISwift.Config.makeDefaultOpenAI(apiKey: "TOKEN"))` + + +To follow [OpenAI requirements](https://platform.openai.com/docs/api-reference/authentication) + +> Remember that your API key is a secret! Do not share it with others or expose it in any client-side code (browsers, apps). Production requests must be routed through your own backend server where your API key can be securely loaded from an environment variable or key management service. + +and basic industrial safety you should not call OpenAI API directly. + +```swift +private lazy var proxyOpenAIBackend: OpenAISwift = .init( + config: OpenAISwift.Config( + baseURL: "http://localhost", + endpointPrivider: OpenAIEndpointProvider(source: .proxy(path: { _ -> String in + "/chat/completions" + }, method: { _ -> String in + "POST" + })), + session: session, + authorizeRequest: { [weak self] request in + self?.authorizeRequest(&request) + } + )) + + private func authorizeRequest(_ request: inout URLRequest) { + if let apiKey = try? Encryptor.getApiToken() { + request.setValue(apiKey, forHTTPHeaderField: "X-API-KEY") + } + } +``` This framework supports Swift concurrency; each example below has both an async/await and completion handler variant. diff --git a/Sources/OpenAISwift/Models/ChatMessage.swift b/Sources/OpenAISwift/Models/ChatMessage.swift index c987cf9..a99b0c9 100644 --- a/Sources/OpenAISwift/Models/ChatMessage.swift +++ b/Sources/OpenAISwift/Models/ChatMessage.swift @@ -34,6 +34,18 @@ public struct ChatMessage: Codable, Identifiable { self.role = role self.content = content } + + + // MARK: - Custom Encoding + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(role, forKey: .role) + try container.encodeIfPresent(content, forKey: .content) + } + + private enum CodingKeys: String, CodingKey { + case role, content + } } /// A structure that represents a chat conversation. diff --git a/Sources/OpenAISwift/OpenAISwift.swift b/Sources/OpenAISwift/OpenAISwift.swift index 67090ac..b8f3899 100644 --- a/Sources/OpenAISwift/OpenAISwift.swift +++ b/Sources/OpenAISwift/OpenAISwift.swift @@ -5,6 +5,7 @@ import FoundationXML #endif public enum OpenAIError: Error { + case networkError(code: Int) case genericError(error: Error) case decodingError(error: Error) case chatError(error: ChatError.Payload) @@ -294,11 +295,15 @@ extension OpenAISwift { let task = session.dataTask(with: request) { (data, response, error) in if let error = error { completionHandler(.failure(error)) + } else if let response = response as? HTTPURLResponse, !(200...299).contains(response.statusCode) { + completionHandler(.failure(OpenAIError.networkError(code: response.statusCode))) } else if let data = data { completionHandler(.success(data)) + } else { + let error = NSError(domain: "OpenAI", code: 6666, userInfo: [NSLocalizedDescriptionKey: "Unknown error"]) + completionHandler(.failure(OpenAIError.genericError(error: error))) } } - task.resume() }