a225e66ad7
- 创建 Server Agents API 路由 (CRUD + presets + defaults) - 添加 UI Agent 类型定义和 API 客户端函数 - 实现 AgentsPanel 组件 (预设/自定义 Agent 列表) - 实现 AgentEditor 组件 (创建/编辑 Agent) - 实现 AgentDefaultsEditor 组件 (全局默认配置) - 集成 AgentsPanel 到 Web 和 Desktop 应用
591 lines
13 KiB
TypeScript
591 lines
13 KiB
TypeScript
/**
|
|
* Configurable API Client
|
|
*/
|
|
|
|
import type {
|
|
Session,
|
|
Message,
|
|
HealthStatus,
|
|
FileListResponse,
|
|
FileReadResponse,
|
|
FileTreeResponse,
|
|
ServerConfig,
|
|
CommandInfo,
|
|
CommandSearchResult,
|
|
CommandExecuteResult,
|
|
CommandListResponse,
|
|
CreateCommandInput,
|
|
UpdateCommandInput,
|
|
CommandContent,
|
|
MCPServerStatus,
|
|
MCPToolInfo,
|
|
MCPConfig,
|
|
HookConfig,
|
|
FileHookConfig,
|
|
ShellCommandConfig,
|
|
HookTestResult,
|
|
AgentListItem,
|
|
AgentDetail,
|
|
AgentInput,
|
|
AgentDefaults,
|
|
} from './types.js';
|
|
|
|
// Re-export types
|
|
export type {
|
|
Session,
|
|
Message,
|
|
HealthStatus,
|
|
FileInfo,
|
|
FileListResponse,
|
|
FileReadResponse,
|
|
FileTreeNode,
|
|
FileTreeResponse,
|
|
ServerConfig,
|
|
CommandInfo,
|
|
CommandSearchResult,
|
|
CommandExecuteResult,
|
|
CommandListResponse,
|
|
CreateCommandInput,
|
|
UpdateCommandInput,
|
|
CommandContent,
|
|
MCPServerStatus,
|
|
MCPServerStatusType,
|
|
MCPToolInfo,
|
|
MCPConfig,
|
|
MCPServerConfigInfo,
|
|
// Hooks types
|
|
HookConfig,
|
|
FileHookConfig,
|
|
ShellCommandConfig,
|
|
HookTestResult,
|
|
// Agent types
|
|
AgentMode,
|
|
AgentModelConfig,
|
|
AgentToolConfig,
|
|
PermissionRule,
|
|
AgentBashPermission,
|
|
AgentFilePermission,
|
|
AgentGitPermission,
|
|
AgentPermission,
|
|
AgentListItem,
|
|
AgentDetail,
|
|
AgentInput,
|
|
AgentDefaults,
|
|
} from './types.js';
|
|
|
|
// API Configuration
|
|
interface ApiConfig {
|
|
baseUrl: string;
|
|
wsBaseUrl: () => string;
|
|
healthUrl: () => string;
|
|
}
|
|
|
|
let apiConfig: ApiConfig = {
|
|
baseUrl: '/api',
|
|
wsBaseUrl: () => {
|
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
return `${protocol}//${window.location.host}/api`;
|
|
},
|
|
healthUrl: () => '/health',
|
|
};
|
|
|
|
/**
|
|
* Configure API client for different environments
|
|
*/
|
|
export function configureApiClient(config: {
|
|
baseUrl: string;
|
|
wsBaseUrl: string | (() => string);
|
|
healthUrl?: string | (() => string);
|
|
}) {
|
|
apiConfig = {
|
|
baseUrl: config.baseUrl,
|
|
wsBaseUrl:
|
|
typeof config.wsBaseUrl === 'function'
|
|
? config.wsBaseUrl
|
|
: () => config.wsBaseUrl as string,
|
|
healthUrl: config.healthUrl
|
|
? typeof config.healthUrl === 'function'
|
|
? config.healthUrl
|
|
: () => config.healthUrl as string
|
|
: () => '/health',
|
|
};
|
|
}
|
|
|
|
async function request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
const response = await fetch(`${apiConfig.baseUrl}${path}`, {
|
|
method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
throw new Error(error.error || `HTTP ${response.status}`);
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
// Health
|
|
export async function getHealth(): Promise<HealthStatus> {
|
|
const healthUrl = apiConfig.healthUrl();
|
|
const response = await fetch(healthUrl);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}`);
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
// Sessions
|
|
export async function listSessions(): Promise<{ success: boolean; data: Session[] }> {
|
|
return request('GET', '/sessions');
|
|
}
|
|
|
|
export async function createSession(name?: string): Promise<{ success: boolean; data: Session }> {
|
|
return request('POST', '/sessions', { name });
|
|
}
|
|
|
|
export async function getSession(id: string): Promise<{ success: boolean; data: Session }> {
|
|
return request('GET', `/sessions/${id}`);
|
|
}
|
|
|
|
export async function deleteSession(id: string): Promise<{ success: boolean }> {
|
|
return request('DELETE', `/sessions/${id}`);
|
|
}
|
|
|
|
// Messages
|
|
export async function getMessages(
|
|
sessionId: string
|
|
): Promise<{ success: boolean; data: Message[] }> {
|
|
return request('GET', `/sessions/${sessionId}/messages`);
|
|
}
|
|
|
|
export async function sendMessage(
|
|
sessionId: string,
|
|
content: string
|
|
): Promise<{ success: boolean; data: Message }> {
|
|
return request('POST', `/sessions/${sessionId}/messages`, { content });
|
|
}
|
|
|
|
// WebSocket
|
|
export function createWebSocket(sessionId: string): WebSocket {
|
|
const wsBase = apiConfig.wsBaseUrl();
|
|
return new WebSocket(`${wsBase}/ws/${sessionId}`);
|
|
}
|
|
|
|
// Files
|
|
export async function getWorkingDirectory(): Promise<{
|
|
success: boolean;
|
|
data: { workingDirectory: string; separator: string };
|
|
}> {
|
|
return request('GET', '/files');
|
|
}
|
|
|
|
export async function listFiles(
|
|
path: string = '.',
|
|
showHidden: boolean = false
|
|
): Promise<FileListResponse> {
|
|
const params = new URLSearchParams({ path });
|
|
if (showHidden) params.set('hidden', 'true');
|
|
return request('GET', `/files/list?${params}`);
|
|
}
|
|
|
|
export async function readFile(path: string): Promise<FileReadResponse> {
|
|
return request('GET', `/files/read?path=${encodeURIComponent(path)}`);
|
|
}
|
|
|
|
export async function getFileTree(path: string = '.', depth: number = 3): Promise<FileTreeResponse> {
|
|
const params = new URLSearchParams({ path, depth: String(depth) });
|
|
return request('GET', `/files/tree?${params}`);
|
|
}
|
|
|
|
// Config
|
|
export async function getConfig(): Promise<{ success: boolean; data: ServerConfig }> {
|
|
return request('GET', '/config');
|
|
}
|
|
|
|
export async function updateConfig(
|
|
config: Partial<ServerConfig>
|
|
): Promise<{ success: boolean; data: ServerConfig }> {
|
|
return request('PATCH', '/config', config);
|
|
}
|
|
|
|
// Commands
|
|
export async function listCommands(): Promise<{ success: boolean; data: CommandListResponse }> {
|
|
return request('GET', '/commands');
|
|
}
|
|
|
|
export async function getCommand(name: string): Promise<{ success: boolean; data: CommandInfo }> {
|
|
return request('GET', `/commands/${encodeURIComponent(name)}`);
|
|
}
|
|
|
|
export async function executeCommand(
|
|
name: string,
|
|
args: string = ''
|
|
): Promise<{ success: boolean; data?: CommandExecuteResult; error?: string }> {
|
|
return request('POST', `/commands/${encodeURIComponent(name)}/execute`, {
|
|
arguments: args,
|
|
});
|
|
}
|
|
|
|
export async function searchCommands(
|
|
query: string,
|
|
limit: number = 10
|
|
): Promise<{ success: boolean; data: CommandSearchResult[] }> {
|
|
return request('POST', '/commands/search', { query, limit });
|
|
}
|
|
|
|
export async function reloadCommands(): Promise<{
|
|
success: boolean;
|
|
data: { message: string; stats: { total: number; bySource: Record<string, number> } };
|
|
}> {
|
|
return request('POST', '/commands/reload');
|
|
}
|
|
|
|
// Commands CRUD
|
|
export async function createCommand(
|
|
input: CreateCommandInput
|
|
): Promise<{ success: boolean; data?: { name: string; path: string }; error?: string }> {
|
|
return request('POST', '/commands', input);
|
|
}
|
|
|
|
export async function getCommandContent(
|
|
name: string
|
|
): Promise<{ success: boolean; data?: CommandContent; error?: string }> {
|
|
return request('GET', `/commands/${encodeURIComponent(name)}/content`);
|
|
}
|
|
|
|
export async function updateCommand(
|
|
name: string,
|
|
input: UpdateCommandInput
|
|
): Promise<{ success: boolean; data?: { name: string; path: string }; error?: string }> {
|
|
return request('PUT', `/commands/${encodeURIComponent(name)}`, input);
|
|
}
|
|
|
|
export async function deleteCommand(
|
|
name: string
|
|
): Promise<{ success: boolean; data?: { name: string; path: string }; error?: string }> {
|
|
return request('DELETE', `/commands/${encodeURIComponent(name)}`);
|
|
}
|
|
|
|
// ============ MCP API ============
|
|
|
|
/**
|
|
* 获取所有 MCP 服务器状态
|
|
*/
|
|
export async function listMCPServers(): Promise<{
|
|
success: boolean;
|
|
data: MCPServerStatus[];
|
|
error?: string;
|
|
}> {
|
|
return request('GET', '/mcp/servers');
|
|
}
|
|
|
|
/**
|
|
* 获取单个 MCP 服务器详情
|
|
*/
|
|
export async function getMCPServer(name: string): Promise<{
|
|
success: boolean;
|
|
data?: MCPServerStatus;
|
|
error?: string;
|
|
}> {
|
|
return request('GET', `/mcp/servers/${encodeURIComponent(name)}`);
|
|
}
|
|
|
|
/**
|
|
* 连接 MCP 服务器
|
|
*/
|
|
export async function connectMCPServer(name: string): Promise<{
|
|
success: boolean;
|
|
data?: { message: string; status: MCPServerStatus };
|
|
error?: string;
|
|
}> {
|
|
return request('POST', `/mcp/servers/${encodeURIComponent(name)}/connect`);
|
|
}
|
|
|
|
/**
|
|
* 断开 MCP 服务器
|
|
*/
|
|
export async function disconnectMCPServer(name: string): Promise<{
|
|
success: boolean;
|
|
data?: { message: string; status: MCPServerStatus };
|
|
error?: string;
|
|
}> {
|
|
return request('POST', `/mcp/servers/${encodeURIComponent(name)}/disconnect`);
|
|
}
|
|
|
|
/**
|
|
* 启用 MCP 服务器
|
|
*/
|
|
export async function enableMCPServer(name: string): Promise<{
|
|
success: boolean;
|
|
data?: { message: string; status: MCPServerStatus };
|
|
error?: string;
|
|
}> {
|
|
return request('POST', `/mcp/servers/${encodeURIComponent(name)}/enable`);
|
|
}
|
|
|
|
/**
|
|
* 禁用 MCP 服务器
|
|
*/
|
|
export async function disableMCPServer(name: string): Promise<{
|
|
success: boolean;
|
|
data?: { message: string; status: MCPServerStatus };
|
|
error?: string;
|
|
}> {
|
|
return request('POST', `/mcp/servers/${encodeURIComponent(name)}/disable`);
|
|
}
|
|
|
|
/**
|
|
* 获取所有 MCP 工具
|
|
*/
|
|
export async function listMCPTools(): Promise<{
|
|
success: boolean;
|
|
data: MCPToolInfo[];
|
|
error?: string;
|
|
}> {
|
|
return request('GET', '/mcp/tools');
|
|
}
|
|
|
|
/**
|
|
* 获取单个 MCP 工具详情
|
|
*/
|
|
export async function getMCPTool(name: string): Promise<{
|
|
success: boolean;
|
|
data?: MCPToolInfo;
|
|
error?: string;
|
|
}> {
|
|
return request('GET', `/mcp/tools/${encodeURIComponent(name)}`);
|
|
}
|
|
|
|
/**
|
|
* 获取 MCP 配置
|
|
*/
|
|
export async function getMCPConfig(): Promise<{
|
|
success: boolean;
|
|
data?: MCPConfig;
|
|
error?: string;
|
|
}> {
|
|
return request('GET', '/mcp/config');
|
|
}
|
|
|
|
// ============ Hooks API ============
|
|
|
|
/**
|
|
* 获取完整钩子配置
|
|
*/
|
|
export async function getHooksConfig(): Promise<{
|
|
success: boolean;
|
|
data: HookConfig;
|
|
error?: string;
|
|
}> {
|
|
return request('GET', '/hooks/config');
|
|
}
|
|
|
|
/**
|
|
* 更新完整钩子配置
|
|
*/
|
|
export async function updateHooksConfig(config: HookConfig): Promise<{
|
|
success: boolean;
|
|
data: HookConfig;
|
|
error?: string;
|
|
}> {
|
|
return request('PUT', '/hooks/config', config);
|
|
}
|
|
|
|
/**
|
|
* 获取 file_edited 钩子配置
|
|
*/
|
|
export async function getFileEditedHooks(): Promise<{
|
|
success: boolean;
|
|
data: FileHookConfig;
|
|
error?: string;
|
|
}> {
|
|
return request('GET', '/hooks/file-edited');
|
|
}
|
|
|
|
/**
|
|
* 更新 file_edited 钩子配置
|
|
*/
|
|
export async function updateFileEditedHooks(hooks: FileHookConfig): Promise<{
|
|
success: boolean;
|
|
data: FileHookConfig;
|
|
error?: string;
|
|
}> {
|
|
return request('PUT', '/hooks/file-edited', hooks);
|
|
}
|
|
|
|
/**
|
|
* 获取 file_created 钩子配置
|
|
*/
|
|
export async function getFileCreatedHooks(): Promise<{
|
|
success: boolean;
|
|
data: FileHookConfig;
|
|
error?: string;
|
|
}> {
|
|
return request('GET', '/hooks/file-created');
|
|
}
|
|
|
|
/**
|
|
* 更新 file_created 钩子配置
|
|
*/
|
|
export async function updateFileCreatedHooks(hooks: FileHookConfig): Promise<{
|
|
success: boolean;
|
|
data: FileHookConfig;
|
|
error?: string;
|
|
}> {
|
|
return request('PUT', '/hooks/file-created', hooks);
|
|
}
|
|
|
|
/**
|
|
* 获取 file_deleted 钩子配置
|
|
*/
|
|
export async function getFileDeletedHooks(): Promise<{
|
|
success: boolean;
|
|
data: FileHookConfig;
|
|
error?: string;
|
|
}> {
|
|
return request('GET', '/hooks/file-deleted');
|
|
}
|
|
|
|
/**
|
|
* 更新 file_deleted 钩子配置
|
|
*/
|
|
export async function updateFileDeletedHooks(hooks: FileHookConfig): Promise<{
|
|
success: boolean;
|
|
data: FileHookConfig;
|
|
error?: string;
|
|
}> {
|
|
return request('PUT', '/hooks/file-deleted', hooks);
|
|
}
|
|
|
|
/**
|
|
* 获取 session_completed 钩子配置
|
|
*/
|
|
export async function getSessionCompletedHooks(): Promise<{
|
|
success: boolean;
|
|
data: ShellCommandConfig[];
|
|
error?: string;
|
|
}> {
|
|
return request('GET', '/hooks/session-completed');
|
|
}
|
|
|
|
/**
|
|
* 更新 session_completed 钩子配置
|
|
*/
|
|
export async function updateSessionCompletedHooks(hooks: ShellCommandConfig[]): Promise<{
|
|
success: boolean;
|
|
data: ShellCommandConfig[];
|
|
error?: string;
|
|
}> {
|
|
return request('PUT', '/hooks/session-completed', hooks);
|
|
}
|
|
|
|
/**
|
|
* 测试执行钩子命令
|
|
*/
|
|
export async function testHookCommand(command: ShellCommandConfig): Promise<{
|
|
success: boolean;
|
|
data?: HookTestResult;
|
|
error?: string;
|
|
}> {
|
|
return request('POST', '/hooks/test', command);
|
|
}
|
|
|
|
// ============ Agents API ============
|
|
|
|
/**
|
|
* 获取所有 Agent 列表
|
|
*/
|
|
export async function listAgents(): Promise<{
|
|
success: boolean;
|
|
data: AgentListItem[];
|
|
error?: string;
|
|
}> {
|
|
return request('GET', '/agents');
|
|
}
|
|
|
|
/**
|
|
* 获取单个 Agent 详情
|
|
*/
|
|
export async function getAgent(name: string): Promise<{
|
|
success: boolean;
|
|
data?: AgentDetail;
|
|
error?: string;
|
|
}> {
|
|
return request('GET', `/agents/${encodeURIComponent(name)}`);
|
|
}
|
|
|
|
/**
|
|
* 创建新 Agent
|
|
*/
|
|
export async function createAgent(
|
|
name: string,
|
|
config: AgentInput
|
|
): Promise<{
|
|
success: boolean;
|
|
data?: AgentDetail;
|
|
error?: string;
|
|
}> {
|
|
return request('POST', '/agents', { name, ...config });
|
|
}
|
|
|
|
/**
|
|
* 更新 Agent
|
|
*/
|
|
export async function updateAgent(
|
|
name: string,
|
|
config: AgentInput
|
|
): Promise<{
|
|
success: boolean;
|
|
data?: AgentDetail;
|
|
error?: string;
|
|
}> {
|
|
return request('PUT', `/agents/${encodeURIComponent(name)}`, config);
|
|
}
|
|
|
|
/**
|
|
* 删除 Agent
|
|
*/
|
|
export async function deleteAgent(name: string): Promise<{
|
|
success: boolean;
|
|
error?: string;
|
|
}> {
|
|
return request('DELETE', `/agents/${encodeURIComponent(name)}`);
|
|
}
|
|
|
|
/**
|
|
* 获取预设 Agent 列表
|
|
*/
|
|
export async function listPresetAgents(): Promise<{
|
|
success: boolean;
|
|
data: AgentListItem[];
|
|
error?: string;
|
|
}> {
|
|
return request('GET', '/agents/presets');
|
|
}
|
|
|
|
/**
|
|
* 获取全局默认配置
|
|
*/
|
|
export async function getAgentDefaults(): Promise<{
|
|
success: boolean;
|
|
data: AgentDefaults;
|
|
error?: string;
|
|
}> {
|
|
return request('GET', '/agents/defaults');
|
|
}
|
|
|
|
/**
|
|
* 更新全局默认配置
|
|
*/
|
|
export async function updateAgentDefaults(defaults: AgentDefaults): Promise<{
|
|
success: boolean;
|
|
data: AgentDefaults;
|
|
error?: string;
|
|
}> {
|
|
return request('PUT', '/agents/defaults', defaults);
|
|
}
|