agent-Specialization/easyagent/src/tools/dispatcher.js

218 lines
7.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
const { readFileTool } = require('./read_file');
const { editFileTool } = require('./edit_file');
const { runCommandTool } = require('./run_command');
const { webSearchTool, extractWebpageTool } = require('./web_search');
const { searchWorkspaceTool } = require('./search_workspace');
const { readMediafileTool } = require('./read_mediafile');
const path = require('path');
const MAX_RUN_COMMAND_CHARS = 10000;
const MAX_EXTRACT_WEBPAGE_CHARS = 80000;
function formatFailure(err) {
return `失败: ${err}`;
}
function formatReadFile(result) {
if (!result.success) return formatFailure(result.error || '读取失败');
if (result.type === 'read') {
return result.content || '';
}
if (result.type === 'search') {
const parts = [];
for (const match of result.matches || []) {
const hits = (match.hits || []).join(',') || '';
parts.push(`[${match.id}] L${match.line_start}-${match.line_end} hits:${hits}`);
parts.push(match.snippet || '');
parts.push('');
}
return parts.join('\n').trim();
}
if (result.type === 'extract') {
const parts = [];
for (const seg of result.segments || []) {
const label = seg.label || 'segment';
parts.push(`[${label}] L${seg.line_start}-${seg.line_end}`);
parts.push(seg.content || '');
parts.push('');
}
return parts.join('\n').trim();
}
return '';
}
function formatEditFile(result) {
if (!result.success) return formatFailure(result.error || '修改失败');
const count = typeof result.replacements === 'number' ? result.replacements : 0;
return `已替换 ${count} 处: ${result.path}`;
}
function formatWebSearch(result) {
if (!result.success) return formatFailure(result.error || '搜索失败');
const time = result.searched_at || new Date().toISOString();
const summary = result.answer || '';
const lines = [];
lines.push(`🔍 搜索查询: ${result.query}`);
lines.push(`📅 搜索时间: ${time}`);
lines.push('');
lines.push('📝 AI摘要:');
lines.push(summary || '');
lines.push('');
lines.push('---');
lines.push('');
lines.push('📊 搜索结果:');
const results = result.results || [];
results.forEach((item, idx) => {
lines.push(`${idx + 1}. ${item.title || ''}`);
lines.push(` 🔗 ${item.url || ''}`);
lines.push(` 📄 ${item.content || ''}`);
});
return lines.join('\n').trim();
}
function formatExtractWebpage(result, mode, url, targetPath) {
if (!result.success) return formatFailure(result.error || '提取失败');
if (mode === 'save') {
return `已保存: ${targetPath} (chars=${result.chars}, bytes=${result.bytes})`;
}
let content = result.content || '';
if (content.length > MAX_EXTRACT_WEBPAGE_CHARS) content = content.slice(0, MAX_EXTRACT_WEBPAGE_CHARS);
return `URL: ${url}\n${content}`;
}
function formatRunCommand(result) {
if (!result.success) {
if (result.status === 'timeout') {
const output = result.output ? result.output.slice(-MAX_RUN_COMMAND_CHARS) : '';
return `[timeout after ${result.timeout}s]\n${output}`.trim();
}
return `[error rc=${result.return_code ?? '1'}] ${result.error || ''}\n${result.output || ''}`.trim();
}
let output = result.output || '';
if (output.length > MAX_RUN_COMMAND_CHARS) output = output.slice(-MAX_RUN_COMMAND_CHARS);
return output || '[no_output]';
}
function formatSearchWorkspace(result) {
if (!result.success) return formatFailure(result.error || '搜索失败');
if (result.mode === 'file') {
const lines = [`命中文件(${result.matches.length}):`];
result.matches.forEach((p, idx) => lines.push(`${idx + 1}) ${p}`));
return lines.join('\n');
}
if (result.mode === 'content') {
const parts = [];
for (const item of result.results || []) {
parts.push(item.file);
for (const m of item.matches || []) {
parts.push(`- L${m.line}: ${m.snippet}`);
}
parts.push('');
}
return parts.join('\n').trim();
}
return '';
}
function formatReadMediafile(result) {
if (!result.success) return formatFailure(result.error || '读取失败');
if (result.type === 'image') return `已附加图片: ${result.path}`;
return `已附加视频: ${result.path}`;
}
function buildToolContent(result) {
if (result.type === 'image' || result.type === 'video') {
const payload = {
type: result.type === 'image' ? 'image_url' : 'video_url',
[result.type === 'image' ? 'image_url' : 'video_url']: {
url: `data:${result.mime};base64,${result.b64}`,
},
};
return [
{ type: 'text', text: formatReadMediafile(result) },
payload,
];
}
return null;
}
async function executeTool({ workspace, config, allowMode, toolCall, abortSignal }) {
const name = toolCall.function.name;
let args = {};
try {
args = JSON.parse(toolCall.function.arguments || '{}');
} catch (err) {
return {
success: false,
tool: name,
error: `JSON解析失败: ${err.message || err}`,
formatted: formatFailure('参数解析失败'),
};
}
if (allowMode === 'read_only' && (name === 'edit_file' || name === 'run_command')) {
const note = '当前为只读模式,已禁用 edit_file、run_command。若需使用请告知当前无权限需要用户输入 /allow 切换权限。';
return {
success: false,
tool: name,
error: note,
formatted: note,
};
}
if (abortSignal && abortSignal.aborted) {
return {
success: false,
tool: name,
error: '任务被用户取消',
formatted: '任务被用户取消',
raw: { success: false, error: '任务被用户取消', cancelled: true },
};
}
let raw;
if (name === 'read_file') raw = readFileTool(workspace, args);
else if (name === 'edit_file') raw = editFileTool(workspace, args);
else if (name === 'run_command') raw = await runCommandTool(workspace, args, abortSignal);
else if (name === 'web_search') raw = await webSearchTool(config, args, abortSignal);
else if (name === 'extract_webpage') {
if (!args.mode) args.mode = 'read';
const targetPath = args.target_path ? (path.isAbsolute(args.target_path) ? args.target_path : path.join(workspace, args.target_path)) : null;
if (args.mode === 'save') {
if (!targetPath || !targetPath.toLowerCase().endsWith('.md')) {
raw = { success: false, error: 'target_path 必须是 .md 文件' };
} else {
raw = await extractWebpageTool(config, args, targetPath, abortSignal);
}
} else {
raw = await extractWebpageTool(config, args, targetPath, abortSignal);
}
} else if (name === 'search_workspace') raw = await searchWorkspaceTool(workspace, args);
else if (name === 'read_mediafile') raw = readMediafileTool(workspace, args);
else raw = { success: false, error: '未知工具' };
let formatted = '';
if (name === 'read_file') formatted = formatReadFile(raw);
else if (name === 'edit_file') formatted = formatEditFile(raw);
else if (name === 'run_command') formatted = formatRunCommand(raw);
else if (name === 'web_search') formatted = formatWebSearch(raw);
else if (name === 'extract_webpage') formatted = formatExtractWebpage(raw, args.mode, args.url, args.target_path);
else if (name === 'search_workspace') formatted = formatSearchWorkspace(raw);
else if (name === 'read_mediafile') formatted = formatReadMediafile(raw);
else formatted = raw.success ? '' : formatFailure(raw.error || '失败');
const toolContent = buildToolContent(raw);
return {
success: raw.success,
tool: name,
raw,
formatted,
tool_content: toolContent,
};
}
module.exports = { executeTool };