From e85faf59d49e2e6a4b920a1375bde9064d7eadad Mon Sep 17 00:00:00 2001 From: utkarsh patrikar Date: Fri, 13 Nov 2026 22:05:43 +0530 Subject: [PATCH 1/9] fix(ai): configure provider endpoints in auth dashboard --- src/analysis/analysis.ts | 3 +++ src/ui/AuthDashboard.tsx | 52 +++++++++++++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/analysis/analysis.ts b/src/analysis/analysis.ts index dd7dc39c..e63abddb 100644 --- a/src/analysis/analysis.ts +++ b/src/analysis/analysis.ts @@ -161,6 +161,7 @@ export interface ExplainSingleParams { shouldAnonymize: boolean; noCache: boolean; customHeaders?: Record; + baseUrl?: string; onAgentProgress?: (event: AgentProgressEvent) => void; } @@ -219,6 +220,7 @@ async function tryRunAgentCouncil( namespace: params.result.namespace, }, model: model || 'gemma:2b', + baseUrl: params.baseUrl, onProgress: params.onAgentProgress, }); return formatConsensusExplanation(consensus); @@ -318,6 +320,7 @@ async function explainResults(results: AnalyzerResult[], options: AnalysisOption shouldAnonymize, noCache, customHeaders: options.customHeaders, + baseUrl: getAIConfig().providers.find((provider) => provider.name.toLowerCase() === backend.toLowerCase())?.baseUrl, }); } } diff --git a/src/ui/AuthDashboard.tsx b/src/ui/AuthDashboard.tsx index 048eb3db..5f1e30e9 100644 --- a/src/ui/AuthDashboard.tsx +++ b/src/ui/AuthDashboard.tsx @@ -273,6 +273,8 @@ export const AuthDashboard: React.FC = ({ onBack, onExit }) model: '', apiKey: '', temp: '0.7', + baseUrl: '', + customHeaders: '{}', }); const selectedBackend = VALID_BACKENDS[selectedIndex]; @@ -334,7 +336,7 @@ export const AuthDashboard: React.FC = ({ onBack, onExit }) }; const handleAddReturn = () => { - if (wizardStep < 3) { + if (wizardStep < 5) { if (wizardStep === 0) { const prov = wizardValues.provider.trim().toLowerCase(); if (!VALID_BACKENDS.includes(prov)) { @@ -361,6 +363,17 @@ export const AuthDashboard: React.FC = ({ onBack, onExit }) return; } + let customHeaders: Record; + try { + const parsed: unknown = JSON.parse(wizardValues.customHeaders || '{}'); + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || + Object.values(parsed).some((value) => typeof value !== 'string')) throw new Error(); + customHeaders = parsed as Record; + } catch { + setErrorMsg('Custom headers must be a JSON object with string values.'); + return; + } + const tempFloat = parseTemperature(wizardValues.temp); if (tempFloat === null) { setErrorMsg('Temperature must be a valid number.'); @@ -372,6 +385,8 @@ export const AuthDashboard: React.FC = ({ onBack, onExit }) model: wizardValues.model.trim() || DEFAULT_MODELS[prov] || 'default', password: wizardValues.apiKey.trim() || undefined, temperature: tempFloat, + baseUrl: wizardValues.baseUrl.trim() || undefined, + customHeaders, }; const newProviders = [...currentProviders, newProvider]; @@ -387,7 +402,7 @@ export const AuthDashboard: React.FC = ({ onBack, onExit }) }; const handleEditReturn = () => { - if (wizardStep < 2) { + if (wizardStep < 4) { setWizardStep((prev) => prev + 1); return; } @@ -398,6 +413,17 @@ export const AuthDashboard: React.FC = ({ onBack, onExit }) return; } + let customHeaders: Record | undefined; + try { + const parsed: unknown = JSON.parse(wizardValues.customHeaders || '{}'); + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || + Object.values(parsed).some((value) => typeof value !== 'string')) throw new Error(); + customHeaders = parsed as Record; + } catch { + setErrorMsg('Custom headers must be a JSON object with string values.'); + return; + } + const updatedProviders = currentProviders.map((p) => { if (p.name.toLowerCase() === selectedBackend.toLowerCase()) { return { @@ -405,6 +431,8 @@ export const AuthDashboard: React.FC = ({ onBack, onExit }) model: wizardValues.model.trim() || p.model, password: wizardValues.apiKey.trim() ? wizardValues.apiKey.trim() : p.password, temperature: tempFloat, + baseUrl: wizardValues.baseUrl.trim() || undefined, + customHeaders, }; } return p; @@ -422,12 +450,18 @@ export const AuthDashboard: React.FC = ({ onBack, onExit }) }; const selectAddProvider = () => { + if (selectedConfig) { + setErrorMsg(`Provider "${selectedProviderName}" is already configured. Use edit (E) instead.`); + return; + } const defaultModel = DEFAULT_MODELS[selectedBackend] || 'default'; setWizardValues({ provider: selectedBackend, model: defaultModel, apiKey: '', temp: '0.7', + baseUrl: '', + customHeaders: '{}', }); setWizardStep(0); setMode('add'); @@ -443,6 +477,8 @@ export const AuthDashboard: React.FC = ({ onBack, onExit }) model: selectedConfig.model || '', apiKey: '', temp: selectedConfig.temperature !== undefined ? String(selectedConfig.temperature) : '0.7', + baseUrl: selectedConfig.baseUrl ?? '', + customHeaders: JSON.stringify(selectedConfig.customHeaders ?? {}), }); setWizardStep(0); setMode('edit'); @@ -514,7 +550,7 @@ export const AuthDashboard: React.FC = ({ onBack, onExit }) return; } if (key.downArrow) { - setWizardStep((prev) => Math.min(3, prev + 1)); + setWizardStep((prev) => Math.min(5, prev + 1)); return; } @@ -523,7 +559,7 @@ export const AuthDashboard: React.FC = ({ onBack, onExit }) return; } - handleTyping({ input, key, fields: ['provider', 'model', 'apiKey', 'temp'], activeStep: wizardStep }); + handleTyping({ input, key, fields: ['provider', 'model', 'apiKey', 'temp', 'baseUrl', 'customHeaders'], activeStep: wizardStep }); }; const handleEditInput = ({ input, key }: { input: string; key: any }) => { @@ -537,7 +573,7 @@ export const AuthDashboard: React.FC = ({ onBack, onExit }) return; } if (key.downArrow) { - setWizardStep((prev) => Math.min(2, prev + 1)); + setWizardStep((prev) => Math.min(4, prev + 1)); return; } @@ -546,7 +582,7 @@ export const AuthDashboard: React.FC = ({ onBack, onExit }) return; } - handleTyping({ input, key, fields: ['model', 'apiKey', 'temp'], activeStep: wizardStep }); + handleTyping({ input, key, fields: ['model', 'apiKey', 'temp', 'baseUrl', 'customHeaders'], activeStep: wizardStep }); }; const handleRemoveConfirmInput = ({ input, key }: { input: string; key: any }) => { @@ -609,6 +645,8 @@ export const AuthDashboard: React.FC = ({ onBack, onExit }) { label: 'Model: ', value: wizardValues.model }, { label: 'API Key: ', value: '*'.repeat(wizardValues.apiKey.length) }, { label: 'Temp: ', value: wizardValues.temp }, + { label: 'Base URL: ', value: wizardValues.baseUrl || '(provider default)' }, + { label: 'Headers: ', value: wizardValues.customHeaders }, ]} activeStep={wizardStep} errorMsg={errorMsg} @@ -627,6 +665,8 @@ export const AuthDashboard: React.FC = ({ onBack, onExit }) : '(leave empty to keep existing)', }, { label: 'Temp: ', value: wizardValues.temp }, + { label: 'Base URL: ', value: wizardValues.baseUrl || '(provider default)' }, + { label: 'Headers: ', value: wizardValues.customHeaders }, ]} activeStep={wizardStep} errorMsg={errorMsg} From c4b4227638f8500001c9e4a0ebae7948d238fd96 Mon Sep 17 00:00:00 2001 From: utkarsh patrikar Date: Fri, 13 Nov 2026 22:13:07 +0530 Subject: [PATCH 2/9] test(ai): update provider wizard steps --- src/__tests__/auth-dashboard.test.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/__tests__/auth-dashboard.test.tsx b/src/__tests__/auth-dashboard.test.tsx index 41908e6e..2cf0dd38 100644 --- a/src/__tests__/auth-dashboard.test.tsx +++ b/src/__tests__/auth-dashboard.test.tsx @@ -148,7 +148,7 @@ describe('AuthDashboard', () => { const enterAddWizard = async () => { await sendInputs(['a']); - await waitForFrameToContain({ mockStdout, substring: 'Step 1/4 — Provider:' }); + await waitForFrameToContain({ mockStdout, substring: 'Step 1/6 — Provider:' }); await sleep(50); }; @@ -194,7 +194,7 @@ describe('AuthDashboard', () => { await enterAddWizard(); - await sendInputs([{ key: 'return' }, { key: 'return' }, 'secret-api-key', { key: 'return' }, { key: 'return' }]); + await sendInputs([{ key: 'return' }, { key: 'return' }, 'secret-api-key', { key: 'return' }, { key: 'return' }, { key: 'return' }, { key: 'return' }]); await waitForFrameToContain({ mockStdout, substring: 'Successfully added AI provider "OpenAI"' }); expect(mockStore.providers).toHaveLength(1); @@ -215,7 +215,7 @@ describe('AuthDashboard', () => { await enterEditWizard(); - await sendInputs([{ key: 'backspace' }, '-turbo', { key: 'return' }, 'new-key', { key: 'return' }, { key: 'return' }]); + await sendInputs([{ key: 'backspace' }, '-turbo', { key: 'return' }, 'new-key', { key: 'return' }, { key: 'return' }, { key: 'return' }, { key: 'return' }]); await waitForFrameToContain({ mockStdout, substring: 'Successfully updated AI provider "OpenAI"' }); expect(mockStore.providers[0].model).toBe('gpt-4-turbo'); @@ -300,6 +300,8 @@ describe('AuthDashboard', () => { { key: 'backspace' }, { key: 'backspace' }, '0.7abc', + { key: 'return' }, + { key: 'return' }, { key: 'return' } ]); await waitForFrameToContain({ mockStdout, substring: 'Temperature must be a valid number.' }); @@ -314,9 +316,7 @@ describe('AuthDashboard', () => { ], }); - await enterAddWizard(); - - await sendInputs([{ key: 'return' }, { key: 'return' }, { key: 'return' }, { key: 'return' }]); + await sendInputs(['a']); await waitForFrameToContain({ mockStdout, substring: 'Provider "openai" is already configured. Use edit (E) instead.' }); await cleanupTest(unmount); @@ -338,6 +338,8 @@ describe('AuthDashboard', () => { { key: 'backspace' }, { key: 'backspace' }, 'invalidtemp', + { key: 'return' }, + { key: 'return' }, { key: 'return' } ]); await waitForFrameToContain({ mockStdout, substring: 'Temperature must be a valid number.' }); @@ -374,7 +376,7 @@ describe('AuthDashboard', () => { await sendInputs([{ key: 'escape' }]); const lastRendered = [...mockStdout.frames].reverse().find((f) => f.includes('AI Provider Manager')); expect(lastRendered).toBeDefined(); - expect(lastRendered).not.toContain('Step 1/4'); + expect(lastRendered).not.toContain('Step 1/6'); await cleanupTest(unmount); }); From a8359f1b06044640c4dd5008d214f2e84a857566 Mon Sep 17 00:00:00 2001 From: utkarsh patrikar Date: Fri, 13 Nov 2026 22:15:26 +0530 Subject: [PATCH 3/9] test(ai): match duplicate provider message --- src/__tests__/auth-dashboard.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/auth-dashboard.test.tsx b/src/__tests__/auth-dashboard.test.tsx index 2cf0dd38..a043857a 100644 --- a/src/__tests__/auth-dashboard.test.tsx +++ b/src/__tests__/auth-dashboard.test.tsx @@ -317,7 +317,7 @@ describe('AuthDashboard', () => { }); await sendInputs(['a']); - await waitForFrameToContain({ mockStdout, substring: 'Provider "openai" is already configured. Use edit (E) instead.' }); + await waitForFrameToContain({ mockStdout, substring: 'Provider "OpenAI" is already configured. Use edit (E) instead.' }); await cleanupTest(unmount); }); From 2434248eeaea7101cf307c02f0d3c6d43027de7e Mon Sep 17 00:00:00 2001 From: utkarsh patrikar Date: Fri, 13 Nov 2026 22:26:06 +0530 Subject: [PATCH 4/9] test(ai): cover provider endpoint and header settings --- src/__tests__/auth-dashboard.test.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/__tests__/auth-dashboard.test.tsx b/src/__tests__/auth-dashboard.test.tsx index a043857a..005013c6 100644 --- a/src/__tests__/auth-dashboard.test.tsx +++ b/src/__tests__/auth-dashboard.test.tsx @@ -194,13 +194,18 @@ describe('AuthDashboard', () => { await enterAddWizard(); - await sendInputs([{ key: 'return' }, { key: 'return' }, 'secret-api-key', { key: 'return' }, { key: 'return' }, { key: 'return' }, { key: 'return' }]); + await sendInputs([ + { key: 'return' }, { key: 'return' }, 'secret-api-key', { key: 'return' }, { key: 'return' }, + 'http://ollama.example:11434', { key: 'return' }, '{"X-Test":"yes"}', { key: 'return' }, + ]); await waitForFrameToContain({ mockStdout, substring: 'Successfully added AI provider "OpenAI"' }); expect(mockStore.providers).toHaveLength(1); expect(mockStore.providers[0].name).toBe('openai'); expect(mockStore.providers[0].model).toBe('gpt-4o'); expect(mockStore.providers[0].password).toBe('secret-api-key'); + expect(mockStore.providers[0].baseUrl).toBe('http://ollama.example:11434'); + expect(mockStore.providers[0].customHeaders).toEqual({ 'X-Test': 'yes' }); await cleanupTest(unmount); }); @@ -208,7 +213,7 @@ describe('AuthDashboard', () => { it('performs Edit Provider Wizard flow successfully', async () => { const { unmount } = await setupDashboardTest({ initialProviders: [ - { name: 'openai', model: 'gpt-4o', password: 'old-key', temperature: 0.7 }, + { name: 'openai', model: 'gpt-4o', password: 'old-key', temperature: 0.7, baseUrl: 'https://proxy.example/v1', customHeaders: { 'X-Org': 'kdm' } }, { name: 'ollama', model: 'llama3.1', password: 'ollama-key', temperature: 0.7 }, ], }); @@ -220,6 +225,8 @@ describe('AuthDashboard', () => { expect(mockStore.providers[0].model).toBe('gpt-4-turbo'); expect(mockStore.providers[0].password).toBe('new-key'); + expect(mockStore.providers[0].baseUrl).toBe('https://proxy.example/v1'); + expect(mockStore.providers[0].customHeaders).toEqual({ 'X-Org': 'kdm' }); await cleanupTest(unmount); }); From ae4e57bde4d9383f272a12472fc4d4792aa7caac Mon Sep 17 00:00:00 2001 From: utkarsh patrikar Date: Fri, 13 Nov 2026 22:28:36 +0530 Subject: [PATCH 5/9] test(ai): replace default header object in wizard --- src/__tests__/auth-dashboard.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/__tests__/auth-dashboard.test.tsx b/src/__tests__/auth-dashboard.test.tsx index 005013c6..f012ee1d 100644 --- a/src/__tests__/auth-dashboard.test.tsx +++ b/src/__tests__/auth-dashboard.test.tsx @@ -196,7 +196,8 @@ describe('AuthDashboard', () => { await sendInputs([ { key: 'return' }, { key: 'return' }, 'secret-api-key', { key: 'return' }, { key: 'return' }, - 'http://ollama.example:11434', { key: 'return' }, '{"X-Test":"yes"}', { key: 'return' }, + 'http://ollama.example:11434', { key: 'return' }, { key: 'backspace' }, { key: 'backspace' }, + '{"X-Test":"yes"}', { key: 'return' }, ]); await waitForFrameToContain({ mockStdout, substring: 'Successfully added AI provider "OpenAI"' }); From b98e53272081c2caf88557e3cc524b58d5115c1a Mon Sep 17 00:00:00 2001 From: utkarsh patrikar Date: Fri, 13 Nov 2026 22:32:33 +0530 Subject: [PATCH 6/9] test(ai): cover configured Ollama council endpoint --- .../__tests__/analysis-baseline.test.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/analysis/__tests__/analysis-baseline.test.ts b/src/analysis/__tests__/analysis-baseline.test.ts index e607b595..f7008dd7 100644 --- a/src/analysis/__tests__/analysis-baseline.test.ts +++ b/src/analysis/__tests__/analysis-baseline.test.ts @@ -11,7 +11,8 @@ */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { runAnalysis, explainSingleResult, resolveBackend } from '../analysis'; -import { clearConfig } from '../../config/store'; +import { clearConfig, setAIConfig } from '../../config/store'; +import { isPythonAgentAvailable, runPythonAgentCouncil } from '../../agent/python-bridge'; import { registry, PodAnalyzer, @@ -254,6 +255,30 @@ describe('Baseline Pipeline Regression Tests', () => { expect(resolvedOllama).toBe('ollama'); }); + it('passes the configured Ollama URL to the Python council', async () => { + setAIConfig({ providers: [{ name: 'ollama', model: 'gemma:2b', baseUrl: 'http://ollama.remote:11434' }] }); + vi.mocked(isPythonAgentAvailable).mockResolvedValueOnce(true); + vi.mocked(runPythonAgentCouncil).mockResolvedValueOnce({ + confidence: 'high', + rootCause: 'test cause', + bestSolution: { actionTitle: 'test fix', steps: [] }, + findings: [], + } as any); + + const result = { ...OOM_KILLED_FIXTURE, errors: [...OOM_KILLED_FIXTURE.errors] }; + await explainSingleResult({ + result, + backend: 'ollama', + language: 'english', + shouldAnonymize: false, + noCache: true, + }); + + expect(runPythonAgentCouncil).toHaveBeenCalledWith(expect.objectContaining({ + baseUrl: 'http://ollama.remote:11434', + })); + }); + // ─── Test 6: Feature Flag Toggle ───────────────────────────────────────── it('respects KDM_MULTI_AGENT feature flag toggle', async () => { From adf7ece3ed2ae702515f970a477781436e3587e7 Mon Sep 17 00:00:00 2001 From: utkarsh patrikar Date: Fri, 13 Nov 2026 22:34:57 +0530 Subject: [PATCH 7/9] test(ai): exercise configured Ollama council through analysis --- src/analysis/__tests__/analysis-baseline.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/analysis/__tests__/analysis-baseline.test.ts b/src/analysis/__tests__/analysis-baseline.test.ts index f7008dd7..b987f918 100644 --- a/src/analysis/__tests__/analysis-baseline.test.ts +++ b/src/analysis/__tests__/analysis-baseline.test.ts @@ -256,6 +256,8 @@ describe('Baseline Pipeline Regression Tests', () => { }); it('passes the configured Ollama URL to the Python council', async () => { + registry.clear(); + registry.register(createMockAnalyzer('Pod', [OOM_KILLED_FIXTURE])); setAIConfig({ providers: [{ name: 'ollama', model: 'gemma:2b', baseUrl: 'http://ollama.remote:11434' }] }); vi.mocked(isPythonAgentAvailable).mockResolvedValueOnce(true); vi.mocked(runPythonAgentCouncil).mockResolvedValueOnce({ @@ -265,12 +267,10 @@ describe('Baseline Pipeline Regression Tests', () => { findings: [], } as any); - const result = { ...OOM_KILLED_FIXTURE, errors: [...OOM_KILLED_FIXTURE.errors] }; - await explainSingleResult({ - result, + await runAnalysis({ + filters: ['Pod'], + explain: true, backend: 'ollama', - language: 'english', - shouldAnonymize: false, noCache: true, }); From 2583396af920f4a030166d412423e4ba6d50ba04 Mon Sep 17 00:00:00 2001 From: utkarsh patrikar Date: Fri, 13 Nov 2026 22:38:04 +0530 Subject: [PATCH 8/9] test(ai): cover editing provider URL and headers --- src/__tests__/auth-dashboard.test.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/__tests__/auth-dashboard.test.tsx b/src/__tests__/auth-dashboard.test.tsx index f012ee1d..ee679802 100644 --- a/src/__tests__/auth-dashboard.test.tsx +++ b/src/__tests__/auth-dashboard.test.tsx @@ -221,13 +221,18 @@ describe('AuthDashboard', () => { await enterEditWizard(); - await sendInputs([{ key: 'backspace' }, '-turbo', { key: 'return' }, 'new-key', { key: 'return' }, { key: 'return' }, { key: 'return' }, { key: 'return' }]); + await sendInputs([ + { key: 'backspace' }, '-turbo', { key: 'return' }, 'new-key', { key: 'return' }, { key: 'return' }, + 'https://edited.example/v1', { key: 'return' }, + ...Array.from({ length: 15 }, () => ({ key: 'backspace' })), + '{"X-Env":"test"}', { key: 'return' }, + ]); await waitForFrameToContain({ mockStdout, substring: 'Successfully updated AI provider "OpenAI"' }); expect(mockStore.providers[0].model).toBe('gpt-4-turbo'); expect(mockStore.providers[0].password).toBe('new-key'); - expect(mockStore.providers[0].baseUrl).toBe('https://proxy.example/v1'); - expect(mockStore.providers[0].customHeaders).toEqual({ 'X-Org': 'kdm' }); + expect(mockStore.providers[0].baseUrl).toBe('https://edited.example/v1'); + expect(mockStore.providers[0].customHeaders).toEqual({ 'X-Env': 'test' }); await cleanupTest(unmount); }); From f786addcef4d83282fc205877b115a1f621f869a Mon Sep 17 00:00:00 2001 From: utkarsh patrikar Date: Fri, 13 Nov 2026 22:39:58 +0530 Subject: [PATCH 9/9] test(ai): replace existing provider base URL --- src/__tests__/auth-dashboard.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/__tests__/auth-dashboard.test.tsx b/src/__tests__/auth-dashboard.test.tsx index ee679802..cb57239d 100644 --- a/src/__tests__/auth-dashboard.test.tsx +++ b/src/__tests__/auth-dashboard.test.tsx @@ -223,6 +223,7 @@ describe('AuthDashboard', () => { await sendInputs([ { key: 'backspace' }, '-turbo', { key: 'return' }, 'new-key', { key: 'return' }, { key: 'return' }, + ...Array.from({ length: 24 }, () => ({ key: 'backspace' })), 'https://edited.example/v1', { key: 'return' }, ...Array.from({ length: 15 }, () => ({ key: 'backspace' })), '{"X-Env":"test"}', { key: 'return' },