feat: add runtime event handling for providers in ProvidersSection feat: update routing to include Channels and Agents pages feat: extend route types and navigation items for Channels and Agents feat: implement agents store for managing agent data and interactions fix: update chat store to utilize agents store for agent-related functionality chore: export agents store from index fix: enhance runtime types for better event handling fix: update Vite config to handle dev server URL correctly
190 lines
6.2 KiB
TypeScript
190 lines
6.2 KiB
TypeScript
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;
|
|
}
|