import { httpClient } from '@/lib/http-client'
import { appConfig } from '@/app/config/index'
import { ApiError, ApiErrorCode } from '@/lib/error-handler'
import {
CveConfig,
CveConfigResponse,
CveConfigUpdateResponse,
CveArtifactResponse,
CveIssueListResponse,
CveWorkbenchResponse,
} from '@/lib/types'
type CvePrReadinessResponse = {
cve_id: string
ready: boolean
branches: Array<{
branch: string
fix_branch: string
ready: boolean
reason: string
}>
}
function extractCveId(title: string): string {
const match = title.match(/CVE-\d{4}-\d+/i)
return match ? match[0].toUpperCase() : title.trim()
}
function buildTaskSetupConfig(setupConfig: CveConfig) {
return {
signer_name: setupConfig.signer_name,
signer_email: setupConfig.signer_email,
clone_dir: setupConfig.clone_dir,
branches: setupConfig.branches,
fork_repo_url: setupConfig.fork_repo_url,
repo_url: setupConfig.repo_url,
}
}
export function buildTaskMessage(
action: 'cve-analysis' | 'cve-submit-pr',
issueTitle: string,
setupConfig: CveConfig,
options?: {
prReadyBranches?: Array<{
branch: string
fix_branch: string
}>
}
): string {
const cveId = extractCveId(issueTitle)
const setupConfigStr = JSON.stringify(buildTaskSetupConfig(setupConfig), null, 2)
if (action === 'cve-analysis') {
return `你需要完成一个CVE漏洞分析任务。
【任务信息】
- CVE编号:${cveId}
- 配置信息:${setupConfigStr}
【必须遵守的规则】
1. 只能使用 cvekit_mcp 提供的工具:parse_issue、setup_env、get_commits、analyze_branches、apply_patch
2. 按照以下顺序执行:
parse_issue → setup_env → get_commits → analyze_branches →(必要时)apply_patch
3. 如果存在需要修复的分支,必须调用 apply_patch 逐个进行处理
4. 严禁调用任何 PR 提交相关工具(如 create_pr)
5. 不允许伪造任何工具结果或仓库状态
6. 如果任一步失败,立即停止,并返回:
- status: "failed"
- summary: 失败原因
7. 如果 get_commits 或其他关键步骤返回空结果,导致下一步无法继续,直接终止流程,不要重试
【输出要求】
最终返回一个markdown格式的总结,包含:
- status: "success" 或 "failed"
- affected_branches
- patched_branches
- failed_branches
- summary(漏洞分析与修复情况)
如果 status 为 "success",且 patched_branches 非空,必须在总结最后明确提示:
"补丁已成功应用,可在右侧 CVE 面板中创建 PR。"
如果 status 为 "failed",或 patched_branches 为空,则不要输出任何创建 PR 的提示。
请直接开始执行,不要解释你的计划。
`
} else {
const createPrArgs = (options?.prReadyBranches || []).map(item => ({
cve_id: cveId,
branch: item.branch,
clone_dir: setupConfig.clone_dir,
fork_repo_url: setupConfig.fork_repo_url,
repo_url: setupConfig.repo_url,
}))
const prReadyBranchesText = JSON.stringify(
(options?.prReadyBranches || []).map(item => ({
branch: item.branch,
fix_branch: item.fix_branch,
})),
null,
2
)
const createPrArgsText = JSON.stringify(createPrArgs, null, 2)
return `
你需要完成一个CVE漏洞的PR提交任务。
【任务信息】
- CVE编号:${cveId}
- 配置信息:${setupConfigStr}
- 后端已确认可提交 PR 的本地修复分支:
${prReadyBranchesText}
【create_pr 参数清单】
${createPrArgsText}
【执行要求】
1. 仅使用 cvekit_mcp 的 create_pr 工具提交PR
2. 必须逐条使用上方“create_pr 参数清单”调用 create_pr,不要自行补全、猜测或改写参数
3. 不得重新执行漏洞分析流程(禁止调用 parse_issue、setup_env、get_commits、analyze_branches、apply_patch)
4. 若某条 create_pr 失败,记录失败原因后继续处理下一条,不得重试同一条
5. 不允许伪造任何工具调用结果
【输出要求】
最终返回一个markdown格式的简短总结,包含:
- status: "success" 或 "failed"
- pr_links(成功创建的PR链接列表)
- failed_branches(失败分支与原因)
- summary(结果说明)
请直接开始执行,不要解释过程。
`
}
}
async function fetchIssuesFromApi(
issueUrl: string,
limit: number,
query = ''
): Promise<CveIssueListResponse['items']> {
let path = `/cve/issues?issue_url=${encodeURIComponent(issueUrl)}&limit=${limit}`
if (query) {
path = `/cve/issues/search?issue_url=${encodeURIComponent(issueUrl)}&query=${encodeURIComponent(query)}&limit=${limit}`
}
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), appConfig.api.timeout)
const authToken = appConfig.auth.token
try {
const response = await fetch(`${appConfig.api.baseUrl}${path}`, {
method: 'GET',
headers: {
Accept: 'application/json',
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
},
signal: controller.signal,
})
const contentType = response.headers.get('content-type')
const responseData = contentType?.includes('application/json')
? await response.json()
: await response.text()
if (!response.ok) {
throw new ApiError(
`HTTP Error: ${response.status} ${response.statusText}`,
response.status === 401 ? ApiErrorCode.AUTH_ERROR : ApiErrorCode.SERVER_ERROR,
response.status,
responseData
)
}
const items = (responseData as CveIssueListResponse).items
if (!Array.isArray(items)) throw new Error('GitCode response is not an array')
return items
} finally {
clearTimeout(timeoutId)
}
}
class CveService {
public async getConfig(): Promise<CveConfigResponse> {
return httpClient.get<CveConfigResponse>('/cve/config')
}
public async updateConfig(payload: CveConfig): Promise<CveConfigUpdateResponse> {
return httpClient.put<CveConfigUpdateResponse>('/cve/config', payload)
}
public async updateToken(token: string): Promise<CveConfigUpdateResponse> {
return httpClient.put<CveConfigUpdateResponse>('/cve/token', undefined, {
headers: {
'X-GitCode-Token': token,
},
})
}
public async getIssues(issueUrl: string, limit = 20): Promise<CveIssueListResponse> {
const safeLimit = Math.min(Math.max(1, limit || 20), 100)
const items = await fetchIssuesFromApi(issueUrl, safeLimit)
return { items: items.slice(0, safeLimit) }
}
public async searchIssues(
issueUrl: string,
query: string,
limit = 20
): Promise<CveIssueListResponse> {
const safeLimit = Math.min(Math.max(1, limit || 20), 100)
const needle = query.trim().toLowerCase()
if (!needle) return this.getIssues(issueUrl, safeLimit)
const items = await fetchIssuesFromApi(issueUrl, Math.max(safeLimit, 50), needle)
return { items: items.slice(0, safeLimit) }
}
public async getWorkbench(
cveId: string,
branches: string,
cloneDir: string
): Promise<CveWorkbenchResponse> {
const query = new URLSearchParams({
cve_id: cveId,
branches,
clone_dir: cloneDir,
})
return httpClient.get<CveWorkbenchResponse>(`/cve/workbench?${query.toString()}`)
}
public async getPrReadiness(
cveId: string,
branches: string,
cloneDir: string,
issueNumber: number
): Promise<CvePrReadinessResponse> {
const query = new URLSearchParams({
cve_id: cveId,
branches,
clone_dir: cloneDir,
issue_number: String(issueNumber),
})
return httpClient.get<CvePrReadinessResponse>(`/cve/pr-readiness?${query.toString()}`)
}
public async getArtifact(path: string): Promise<CveArtifactResponse> {
const query = new URLSearchParams({ path })
return httpClient.get<CveArtifactResponse>(`/cve/artifact?${query.toString()}`)
}
}
export const cveService = new CveService()