import type { HostApiContext } from '../context'; import type { NormalizedHostApiRequest } from '../route-utils'; import { fail, ok, parseJsonBody } from '../route-utils'; import { syncProviderRuntimeSnapshot } from '@electron/service/provider-runtime-sync'; import { assignChannelToAgent, clearChannelBinding, createAgentConfig, deleteAgentConfig, listAgentsSnapshot, updateAgentModelConfig, updateAgentName, } from '../../utils/agent-config'; function getProviderSnapshot(ctx: HostApiContext) { const accounts = ctx.providerApiService .getAccounts() .filter((account) => account.enabled !== false); const defaultAccountId = ctx.providerApiService.getDefault().accountId; return { accounts, defaultAccountId }; } function formatRuntimeWarning(warnings: string[]): string | null { if (warnings.length === 0) return null; return `Runtime sync warnings: ${warnings.join('; ')}`; } export async function handleAgentRoutes( request: NormalizedHostApiRequest, ctx: HostApiContext, ) { const { pathname, method } = request; const { accounts, defaultAccountId } = getProviderSnapshot(ctx); if (pathname === '/api/agents' && method === 'GET') { return ok({ success: true, ...listAgentsSnapshot(accounts, defaultAccountId), }); } if (pathname === '/api/agents' && method === 'POST') { try { const body = parseJsonBody<{ name?: string; inheritWorkspace?: boolean }>(request.body); if (!body?.name || !String(body.name).trim()) { return fail(400, 'name is required'); } const snapshot = createAgentConfig(body.name, { inheritWorkspace: body.inheritWorkspace }, accounts, defaultAccountId); const runtimeSync = syncProviderRuntimeSnapshot({ accounts, defaultAccountId, snapshot }); ctx.gatewayManager.reloadProviders({ topics: ['agents', 'providers', 'models'], reason: 'agents:created', warnings: runtimeSync.warnings, }); return ok({ success: true, warning: formatRuntimeWarning(runtimeSync.warnings), ...snapshot, }); } catch (error) { return fail(500, error instanceof Error ? error.message : String(error)); } } if (!pathname.startsWith('/api/agents/')) { return null; } const suffix = pathname.slice('/api/agents/'.length); const parts = suffix.split('/').filter(Boolean).map((part) => decodeURIComponent(part)); if (parts.length === 0) { return null; } if (method === 'PUT' && parts.length === 1) { try { const body = parseJsonBody<{ name?: string }>(request.body); if (!body?.name || !String(body.name).trim()) { return fail(400, 'name is required'); } const snapshot = updateAgentName(parts[0], body.name, accounts, defaultAccountId); const runtimeSync = syncProviderRuntimeSnapshot({ accounts, defaultAccountId, snapshot }); ctx.gatewayManager.notifyRuntimeChanged({ topics: ['agents'], reason: 'agents:renamed', warnings: runtimeSync.warnings, }); return ok({ success: true, warning: formatRuntimeWarning(runtimeSync.warnings), ...snapshot, }); } catch (error) { return fail(500, error instanceof Error ? error.message : String(error)); } } if (method === 'PUT' && parts.length === 2 && parts[1] === 'model') { try { const body = parseJsonBody<{ modelRef?: string | null; providerAccountId?: string | null }>(request.body); const snapshot = updateAgentModelConfig( parts[0], body?.modelRef ?? null, body?.providerAccountId ?? null, accounts, defaultAccountId, ); const runtimeSync = syncProviderRuntimeSnapshot({ accounts, defaultAccountId, snapshot }); ctx.gatewayManager.reloadProviders({ topics: ['agents', 'providers', 'models'], reason: 'agents:model-updated', warnings: runtimeSync.warnings, }); return ok({ success: true, warning: formatRuntimeWarning(runtimeSync.warnings), ...snapshot, }); } catch (error) { return fail(500, error instanceof Error ? error.message : String(error)); } } if (method === 'PUT' && parts.length === 3 && parts[1] === 'channels') { try { const body = parseJsonBody<{ accountId?: string | null }>(request.body); const snapshot = assignChannelToAgent(parts[0], parts[2], body?.accountId ?? null, accounts, defaultAccountId); ctx.gatewayManager.notifyRuntimeChanged({ topics: ['agents', 'channels', 'channel-targets'], reason: 'agents:channel-assigned', channelType: parts[2], accountId: body?.accountId ?? undefined, }); return ok({ success: true, ...snapshot, }); } catch (error) { return fail(500, error instanceof Error ? error.message : String(error)); } } if (method === 'DELETE' && parts.length === 1) { try { const snapshot = deleteAgentConfig(parts[0], accounts, defaultAccountId); const runtimeSync = syncProviderRuntimeSnapshot({ accounts, defaultAccountId, snapshot }); await ctx.gatewayManager.restart({ topics: ['agents', 'providers', 'models', 'channels', 'channel-targets'], reason: 'agents:deleted', warnings: runtimeSync.warnings, }); return ok({ success: true, warning: formatRuntimeWarning(runtimeSync.warnings), ...snapshot, }); } catch (error) { return fail(500, error instanceof Error ? error.message : String(error)); } } if (method === 'DELETE' && parts.length === 3 && parts[1] === 'channels') { try { const accountId = request.url.searchParams.get('accountId')?.trim() || null; const snapshot = clearChannelBinding(parts[2], accountId, accounts, defaultAccountId); ctx.gatewayManager.notifyRuntimeChanged({ topics: ['agents', 'channels', 'channel-targets'], reason: 'agents:channel-cleared', channelType: parts[2], accountId: accountId ?? undefined, }); return ok({ success: true, ...snapshot, }); } catch (error) { return fail(500, error instanceof Error ? error.message : String(error)); } } return null; }