From 7da4cad08676104c2b69f1062abc55f2201096d7 Mon Sep 17 00:00:00 2001 From: Konstantinos Thermos Date: Tue, 4 Aug 2026 14:18:12 +0300 Subject: [PATCH] fix(admin): stop a stale base URL from hijacking the Anthropic endpoint (#1777) The AI Parsing admin panel kept the Base URL in state after switching provider and saved it unconditionally, so moving a configured Local/OpenAI provider to Anthropic left the old host in the stored config and misrouted every Anthropic request away from api.anthropic.com. Clear baseUrl on save when the provider is Anthropic, matching the per-user LlmConnectionSection behaviour. Fixes #1748 --- .../components/Admin/AddonManager.test.tsx | 28 +++++++++++++++++++ client/src/components/Admin/AddonManager.tsx | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/client/src/components/Admin/AddonManager.test.tsx b/client/src/components/Admin/AddonManager.test.tsx index 6f13fcf7d..cfffacead 100644 --- a/client/src/components/Admin/AddonManager.test.tsx +++ b/client/src/components/Admin/AddonManager.test.tsx @@ -560,6 +560,34 @@ describe('AddonManager', () => { }); }); + it('FE-ADMIN-ADDON-029: switching to Anthropic clears a stale base URL before saving', async () => { + const user = userEvent.setup(); + const bodies: unknown[] = []; + server.use( + addonsRoute([llmAddon({ provider: 'local', model: '', baseUrl: 'http://ollama.lan:11434/v1', apiKey: '', multimodal: false })]), + modelsRoute([]), + http.put('/api/admin/addons/llm_parsing', async ({ request }) => { + bodies.push(await request.json()); + return HttpResponse.json({ success: true }); + }), + ); + render(<>); + + await screen.findByText('Installed on the server'); + + await user.click(screen.getByRole('button', { name: /Local · OpenAI-compatible/ })); + await user.click(screen.getByRole('button', { name: 'Anthropic' })); + await user.type(screen.getByPlaceholderText('claude-opus-4-8'), 'claude-haiku-4-5-20251001'); + await user.type(screen.getByPlaceholderText('sk-…'), 'sk-ant-live'); + + await user.click(screen.getByRole('button', { name: 'Save' })); + await screen.findByText('Saved'); + // The stale local base URL must not ride along to Anthropic — it would hijack the endpoint. + expect(bodies[0]).toEqual({ + config: { provider: 'anthropic', model: 'claude-haiku-4-5-20251001', baseUrl: '', apiKey: 'sk-ant-live', multimodal: false }, + }); + }); + it('FE-ADMIN-ADDON-027: an error frame in the pull stream aborts the pull and is reported', async () => { const user = userEvent.setup(); server.use( diff --git a/client/src/components/Admin/AddonManager.tsx b/client/src/components/Admin/AddonManager.tsx index 1c658ee11..99b9c84d8 100644 --- a/client/src/components/Admin/AddonManager.tsx +++ b/client/src/components/Admin/AddonManager.tsx @@ -399,7 +399,7 @@ function LlmParsingConfig({ addon }: { addon: Addon }) { setSaving(true) try { // Send the masked sentinel unchanged so the server keeps the stored key. - await adminApi.updateAddon(addon.id, { config: { provider, model: model.trim(), baseUrl: baseUrl.trim(), apiKey, multimodal: cfg.multimodal === true } }) + await adminApi.updateAddon(addon.id, { config: { provider, model: model.trim(), baseUrl: provider === 'anthropic' ? '' : baseUrl.trim(), apiKey, multimodal: cfg.multimodal === true } }) toast.success('Saved') } catch { toast.error('Failed to save')