From 07feab1ce78bf2c9400314487073d63518d40e1e Mon Sep 17 00:00:00 2001 From: ainopara Date: Tue, 21 Apr 2026 14:18:40 +0800 Subject: [PATCH] Allow empty API keys for custom OpenAI endpoints --- .../Models/Providers/OpenAIProvider.swift | 24 +++++++++++-------- .../Views/Settings/SettingsView.swift | 5 +++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/macOS/WritingTools/Models/Providers/OpenAIProvider.swift b/macOS/WritingTools/Models/Providers/OpenAIProvider.swift index f1fc3044..6159ec97 100644 --- a/macOS/WritingTools/Models/Providers/OpenAIProvider.swift +++ b/macOS/WritingTools/Models/Providers/OpenAIProvider.swift @@ -44,15 +44,15 @@ final class OpenAIProvider: AIProvider { isProcessing = false } - guard !config.apiKey.isEmpty else { - throw NSError(domain: "OpenAIAPI", code: -1, userInfo: [NSLocalizedDescriptionKey: "API key is missing."]) - } - // Check for custom Base URL if !config.baseURL.isEmpty && config.baseURL != OpenAIConfig.defaultBaseURL { return try await Self.performCustomOpenAIRequest(config: config, systemPrompt: systemPrompt, userPrompt: userPrompt, images: images) } + guard !config.apiKey.isEmpty else { + throw NSError(domain: "OpenAIAPI", code: -1, userInfo: [NSLocalizedDescriptionKey: "API key is missing."]) + } + let baseURL = config.baseURL.isEmpty ? OpenAIConfig.defaultBaseURL : config.baseURL let openAIService = AIProxy.openAIDirectService( unprotectedAPIKey: config.apiKey, @@ -141,7 +141,9 @@ final class OpenAIProvider: AIProvider { var request = URLRequest(url: url) request.httpMethod = "POST" - request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization") + if !config.apiKey.isEmpty { + request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization") + } request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.timeoutInterval = 60 @@ -236,7 +238,9 @@ final class OpenAIProvider: AIProvider { var request = URLRequest(url: url) request.httpMethod = "POST" - request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization") + if !config.apiKey.isEmpty { + request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization") + } request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.timeoutInterval = 60 @@ -318,10 +322,6 @@ final class OpenAIProvider: AIProvider { activeTask = nil } - guard !config.apiKey.isEmpty else { - throw NSError(domain: "OpenAIAPI", code: -1, userInfo: [NSLocalizedDescriptionKey: "API key is missing."]) - } - // For custom base URLs, use manual SSE streaming if !config.baseURL.isEmpty && config.baseURL != OpenAIConfig.defaultBaseURL { let config = self.config @@ -338,6 +338,10 @@ final class OpenAIProvider: AIProvider { try await streamTask.value return } + + guard !config.apiKey.isEmpty else { + throw NSError(domain: "OpenAIAPI", code: -1, userInfo: [NSLocalizedDescriptionKey: "API key is missing."]) + } let baseURL = config.baseURL.isEmpty ? OpenAIConfig.defaultBaseURL : config.baseURL let openAIService = AIProxy.openAIDirectService( diff --git a/macOS/WritingTools/Views/Settings/SettingsView.swift b/macOS/WritingTools/Views/Settings/SettingsView.swift index 26b73b81..e623c26b 100644 --- a/macOS/WritingTools/Views/Settings/SettingsView.swift +++ b/macOS/WritingTools/Views/Settings/SettingsView.swift @@ -260,7 +260,10 @@ struct SettingsView: View { return "Custom OpenRouter model name is required." } case "openai": - if settings.openAIApiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + let trimmedOpenAIBaseURL = settings.openAIBaseURL.trimmingCharacters(in: .whitespacesAndNewlines) + let isUsingDefaultOpenAIEndpoint = trimmedOpenAIBaseURL.isEmpty || trimmedOpenAIBaseURL == OpenAIConfig.defaultBaseURL + if isUsingDefaultOpenAIEndpoint && + settings.openAIApiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { return "OpenAI API key is required." } if settings.openAIModel.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {