import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif extension GeminiStatusProbe { public static func defaultDataLoader(for request: URLRequest) async throws -> (Data, URLResponse) { let loader = Self.dataLoaderWithCurlFallback( primary: { request in try await ProviderHTTPClient.shared.data(for: request) }, fallback: Self.curlDataLoader) return try await loader(request) } public static func dataLoaderWithCurlFallback( primary: @escaping @Sendable (URLRequest) async throws -> (Data, URLResponse), fallback: @escaping @Sendable (URLRequest) async throws -> (Data, URLResponse)) -> @Sendable (URLRequest) async throws -> (Data, URLResponse) { { request in do { return try await primary(request) } catch { guard Self.isURLSessionTimeout(error) else { throw error } CodexBarLog.logger(LogCategories.geminiProbe) .warning("Gemini URLSession timed out; retrying with curl") return try await fallback(request) } } } private static func isURLSessionTimeout(_ error: Error) -> Bool { if let urlError = error as? URLError { return urlError.code == .timedOut } let nsError = error as NSError return nsError.domain == NSURLErrorDomain && nsError.code == NSURLErrorTimedOut } private static func curlDataLoader(for request: URLRequest) async throws -> (Data, URLResponse) { guard let url = request.url else { throw URLError(.badURL) } let fileManager = FileManager.default let tempDir = fileManager.temporaryDirectory .appendingPathComponent("codexbar-gemini-curl-\(UUID().uuidString)", isDirectory: true) try fileManager.createDirectory( at: tempDir, withIntermediateDirectories: true, attributes: [.posixPermissions: 0o700]) defer { try? fileManager.removeItem(at: tempDir) } let configURL = tempDir.appendingPathComponent("curl.conf") var config = [ "silent", "show-error", "location", "url = \(Self.curlConfigQuote(url.absoluteString))", "max-time = \(max(1, Int(ceil(request.timeoutInterval))))", ] if let method = request.httpMethod, !method.isEmpty { config.append("request = \(Self.curlConfigQuote(method))") } for (name, value) in (request.allHTTPHeaderFields ?? [:]).sorted(by: { $0.key < $1.key }) { let header = "\(name): \(value)" guard !header.contains("\n"), !header.contains("\r") else { throw GeminiStatusProbeError.apiError("Invalid request header") } config.append("header = \(Self.curlConfigQuote(header))") } if let body = request.httpBody { let bodyURL = tempDir.appendingPathComponent("body") try body.write(to: bodyURL, options: .atomic) try fileManager.setAttributes([.posixPermissions: 0o600], ofItemAtPath: bodyURL.path) config.append("data-binary = \(Self.curlConfigQuote("@\(bodyURL.path)"))") } config.append("write-out = \(Self.curlConfigQuote(Self.curlHTTPStatusMarker + "%{http_code}"))") try config.joined(separator: "\n").write(to: configURL, atomically: true, encoding: .utf8) try fileManager.setAttributes([.posixPermissions: 0o600], ofItemAtPath: configURL.path) let result = try await SubprocessRunner.run( binary: "/usr/bin/curl", arguments: ["--config", configURL.path], environment: TTYCommandRunner.enrichedEnvironment(), timeout: max(5, request.timeoutInterval + 2), label: "gemini-api-curl") return try Self.parseCurlDataLoaderResult(result.stdout, url: url) } private static let curlHTTPStatusMarker = "__CODEXBAR_HTTP_STATUS__:" private static func curlConfigQuote(_ value: String) -> String { let escaped = value .replacingOccurrences(of: "\\", with: "\\\\") .replacingOccurrences(of: "\"", with: "\\\"") return "\"\(escaped)\"" } private static func parseCurlDataLoaderResult(_ output: String, url: URL) throws -> (Data, URLResponse) { guard let markerRange = output.range(of: curlHTTPStatusMarker, options: .backwards) else { throw GeminiStatusProbeError.apiError("curl response missing HTTP status") } let body = String(output[.. 0, let response = HTTPURLResponse( url: url, statusCode: statusCode, httpVersion: nil, headerFields: nil) else { throw GeminiStatusProbeError.apiError("curl response had invalid HTTP status") } return (Data(body.utf8), response) } }