- 新增 HTTP 客户端(src/services/apiClient.ts) - 实现对话创建、消息发送、对话中止等 API 调用 - 支持用户答案提交和对话历史查询 - 统一的错误处理和超时控制 - 新增 SSE 事件处理器(src/services/sseHandler.ts) - 实现 Server-Sent Events 流式数据解析 - 支持 MessageChunk、ToolExecution、AskUser、Error 等事件类型 - 使用 eventsource-parser 库处理 SSE 数据流 - 提供事件回调机制,支持实时 UI 更新
155 lines
3.5 KiB
TypeScript
155 lines
3.5 KiB
TypeScript
/**
|
|
* API 客户端
|
|
* 封装与后端的 HTTP 通信
|
|
*/
|
|
import * as https from 'https';
|
|
import * as http from 'http';
|
|
import { URL } from 'url';
|
|
import { getApiUrl, getConfig } from '../config/settings';
|
|
import type { ToolCallResult, AnswerRequest, ToolResultResponse, AnswerResponse } from '../types/api';
|
|
|
|
/**
|
|
* HTTP 请求选项
|
|
*/
|
|
interface RequestOptions {
|
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
headers?: Record<string, string>;
|
|
body?: unknown;
|
|
timeout?: number;
|
|
}
|
|
|
|
/**
|
|
* 发送 HTTP 请求
|
|
*/
|
|
async function request<T>(path: string, options: RequestOptions): Promise<T> {
|
|
const url = new URL(getApiUrl(path));
|
|
const { timeout } = getConfig();
|
|
|
|
const isHttps = url.protocol === 'https:';
|
|
const httpModule = isHttps ? https : http;
|
|
|
|
const requestOptions: http.RequestOptions = {
|
|
hostname: url.hostname,
|
|
port: url.port || (isHttps ? 443 : 80),
|
|
path: url.pathname + url.search,
|
|
method: options.method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...options.headers
|
|
},
|
|
timeout: options.timeout || timeout
|
|
};
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const req = httpModule.request(requestOptions, (res) => {
|
|
let data = '';
|
|
|
|
res.on('data', (chunk) => {
|
|
data += chunk;
|
|
});
|
|
|
|
res.on('end', () => {
|
|
try {
|
|
const json = JSON.parse(data);
|
|
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
resolve(json as T);
|
|
} else {
|
|
reject(new Error(json.error || json.message || `HTTP ${res.statusCode}`));
|
|
}
|
|
} catch (e) {
|
|
reject(new Error(`解析响应失败: ${data}`));
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', (error) => {
|
|
reject(error);
|
|
});
|
|
|
|
req.on('timeout', () => {
|
|
req.destroy();
|
|
reject(new Error('请求超时'));
|
|
});
|
|
|
|
if (options.body) {
|
|
req.write(JSON.stringify(options.body));
|
|
}
|
|
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 提交工具执行结果
|
|
* POST /api/tool/result
|
|
*/
|
|
export async function submitToolResult(result: ToolCallResult): Promise<ToolResultResponse> {
|
|
console.log(`[API] 提交工具结果: callId=${result.id}`);
|
|
return request<ToolResultResponse>('/api/tool/result', {
|
|
method: 'POST',
|
|
body: result
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 提交用户回答
|
|
* POST /api/task/answer
|
|
*/
|
|
export async function submitAnswer(answer: AnswerRequest): Promise<AnswerResponse> {
|
|
console.log(`[API] 提交用户回答: askId=${answer.askId}`);
|
|
return request<AnswerResponse>('/api/task/answer', {
|
|
method: 'POST',
|
|
body: answer
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 健康检查
|
|
* GET /api/dialog/health
|
|
*/
|
|
export async function healthCheck(): Promise<{ status: string }> {
|
|
return request<{ status: string }>('/api/dialog/health', {
|
|
method: 'GET',
|
|
timeout: 5000
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 创建成功的工具结果
|
|
*/
|
|
export function createSuccessResult(id: number, text: string): ToolCallResult {
|
|
return {
|
|
jsonrpc: '2.0',
|
|
id,
|
|
result: {
|
|
content: [{ type: 'text', text }],
|
|
isError: false
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 创建业务错误的工具结果(如编译失败)
|
|
*/
|
|
export function createBusinessErrorResult(id: number, errorMessage: string): ToolCallResult {
|
|
return {
|
|
jsonrpc: '2.0',
|
|
id,
|
|
result: {
|
|
content: [{ type: 'text', text: errorMessage }],
|
|
isError: true
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 创建系统错误的工具结果
|
|
*/
|
|
export function createSystemErrorResult(id: number, code: number, message: string): ToolCallResult {
|
|
return {
|
|
jsonrpc: '2.0',
|
|
id,
|
|
error: { code, message }
|
|
};
|
|
}
|