1→import Foundation 2→ 3→actor LMStudioService { 4→ static let shared = LMStudioService() 5→ 6→ private let timeout = AppConstants.AI.timeout 7→ private let maxRetries = AppConstants.AI.maxRetries 8→ 9→ private init() {} 10→ 11→ // MARK: - Configuration 12→ 13→ private var baseURL: String { 14→ KeychainService.lmstudioURL 15→ } 16→ 17→ private var apiKey: String? { 18→ KeychainService.lmstudioAPIKey 19→ } 20→ 21→ // MARK: - Health Check 22→ 23→ func isAvailable() async -> Bool { 24→ guard let url = URL(string: "\(baseURL)/models") else { 25→ return false 26→ } 27→ 28→ var request = URLRequest(url: url) 29→ request.timeoutInterval = 5 // Quick health check 30→ request.httpMethod = "GET" 31→ 32→ if let key = apiKey, !key.isEmpty { 33→ request.setValue("Bearer \(key)", forHTTPHeaderField: "Authorization") 34→ } 35→ 36→ do { 37→ let (_, response) = try await URLSession.shared.data(for: request) 38→ return (response as? HTTPURLResponse)?.statusCode == 200 39→ } catch { 40→ return false 41→ } 42→ } 43→ 44→ // MARK: - List Models 45→ 46→ func listModels() async throws -> [String] { 47→ guard let url = URL(string: "\(baseURL)/models") else { 48→ throw GhostAIError.invalidURL 49→ } 50→ 51→ var request = URLRequest(url: url) 52→ request.timeoutInterval = 10 53→ request.httpMethod = "GET" 54→ 55→ if let key = apiKey, !key.isEmpty { 56→ request.setValue("Bearer \(key)", forHTTPHeaderField: "Authorization") 57→ } 58→ 59→ let (data, response) = try await URLSession.shared.data(for: request) 60→ 61→ guard let httpResponse = response as? HTTPURLResponse, 62→ httpResponse.statusCode == 200 else { 63→ throw GhostAIError.invalidResponse 64→ } 65→ 66→ struct ModelsResponse: Codable { 67→ let data: [ModelInfo] 68→ } 69→ struct ModelInfo: Codable { 70→ let id: String 71→ } 72→ 73→ let result = try JSONDecoder().decode(ModelsResponse.self, from: data) 74→ return result.data.map { $0.id } 75→ } 76→ 77→ // MARK: - Generate 78→ 79→ func generate( 80→ prompt: String, 81→ systemPrompt: String? = nil, 82→ model: String? = nil, 83→ maxTokens: Int = AppConstants.AI.maxTokens, 84→ temperature: Double = AppConstants.AI.temperature 85→ ) async throws -> String { 86→ guard let url = URL(string: "\(baseURL)/chat/completions") else { 87→ throw GhostAIError.invalidURL 88→ } 89→ 90→ var request = URLRequest(url: url) 91→ request.httpMethod = "POST" 92→ request.timeoutInterval = timeout 93→ request.setValue("application/json", forHTTPHeaderField: "Content-Type") 94→ 95→ if let key = apiKey, !key.isEmpty { 96→ request.setValue("Bearer \(key)", forHTTPHeaderField: "Authorization") 97→ } 98→ 99→ // Build messages array 100→ var messages: [[String: String]] = [] 101→ 102→ if let system = systemPrompt, !system.isEmpty { 103→ messages.append(["role": "system", "content": system]) 104→ } 105→ 106→ messages.append(["role": "user", "content": prompt]) 107→ 108→ // Build request body (OpenAI-compatible format) 109→ var body: [String: Any] = [ 110→ "messages": messages, 111→ "max_tokens": maxTokens, 112→ "temperature": temperature, 113→ "stream": false 114→ ] 115→ 116→ if let model = model { 117→ body["model"] = model 118→ } 119→ 120→ request.httpBody = try JSONSerialization.data(withJSONObject: body) 121→ 122→ // Retry logic 123→ var lastError: Error? 124→ 125→ for attempt in 0.. Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.