From 05e6dac531ab4d437c702c266e38f6c3b1da55ba Mon Sep 17 00:00:00 2001 From: Eric Provencher Date: Fri, 21 Feb 2025 16:39:40 -0500 Subject: [PATCH] Fix issue where path components are discarded for custom URLs --- .../OpenAI/Private/Networking/Endpoint.swift | 86 ++++++++++++------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/Sources/OpenAI/Private/Networking/Endpoint.swift b/Sources/OpenAI/Private/Networking/Endpoint.swift index 3b79a9c..ec7957a 100644 --- a/Sources/OpenAI/Private/Networking/Endpoint.swift +++ b/Sources/OpenAI/Private/Networking/Endpoint.swift @@ -41,38 +41,60 @@ extension Endpoint { return components } - func request( - apiKey: Authorization, - openAIEnvironment: OpenAIEnvironment, - organizationID: String?, - method: HTTPMethod, - params: Encodable? = nil, - queryItems: [URLQueryItem] = [], - betaHeaderField: String? = nil, - extraHeaders: [String: String]? = nil) - throws -> URLRequest - { - let finalPath = path(in: openAIEnvironment) - var request = URLRequest(url: urlComponents(base: openAIEnvironment.baseURL, path: finalPath, queryItems: queryItems).url!) - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - request.addValue(apiKey.value, forHTTPHeaderField: apiKey.headerField) - if let organizationID { - request.addValue(organizationID, forHTTPHeaderField: "OpenAI-Organization") - } - if let betaHeaderField { - request.addValue(betaHeaderField, forHTTPHeaderField: "OpenAI-Beta") - } - if let extraHeaders { - for header in extraHeaders { - request.addValue(header.value, forHTTPHeaderField: header.key) - } - } - request.httpMethod = method.rawValue - if let params { - request.httpBody = try JSONEncoder().encode(params) - } - return request - } + func request( + apiKey: Authorization, + openAIEnvironment: OpenAIEnvironment, + organizationID: String?, + method: HTTPMethod, + params: Encodable? = nil, + queryItems: [URLQueryItem] = [], + betaHeaderField: String? = nil, + extraHeaders: [String: String]? = nil + ) throws -> URLRequest { + // Use path(in:) for flexibility, but ensure correct appending + let finalPath = path(in: openAIEnvironment) // Typically "/v1/chat/completions" + + // Construct URL components + var components = URLComponents(string: openAIEnvironment.baseURL)! + let currentPath = components.path.isEmpty ? "" : components.path + components.path = currentPath + (finalPath.hasPrefix("/") ? "" : "/") + finalPath + if !queryItems.isEmpty { + components.queryItems = queryItems + } + + // Validate and create request + guard let url = components.url else { + throw URLError(.badURL) + } + var request = URLRequest(url: url) + + // Set headers + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue(apiKey.value, forHTTPHeaderField: apiKey.headerField) + if let organizationID { + request.setValue(organizationID, forHTTPHeaderField: "OpenAI-Organization") + } + if let betaHeaderField { + request.setValue(betaHeaderField, forHTTPHeaderField: "OpenAI-Beta") + } + if let extraHeaders { + extraHeaders.forEach { request.setValue($1, forHTTPHeaderField: $0) } + } + + // Set method and body + request.httpMethod = method.rawValue + if let params { + let encoder = JSONEncoder() + let encodedData = try encoder.encode(params) + if let jsonString = String(data: encodedData, encoding: .utf8) { + request.httpBody = Data(jsonString.replacingOccurrences(of: "\\/", with: "/").utf8) + } else { + throw EncodingError.invalidValue(params, EncodingError.Context(codingPath: [], debugDescription: "Failed to convert encoded data to UTF-8 string")) + } + } + + return request + } func multiPartRequest( apiKey: Authorization,