396 lines
9.1 KiB
TypeScript
396 lines
9.1 KiB
TypeScript
/**
|
||
* API 客户端
|
||
* 封装与后端的 HTTP 通信
|
||
*/
|
||
import * as vscode from "vscode";
|
||
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,
|
||
ToolConfirmResponse,
|
||
UserInfoResponse,
|
||
InvitationVerifyRequest,
|
||
InvitationVerifyResponse,
|
||
InvitationStatusResponse,
|
||
} from "../types/api";
|
||
|
||
/**
|
||
* HTTP 请求选项
|
||
*/
|
||
interface RequestOptions {
|
||
method: "GET" | "POST" | "PUT" | "DELETE";
|
||
headers?: Record<string, string>;
|
||
body?: unknown;
|
||
timeout?: number;
|
||
}
|
||
|
||
/**
|
||
* 获取当前登录的 Token
|
||
*/
|
||
async function getAuthToken(): Promise<string | undefined> {
|
||
try {
|
||
const session = await vscode.authentication.getSession("iccoder", [], {
|
||
silent: true,
|
||
});
|
||
return session?.accessToken;
|
||
} catch {
|
||
return undefined;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 发送 HTTP 请求
|
||
*/
|
||
async function request<T>(path: string, options: RequestOptions): Promise<T> {
|
||
const url = new URL(getApiUrl(path));
|
||
const { timeout } = getConfig();
|
||
|
||
// 自动获取 Token
|
||
const token = await getAuthToken();
|
||
|
||
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",
|
||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||
...options.headers,
|
||
},
|
||
timeout: options.timeout || timeout,
|
||
};
|
||
|
||
console.log("[HTTP] 请求详情:", {
|
||
url: url.toString(),
|
||
method: options.method,
|
||
headers: requestOptions.headers,
|
||
hasToken: !!token,
|
||
body: options.body,
|
||
});
|
||
|
||
return new Promise((resolve, reject) => {
|
||
const req = httpModule.request(requestOptions, (res) => {
|
||
let data = "";
|
||
|
||
// console.log('[HTTP] 响应状态码:', res.statusCode);
|
||
// console.log('[HTTP] 响应头:', res.headers);
|
||
|
||
res.on("data", (chunk) => {
|
||
data += chunk;
|
||
});
|
||
|
||
res.on("end", () => {
|
||
console.log("[HTTP] 响应体:", data);
|
||
try {
|
||
const json = JSON.parse(data);
|
||
// console.log('[HTTP] 解析后的响应:', JSON.stringify(json, null, 2));
|
||
|
||
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||
console.log("[HTTP] 请求成功");
|
||
resolve(json as T);
|
||
} else {
|
||
console.error("[HTTP] 请求失败:", {
|
||
statusCode: res.statusCode,
|
||
error: json.error,
|
||
message: json.message,
|
||
msg: json.msg,
|
||
});
|
||
reject(
|
||
new Error(
|
||
json.error ||
|
||
json.message ||
|
||
json.msg ||
|
||
`HTTP ${res.statusCode}`,
|
||
),
|
||
);
|
||
}
|
||
} catch (e) {
|
||
// console.error('[HTTP] 解析响应失败:', e);
|
||
// console.error('[HTTP] 原始响应:', data);
|
||
reject(new Error(`解析响应失败: ${data}`));
|
||
}
|
||
});
|
||
});
|
||
|
||
req.on("error", (error) => {
|
||
// console.error('[HTTP] 请求错误:', error);
|
||
reject(error);
|
||
});
|
||
|
||
req.on("timeout", () => {
|
||
// console.error('[HTTP] 请求超时');
|
||
req.destroy();
|
||
reject(new Error("请求超时"));
|
||
});
|
||
|
||
if (options.body) {
|
||
const bodyStr = JSON.stringify(options.body);
|
||
// console.log('[HTTP] 发送请求体:', bodyStr);
|
||
req.write(bodyStr);
|
||
}
|
||
|
||
req.end();
|
||
// console.log('[HTTP] 请求已发送');
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 提交工具执行结果
|
||
* 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,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 提交工具确认响应(Ask 模式)
|
||
* POST /api/tool/confirm
|
||
*/
|
||
export async function submitToolConfirm(
|
||
response: ToolConfirmResponse,
|
||
): Promise<ToolResultResponse> {
|
||
console.log(
|
||
`[API] 提交工具确认: confirmId=${response.confirmId}, approved=${response.approved}`,
|
||
);
|
||
return request<ToolResultResponse>("/api/tool/confirm", {
|
||
method: "POST",
|
||
body: response,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 健康检查
|
||
* GET /api/dialog/health
|
||
*/
|
||
export async function healthCheck(): Promise<{ status: string }> {
|
||
return request<{ status: string }>("/api/dialog/health", {
|
||
method: "GET",
|
||
timeout: 5000,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 停止对话请求
|
||
*/
|
||
export interface StopDialogRequest {
|
||
taskId: string;
|
||
}
|
||
|
||
/**
|
||
* 停止对话响应
|
||
*/
|
||
export interface StopDialogResponse {
|
||
success: boolean;
|
||
taskId: string;
|
||
message?: string;
|
||
error?: string;
|
||
}
|
||
|
||
/**
|
||
* 停止对话
|
||
* POST /api/dialog/stop
|
||
*/
|
||
export async function stopDialog(taskId: string): Promise<StopDialogResponse> {
|
||
console.log(`[API] 停止对话: taskId=${taskId}`);
|
||
return request<StopDialogResponse>("/api/dialog/stop", {
|
||
method: "POST",
|
||
body: { taskId },
|
||
});
|
||
}
|
||
|
||
/** 压缩对话响应 */
|
||
export interface CompactDialogResponse {
|
||
success: boolean;
|
||
taskId: string;
|
||
message?: string;
|
||
error?: string;
|
||
}
|
||
|
||
/**
|
||
* 手动压缩对话历史
|
||
* POST /api/dialog/compact
|
||
*/
|
||
export async function compactDialog(
|
||
taskId: string,
|
||
): Promise<CompactDialogResponse> {
|
||
console.log(`[API] 压缩对话: taskId=${taskId}`);
|
||
return request<CompactDialogResponse>("/api/dialog/compact", {
|
||
method: "POST",
|
||
body: { taskId },
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 创建成功的工具结果
|
||
*/
|
||
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 },
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取用户信息
|
||
* GET /system/user/getInfo
|
||
*/
|
||
export async function getUserInfo(): Promise<UserInfoResponse> {
|
||
console.log("[API] 获取用户信息");
|
||
return request<UserInfoResponse>("/system/user/getInfo", {
|
||
method: "GET",
|
||
});
|
||
}
|
||
|
||
/** 余额查询响应 */
|
||
export interface CreditBalanceResponse {
|
||
success: boolean;
|
||
balance?: number;
|
||
error?: string;
|
||
}
|
||
|
||
/**
|
||
* 查询用户资源点余额
|
||
* GET /api/dialog/balance?userId=xxx
|
||
*/
|
||
export async function getCreditBalance(
|
||
userId: string,
|
||
): Promise<CreditBalanceResponse> {
|
||
console.log("[API] 查询余额: userId=", userId);
|
||
return request<CreditBalanceResponse>(
|
||
`/api/dialog/balance?userId=${userId}`,
|
||
{
|
||
method: "GET",
|
||
timeout: 5000,
|
||
},
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 验证邀请码
|
||
* POST /api/invitation/verify
|
||
*/
|
||
export async function verifyInvitationCode(
|
||
code: string,
|
||
): Promise<InvitationVerifyResponse> {
|
||
// console.log('[API] 验证邀请码 - 开始');
|
||
console.log("[API] 邀请码:", code);
|
||
const body: InvitationVerifyRequest = { code };
|
||
console.log("[API] 请求体:", JSON.stringify(body));
|
||
|
||
try {
|
||
const response = await request<InvitationVerifyResponse>(
|
||
"/api/invitation/verify",
|
||
{
|
||
method: "POST",
|
||
body,
|
||
},
|
||
);
|
||
console.log("[API] 验证邀请码 - 响应:", JSON.stringify(response));
|
||
return response;
|
||
} catch (error) {
|
||
console.error("[API] 验证邀请码 - 错误:", error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 查询邀请码验证状态
|
||
* GET /api/invitation/status
|
||
*/
|
||
export async function checkInvitationStatus(): Promise<InvitationStatusResponse> {
|
||
console.log("[API] 查询邀请码验证状态");
|
||
return request<InvitationStatusResponse>("/api/invitation/status", {
|
||
method: "GET",
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 重置邀请码验证状态(退出登录时调用)
|
||
* POST /api/invitation/reset
|
||
*/
|
||
export async function resetInvitationVerification(): Promise<{
|
||
code: number;
|
||
msg: string;
|
||
}> {
|
||
console.log("[API] 重置邀请码验证状态");
|
||
try {
|
||
const response = await request<{ code: number; msg: string }>(
|
||
"/api/invitation/reset",
|
||
{
|
||
method: "POST",
|
||
},
|
||
);
|
||
console.log("[API] 重置邀请码验证状态 - 响应:", JSON.stringify(response));
|
||
return response;
|
||
} catch (error) {
|
||
console.warn("[API] 重置邀请码验证状态 - 错误:", error);
|
||
// 即使失败也不影响退出登录流程
|
||
throw error;
|
||
}
|
||
}
|