// retoor import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif actor OpenRouterClient { private let apiKey: String private let baseURL = "https://openrouter.ai/api/v1" private let model: String private let systemPrompt: String private let maxTokens: Int private let temperature: Double init( apiKey: String, model: String = "x-ai/grok-code-fast-1", systemPrompt: String? = nil, maxTokens: Int = 500, temperature: Double = 0.7 ) { self.apiKey = apiKey self.model = model self.maxTokens = maxTokens self.temperature = temperature self.systemPrompt = systemPrompt ?? """ You are Grokii, an AI assistant bot on devRant - a community platform for developers to rant about their work, share experiences, and help each other. Your personality: - Knowledgeable about programming, software development, DevOps, and tech industry - Friendly but professional, with subtle developer humor - Direct and concise - developers appreciate efficiency - Empathetic to developer frustrations (bugs, legacy code, meetings, etc.) Response guidelines: - Keep responses under 800 characters (devRant has limits) - Do NOT use markdown formatting (no **, ##, ```, etc.) - devRant renders plain text only - Use plain text formatting: CAPS for emphasis, dashes for lists - Include code snippets only when essential, keep them short - Be helpful but avoid being preachy or over-explaining You have context about the rant and recent discussion. Answer naturally as part of the conversation. """ } func chat(message: String, context: String? = nil) async throws -> String { var messages: [[String: String]] = [ ["role": "system", "content": systemPrompt] ] if let context = context { messages.append(["role": "system", "content": "Context from the discussion:\n\(context)"]) } messages.append(["role": "user", "content": message]) let requestBody: [String: Any] = [ "model": model, "messages": messages, "max_tokens": maxTokens, "temperature": temperature ] let jsonData = try JSONSerialization.data(withJSONObject: requestBody) var request = URLRequest(url: URL(string: "\(baseURL)/chat/completions")!) request.httpMethod = "POST" request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("https://github.com/user/grokii", forHTTPHeaderField: "HTTP-Referer") request.setValue("Grokii", forHTTPHeaderField: "X-Title") request.httpBody = jsonData let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { throw OpenRouterError.invalidResponse } guard httpResponse.statusCode == 200 else { let errorBody = String(data: data, encoding: .utf8) ?? "Unknown error" throw OpenRouterError.apiError(status: httpResponse.statusCode, message: errorBody) } let responseObject = try JSONDecoder().decode(ChatCompletionResponse.self, from: data) guard let content = responseObject.choices.first?.message.content else { throw OpenRouterError.noContent } return content } } enum OpenRouterError: Error, CustomStringConvertible { case invalidResponse case apiError(status: Int, message: String) case noContent case missingAPIKey var description: String { switch self { case .invalidResponse: return "Invalid response from OpenRouter" case .apiError(let status, let message): return "OpenRouter API error (\(status)): \(message)" case .noContent: return "No content in OpenRouter response" case .missingAPIKey: return "OPENROUTER_API_KEY environment variable not set" } } } struct ChatCompletionResponse: Decodable { let choices: [Choice] struct Choice: Decodable { let message: Message } struct Message: Decodable { let content: String } }