-
Notifications
You must be signed in to change notification settings - Fork 404
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: tools view #4457
base: main
Are you sure you want to change the base?
fix: tools view #4457
Changes from all commits
907611a
806e84c
a9e2e07
85c4a87
3c000e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import AnsiUp from 'ansi_up'; | ||
|
||
import filterEraseMultipleLine from './filterEraseMultipleLine'; | ||
|
||
type LogContent = string; | ||
|
||
const ansiUp = new AnsiUp(); | ||
|
||
export function computeAnsiLogString(logs: LogContent, enableEraseLineFilter = true, hideEmptyLine = false): string { | ||
const splittedLogs = logs.split('\n'); | ||
// 处理清空上行逻辑 | ||
// 上移 cursor + 清空整行 | ||
let filteredLogs = enableEraseLineFilter ? filterEraseMultipleLine(splittedLogs) : splittedLogs; | ||
if (hideEmptyLine) { | ||
filteredLogs = filteredLogs.map((line) => line.replace('\r', '')).filter((line) => !!line); | ||
} | ||
|
||
const htmlLogLines = filteredLogs.map((line) => { | ||
const htmlLog = ansiUp.ansi_to_html(line); | ||
|
||
return htmlLog; | ||
}); | ||
return htmlLogLines.join('\n'); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import ansiEscapes from 'ansi-escapes'; | ||
Check failure on line 1 in packages/ai-native/src/browser/mcp/tools/components/filterEraseMultipleLine.ts
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 导入模块的兼容性问题 静态分析工具发现导入 请修改为使用动态导入或更新导入方式: -import ansiEscapes from 'ansi-escapes';
+// 方案1:使用动态导入
+const ansiEscapes = await import('ansi-escapes').then(m => m.default);
+
+// 或方案2:如果项目支持 ES 模块
+import ansiEscapes from 'ansi-escapes'; 在模块系统不一致的情况下,建议检查项目配置或更新
🧰 Tools🪛 GitHub Check: build (ubuntu-latest, 20.x)[failure] 1-1: |
||
|
||
/** | ||
* 处理过滤清空上行,清空本行逻辑。 | ||
* | ||
* 关于清空上 n 行: | ||
* 一般在日志中,出现覆盖上行的情况,ascii 编码为 2K [1A 2K ...] 1G 的样式。 如 \u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[G\r\n,代表清空上两行。 | ||
* 其中,2K 代表清空整行,1A 代表光标上移,配合下一个 2K 则最终效果为清空上行,而 1G 是移动光标到本行开始(位置 1)。 | ||
* 在日志过滤过程中,可以只处理 1A 2K 这个序列,遇到后把该日志的上一行删掉即可。 | ||
* | ||
* 关于清空本行,按顺序执行: | ||
* 1. 在当前行没有 Cursor 操作符(如上移时),匹配最后一个 [2K (清空本行)或 \r[K(指针回 0,再清空本行到末尾,相当于清空本行),只输出 [2K 后的内容, | ||
* 2. 在当前行没有 Cursor 操作符时且有多个 \r (carriage return charactor,移动光标到行首)时,reduce 按 \r \x1b[G 或 \x1b[1G 切分的片断,不段用后一 part 的部分从头覆盖得出结果。 | ||
*/ | ||
export default function filterEraseMultipleLine(logs: string[]) { | ||
// 上移 cursor + 清空整行 | ||
const eraseLastLine = ansiEscapes.cursorUp(1) + ansiEscapes.eraseLine; | ||
const eraseCurrentLine = ansiEscapes.eraseLine; | ||
const eraseCurrentLine2 = `\r${ansiEscapes.eraseEndLine}`; | ||
|
||
const moveCursorToLeftRegStrs = ['\\r', '\\u001b\\[G', '\\u001b\\[1G']; | ||
const moveCursorToLeftRegStr = new RegExp(`${moveCursorToLeftRegStrs.join('|')}`); | ||
|
||
const filteredLogs = logs.reduce((acc: string[], nowLine) => { | ||
// 当前清空上行搜索指针 | ||
let pos = 0; | ||
const step = eraseLastLine.length; | ||
|
||
while (true) { | ||
pos = nowLine.indexOf(eraseLastLine, pos); | ||
// 出现清空上行 | ||
if (pos >= 0) { | ||
pos += step; | ||
acc.pop(); | ||
} else { | ||
break; | ||
} | ||
} | ||
|
||
// 对单行日志的重写做处理 | ||
// 简单处理,不去解析真正的 Cursor 所在行,否则逻辑过于麻烦 | ||
// 处理 [2K | ||
let lastErasePos = nowLine.lastIndexOf(eraseCurrentLine); | ||
if (lastErasePos < 0) { | ||
// 处理 \r[K | ||
lastErasePos = nowLine.lastIndexOf(eraseCurrentLine2); | ||
} | ||
if (lastErasePos > 0) { | ||
// 从后向前搜索最后一个清行操作 | ||
nowLine = nowLine.slice(lastErasePos); | ||
} | ||
|
||
// 处理多 \r 情况,当 \r 连续时,切分出的空字段无用,过滤掉 | ||
const carriageRewrites = nowLine.split(moveCursorToLeftRegStr).filter((part) => !!part); | ||
if (carriageRewrites.length > 1) { | ||
nowLine = carriageRewrites.reduce((nextNowLine, nowPart) => { | ||
const leftPart = nextNowLine.slice(nowPart.length); | ||
return nowPart + leftPart; | ||
}, ''); | ||
} | ||
|
||
acc.push(nowLine); | ||
return acc; | ||
}, []); | ||
|
||
return filteredLogs; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
使用dangerouslySetInnerHTML可能存在安全风险
使用
dangerouslySetInnerHTML
渲染ANSI日志内容时,需要注意潜在的XSS攻击风险。确保computeAnsiLogString
函数对输入进行了充分的安全处理。建议在
computeAnsiLogString
函数中添加对输入内容的安全过滤逻辑,或考虑使用更安全的替代方案来渲染富文本内容。🧰 Tools
🪛 Biome (1.9.4)
[error] 76-76: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)