import type { HostApiContext } from '../context'; import type { NormalizedHostApiRequest } from '../route-utils'; import { fail, ok, parseJsonBody } from '../route-utils'; import { assignChannelToAgent, clearChannelBinding, listAgentsSnapshot, } from '../../utils/agent-config'; import { listSelectedChannelAccountGroups, listSelectedChannelTargets, } from '../../utils/channels'; function getProviderSnapshot(ctx: HostApiContext) { const accounts = ctx.providerApiService .getAccounts() .filter((account) => account.enabled !== false); const defaultAccountId = ctx.providerApiService.getDefault().accountId; return { accounts, defaultAccountId }; } export async function handleChannelRoutes( request: NormalizedHostApiRequest, ctx: HostApiContext, ) { const { pathname, method } = request; const { accounts, defaultAccountId } = getProviderSnapshot(ctx); const snapshot = listAgentsSnapshot(accounts, defaultAccountId); if (pathname === '/api/channels/accounts' && method === 'GET') { return ok({ success: true, channels: listSelectedChannelAccountGroups(snapshot), }); } if (pathname === '/api/channels/targets' && method === 'GET') { const channelType = request.url.searchParams.get('channelType')?.trim() || ''; const accountId = request.url.searchParams.get('accountId')?.trim() || null; const query = request.url.searchParams.get('query')?.trim() || null; if (!channelType) { return fail(400, 'channelType is required'); } return ok({ success: true, targets: listSelectedChannelTargets(channelType, accountId, query), }); } if (pathname === '/api/channels/binding' && method === 'PUT') { try { const body = parseJsonBody<{ channelType?: string; accountId?: string | null; agentId?: string; }>(request.body); const channelType = String(body?.channelType ?? '').trim(); const accountId = String(body?.accountId ?? '').trim(); const agentId = String(body?.agentId ?? '').trim(); if (!channelType) { return fail(400, 'channelType is required'); } if (!accountId) { return fail(400, 'accountId is required'); } if (!agentId) { return fail(400, 'agentId is required'); } if (!snapshot.agents.some((agent) => agent.id === agentId)) { return fail(404, `Agent "${agentId}" not found`); } const groupedChannels = listSelectedChannelAccountGroups(snapshot); const group = groupedChannels.find((entry) => entry.channelType === channelType); if (!group || !group.accounts.some((entry) => entry.accountId === accountId)) { return fail(404, `Channel account "${channelType}:${accountId}" not found`); } const result = assignChannelToAgent(agentId, channelType, accountId, accounts, defaultAccountId); ctx.gatewayManager.notifyRuntimeChanged({ topics: ['agents', 'channels', 'channel-targets'], reason: 'channels:binding-updated', channelType, accountId, }); return ok({ success: true, ...result, }); } catch (error) { return fail(500, error instanceof Error ? error.message : String(error)); } } if (pathname === '/api/channels/binding' && method === 'DELETE') { try { const body = parseJsonBody<{ channelType?: string; accountId?: string | null; }>(request.body); const channelType = String(body?.channelType ?? '').trim(); const accountId = String(body?.accountId ?? '').trim(); if (!channelType) { return fail(400, 'channelType is required'); } if (!accountId) { return fail(400, 'accountId is required'); } const result = clearChannelBinding(channelType, accountId, accounts, defaultAccountId); ctx.gatewayManager.notifyRuntimeChanged({ topics: ['agents', 'channels', 'channel-targets'], reason: 'channels:binding-cleared', channelType, accountId, }); return ok({ success: true, ...result, }); } catch (error) { return fail(500, error instanceof Error ? error.message : String(error)); } } return null; }