feat(web): MS23-P3-003 OpenClaw provider config UI
All checks were successful
ci/woodpecker/push/ci Pipeline was successful

This commit is contained in:
2026-03-07 16:18:21 -06:00
parent 563d59ad5d
commit 54ee5cf945
6 changed files with 697 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import * as client from "./client";
import {
createAgentProvider,
deleteAgentProvider,
fetchAgentProviders,
updateAgentProvider,
} from "./agent-providers";
vi.mock("./client");
beforeEach((): void => {
vi.clearAllMocks();
});
describe("fetchAgentProviders", (): void => {
it("calls provider list endpoint", async (): Promise<void> => {
vi.mocked(client.apiGet).mockResolvedValueOnce([] as never);
await fetchAgentProviders();
expect(client.apiGet).toHaveBeenCalledWith("/api/agent-providers");
});
});
describe("createAgentProvider", (): void => {
it("posts create payload", async (): Promise<void> => {
vi.mocked(client.apiPost).mockResolvedValueOnce({ id: "provider-1" } as never);
await createAgentProvider({
name: "openclaw-primary",
provider: "openclaw",
gatewayUrl: "https://openclaw.example.com",
credentials: {
apiToken: "top-secret",
},
isActive: true,
});
expect(client.apiPost).toHaveBeenCalledWith("/api/agent-providers", {
name: "openclaw-primary",
provider: "openclaw",
gatewayUrl: "https://openclaw.example.com",
credentials: {
apiToken: "top-secret",
},
isActive: true,
});
});
});
describe("updateAgentProvider", (): void => {
it("sends PUT request with update payload", async (): Promise<void> => {
vi.mocked(client.apiRequest).mockResolvedValueOnce({ id: "provider-1" } as never);
await updateAgentProvider("provider-1", {
gatewayUrl: "https://new-openclaw.example.com",
isActive: false,
});
expect(client.apiRequest).toHaveBeenCalledWith("/api/agent-providers/provider-1", {
method: "PUT",
body: JSON.stringify({
gatewayUrl: "https://new-openclaw.example.com",
isActive: false,
}),
});
});
});
describe("deleteAgentProvider", (): void => {
it("calls delete endpoint", async (): Promise<void> => {
vi.mocked(client.apiDelete).mockResolvedValueOnce(undefined as never);
await deleteAgentProvider("provider-1");
expect(client.apiDelete).toHaveBeenCalledWith("/api/agent-providers/provider-1");
});
});

View File

@@ -0,0 +1,61 @@
import { apiDelete, apiGet, apiPost, apiRequest } from "./client";
export type AgentProviderType = "openclaw";
export interface AgentProviderCredentials {
apiToken?: string;
}
export interface AgentProviderConfig {
id: string;
workspaceId: string;
name: string;
provider: AgentProviderType;
gatewayUrl: string;
credentials: AgentProviderCredentials | null;
isActive: boolean;
createdAt: string;
updatedAt: string;
}
export interface CreateAgentProviderRequest {
name: string;
provider: AgentProviderType;
gatewayUrl: string;
credentials: {
apiToken: string;
};
isActive: boolean;
}
export interface UpdateAgentProviderRequest {
name?: string;
provider?: AgentProviderType;
gatewayUrl?: string;
credentials?: AgentProviderCredentials;
isActive?: boolean;
}
export async function fetchAgentProviders(): Promise<AgentProviderConfig[]> {
return apiGet<AgentProviderConfig[]>("/api/agent-providers");
}
export async function createAgentProvider(
data: CreateAgentProviderRequest
): Promise<AgentProviderConfig> {
return apiPost<AgentProviderConfig>("/api/agent-providers", data);
}
export async function updateAgentProvider(
providerId: string,
data: UpdateAgentProviderRequest
): Promise<AgentProviderConfig> {
return apiRequest<AgentProviderConfig>(`/api/agent-providers/${providerId}`, {
method: "PUT",
body: JSON.stringify(data),
});
}
export async function deleteAgentProvider(providerId: string): Promise<void> {
await apiDelete<unknown>(`/api/agent-providers/${providerId}`);
}

View File

@@ -18,4 +18,5 @@ export * from "./projects";
export * from "./workspaces";
export * from "./admin";
export * from "./fleet-settings";
export * from "./agent-providers";
export * from "./activity";