index.ts•7.14 kB
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
import { Config } from '../../config/config.js';
import { ToolDefinition, ToolResponse, createErrorResponse, createSuccessResponse } from '../types.js';
import * as fs from 'fs';
import * as path from 'path';
// 扩展AxiosRequestConfig类型
interface ExtendedAxiosRequestConfig extends AxiosRequestConfig {
timing?: {
startTime: number;
endTime?: number;
};
}
// 格式化响应数据
function formatResponse(response: AxiosResponse) {
const config = response.config as ExtendedAxiosRequestConfig;
return {
status: response.status,
statusText: response.statusText,
headers: response.headers,
data: response.data,
timing: config.timing ? {
total: (config.timing.endTime || 0) - config.timing.startTime
} : undefined
};
}
// 格式化文件大小
function formatFileSize(bytes: number): string {
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
}
// 网络管理器类
class NetworkManager {
private downloadDir: string;
constructor(
private readonly config: Config
) {
// 使用当前目录下的 downloads 目录存储下载的文件
this.downloadDir = path.join(process.cwd(), 'downloads');
// 确保下载目录存在
if (!fs.existsSync(this.downloadDir)) {
fs.mkdirSync(this.downloadDir, { recursive: true });
}
}
// 生成文件名
private generateFileName(url: string, contentType?: string): string {
const urlObj = new URL(url);
let fileName = path.basename(urlObj.pathname) || 'download';
// 如果没有扩展名,尝试从 Content-Type 添加
if (!path.extname(fileName) && contentType) {
const ext = contentType.split('/').pop()?.split('+')[0];
if (ext && ext !== 'octet-stream') {
fileName += `.${ext}`;
}
}
// 添加时间戳
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const nameWithoutExt = path.basename(fileName, path.extname(fileName));
return `${nameWithoutExt}-${timestamp}${path.extname(fileName)}`;
}
// 发送HTTP请求
async sendRequest(args: {
url: string;
method?: string;
headers?: Record<string, string>;
data?: any;
params?: Record<string, string>;
timeout?: number;
download?: boolean;
downloadPath?: string;
}): Promise<ToolResponse> {
try {
// 构建请求配置
const requestConfig: ExtendedAxiosRequestConfig = {
url: args.url,
method: args.method || 'GET',
headers: args.headers || {},
data: args.data,
params: args.params,
timeout: args.timeout || 30000,
validateStatus: null, // 不抛出HTTP错误
timing: {
startTime: Date.now()
},
responseType: args.download ? 'stream' : 'json'
};
// 发送请求
const response = await axios(requestConfig);
// 更新计时
const config = response.config as ExtendedAxiosRequestConfig;
if (config.timing) {
config.timing.endTime = Date.now();
}
// 如果是下载请求
if (args.download) {
// 确保下载目录存在
if (!fs.existsSync(this.downloadDir)) {
await fs.promises.mkdir(this.downloadDir, { recursive: true });
}
// 生成文件名
const fileName = args.downloadPath ||
this.generateFileName(args.url, response.headers['content-type']);
const filePath = path.join(this.downloadDir, fileName);
// 获取文件大小
const totalSize = parseInt(response.headers['content-length'] || '0', 10);
let downloadedSize = 0;
// 创建写入流
const writer = fs.createWriteStream(filePath);
// 下载文件
await new Promise<void>((resolve, reject) => {
response.data.pipe(writer);
response.data.on('data', (chunk: Buffer) => {
downloadedSize += chunk.length;
if (totalSize > 0) {
const progress = (downloadedSize / totalSize * 100).toFixed(2);
const downloaded = formatFileSize(downloadedSize);
const total = formatFileSize(totalSize);
console.error(`下载进度: ${progress}% (${downloaded}/${total})`);
}
});
writer.on('finish', resolve);
writer.on('error', reject);
});
return createSuccessResponse({
type: 'download',
data: {
path: filePath,
size: downloadedSize,
contentType: response.headers['content-type'],
timing: config.timing ? {
total: config.timing.endTime! - config.timing.startTime
} : undefined
}
});
}
// 格式化并返回响应
return createSuccessResponse({
type: 'json',
data: formatResponse(response)
});
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
return createErrorResponse(`请求失败: ${error.message}`);
}
return createErrorResponse(`未知错误: ${error instanceof Error ? error.message : '未知错误'}`);
}
}
// 清理资源
dispose(): void {
// 不需要清理资源
}
}
// 创建网络工具
export function createNetworkTools(
config: Config
): ToolDefinition[] {
const manager = new NetworkManager(config);
return [
{
name: 'http_request',
description: '发送HTTP请求并返回完整响应信息',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: '请求URL'
},
method: {
type: 'string',
description: '请求方法(GET, POST, PUT, DELETE等)',
enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']
},
headers: {
type: 'object',
description: '请求头',
additionalProperties: true
},
data: {
type: 'object',
description: '请求体数据',
additionalProperties: true
},
params: {
type: 'object',
description: 'URL查询参数',
additionalProperties: true
},
timeout: {
type: 'number',
description: '请求超时时间(毫秒)'
},
download: {
type: 'boolean',
description: '是否下载文件。如果为 true,将把响应保存为文件。'
},
downloadPath: {
type: 'string',
description: '下载文件的保存路径(相对于当前工作目录的 downloads 目录)。如果不指定,将根据 URL 和内容类型自动生成文件名。'
}
},
required: ['url']
},
handler: args => manager.sendRequest(args)
}
];
}