Files
ai-terminal-assistant/packages/cli/src/client/api.ts
T

317 lines
8.2 KiB
TypeScript

/**
* API Client
*
* 用于连接远程 Server 的客户端
*/
export interface ClientConfig {
/** 服务器地址 */
baseUrl: string;
/** 认证 token */
token?: string;
/** 请求超时 (ms) */
timeout?: number;
}
export interface Session {
id: string;
name?: string;
createdAt: string;
updatedAt: string;
status: string;
messageCount: number;
}
export interface Message {
id: string;
role: 'user' | 'assistant' | 'system';
content: string;
timestamp: string;
}
export interface HealthStatus {
status: string;
timestamp: string;
agent: {
coreAvailable: boolean;
};
auth: {
enabled: boolean;
tokenCount: number;
};
stats: {
sessions: number;
websocket: { connections: number };
sse: { connections: number };
};
}
// ============ Command 相关 ============
export interface CommandInfo {
name: string;
description?: string;
agent?: string;
model?: string;
subtask?: boolean;
source: string;
hasTemplate: boolean;
}
export interface CommandSearchResult {
name: string;
description?: string;
source: string;
score: number;
}
export interface CommandExecuteResult {
prompt: string;
agent?: string;
model?: string;
subtask?: boolean;
}
export interface CommandListResponse {
commands: Array<{ name: string; description?: string; source: string }>;
stats: { total: number; bySource: Record<string, number> };
}
// ============ Command CRUD 相关 ============
export interface CreateCommandInput {
name: string;
description?: string;
template: string;
agent?: string;
model?: string;
subtask?: boolean;
scope: 'user' | 'project';
}
export interface UpdateCommandInput {
description?: string;
template?: string;
agent?: string;
model?: string;
subtask?: boolean;
}
export interface CommandContent {
name: string;
description?: string;
template: string;
agent?: string;
model?: string;
subtask?: boolean;
source: string;
sourcePath?: string;
}
/**
* API Client 类
*/
export class APIClient {
private baseUrl: string;
private token?: string;
private timeout: number;
constructor(config: ClientConfig) {
this.baseUrl = config.baseUrl.replace(/\/$/, '');
this.token = config.token;
this.timeout = config.timeout || 30000;
}
/**
* 发送 HTTP 请求
*/
private async request<T>(
method: string,
path: string,
body?: unknown
): Promise<T> {
const url = `${this.baseUrl}${path}`;
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
if (this.token) {
headers['Authorization'] = `Bearer ${this.token}`;
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(url, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: response.statusText })) as { error?: string };
throw new Error(errorData.error || `HTTP ${response.status}`);
}
return response.json() as Promise<T>;
} catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error && error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
}
}
// ============================================================================
// Health
// ============================================================================
async health(): Promise<HealthStatus> {
return this.request('GET', '/health');
}
// ============================================================================
// Sessions
// ============================================================================
async listSessions(): Promise<{ success: boolean; data: Session[] }> {
return this.request('GET', '/api/sessions');
}
async createSession(name?: string): Promise<{ success: boolean; data: Session }> {
return this.request('POST', '/api/sessions', { name });
}
async getSession(id: string): Promise<{ success: boolean; data: Session }> {
return this.request('GET', `/api/sessions/${id}`);
}
async deleteSession(id: string): Promise<{ success: boolean }> {
return this.request('DELETE', `/api/sessions/${id}`);
}
// ============================================================================
// Messages
// ============================================================================
async getMessages(sessionId: string): Promise<{ success: boolean; data: Message[] }> {
return this.request('GET', `/api/sessions/${sessionId}/messages`);
}
async sendMessage(
sessionId: string,
content: string
): Promise<{ success: boolean; data: Message }> {
return this.request('POST', `/api/sessions/${sessionId}/messages`, { content });
}
// ============================================================================
// WebSocket
// ============================================================================
/**
* 创建 WebSocket 连接
*/
connectWebSocket(sessionId: string): WebSocket {
const wsUrl = this.baseUrl
.replace(/^http/, 'ws')
.concat(`/api/ws/${sessionId}`);
const url = this.token
? `${wsUrl}?token=${encodeURIComponent(this.token)}`
: wsUrl;
return new WebSocket(url);
}
// ============================================================================
// SSE
// ============================================================================
/**
* 获取 SSE URL (用于外部 EventSource 连接)
*/
getSSEUrl(sessionId: string): string {
const sseUrl = `${this.baseUrl}/api/sessions/${sessionId}/events`;
return this.token
? `${sseUrl}?token=${encodeURIComponent(this.token)}`
: sseUrl;
}
// ============================================================================
// Commands
// ============================================================================
async listCommands(): Promise<{ success: boolean; data: CommandListResponse }> {
return this.request('GET', '/api/commands');
}
async getCommand(name: string): Promise<{ success: boolean; data: CommandInfo }> {
return this.request('GET', `/api/commands/${encodeURIComponent(name)}`);
}
async executeCommand(
name: string,
args: string = ''
): Promise<{ success: boolean; data?: CommandExecuteResult; error?: string }> {
return this.request('POST', `/api/commands/${encodeURIComponent(name)}/execute`, {
arguments: args,
});
}
async searchCommands(
query: string,
limit: number = 10
): Promise<{ success: boolean; data: CommandSearchResult[] }> {
return this.request('POST', '/api/commands/search', { query, limit });
}
async reloadCommands(): Promise<{
success: boolean;
data: { message: string; stats: { total: number; bySource: Record<string, number> } };
}> {
return this.request('POST', '/api/commands/reload');
}
// ============================================================================
// Commands CRUD
// ============================================================================
async createCommand(
input: CreateCommandInput
): Promise<{ success: boolean; data?: { name: string; path: string }; error?: string }> {
return this.request('POST', '/api/commands', input);
}
async getCommandContent(
name: string
): Promise<{ success: boolean; data?: CommandContent; error?: string }> {
return this.request('GET', `/api/commands/${encodeURIComponent(name)}/content`);
}
async updateCommand(
name: string,
input: UpdateCommandInput
): Promise<{ success: boolean; data?: { name: string; path: string }; error?: string }> {
return this.request('PUT', `/api/commands/${encodeURIComponent(name)}`, input);
}
async deleteCommand(
name: string
): Promise<{ success: boolean; data?: { name: string; path: string }; error?: string }> {
return this.request('DELETE', `/api/commands/${encodeURIComponent(name)}`);
}
}
/**
* 创建 API Client
*/
export function createClient(config: ClientConfig): APIClient {
return new APIClient(config);
}