import { app } from './index.ts'; import { parse } from '@kevisual/router/src/commander.ts'; import { useIssueEnv, useCommentEnv, useRepoInfoEnv, IssueLabel } from '../src/index.ts' import { pick } from 'es-toolkit'; import z from 'zod'; import { useKey } from '@kevisual/context'; const writeToProcess = (message: string) => { if (process.send) { process.send(message); } else { console.log(message); } } const getIssuesLabels = async () => { const issueEnv = useIssueEnv(); const repoInfoEnv = useRepoInfoEnv(); const issueId = issueEnv.issueId; const repoSlug = repoInfoEnv.repoSlug; if (!issueId || !repoSlug) { return []; } const res = await app.run({ path: 'cnb', key: 'getIssue', payload: { repo: repoSlug, issueNumber: issueId } }); if (res.code === 200) { const issueData = res.data as any; const labels = issueData.labels || []; return labels as IssueLabel[]; } console.error('获取 Issue 详情失败', res); return [] } const main = async ({ exit, question }: { exit: (code: number) => void, question?: string }) => { const repoInfoEnv = useRepoInfoEnv(); const commentEnv = useCommentEnv(); const issueEnv = useIssueEnv(); const pickCommentEnv = pick(commentEnv, ['commentId', 'commentIdLabel']); const pickIssueEnv = pick(issueEnv, ['issueId', 'issueIdLabel', 'issueIid', 'issueIidLabel', 'issueTitle', 'issueTitleLabel', 'issueDescription', 'issueDescriptionLabel']); const pickRepoInfoEnv = pick(repoInfoEnv, ['repoId', 'repoIdLabel', 'repoName', 'repoNameLabel', 'repoSlug', 'repoSlugLabel']); // const issueLabels = issueEnv.issueLabels || []; const isComment = !!commentEnv.commentId; const envList = [ ...Object.entries(pickRepoInfoEnv).map(([key, value]) => `${key}: ${value}`), ...Object.entries(issueEnv).map(([key, value]) => `${key}: ${value}`), ...Object.entries(pickCommentEnv).map(([key, value]) => `${key}: ${value}`), ] writeToProcess('当前环境变量:'); const issueLabels = await getIssuesLabels(); const issueLabelsNames = issueLabels.map(label => label.name) || []; envList.forEach(item => writeToProcess(item)); if (!isComment && !issueLabelsNames.includes('Run')) { writeToProcess('当前 Issue 不包含 Run 标签,跳过执行'); return exit(0); } const messages = [ { role: 'system', content: `你是一个智能的代码助手, 根据用户提供的上下文信息,提供有用的建议和帮助, 如果用户的要求和执行工具不一致,请说出你不能这么做。并把最后的结果提交一个评论到对应的issue中,提交的内容必须不能包含 @ 提及。用户提供的上下文信息如下:` }, { role: 'system', content: `相关变量:${JSON.stringify({ ...pickCommentEnv, ...pickIssueEnv, ...pickRepoInfoEnv })}` }, { role: 'user', content: question || commentEnv.commentBody || pickIssueEnv.issueDescription || '无' } ] writeToProcess('输入消息:'); writeToProcess(JSON.stringify(messages, null, 2)); const result = await app.run({ path: 'cnb', key: 'chat', payload: { messages } }, { appId: app.appId }) if (result.code === 200) { let _message = result.data.message || [] writeToProcess('执行完成') writeToProcess(JSON.stringify(_message, null, 2)) exit(0); } else { writeToProcess(result.message || '执行错误') exit(1); } } app.route({ path: 'cnb', key: 'npc', description: 'CNB智能助手,提供智能建议和帮助, 程序入口', metadata: { tags: ['notInNpcAgent'], args: { needExit: z.boolean().optional().describe('是否需要在执行完成后退出进程'), owner: z.string().optional().describe('用户名称') } } }).define(async (ctx) => { const needExit = ctx.args.needExit ?? true; const owner = ctx.args.owner || ''; const exit = (code: number) => { if (needExit) { process.exit(code); } } const buildUserNickName = useKey('CNB_BUILD_USER_NICKNAME') let admins = owner.split(',').map(item => item.trim()); if (owner && admins.includes(buildUserNickName)) { await main({ exit }); } else { await main({ exit, question: `你是${owner}的专属助手,请生成一条评论,说明你不具备其他用户能访问的能力。同时你需要提示说明,fork当前仓库后,即可成为你的专属助手` }); } }).addTo(app) parse({ app: app, description: 'CNB控制台命令行工具', parse: true })