Files
ars-backend/app/Services/Tool/Tools/FileReadTool.php
ROOG e956df9daa main: 增强工具功能与消息处理
- 添加 `FileReadTool`,支持文件内容读取与安全验证
- 引入 `hasToolMessages` 逻辑,优化工具历史上下文处理
- 修改工具选项逻辑,支持禁用工具时的动态调整
- 增加消息序列化逻辑,优化 Redis 序列管理与数据同步
- 扩展测试覆盖,验证序列化与工具调用场景
- 增强 Docker Compose 脚本,支持应用重置与日志清理
- 调整工具调用超时设置,提升运行时用户体验
2025-12-24 00:55:54 +08:00

218 lines
6.2 KiB
PHP
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.

<?php
namespace App\Services\Tool\Tools;
use App\Services\Tool\Tool;
use InvalidArgumentException;
class FileReadTool implements Tool
{
public function name(): string
{
return 'file_read';
}
public function description(): string
{
return '读取文件内容,支持指定行范围、编码和大文件分段读取。';
}
/**
* @return array<string, mixed>
*/
public function parameters(): array
{
return [
'type' => 'object',
'properties' => [
'path' => [
'type' => 'string',
'description' => '要读取的文件路径(相对或绝对路径)。',
],
'start_line' => [
'type' => 'integer',
'description' => '起始行号从1开始默认从第一行开始。',
'minimum' => 1,
'default' => 1,
],
'end_line' => [
'type' => 'integer',
'description' => '结束行号(包含),默认读取到文件末尾。',
'minimum' => 1,
],
'max_size' => [
'type' => 'integer',
'description' => '最大读取字节数1-10MB默认1MB防止读取过大文件。',
'minimum' => 1,
'maximum' => 10485760,
'default' => 1048576,
],
'encoding' => [
'type' => 'string',
'description' => '文件编码默认UTF-8。',
'enum' => ['UTF-8', 'GBK', 'GB2312', 'ISO-8859-1'],
'default' => 'UTF-8',
],
],
'required' => ['path'],
];
}
/**
* @param array<string, mixed> $arguments
* @return array<string, mixed>
*/
public function execute(array $arguments): array
{
$path = $arguments['path'] ?? '';
// 验证路径
if (empty($path)) {
throw new InvalidArgumentException('文件路径不能为空。');
}
// 安全检查:防止路径遍历攻击
$realPath = realpath($path);
if ($realPath === false) {
throw new InvalidArgumentException("文件不存在:{$path}");
}
if (!is_file($realPath)) {
throw new InvalidArgumentException("路径不是文件:{$path}");
}
if (!is_readable($realPath)) {
throw new InvalidArgumentException("文件不可读:{$path}");
}
// 获取参数
$startLine = max(1, (int)($arguments['start_line'] ?? 1));
$endLine = isset($arguments['end_line']) ? max(1, (int)$arguments['end_line']) : null;
$maxSize = min(10485760, max(1, (int)($arguments['max_size'] ?? 1048576)));
$encoding = $arguments['encoding'] ?? 'UTF-8';
// 检查文件大小
$fileSize = filesize($realPath);
if ($fileSize === false) {
throw new InvalidArgumentException("无法获取文件大小:{$path}");
}
return $this->readFileContent($realPath, $startLine, $endLine, $maxSize, $encoding, $fileSize);
}
/**
* 读取文件内容
*
* @param string $path
* @param int $startLine
* @param int|null $endLine
* @param int $maxSize
* @param string $encoding
* @param int $fileSize
* @return array<string, mixed>
*/
private function readFileContent(
string $path,
int $startLine,
?int $endLine,
int $maxSize,
string $encoding,
int $fileSize
): array {
$result = [
'path' => $path,
'size' => $fileSize,
'encoding' => $encoding,
];
// 如果文件为空
if ($fileSize === 0) {
$result['content'] = '';
$result['lines_read'] = 0;
$result['truncated'] = false;
return $result;
}
// 读取文件
$handle = fopen($path, 'r');
if ($handle === false) {
throw new InvalidArgumentException("无法打开文件:{$path}");
}
try {
return $this->readLines($handle, $startLine, $endLine, $maxSize, $encoding, $result);
} finally {
fclose($handle);
}
}
/**
* 按行读取文件
*
* @param resource $handle
* @param int $startLine
* @param int|null $endLine
* @param int $maxSize
* @param string $encoding
* @param array<string, mixed> $result
* @return array<string, mixed>
*/
private function readLines(
$handle,
int $startLine,
?int $endLine,
int $maxSize,
string $encoding,
array $result
): array {
$lines = [];
$currentLine = 0;
$bytesRead = 0;
$truncated = false;
while (($line = fgets($handle)) !== false) {
$currentLine++;
// 跳过起始行之前的内容
if ($currentLine < $startLine) {
continue;
}
// 检查是否超过结束行
if ($endLine !== null && $currentLine > $endLine) {
break;
}
// 检查大小限制
$lineLength = strlen($line);
if ($bytesRead + $lineLength > $maxSize) {
$truncated = true;
break;
}
$lines[] = $line;
$bytesRead += $lineLength;
}
$content = implode('', $lines);
// 编码转换
if ($encoding !== 'UTF-8' && function_exists('mb_convert_encoding')) {
$content = mb_convert_encoding($content, 'UTF-8', $encoding);
}
$result['content'] = $content;
$result['lines_read'] = count($lines);
$result['start_line'] = $startLine;
$result['end_line'] = $endLine ?? $currentLine;
$result['truncated'] = $truncated;
$result['bytes_read'] = $bytesRead;
if ($truncated) {
$result['warning'] = "内容已截断,已读取 {$bytesRead} 字节(限制:{$maxSize} 字节)";
}
return $result;
}
}