dylanshine / openai-kit

A community Swift package used to interact with the OpenAI API
https://platform.openai.com/docs/api-reference
MIT License
692 stars 107 forks source link

There's no way to use a background session to make Chat requests #61

Open mumme opened 1 year ago

mumme commented 1 year ago

Using a background session prevents Chat requests from failing if the app goes to background. The extension on URLSession uses dataTask with completionHandler, creating an exception:

    func dataTask(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTaskProtocol {
        dataTask(with: request, completionHandler: completionHandler) as URLSessionDataTask
    }

*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Completion handler blocks are not supported in background sessions. Use a delegate instead.'

One way to fix this issue is to save an array of continuations, use downloadTask and expose a way to send the URLSession configuration so we can set a delegate an control downloads:

    extension OpenAIService: URLSessionDownloadDelegate {
          func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
               guard let data = try? Data(contentsOf: location) else {
               continuations[downloadTask]?.resume(throwing: OpenAIServiceError.errorRequestingPrompt)
               return
            }
          continuations[downloadTask]?.resume(returning: data)
          continuations[downloadTask] = nil
        }
    }

    private lazy var backgrounUrlSession: URLSession = {
        let config = URLSessionConfiguration.background(withIdentifier: "com.mumme.fanart.openai")
        config.sessionSendsLaunchEvents = true
        config.shouldUseExtendedBackgroundIdleMode = true
        return URLSession(configuration: config, delegate: self, delegateQueue: nil)
    }()

    var continuations = [URLSessionTask: CheckedContinuation<Data, Error>]()

    func task(for request: URLRequest) async throws -> Data {
        try await withCheckedThrowingContinuation { continuation in
            let downloadTask = backgroundSession.downloadTask(with: request)
            continuations[downloadTask] = continuation
            downloadTask.resume()
        }
    }