From d095a4b5106d18971abda356fed4241f943cf5c3 Mon Sep 17 00:00:00 2001 From: Kelvin Ng Date: Sat, 11 Jul 2026 16:16:36 +0800 Subject: [PATCH] fix(ai): tolerate full chat endpoint as custom provider base URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fetchModels appended "/models" to the base URL verbatim, so a provider whose base URL was pasted as the full endpoint (e.g. .../v1/chat/completions) hit .../v1/chat/completions/models and got a 404 HTML page — the model refresh never populated. Normalize the base URL first: strip a trailing /chat/completions or /responses segment (and any trailing slash) before appending /models, so the list resolves to /models. Chat requests were unaffected (LLMClient already normalizes the endpoint). Co-Authored-By: Claude Opus 4.8 (cherry picked from commit 009882879d739ce0634598bf2dac621f0bca8c4c) --- Sources/Fluid/Services/ModelRepository.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/Fluid/Services/ModelRepository.swift b/Sources/Fluid/Services/ModelRepository.swift index 335e21a7..b7ebcb76 100644 --- a/Sources/Fluid/Services/ModelRepository.swift +++ b/Sources/Fluid/Services/ModelRepository.swift @@ -249,8 +249,17 @@ final class ModelRepository { let isAnthropic = providerID == "anthropic" || baseURL.contains("anthropic.com") - // Construct the models endpoint URL - let urlString = baseURL.hasSuffix("/") ? "\(baseURL)models" : "\(baseURL)/models" + // Construct the models endpoint URL. Users frequently paste the full chat + // endpoint (…/chat/completions or …/responses) as the base URL; append `/models` + // to that verbatim and it 404s. Strip a trailing endpoint segment (and slash) so + // the list resolves to /models. + var normalizedBase = baseURL.trimmingCharacters(in: .whitespacesAndNewlines) + for suffix in ["/chat/completions", "/responses"] where normalizedBase.hasSuffix(suffix) { + normalizedBase = String(normalizedBase.dropLast(suffix.count)) + break + } + while normalizedBase.hasSuffix("/") { normalizedBase = String(normalizedBase.dropLast()) } + let urlString = "\(normalizedBase)/models" guard let url = URL(string: urlString) else { DebugLogger.shared.error( "fetchModels: Invalid URL constructed from baseURL='\(baseURL)' -> '\(urlString)'",