feat: enable/disable providers from settings

Adds a per-provider toggle in Settings → Providers, persisted via daemon
config, so users can turn off providers they don't have installed (or
don't want to use) without removing them from the registry.

Disabled providers stay visible everywhere — settings, `paseo provider
ls`, MCP `list_providers`, model/mode lists — marked as disabled rather
than silently filtered. Availability probes are skipped for them, and
attempts to launch an agent or fetch models/modes from a disabled
provider return a clear "Provider '<id>' is disabled" error.

Schema additions (`enabled` on `ProviderSnapshotEntry`, MCP provider
summary) are backward-compatible: optional with a default of true so
older clients continue to parse new daemon messages.

CLI: `paseo provider ls` gains an ENABLED column; JSON output includes
the new field.
This commit is contained in:
Mohamed Boudra
2026-04-25 14:00:38 +07:00
parent de42858213
commit 4b3603604a
31 changed files with 1920 additions and 216 deletions
@@ -0,0 +1,84 @@
import { describe, expect, test } from "vitest";
import {
GetProvidersSnapshotResponseMessageSchema,
ProviderSnapshotEntrySchema,
ProvidersSnapshotUpdateMessageSchema,
} from "./messages.js";
describe("provider snapshot message schemas", () => {
test("defaults missing provider snapshot entry enabled state to true", () => {
const parsed = ProviderSnapshotEntrySchema.parse({
provider: "codex",
status: "ready",
label: "Codex",
});
expect(parsed.enabled).toBe(true);
});
test("preserves disabled provider snapshot entries", () => {
const parsed = ProviderSnapshotEntrySchema.parse({
provider: "claude",
status: "unavailable",
enabled: false,
label: "Claude",
});
expect(parsed.enabled).toBe(false);
});
test("preserves enabled provider snapshot entries", () => {
const parsed = ProviderSnapshotEntrySchema.parse({
provider: "opencode",
status: "loading",
enabled: true,
label: "OpenCode",
});
expect(parsed.enabled).toBe(true);
});
test("defaults missing enabled state in providers snapshot response entries", () => {
const parsed = GetProvidersSnapshotResponseMessageSchema.parse({
type: "get_providers_snapshot_response",
payload: {
entries: [
{
provider: "codex",
status: "ready",
label: "Codex",
},
{
provider: "claude",
status: "unavailable",
enabled: false,
label: "Claude",
},
],
generatedAt: "2026-04-24T00:00:00.000Z",
requestId: "req-providers",
},
});
expect(parsed.payload.entries.map((entry) => entry.enabled)).toEqual([true, false]);
});
test("defaults missing enabled state in providers snapshot update entries", () => {
const parsed = ProvidersSnapshotUpdateMessageSchema.parse({
type: "providers_snapshot_update",
payload: {
cwd: "/tmp/repo",
entries: [
{
provider: "codex",
status: "ready",
label: "Codex",
},
],
generatedAt: "2026-04-24T00:00:00.000Z",
},
});
expect(parsed.payload.entries[0]?.enabled).toBe(true);
});
});
+2 -2
View File
@@ -98,7 +98,6 @@ import type {
AgentPermissionRequest,
AgentPermissionResponse,
AgentPersistenceHandle,
ProviderSnapshotEntry,
ProviderStatus,
AgentRuntimeInfo,
AgentTimelineItem,
@@ -169,9 +168,10 @@ const AgentModelDefinitionSchema: z.ZodType<AgentModelDefinition> = z.object({
defaultThinkingOptionId: z.string().optional(),
});
const ProviderSnapshotEntrySchema: z.ZodType<ProviderSnapshotEntry> = z.object({
export const ProviderSnapshotEntrySchema = z.object({
provider: AgentProviderSchema,
status: ProviderStatusSchema,
enabled: z.boolean().optional().default(true),
error: z.string().optional(),
models: z.array(AgentModelDefinitionSchema).optional(),
modes: z.array(AgentModeSchema).optional(),