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
+43 -29
View File
@@ -169,6 +169,43 @@ function resolveRegisteredProviderIds(
: agentManager.getRegisteredProviderIds();
}
interface ProviderSummary {
id: AgentProvider;
label: string;
description: string;
enabled: boolean;
modes: ProviderDefinition["modes"];
status: string;
error?: string;
}
async function resolveProviderSummary(
provider: ProviderDefinition,
logger: Logger,
): Promise<ProviderSummary> {
const base = {
id: provider.id,
label: provider.label,
description: provider.description,
modes: provider.modes,
};
if (!provider.enabled) {
return { ...base, enabled: false, status: "unavailable" };
}
try {
const available = await provider.createClient(logger).isAvailable();
return { ...base, enabled: true, status: available ? "available" : "unavailable" };
} catch (availabilityError) {
return {
...base,
enabled: true,
status: "unavailable",
error:
availabilityError instanceof Error ? availabilityError.message : String(availabilityError),
};
}
}
function compareAgentListItems(a: AgentListItemPayload, b: AgentListItemPayload): number {
const attentionDelta =
Number(b.requiresAttention ?? false) - Number(a.requiresAttention ?? false);
@@ -1650,35 +1687,9 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
},
async () => {
const providers = await Promise.all(
Object.values(providerRegistry ?? {}).map(async (provider) => {
let status = "unknown";
let error: string | undefined;
try {
status = (await provider.createClient(childLogger).isAvailable())
? "available"
: "unavailable";
} catch (availabilityError) {
status = "unavailable";
error =
availabilityError instanceof Error
? availabilityError.message
: String(availabilityError);
}
const summary = {
id: provider.id,
label: provider.label,
status,
modes: provider.modes.map((mode) => ({
id: mode.id,
label: mode.label,
...(mode.description ? { description: mode.description } : {}),
})),
};
if (error) {
return Object.assign(summary, { error });
}
return summary;
}),
Object.values(providerRegistry ?? {}).map((provider) =>
resolveProviderSummary(provider, childLogger),
),
);
return {
content: [],
@@ -1709,6 +1720,9 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
if (!definition) {
throw new Error(`Provider ${provider} is not configured`);
}
if (!definition.enabled) {
throw new Error(`Provider '${provider}' is disabled`);
}
const models = await definition.fetchModels({ cwd: resolveSnapshotCwd(), force: false });
return {