@kevisual/cnb 0.0.40 → 0.0.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/app.ts +5 -5
- package/agent/modules/cnb-manager.ts +20 -2
- package/agent/npc.ts +94 -0
- package/agent/routes/build/docker.ts +66 -0
- package/agent/routes/build/index.ts +1 -0
- package/agent/routes/chat/chat.ts +41 -0
- package/agent/routes/index.ts +2 -0
- package/agent/routes/issues/comments.ts +177 -0
- package/agent/routes/issues/index.ts +2 -1
- package/agent/routes/issues/list.ts +35 -2
- package/agent/routes/opencode/index.ts +12 -0
- package/dist/cli.js +30601 -21685
- package/dist/keep.js +34 -16
- package/dist/opencode.js +56568 -47662
- package/dist/routes.d.ts +106 -5
- package/dist/routes.js +52900 -43995
- package/package.json +16 -11
- package/src/index.ts +14 -1
- package/src/issue/index.ts +72 -5
- package/src/issue/npc/build-env.ts +217 -0
- package/src/issue/npc/env.ts +194 -0
- package/src/issue/npc/pr-env.ts +95 -0
- package/src/issue/npc/repo-env.ts +56 -0
package/agent/app.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { useContextKey } from '@kevisual/context'
|
|
|
3
3
|
import { useKey } from '@kevisual/use-config'
|
|
4
4
|
import { CNB } from '../src/index.ts';
|
|
5
5
|
import { CNBManager } from './modules/cnb-manager.ts'
|
|
6
|
+
|
|
6
7
|
export const cnbManager = new CNBManager()
|
|
7
8
|
|
|
8
9
|
// CNB_TOKEN是降级兼容变量,推荐使用CNB_API_KEY
|
|
@@ -18,9 +19,9 @@ try {
|
|
|
18
19
|
cnb: new CNB({ token: token, cookie: cookie })
|
|
19
20
|
})
|
|
20
21
|
} catch (error) {
|
|
21
|
-
|
|
22
|
+
process.exit(1)
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
24
25
|
export const app = await useContextKey<App>('app', () => {
|
|
25
26
|
return new App({})
|
|
26
27
|
})
|
|
@@ -28,8 +29,7 @@ export const app = await useContextKey<App>('app', () => {
|
|
|
28
29
|
export const notCNBCheck = (ctx: any) => {
|
|
29
30
|
const isCNB = useKey('CNB');
|
|
30
31
|
if (!isCNB) {
|
|
31
|
-
ctx.throw(400, '当前环境非 cnb-board
|
|
32
|
-
return true;
|
|
32
|
+
ctx.throw(400, '当前环境非 cnb-board 环境,无法获取内容');
|
|
33
33
|
}
|
|
34
34
|
return false;
|
|
35
|
-
}
|
|
35
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Result } from '@kevisual/query';
|
|
2
2
|
import { CNB } from '../../src/index.ts';
|
|
3
3
|
import { useKey } from '@kevisual/context';
|
|
4
|
+
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
4
5
|
export const getConfig = async (opts: { token?: string }) => {
|
|
5
6
|
const kevisualEnv = useKey('KEVISUAL_ENV')
|
|
6
7
|
const baseUrl = kevisualEnv === 'production' ? 'https://kevisual.cn/api/router' : 'https://kevisual.xiongxiao.me/api/router';
|
|
@@ -32,7 +33,14 @@ type CNBItem = {
|
|
|
32
33
|
runAt?: number
|
|
33
34
|
owner?: boolean
|
|
34
35
|
cnb: CNB
|
|
36
|
+
cnbAi: ReturnType<typeof createOpenAICompatible>
|
|
35
37
|
}
|
|
38
|
+
// const repo = useKey('CNB_REPO_SLUG_LOWERCASE') as string || 'kevision/kevision';
|
|
39
|
+
// export const cnbAi = createOpenAICompatible({
|
|
40
|
+
// baseURL: `https://api.cnb.cool/${repo}/-/ai/`,
|
|
41
|
+
// name: 'custom-cnb',
|
|
42
|
+
// apiKey: token,
|
|
43
|
+
// });
|
|
36
44
|
export class CNBManager {
|
|
37
45
|
cnbMap: Map<string, CNBItem> = new Map()
|
|
38
46
|
constructor() {
|
|
@@ -71,20 +79,24 @@ export class CNBManager {
|
|
|
71
79
|
* @returns CNB 实例
|
|
72
80
|
*/
|
|
73
81
|
async getContext(ctx: any) {
|
|
82
|
+
const item = await this.getCNBItem(ctx)
|
|
83
|
+
return item.cnb
|
|
84
|
+
}
|
|
85
|
+
async getCNBItem(ctx: any) {
|
|
74
86
|
const tokenUser = ctx?.state?.tokenUser
|
|
75
87
|
const username = tokenUser?.username
|
|
76
88
|
if (!username) {
|
|
77
89
|
ctx.throw(403, 'Unauthorized')
|
|
78
90
|
}
|
|
79
91
|
if (username === 'default') {
|
|
80
|
-
return this.getDefaultCNB()
|
|
92
|
+
return this.getDefaultCNB()
|
|
81
93
|
}
|
|
82
94
|
const kevisualToken = ctx.query?.token;
|
|
83
95
|
const item = await this.getCNB({ username, kevisualToken });
|
|
84
96
|
if (!item) {
|
|
85
97
|
ctx.throw(400, '不存在的 CNB 配置项,请检查 登录 Token 是否正确,或添加 CNB 配置')
|
|
86
98
|
}
|
|
87
|
-
return item
|
|
99
|
+
return item;
|
|
88
100
|
}
|
|
89
101
|
addCNB(opts: Partial<CNBItem>) {
|
|
90
102
|
if (!opts.username || !opts.token) {
|
|
@@ -98,6 +110,12 @@ export class CNBManager {
|
|
|
98
110
|
const cnb = opts?.cnb || new CNB({ token: opts.token, cookie: opts.cookie });
|
|
99
111
|
opts.cnb = cnb;
|
|
100
112
|
opts.runAt = Date.now()
|
|
113
|
+
const repoSlug = useKey('CNB_REPO_SLUG_LOWERCASE') as string || 'kevision/kevision';
|
|
114
|
+
opts.cnbAi = createOpenAICompatible({
|
|
115
|
+
baseURL: `https://api.cnb.cool/${repoSlug}/-/ai/`,
|
|
116
|
+
name: `custom-cnb-${opts.username}`,
|
|
117
|
+
apiKey: opts.token,
|
|
118
|
+
})
|
|
101
119
|
this.cnbMap.set(opts.username, opts as CNBItem)
|
|
102
120
|
return opts as CNBItem
|
|
103
121
|
}
|
package/agent/npc.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { app } from './index.ts';
|
|
2
|
+
|
|
3
|
+
import { useIssueEnv, useCommentEnv, useRepoInfoEnv, IssueLabel } from '../src/index.ts'
|
|
4
|
+
import { pick } from 'es-toolkit';
|
|
5
|
+
|
|
6
|
+
const writeToProcess = (message: string) => {
|
|
7
|
+
if (process.send) {
|
|
8
|
+
process.send(message);
|
|
9
|
+
} else {
|
|
10
|
+
console.log(message);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const getIssuesLabels = async () => {
|
|
14
|
+
const issueEnv = useIssueEnv();
|
|
15
|
+
const repoInfoEnv = useRepoInfoEnv();
|
|
16
|
+
const issueId = issueEnv.issueId;
|
|
17
|
+
const repoSlug = repoInfoEnv.repoSlug;
|
|
18
|
+
if (!issueId || !repoSlug) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
const res = await app.run({
|
|
22
|
+
path: 'cnb',
|
|
23
|
+
key: 'getIssue',
|
|
24
|
+
payload: {
|
|
25
|
+
repo: repoSlug,
|
|
26
|
+
issueNumber: issueId
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
if (res.code === 200) {
|
|
30
|
+
const issueData = res.data as any;
|
|
31
|
+
const labels = issueData.labels || [];
|
|
32
|
+
return labels as IssueLabel[];
|
|
33
|
+
}
|
|
34
|
+
console.error('获取 Issue 详情失败', res);
|
|
35
|
+
return []
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const main = async () => {
|
|
40
|
+
const repoInfoEnv = useRepoInfoEnv();
|
|
41
|
+
const commentEnv = useCommentEnv();
|
|
42
|
+
const issueEnv = useIssueEnv();
|
|
43
|
+
const pickCommentEnv = pick(commentEnv, ['commentId', 'commentIdLabel']);
|
|
44
|
+
const pickIssueEnv = pick(issueEnv, ['issueId', 'issueIdLabel', 'issueIid', 'issueIidLabel', 'issueTitle', 'issueTitleLabel', 'issueDescription', 'issueDescriptionLabel']);
|
|
45
|
+
const pickRepoInfoEnv = pick(repoInfoEnv, ['repoId', 'repoIdLabel', 'repoName', 'repoNameLabel', 'repoSlug', 'repoSlugLabel']);
|
|
46
|
+
// const issueLabels = issueEnv.issueLabels || [];
|
|
47
|
+
const isComment = !!commentEnv.commentId;
|
|
48
|
+
const envList = [
|
|
49
|
+
...Object.entries(pickRepoInfoEnv).map(([key, value]) => `${key}: ${value}`),
|
|
50
|
+
...Object.entries(issueEnv).map(([key, value]) => `${key}: ${value}`),
|
|
51
|
+
...Object.entries(pickCommentEnv).map(([key, value]) => `${key}: ${value}`),
|
|
52
|
+
]
|
|
53
|
+
writeToProcess('当前环境变量:');
|
|
54
|
+
const issueLabels = await getIssuesLabels();
|
|
55
|
+
const issueLabelsNames = issueLabels.map(label => label.name) || [];
|
|
56
|
+
envList.forEach(item => writeToProcess(item));
|
|
57
|
+
if (!isComment && !issueLabelsNames.includes('Run')) {
|
|
58
|
+
writeToProcess('当前 Issue 不包含 Run 标签,跳过执行');
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
const messages = [
|
|
62
|
+
{
|
|
63
|
+
role: 'system',
|
|
64
|
+
content: `你是一个智能的代码助手, 根据用户提供的上下文信息,提供有用的建议和帮助, 如果用户的要求和执行工具不一致,请说出你不能这么做。并把最后的结果提交一个评论到对应的issue中,提交的内容必须不能包含 @ 提及。用户提供的上下文信息如下:`
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
role: 'system',
|
|
68
|
+
content: `相关变量:${JSON.stringify({ ...pickCommentEnv, ...pickIssueEnv, ...pickRepoInfoEnv })}`
|
|
69
|
+
}, {
|
|
70
|
+
role: 'user',
|
|
71
|
+
content: commentEnv.commentBody || pickIssueEnv.issueDescription || '无'
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
writeToProcess('输入消息:');
|
|
75
|
+
writeToProcess(JSON.stringify(messages, null, 2));
|
|
76
|
+
const result = await app.run({
|
|
77
|
+
path: 'cnb',
|
|
78
|
+
key: 'chat',
|
|
79
|
+
payload: {
|
|
80
|
+
messages
|
|
81
|
+
}
|
|
82
|
+
}, { appId: app.appId })
|
|
83
|
+
if (result.code === 200) {
|
|
84
|
+
let _message = result.data.message || []
|
|
85
|
+
writeToProcess('执行完成')
|
|
86
|
+
writeToProcess(JSON.stringify(_message, null, 2))
|
|
87
|
+
process.exit(0)
|
|
88
|
+
} else {
|
|
89
|
+
writeToProcess(result.message || '执行错误')
|
|
90
|
+
process.exit(1)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
main();
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { app, notCNBCheck } from '../../app.ts'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
import { title } from 'process';
|
|
5
|
+
|
|
6
|
+
app.route({
|
|
7
|
+
path: 'cnb',
|
|
8
|
+
key: 'docker-sync',
|
|
9
|
+
middleware: ['auth'],
|
|
10
|
+
metadata: {
|
|
11
|
+
tag: ['opencode'],
|
|
12
|
+
skill: 'cnb-docker-sync',
|
|
13
|
+
title: 'CNB Docker 镜像同步',
|
|
14
|
+
args: {
|
|
15
|
+
image: z.string().describe('Docker 同步的具体的镜像名称.'),
|
|
16
|
+
toVersion: z.string().optional().describe('修改后的版本号.'),
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}).define(async (ctx) => {
|
|
20
|
+
const { image, toVersion } = ctx.args;
|
|
21
|
+
notCNBCheck(ctx);
|
|
22
|
+
if (!image) {
|
|
23
|
+
ctx.body = {
|
|
24
|
+
message: '请提供 Docker 镜像名称.',
|
|
25
|
+
data: null,
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const config = {
|
|
30
|
+
registry: 'docker.cnb.cool/kevisual/dev-env',
|
|
31
|
+
dockers: [{ image, toVersion }]
|
|
32
|
+
}
|
|
33
|
+
// docker tag ghcr.io/esm-dev/esm.sh:v137 docker.cnb.cool/kevisual/dev-env/esm.sh:v137
|
|
34
|
+
// docker push docker.cnb.cool/kevisual/dev-env/esm.sh:v137
|
|
35
|
+
const run = async () => {
|
|
36
|
+
const dockers = config.dockers;
|
|
37
|
+
for (const { image, toVersion } of dockers) {
|
|
38
|
+
const imageName = image.split(':')[0].split('/').slice(-1)[0]
|
|
39
|
+
const tag = image.split(':')[1]
|
|
40
|
+
const newImage = `${config.registry}/${imageName}:${toVersion || tag}`
|
|
41
|
+
// console.log(`docker tag ${image} ${newImage}`)
|
|
42
|
+
// console.log(`docker push ${newImage}`)
|
|
43
|
+
const shell = `docker pull ${image} && docker tag ${image} ${newImage} && docker push ${newImage}`
|
|
44
|
+
console.log(shell)
|
|
45
|
+
|
|
46
|
+
console.log('\n-------------new---------------------------------\n')
|
|
47
|
+
console.log(`${newImage}`)
|
|
48
|
+
console.log('\n--------------------------------------------------\n')
|
|
49
|
+
try {
|
|
50
|
+
execSync(shell, { stdio: 'inherit' })
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(`Error: ${error}`)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
run().then(() => {
|
|
57
|
+
// TODO: 通知用户同步完成
|
|
58
|
+
});;
|
|
59
|
+
ctx.body = {
|
|
60
|
+
message: 'Docker 镜像同步任务中,请稍后在目标仓库查看.',
|
|
61
|
+
data: {
|
|
62
|
+
registry: config.registry,
|
|
63
|
+
dockers: config.dockers,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}).addTo(app);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './docker.ts'
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { runAgent } from '@kevisual/ai/agent'
|
|
2
|
+
import { app, cnbManager } from '../../app.ts';
|
|
3
|
+
import z from 'zod';
|
|
4
|
+
|
|
5
|
+
app.route({
|
|
6
|
+
path: 'cnb',
|
|
7
|
+
key: 'chat',
|
|
8
|
+
description: 'cnb智能对话接口',
|
|
9
|
+
middleware: ['auth'],
|
|
10
|
+
metadata: {
|
|
11
|
+
args: {
|
|
12
|
+
question: z.string().describe('用户输入的问题'),
|
|
13
|
+
messages: z.array(z.object({
|
|
14
|
+
role: z.enum(['user', 'assistant']).describe('消息角色,user表示用户输入,assistant表示助手回复'),
|
|
15
|
+
content: z.string().describe('消息内容')
|
|
16
|
+
})).describe('对话消息列表,按照时间顺序排列,包含用户和助手的历史消息'),
|
|
17
|
+
model: z.string().optional().describe('默认auto')
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}).define(async (ctx) => {
|
|
21
|
+
// notCNBCheck(ctx);
|
|
22
|
+
if (!ctx.args.question && !ctx.args.messages) {
|
|
23
|
+
ctx.throw(400, '缺少必要参数,必须提供question或messages');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const model = ctx.args?.model || 'auto'
|
|
27
|
+
const item = await cnbManager.getCNBItem(ctx);
|
|
28
|
+
const cnbAi = item.cnbAi;
|
|
29
|
+
const messages = ctx.args.messages || [{
|
|
30
|
+
role: 'user',
|
|
31
|
+
content: ctx.args.question
|
|
32
|
+
}]
|
|
33
|
+
const result = await runAgent({
|
|
34
|
+
app,
|
|
35
|
+
messages: messages,
|
|
36
|
+
languageModel: cnbAi(model),
|
|
37
|
+
token: '',
|
|
38
|
+
// token: ctx.query.token as string,
|
|
39
|
+
});
|
|
40
|
+
ctx.body = result;
|
|
41
|
+
}).addTo(app);
|
package/agent/routes/index.ts
CHANGED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { createSkill, tool } from '@kevisual/router';
|
|
2
|
+
import { app, cnbManager } from '../../app.ts';
|
|
3
|
+
import { useKey } from '@kevisual/context';
|
|
4
|
+
|
|
5
|
+
// 查询 Issue 评论列表
|
|
6
|
+
app.route({
|
|
7
|
+
path: 'cnb',
|
|
8
|
+
key: 'list-issue-comments',
|
|
9
|
+
description: '查询 Issue 评论列表, 参数 repo, issueNumber, page, page_size',
|
|
10
|
+
middleware: ['auth'],
|
|
11
|
+
metadata: {
|
|
12
|
+
tags: ['opencode'],
|
|
13
|
+
...createSkill({
|
|
14
|
+
skill: 'list-issue-comments',
|
|
15
|
+
title: '查询 Issue 评论列表',
|
|
16
|
+
args: {
|
|
17
|
+
repo: tool.schema.string().optional().describe('代码仓库名称, 如 my-user/my-repo'),
|
|
18
|
+
issueNumber: tool.schema.number().describe('Issue 编号'),
|
|
19
|
+
page: tool.schema.number().optional().describe('分页页码,默认: 1'),
|
|
20
|
+
page_size: tool.schema.number().optional().describe('分页每页大小,默认: 30'),
|
|
21
|
+
},
|
|
22
|
+
summary: '查询 Issue 评论列表',
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
}).define(async (ctx) => {
|
|
26
|
+
const cnb = await cnbManager.getContext(ctx);
|
|
27
|
+
let repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
|
|
28
|
+
const issueNumber = ctx.query?.issueNumber;
|
|
29
|
+
const page = ctx.query?.page ? Number(ctx.query.page) : undefined;
|
|
30
|
+
const page_size = ctx.query?.page_size ? Number(ctx.query.page_size) : undefined;
|
|
31
|
+
|
|
32
|
+
if (!repo) {
|
|
33
|
+
ctx.throw(400, '缺少参数 repo');
|
|
34
|
+
}
|
|
35
|
+
if (!issueNumber) {
|
|
36
|
+
ctx.throw(400, '缺少参数 issueNumber');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const params: Record<string, any> = {};
|
|
40
|
+
if (page) params.page = page;
|
|
41
|
+
if (page_size) params.page_size = page_size;
|
|
42
|
+
|
|
43
|
+
const res = await cnb.issue.getCommentList(repo, issueNumber, params);
|
|
44
|
+
ctx.forward(res);
|
|
45
|
+
}).addTo(app);
|
|
46
|
+
|
|
47
|
+
// 创建 Issue 评论
|
|
48
|
+
app.route({
|
|
49
|
+
path: 'cnb',
|
|
50
|
+
key: 'create-issue-comment',
|
|
51
|
+
description: '创建 Issue 评论, 参数 repo, issueNumber, body',
|
|
52
|
+
middleware: ['auth'],
|
|
53
|
+
metadata: {
|
|
54
|
+
tags: ['opencode'],
|
|
55
|
+
...createSkill({
|
|
56
|
+
skill: 'create-issue-comment',
|
|
57
|
+
title: '创建 Issue 评论',
|
|
58
|
+
args: {
|
|
59
|
+
repo: tool.schema.string().optional().describe('代码仓库名称, 如 my-user/my-repo'),
|
|
60
|
+
issueNumber: tool.schema.number().describe('Issue 编号'),
|
|
61
|
+
body: tool.schema.string().describe('评论内容'),
|
|
62
|
+
clearAt: tool.schema.boolean().optional().describe('是否清除评论内容中的 @ 提及,默认: true'),
|
|
63
|
+
},
|
|
64
|
+
summary: '创建 Issue 评论',
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
}).define(async (ctx) => {
|
|
68
|
+
const cnb = await cnbManager.getContext(ctx);
|
|
69
|
+
let repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
|
|
70
|
+
const issueNumber = ctx.query?.issueNumber;
|
|
71
|
+
let body = ctx.query?.body;
|
|
72
|
+
const clearAt = ctx.query?.clearAt ?? true;
|
|
73
|
+
|
|
74
|
+
if (!repo) {
|
|
75
|
+
ctx.throw(400, '缺少参数 repo');
|
|
76
|
+
}
|
|
77
|
+
if (!issueNumber) {
|
|
78
|
+
ctx.throw(400, '缺少参数 issueNumber');
|
|
79
|
+
}
|
|
80
|
+
if (!body) {
|
|
81
|
+
ctx.throw(400, '缺少参数 body');
|
|
82
|
+
}
|
|
83
|
+
if (clearAt && body) {
|
|
84
|
+
// 清除评论内容中的 @ 提及
|
|
85
|
+
body = body.replace(/@/g, '');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const res = await cnb.issue.createComment(repo, issueNumber, body);
|
|
89
|
+
ctx.forward(res);
|
|
90
|
+
}).addTo(app);
|
|
91
|
+
|
|
92
|
+
// 获取 Issue 指定评论
|
|
93
|
+
app.route({
|
|
94
|
+
path: 'cnb',
|
|
95
|
+
key: 'get-issue-comment',
|
|
96
|
+
description: '获取 Issue 指定评论, 参数 repo, issueNumber, commentId',
|
|
97
|
+
middleware: ['auth'],
|
|
98
|
+
metadata: {
|
|
99
|
+
tags: ['opencode'],
|
|
100
|
+
...createSkill({
|
|
101
|
+
skill: 'get-issue-comment',
|
|
102
|
+
title: '获取 Issue 评论',
|
|
103
|
+
args: {
|
|
104
|
+
repo: tool.schema.string().optional().describe('代码仓库名称, 如 my-user/my-repo'),
|
|
105
|
+
issueNumber: tool.schema.number().describe('Issue 编号'),
|
|
106
|
+
commentId: tool.schema.number().describe('评论 ID'),
|
|
107
|
+
},
|
|
108
|
+
summary: '获取 Issue 评论',
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
}).define(async (ctx) => {
|
|
112
|
+
const cnb = await cnbManager.getContext(ctx);
|
|
113
|
+
let repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
|
|
114
|
+
const issueNumber = ctx.query?.issueNumber;
|
|
115
|
+
const commentId = ctx.query?.commentId;
|
|
116
|
+
|
|
117
|
+
if (!repo) {
|
|
118
|
+
ctx.throw(400, '缺少参数 repo');
|
|
119
|
+
}
|
|
120
|
+
if (!issueNumber) {
|
|
121
|
+
ctx.throw(400, '缺少参数 issueNumber');
|
|
122
|
+
}
|
|
123
|
+
if (!commentId) {
|
|
124
|
+
ctx.throw(400, '缺少参数 commentId');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const res = await cnb.issue.getComment(repo, issueNumber, commentId);
|
|
128
|
+
ctx.forward(res);
|
|
129
|
+
}).addTo(app);
|
|
130
|
+
|
|
131
|
+
// 修改 Issue 评论
|
|
132
|
+
app.route({
|
|
133
|
+
path: 'cnb',
|
|
134
|
+
key: 'update-issue-comment',
|
|
135
|
+
description: '修改 Issue 评论, 参数 repo, issueNumber, commentId, body',
|
|
136
|
+
middleware: ['auth'],
|
|
137
|
+
metadata: {
|
|
138
|
+
tags: ['opencode'],
|
|
139
|
+
...createSkill({
|
|
140
|
+
skill: 'update-issue-comment',
|
|
141
|
+
title: '修改 Issue 评论',
|
|
142
|
+
args: {
|
|
143
|
+
repo: tool.schema.string().optional().describe('代码仓库名称, 如 my-user/my-repo'),
|
|
144
|
+
issueNumber: tool.schema.number().describe('Issue 编号'),
|
|
145
|
+
commentId: tool.schema.number().describe('评论 ID'),
|
|
146
|
+
body: tool.schema.string().describe('评论内容'),
|
|
147
|
+
clearAt: tool.schema.boolean().optional().describe('是否清除评论内容中的 @ 提及,默认: true'),
|
|
148
|
+
},
|
|
149
|
+
summary: '修改 Issue 评论',
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
}).define(async (ctx) => {
|
|
153
|
+
const cnb = await cnbManager.getContext(ctx);
|
|
154
|
+
let repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
|
|
155
|
+
const issueNumber = ctx.query?.issueNumber;
|
|
156
|
+
const commentId = ctx.query?.commentId;
|
|
157
|
+
let body = ctx.query?.body;
|
|
158
|
+
const clearAt = ctx.query?.clearAt ?? true;
|
|
159
|
+
if (!repo) {
|
|
160
|
+
ctx.throw(400, '缺少参数 repo');
|
|
161
|
+
}
|
|
162
|
+
if (!issueNumber) {
|
|
163
|
+
ctx.throw(400, '缺少参数 issueNumber');
|
|
164
|
+
}
|
|
165
|
+
if (!commentId) {
|
|
166
|
+
ctx.throw(400, '缺少参数 commentId');
|
|
167
|
+
}
|
|
168
|
+
if (!body) {
|
|
169
|
+
ctx.throw(400, '缺少参数 body');
|
|
170
|
+
}
|
|
171
|
+
if (clearAt && body) {
|
|
172
|
+
// 清除评论内容中的 @ 提及
|
|
173
|
+
body = body.replace(/@/g, '');
|
|
174
|
+
}
|
|
175
|
+
const res = await cnb.issue.updateComment(repo, issueNumber, commentId, body);
|
|
176
|
+
ctx.forward(res);
|
|
177
|
+
}).addTo(app);
|
|
@@ -14,7 +14,7 @@ app.route({
|
|
|
14
14
|
skill: 'list-issues',
|
|
15
15
|
title: '查询 Issue 列表',
|
|
16
16
|
args: {
|
|
17
|
-
repo: tool.schema.string().describe('代码仓库名称, 如 my-user/my-repo'),
|
|
17
|
+
repo: tool.schema.string().optional().describe('代码仓库名称, 如 my-user/my-repo'),
|
|
18
18
|
state: tool.schema.string().optional().describe('Issue 状态:open 或 closed'),
|
|
19
19
|
keyword: tool.schema.string().optional().describe('问题搜索关键词'),
|
|
20
20
|
labels: tool.schema.string().optional().describe('问题标签,多个用逗号分隔'),
|
|
@@ -27,7 +27,7 @@ app.route({
|
|
|
27
27
|
}
|
|
28
28
|
}).define(async (ctx) => {
|
|
29
29
|
const cnb = await cnbManager.getContext(ctx);
|
|
30
|
-
|
|
30
|
+
let repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
|
|
31
31
|
const state = ctx.query?.state;
|
|
32
32
|
const keyword = ctx.query?.keyword;
|
|
33
33
|
const labels = ctx.query?.labels;
|
|
@@ -49,4 +49,37 @@ app.route({
|
|
|
49
49
|
|
|
50
50
|
const res = await cnb.issue.getList(repo, params);
|
|
51
51
|
ctx.forward(res);
|
|
52
|
+
}).addTo(app);
|
|
53
|
+
|
|
54
|
+
app.route({
|
|
55
|
+
path: 'cnb',
|
|
56
|
+
key: 'getIssue',
|
|
57
|
+
description: '获取 单个 Issue',
|
|
58
|
+
middleware: ['auth'],
|
|
59
|
+
metadata: {
|
|
60
|
+
tags: ['opencode'],
|
|
61
|
+
...createSkill({
|
|
62
|
+
skill: 'getIssue',
|
|
63
|
+
title: '获取 单个 Issue',
|
|
64
|
+
args: {
|
|
65
|
+
repo: tool.schema.string().optional().describe('代码仓库名称, 如 my-user/my-repo'),
|
|
66
|
+
issueNumber: tool.schema.union([tool.schema.string(), tool.schema.number()]).describe('Issue 编号'),
|
|
67
|
+
},
|
|
68
|
+
summary: '获取 单个 Issue',
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
}).define(async (ctx) => {
|
|
72
|
+
const cnb = await cnbManager.getContext(ctx);
|
|
73
|
+
let repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
|
|
74
|
+
const issueNumber = ctx.query?.issueNumber;
|
|
75
|
+
|
|
76
|
+
if (!repo) {
|
|
77
|
+
ctx.throw(400, '缺少参数 repo');
|
|
78
|
+
}
|
|
79
|
+
if (!issueNumber) {
|
|
80
|
+
ctx.throw(400, '缺少参数 issueNumber');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const res = await cnb.issue.getItem(repo, issueNumber);
|
|
84
|
+
ctx.forward(res);
|
|
52
85
|
}).addTo(app);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createOpencodeClient } from "@opencode-ai/sdk"
|
|
2
|
+
|
|
3
|
+
const client = await createOpencodeClient({
|
|
4
|
+
// baseUrl: "https://yccb64t1z-100.cnb.run",
|
|
5
|
+
// auth: async () => {
|
|
6
|
+
// return 'cm9vdDozR0I2MDg5ZGpYOE5oMDFjM1FteE5DWDd0ZkI='
|
|
7
|
+
// }
|
|
8
|
+
baseUrl: "http://localhost:4096",
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const sessionList = await client.session.list()
|
|
12
|
+
console.log(sessionList.data)
|