@kevisual/cnb 0.0.42 → 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 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
- export const cnb = (await cnbManager.getCNB({ username: 'default' })).cnb
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,7 +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 环境,无法获取 live 内容');
32
+ ctx.throw(400, '当前环境非 cnb-board 环境,无法获取内容');
32
33
  }
33
34
  return false;
34
- }
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().cnb
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.cnb
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,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);
@@ -10,6 +10,7 @@ import './cnb-board/index.ts';
10
10
  import './share/index.ts';
11
11
  import './cnb-manager/index.ts';
12
12
  import './build/index.ts';
13
+ import './chat/chat.ts';
13
14
 
14
15
  /**
15
16
  * 验证上下文中的 App ID 是否与指定的 App ID 匹配
@@ -59,6 +59,7 @@ app.route({
59
59
  repo: tool.schema.string().optional().describe('代码仓库名称, 如 my-user/my-repo'),
60
60
  issueNumber: tool.schema.number().describe('Issue 编号'),
61
61
  body: tool.schema.string().describe('评论内容'),
62
+ clearAt: tool.schema.boolean().optional().describe('是否清除评论内容中的 @ 提及,默认: true'),
62
63
  },
63
64
  summary: '创建 Issue 评论',
64
65
  })
@@ -67,7 +68,8 @@ app.route({
67
68
  const cnb = await cnbManager.getContext(ctx);
68
69
  let repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
69
70
  const issueNumber = ctx.query?.issueNumber;
70
- const body = ctx.query?.body;
71
+ let body = ctx.query?.body;
72
+ const clearAt = ctx.query?.clearAt ?? true;
71
73
 
72
74
  if (!repo) {
73
75
  ctx.throw(400, '缺少参数 repo');
@@ -78,6 +80,10 @@ app.route({
78
80
  if (!body) {
79
81
  ctx.throw(400, '缺少参数 body');
80
82
  }
83
+ if (clearAt && body) {
84
+ // 清除评论内容中的 @ 提及
85
+ body = body.replace(/@/g, '');
86
+ }
81
87
 
82
88
  const res = await cnb.issue.createComment(repo, issueNumber, body);
83
89
  ctx.forward(res);
@@ -138,6 +144,7 @@ app.route({
138
144
  issueNumber: tool.schema.number().describe('Issue 编号'),
139
145
  commentId: tool.schema.number().describe('评论 ID'),
140
146
  body: tool.schema.string().describe('评论内容'),
147
+ clearAt: tool.schema.boolean().optional().describe('是否清除评论内容中的 @ 提及,默认: true'),
141
148
  },
142
149
  summary: '修改 Issue 评论',
143
150
  })
@@ -147,8 +154,8 @@ app.route({
147
154
  let repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
148
155
  const issueNumber = ctx.query?.issueNumber;
149
156
  const commentId = ctx.query?.commentId;
150
- const body = ctx.query?.body;
151
-
157
+ let body = ctx.query?.body;
158
+ const clearAt = ctx.query?.clearAt ?? true;
152
159
  if (!repo) {
153
160
  ctx.throw(400, '缺少参数 repo');
154
161
  }
@@ -161,7 +168,10 @@ app.route({
161
168
  if (!body) {
162
169
  ctx.throw(400, '缺少参数 body');
163
170
  }
164
-
171
+ if (clearAt && body) {
172
+ // 清除评论内容中的 @ 提及
173
+ body = body.replace(/@/g, '');
174
+ }
165
175
  const res = await cnb.issue.updateComment(repo, issueNumber, commentId, body);
166
176
  ctx.forward(res);
167
177
  }).addTo(app);
@@ -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)