新增: http_request AI工具(结构化HTTP·SSRF防御·High审批)
- 后端: Cargo.toml reqwest0.12(json/gzip/brotli native-tls对齐df-ai) + http.rs(handler+SSRF三层防御 validate_url/resolve_and_check/重定向≤3跳每跳校验) + tool_registry register_http_tools + 基线28→29 - 前端: useToolCard formatHttpResult + ToolCard渲染(method/url/status/body折叠/截断) + HIGH_RISK_TOOLS + confirmHighHttp + i18n - SSRF: localhost/私有IP(127/10/172.16/192.168/169.254云元数据/100.64CGNAT/≥224/IPv6保留)拒绝 + DNS resolve校验防重绑定 + 重定向每跳重校验 - 审批: High(统一保守 GET也审批 防漏写操作) - 截断: 响应50KB(T-05)+ 请求1MB + 超时1-60s默认30
This commit is contained in:
@@ -79,6 +79,12 @@ export interface ToolResult {
|
||||
old_size?: number | null
|
||||
encoding?: string
|
||||
cross_volume?: boolean
|
||||
// http_request result fields
|
||||
method?: string
|
||||
url?: string
|
||||
body?: string
|
||||
body_bytes?: number
|
||||
elapsed_ms?: number
|
||||
}
|
||||
|
||||
/** 内联 SVG 图标字符串(经 v-html 注入,常量安全;图标库改造见决策记录) */
|
||||
@@ -414,11 +420,47 @@ export function formatToolResult(tc: AiToolCallInfo): string {
|
||||
if (!execId) return formatJson(r)
|
||||
return t('aiTool.workflowTriggered', { id: r.task_id || '', status: r.target_status || '', exec: execId })
|
||||
}
|
||||
case 'http_request':
|
||||
return formatHttpResult(r)
|
||||
default:
|
||||
return formatJson(r)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* http_request 结果摘要:状态码标题(method + 最终 URL) + 截断的 body(前 N 行) + 耗时/字节数。
|
||||
* 后端已截断 body 到 50KB + parse 格式化(json pretty/auto),此处仅做行数限制(前 20 行)
|
||||
* 防大 JSON 撑爆折叠态卡片。展开态由 ToolCard.vue http_request 分支全量展示。
|
||||
*/
|
||||
export function formatHttpResult(r: ToolResult): string {
|
||||
const status = typeof r.status === 'number' ? r.status : 0
|
||||
const method = typeof r.method === 'string' ? r.method : 'HTTP'
|
||||
const url = typeof r.url === 'string' ? r.url : ''
|
||||
const ms = typeof r.elapsed_ms === 'number' ? r.elapsed_ms : 0
|
||||
const ok = status >= 200 && status < 300
|
||||
const head = ok
|
||||
? t('aiTool.httpOk', { method, status, ms })
|
||||
: t('aiTool.httpFailed', { method, status })
|
||||
const parts: string[] = [head, url]
|
||||
const body = typeof r.body === 'string' ? r.body : ''
|
||||
if (body) {
|
||||
const MAX_LINES = 20
|
||||
const lines = body.split('\n')
|
||||
if (lines.length > MAX_LINES) {
|
||||
parts.push(lines.slice(0, MAX_LINES).join('\n'))
|
||||
parts.push(t('aiTool.commandOutputLines', { n: lines.length }))
|
||||
} else {
|
||||
parts.push(body)
|
||||
}
|
||||
}
|
||||
const trunc = r.truncated === true
|
||||
const bytes = typeof r.body_bytes === 'number' ? r.body_bytes : 0
|
||||
if (trunc) {
|
||||
parts.push(t('aiTool.httpTruncated', { bytes: formatBytes(bytes) }))
|
||||
}
|
||||
return parts.join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* completed 工具结果的一句话摘要(折叠态 header 也能看到,避免光秃秃)。
|
||||
* UX-260616-04:覆盖范围与 formatToolResult 对齐(header+body 一致);
|
||||
@@ -493,6 +535,15 @@ export function toolResultSummary(tc: AiToolCallInfo): string {
|
||||
: (typeof r.id === 'string' ? r.id : '')
|
||||
return execId ? t('aiTool.workflowTriggeredShort', { exec: execId }) : ''
|
||||
}
|
||||
case 'http_request': {
|
||||
const status = typeof r.status === 'number' ? r.status : 0
|
||||
const method = typeof r.method === 'string' ? r.method : 'HTTP'
|
||||
const ms = typeof r.elapsed_ms === 'number' ? r.elapsed_ms : 0
|
||||
const ok = status >= 200 && status < 300
|
||||
return ok
|
||||
? t('aiTool.httpOk', { method, status, ms })
|
||||
: t('aiTool.httpFailed', { method, status })
|
||||
}
|
||||
default: return ''
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user