From e85df938a565b26bcca2dc3b42cd8fe22f59f82d Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 1 Mar 2026 13:17:41 -0600 Subject: [PATCH] fix(web): correct Add Provider form field mapping to match API DTO --- .../settings/providers/page.tsx | 52 ++++++++++--------- apps/web/src/lib/api/fleet-settings.test.ts | 20 ++++--- apps/web/src/lib/api/fleet-settings.ts | 15 +++--- 3 files changed, 50 insertions(+), 37 deletions(-) diff --git a/apps/web/src/app/(authenticated)/settings/providers/page.tsx b/apps/web/src/app/(authenticated)/settings/providers/page.tsx index 982a9b9..c59931a 100644 --- a/apps/web/src/app/(authenticated)/settings/providers/page.tsx +++ b/apps/web/src/app/(authenticated)/settings/providers/page.tsx @@ -85,6 +85,14 @@ const INITIAL_FORM: ProviderFormState = { isActive: true, }; +function mapProviderTypeToApi(type: string): "ollama" | "openai" | "claude" { + if (type === "ollama" || type === "claude") { + return type; + } + + return "openai"; +} + function getErrorMessage(error: unknown, fallback: string): string { if (error instanceof Error && error.message.trim().length > 0) { return error.message; @@ -93,18 +101,6 @@ function getErrorMessage(error: unknown, fallback: string): string { return fallback; } -function buildProviderName(displayName: string, type: string): string { - const slug = displayName - .trim() - .toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .replace(/^-+/, "") - .replace(/-+$/, ""); - - const candidate = `${type}-${slug.length > 0 ? slug : "provider"}`; - return candidate.slice(0, 100); -} - function normalizeProviderModels(models: unknown): FleetProviderModel[] { if (!Array.isArray(models)) { return []; @@ -153,11 +149,11 @@ function modelsToEditorText(models: unknown): string { .join("\n"); } -function parseModelsText(value: string): FleetProviderModel[] { +function parseModelsText(value: string): string[] { const seen = new Set(); return value - .split(/\n|,/g) + .split(/\r?\n/g) .map((segment) => segment.trim()) .filter((segment) => segment.length > 0) .filter((segment) => { @@ -166,8 +162,7 @@ function parseModelsText(value: string): FleetProviderModel[] { } seen.add(segment); return true; - }) - .map((id) => ({ id, name: id })); + }); } function maskApiKey(value: string): string { @@ -279,6 +274,7 @@ export default function ProvidersSettingsPage(): ReactElement { } const models = parseModelsText(form.modelsText); + const providerModels = models.map((id) => ({ id, name: id })); const baseUrl = form.baseUrl.trim(); const apiKey = form.apiKey.trim(); @@ -289,7 +285,7 @@ export default function ProvidersSettingsPage(): ReactElement { const updatePayload: UpdateFleetProviderRequest = { displayName, isActive: form.isActive, - models, + models: providerModels, }; if (baseUrl.length > 0) { @@ -303,21 +299,27 @@ export default function ProvidersSettingsPage(): ReactElement { await updateFleetProvider(editingProvider.id, updatePayload); setSuccessMessage(`Updated provider "${displayName}".`); } else { - const createPayload: CreateFleetProviderRequest = { - name: buildProviderName(displayName, form.type), - displayName, - type: form.type, - models, - }; + const config: CreateFleetProviderRequest["config"] = {}; if (baseUrl.length > 0) { - createPayload.baseUrl = baseUrl; + config.endpoint = baseUrl; } if (apiKey.length > 0) { - createPayload.apiKey = apiKey; + config.apiKey = apiKey; } + if (models.length > 0) { + config.models = models; + } + + const createPayload: CreateFleetProviderRequest = { + displayName, + providerType: mapProviderTypeToApi(form.type), + config, + isEnabled: form.isActive, + }; + await createFleetProvider(createPayload); setSuccessMessage(`Added provider "${displayName}".`); } diff --git a/apps/web/src/lib/api/fleet-settings.test.ts b/apps/web/src/lib/api/fleet-settings.test.ts index 395d423..52833ca 100644 --- a/apps/web/src/lib/api/fleet-settings.test.ts +++ b/apps/web/src/lib/api/fleet-settings.test.ts @@ -34,17 +34,25 @@ describe("createFleetProvider", (): void => { vi.mocked(client.apiPost).mockResolvedValueOnce({ id: "provider-1" } as never); await createFleetProvider({ - name: "openai-main", + providerType: "openai", displayName: "OpenAI Main", - type: "openai", - apiKey: "sk-test", + config: { + endpoint: "https://api.openai.com/v1", + apiKey: "sk-test", + models: ["gpt-4.1-mini", "gpt-4o-mini"], + }, + isEnabled: true, }); expect(client.apiPost).toHaveBeenCalledWith("/api/fleet-settings/providers", { - name: "openai-main", + providerType: "openai", displayName: "OpenAI Main", - type: "openai", - apiKey: "sk-test", + config: { + endpoint: "https://api.openai.com/v1", + apiKey: "sk-test", + models: ["gpt-4.1-mini", "gpt-4o-mini"], + }, + isEnabled: true, }); }); }); diff --git a/apps/web/src/lib/api/fleet-settings.ts b/apps/web/src/lib/api/fleet-settings.ts index 82184c8..a3561fe 100644 --- a/apps/web/src/lib/api/fleet-settings.ts +++ b/apps/web/src/lib/api/fleet-settings.ts @@ -16,13 +16,16 @@ export interface FleetProvider { } export interface CreateFleetProviderRequest { - name: string; + providerType: "ollama" | "openai" | "claude"; displayName: string; - type: string; - baseUrl?: string; - apiKey?: string; - apiType?: string; - models?: FleetProviderModel[]; + config: { + endpoint?: string; + apiKey?: string; + models?: string[]; + timeout?: number; + }; + isDefault?: boolean; + isEnabled?: boolean; } export interface UpdateFleetProviderRequest { -- 2.49.1