Compare commits
2 Commits
da7b06e519
...
b15b11fa02
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b15b11fa02 | ||
|
|
50332fe2f4 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
.env
|
||||
.env.local
|
||||
node_modules
|
||||
.pnpm-store
|
||||
.pnpm-store
|
||||
|
||||
dist
|
||||
3
.npmrc
Normal file
3
.npmrc
Normal file
@@ -0,0 +1,3 @@
|
||||
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
|
||||
//npm.cnb.cool/kevisual/registry/-/packages/:_authToken=${CNB_API_KEY}
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
@@ -1 +1 @@
|
||||
export * from "../../agent/opencode-plugin";
|
||||
export * from "../../agent/opencode";
|
||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"code-runner.executorMap": {
|
||||
"ts": "opencode run $selectedText"
|
||||
},
|
||||
"code-runner.runInTerminal": true
|
||||
}
|
||||
@@ -6,7 +6,10 @@ import { nanoid } from 'nanoid';
|
||||
|
||||
export const config = useConfig()
|
||||
export const cnb = useContextKey<CNB>('cnb', () => {
|
||||
const token = useKey('CNB_API_KEY') as string
|
||||
// CNB_TOKEN是降级兼容变量,推荐使用CNB_API_KEY
|
||||
// CNB_TOKEN 是流水线自己就有的变量,但是权限比较小
|
||||
const token = useKey('CNB_API_KEY') as string || useKey('CNB_TOKEN') as string
|
||||
// cookie 变量是可选的
|
||||
const cookie = useKey('CNB_COOKIE') as string
|
||||
return new CNB({ token: token, cookie: cookie });
|
||||
})
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import { tool } from "@opencode-ai/plugin/tool"
|
||||
import { type Plugin } from "@opencode-ai/plugin"
|
||||
import { app, cnb, appId } from './index.ts';
|
||||
import { } from 'es-toolkit'
|
||||
import { Skill } from "@kevisual/router";
|
||||
|
||||
const routes = app.routes.filter(r => {
|
||||
const metadata = r.metadata as Skill
|
||||
if (metadata && metadata.tags && metadata.tags.includes('opencode')) {
|
||||
return !!metadata.skill
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// opencode run "请使用 cnb-login-verify 工具验证登录信信息,检查cookie"
|
||||
export const CnbPlugin: Plugin = async ({ project, client, $, directory, worktree }) => {
|
||||
return {
|
||||
'tool': {
|
||||
...routes.reduce((acc, route) => {
|
||||
const metadata = route.metadata as Skill
|
||||
acc[metadata.skill!] = {
|
||||
name: metadata.title || metadata.skill,
|
||||
description: metadata.summary || '',
|
||||
args: metadata.args || {},
|
||||
async execute(args: Record<string, any>) {
|
||||
console.log(`Executing skill ${metadata.skill} with args:`, args);
|
||||
await client.app.log({
|
||||
body: {
|
||||
service: 'cnb',
|
||||
level: 'info',
|
||||
message: `Executing skill ${metadata.skill} with args: ${JSON.stringify(args)}`
|
||||
}
|
||||
});
|
||||
const res = await app.run({
|
||||
path: route.path,
|
||||
key: route.key,
|
||||
payload: args
|
||||
}, { appId });
|
||||
if (res.code === 200) {
|
||||
if (res.data?.content) {
|
||||
return res.data.content;
|
||||
}
|
||||
const str = JSON.stringify(res.data || res, null, 2);
|
||||
if (str.length > 5000) {
|
||||
return str.slice(0, 5000) + '... (truncated)';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
return `Error: ${res?.message || '无法获取结果'}`;
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, any>)
|
||||
},
|
||||
'tool.execute.before': async (opts) => {
|
||||
// console.log('CnbPlugin: tool.execute.before', opts.tool);
|
||||
// delete toolSkills['cnb-login-verify']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const demo = {
|
||||
'tool': {
|
||||
"cnb-login-verify": {
|
||||
name: "CNB 登录验证信息",
|
||||
description: "验证 CNB 登录信息是否有效",
|
||||
args: {
|
||||
checkToken: tool.schema.boolean().describe("是否检查 Token 的有效性").default(true),
|
||||
checkCookie: tool.schema.boolean().describe("是否检查 Cookie 的有效性").default(false),
|
||||
},
|
||||
async execute(args) {
|
||||
const res = await app.run({
|
||||
path: 'cnb',
|
||||
key: 'user-check',
|
||||
payload: {
|
||||
...args
|
||||
}
|
||||
}, { appId });
|
||||
if (res.code === 200) {
|
||||
return res.data?.output;
|
||||
}
|
||||
return '无法获取登录状态,请检查配置。';
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
6
agent/opencode.ts
Normal file
6
agent/opencode.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { app} from './index.ts';
|
||||
import { createRouterAgentPluginFn } from '@kevisual/router/opencode'
|
||||
|
||||
export const CnbPlugin = createRouterAgentPluginFn({
|
||||
router: app,
|
||||
})
|
||||
@@ -1 +0,0 @@
|
||||
调用 path: cnb key: get-repo-list payload: { page: 1, per_page: 10 }
|
||||
48
agent/routes/cnb-env/env.ts
Normal file
48
agent/routes/cnb-env/env.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { createSkill, tool } from '@kevisual/router';
|
||||
import { app, cnb } from '../../app.ts';
|
||||
|
||||
// 设置 CNB_COOKIE环境变量和获取环境变量,用于界面操作定制模块功能
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'set-cnb-cookie',
|
||||
description: '设置当前cnb工作空间的cookie环境变量',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'set-cnb-cookie',
|
||||
title: '设置当前cnb工作空间的cookie环境变量',
|
||||
summary: '设置当前cnb工作空间的cookie环境变量,用于界面操作定制模块功能,例子:CNBSESSION=xxxx;csrfkey=2222xxxx;',
|
||||
args: {
|
||||
cookie: tool.schema.string().describe('cnb的cookie值'),
|
||||
}
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const cookie = ctx.query?.cookie;
|
||||
if (!cookie) {
|
||||
ctx.body = { content: '请提供有效的cookie值' };
|
||||
return;
|
||||
}
|
||||
cnb.cookie = cookie;
|
||||
ctx.body = { content: '已成功设置cnb的cookie环境变量' };
|
||||
}).addTo(app);
|
||||
|
||||
// 获取 CNB_COOKIE环境变量
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'get-cnb-cookie',
|
||||
description: '获取当前cnb工作空间的cookie环境变量',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'get-cnb-cookie',
|
||||
title: '获取当前cnb工作空间的cookie环境变量',
|
||||
summary: '获取当前cnb工作空间的cookie环境变量,用于界面操作定制模块功能',
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const cookie = cnb.cookie || '未设置cookie环境变量';
|
||||
ctx.body = { content: `当前cnb工作空间的cookie环境变量为:${cookie}` };
|
||||
}).addTo(app);
|
||||
@@ -1 +1,3 @@
|
||||
// 根据环境变量获取当前的 cnb 启动环境
|
||||
// 根据环境变量获取当前的 cnb 启动环境
|
||||
import './vscode.ts';
|
||||
import './env.ts';
|
||||
94
agent/routes/cnb-env/vscode.ts
Normal file
94
agent/routes/cnb-env/vscode.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { createSkill, tool } from '@kevisual/router';
|
||||
import { app, cnb } from '../../app.ts';
|
||||
|
||||
import { CNB_ENV } from "@/common/cnb-env.ts";
|
||||
|
||||
// 执行技能 get-cnb-port-uri,端口为4096
|
||||
// 执行技能 get-cnb-port-uri,端口为51515
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'get-cnb-port-uri',
|
||||
description: '获取当前cnb工作空间的port代理uri',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'get-cnb-port-uri',
|
||||
title: '获取当前cnb工作空间的port代理uri',
|
||||
summary: '获取当前cnb工作空间的port代理uri,用于端口转发',
|
||||
args: {
|
||||
port: tool.schema.number().optional().describe('端口号,默认为4096'),
|
||||
}
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const port = ctx.query?.port || 4096;
|
||||
const uri = CNB_ENV?.CNB_VSCODE_PROXY_URI as string || '';
|
||||
const finalUri = uri.replace('{{port}}', port.toString());
|
||||
let content = `
|
||||
cnb工作空间的访问uri为:${finalUri}
|
||||
`
|
||||
ctx.body = { content };
|
||||
}).addTo(app);
|
||||
|
||||
// 获取当前cnb工作空间的vscode代理uri,执行技能 get-cnb-vscode-uri
|
||||
// 包括 web 访问uri,vscode 访问uri,codebuddy 访问uri,cursor 访问uri,ssh 连接字符串
|
||||
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'get-cnb-vscode-uri',
|
||||
description: '获取当前cnb工作空间的vscode代理uri, 包括多种访问方式, 如web、vscode、codebuddy、cursor、ssh',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'get-cnb-vscode-uri',
|
||||
title: '获取当前cnb工作空间的编辑器访问地址',
|
||||
summary: '获取当前cnb工作空间的vscode代理uri,用于在浏览器中访问vscode,包含多种访问方式,如web、vscode、codebuddy、cursor、ssh',
|
||||
args: {
|
||||
web: tool.schema.boolean().optional().describe('是否获取vscode web的访问uri,默认为false'),
|
||||
vscode: tool.schema.boolean().optional().describe('是否获取vscode的代理uri,默认为true'),
|
||||
codebuddy: tool.schema.boolean().optional().describe('是否获取codebuddy的代理uri,默认为false'),
|
||||
cursor: tool.schema.boolean().optional().describe('是否获取cursor的代理uri,默认为false'),
|
||||
// trae: tool.schema.boolean().optional().describe('是否获取trae的代理uri,默认为false'),
|
||||
ssh: tool.schema.boolean().optional().describe('是否获取vscode remote ssh的连接字符串,默认为false'),
|
||||
}
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const web = ctx.query?.web ?? false;
|
||||
const vscode = ctx.query?.vscode ?? true; // 默认true
|
||||
const codebuddy = ctx.query?.codebuddy ?? false;
|
||||
const cursor = ctx.query?.cursor ?? false;
|
||||
// const trae = ctx.query?.trae ?? false;
|
||||
const ssh = ctx.query?.ssh ?? false;
|
||||
|
||||
const webUri = CNB_ENV?.CNB_VSCODE_WEB_URL as string || '';
|
||||
const vscodeSchma = CNB_ENV?.CNB_VSCODE_REMOTE_SSH_SCHEMA as string || '';
|
||||
let content = `
|
||||
当前的cnb 仓库为:${CNB_ENV?.CNB_REPO_SLUG}
|
||||
|
||||
`
|
||||
if (web) {
|
||||
content += `VS Code Web 访问 URI:${webUri}\n\n`;
|
||||
}
|
||||
if (vscode) {
|
||||
content += `VS Code 访问 URI:${vscodeSchma}\n\n`;
|
||||
}
|
||||
if (codebuddy) {
|
||||
const codebuddyUri = vscodeSchma.replace('vscode://vscode-remote/ssh-remote+', 'codebuddycn://vscode-remote/codebuddy-remote');
|
||||
content += `CodeBuddy 访问 URI:${codebuddyUri}\n\n`;
|
||||
}
|
||||
if (cursor) {
|
||||
const cursorUri = vscodeSchma.replace('vscode://', 'cursor://');
|
||||
content += `Cursor 访问 URI:${cursorUri}\n\n`;
|
||||
}
|
||||
// if (trae) {
|
||||
// const traeUri = vscodeSchma.replace('vscode://vscode-remote/ssh-remote+', 'traecn://ssh-remote+');
|
||||
// content += `Trae 访问 URI:${traeUri}\n\n`;
|
||||
// }
|
||||
if (ssh) {
|
||||
content += `VS Code Remote SSH 连接字符串:ssh ${CNB_ENV.CNB_PIPELINE_ID}.${CNB_ENV.CNB_VSCODE_SSH_TOKEN}@cnb.space`;
|
||||
}
|
||||
ctx.body = { content };
|
||||
}).addTo(app);
|
||||
@@ -3,6 +3,8 @@ import './user/check.ts'
|
||||
import './repo/index.ts'
|
||||
import './workspace/index.ts'
|
||||
import './call/index.ts'
|
||||
import './cnb-env/index.ts'
|
||||
import './knowledge/index.ts'
|
||||
|
||||
import { isEqual } from 'es-toolkit'
|
||||
/**
|
||||
|
||||
142
agent/routes/knowledge/ai.ts
Normal file
142
agent/routes/knowledge/ai.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { createSkill, tool } from '@kevisual/router';
|
||||
import { app, cnb } from '../../app.ts';
|
||||
import { CNBChat } from '@kevisual/ai/browser'
|
||||
|
||||
/**
|
||||
|
||||
调用cnb-ai-chat技能, repo为kevisual/starred-auto.
|
||||
问题是:用户提供的问题是OpenListTeam/OpenList是什么,有多少star
|
||||
|
||||
*/
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'cnb-ai-chat',
|
||||
description: '调用cnb的知识库ai对话功能进行聊天',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'cnb-ai-chat',
|
||||
title: '调用cnb的知识库ai对话功能进行聊天',
|
||||
summary: '调用cnb的知识库ai对话功能进行聊天,基于cnb提供的ai能力',
|
||||
args: {
|
||||
question: tool.schema.string().describe('用户输入的消息内容'),
|
||||
repo: tool.schema.string().optional().describe('知识库仓库ID,默认为空表示使用默认知识库'),
|
||||
}
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const question = ctx.query?.question;
|
||||
if (!question) {
|
||||
ctx.body = { content: '请提供有效的消息内容' };
|
||||
return;
|
||||
}
|
||||
let repo = ctx.query?.repo;
|
||||
if (!repo) {
|
||||
// 如果未指定知识库仓库ID,则使用默认知识库
|
||||
const res = await cnb.repo.getRepoList({ flags: 'KnowledgeBase' });
|
||||
if (res.code === 200 && res.data.length > 0) {
|
||||
repo = res.data[0].path;
|
||||
}
|
||||
}
|
||||
console.log("Using knowledge base repo:", repo);
|
||||
const ragRes = await cnb.knowledgeBase.queryKnowledgeBase(repo || '', {
|
||||
query: question,
|
||||
score_threshold: 0.62,
|
||||
top_k: 10,
|
||||
});
|
||||
if (ragRes.code !== 200) {
|
||||
ctx.body = { content: `查询知识库失败,错误信息:${ragRes.message}` };
|
||||
return;
|
||||
}
|
||||
const list = ragRes.data || [];
|
||||
// 构建RAG上下文,包含文件来源和相似度信息
|
||||
const ragContext = list.map((item, index) => {
|
||||
const source = item.metadata?.path || item.metadata?.name || '未知来源';
|
||||
const type = item.metadata?.type === 'code' ? '〔代码〕' : '〔文档〕';
|
||||
const scorePercent = Math.round((item.score || 0) * 100);
|
||||
const url = item.metadata?.url || '无';
|
||||
return `〔来源${index + 1}〕${type} ${source} (相似度: ${scorePercent}%)\n${item.chunk}\n访问地址: ${url}`;
|
||||
}).join('\n\n---\n\n');
|
||||
// hunyuan-a13b
|
||||
// enable_thinking
|
||||
const chat = new CNBChat({
|
||||
repo,
|
||||
token: cnb.token,
|
||||
model: 'hunyuan-a13b'
|
||||
});
|
||||
const messages = [
|
||||
{
|
||||
role: 'system',
|
||||
content: `[角色定义]='''\n你是一个专业的技术助手,擅长基于提供的知识库内容进行准确、有条理的分析和回答。你的特点是:\n1. 严格基于RAG检索到的上下文内容进行回答,不添加未经验证的信息\n2. 回答时清晰标注信息来源,便于用户追溯查证\n3. 面对不确定的信息,明确标注「根据提供的内容无法确定」\n4. 代码相关问题注重可执行性和最佳实践\n'''[要求]='''\n1. 严格遵循用户的提问要求,优先解决用户的核心问题\n2. 避免侵犯版权的内容,不复制原文超过100字(技术术语和函数名除外)\n3. 使用中文进行响应,语言专业且易于理解\n4. 如果上下文存在多个来源,优先使用相似度更高的内容\n5. 对于代码片段,确保完整且可直接使用\n6. 当上下文中没有相关信息时,直接说明「知识库中未找到相关内容」\n7. 在思考过程中分析:用户的真实意图是什么?提供的上下文是否足够回答?\n'''[回答格式]='''\n- 先简要说明回答的核心结论\n- 如有必要,分点阐述详细分析过程\n- 标注关键信息来源(标注【来源X】即可)\n- 提供可操作的建议或代码示例\n'''`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `[上下文]='''\n${ragContext}\n'''\n\n[用户问题]='''\n${question}\n'''\n\n请基于以上上下文知识库内容回答用户问题。`
|
||||
}
|
||||
] as Array<{ role: 'system' | 'user' | 'assistant', content: string }>;
|
||||
const response = await chat.chat({
|
||||
messages
|
||||
});
|
||||
const txt = chat.responseText;
|
||||
ctx.body = { content: txt, response };
|
||||
}).addTo(app);
|
||||
|
||||
|
||||
// RAG知识库查询技能: 查询openlist有多少star
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'cnb-rag-query',
|
||||
description: '调用cnb的知识库RAG查询功能进行问答',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'cnb-rag-query',
|
||||
title: '调用cnb的知识库RAG查询功能进行问答',
|
||||
summary: '调用cnb的知识库RAG查询功能进行问答,基于cnb提供的知识库能力',
|
||||
args: {
|
||||
question: tool.schema.string().describe('用户输入的消息内容'),
|
||||
repo: tool.schema.string().optional().describe('知识库仓库ID,默认为空表示使用默认知识库'),
|
||||
}
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const question = ctx.query?.question;
|
||||
if (!question) {
|
||||
ctx.body = { content: '请提供有效的消息内容' };
|
||||
return;
|
||||
}
|
||||
let repo = ctx.query?.repo;
|
||||
if (!repo) {
|
||||
// 如果未指定知识库仓库ID,则使用默认知识库
|
||||
const res = await cnb.repo.getRepoList({ flags: 'KnowledgeBase' });
|
||||
if (res.code === 200 && res.data.length > 0) {
|
||||
repo = res.data[0].path;
|
||||
}
|
||||
}
|
||||
console.log("Using knowledge base repo:", repo);
|
||||
const ragRes = await cnb.knowledgeBase.queryKnowledgeBase(repo || '', {
|
||||
query: question,
|
||||
score_threshold: 0.62,
|
||||
top_k: 10,
|
||||
});
|
||||
if (ragRes.code !== 200) {
|
||||
ctx.body = { content: `查询知识库失败,错误信息:${ragRes.message}` };
|
||||
return;
|
||||
}
|
||||
const list = ragRes.data || [];
|
||||
let answer = `基于知识库「${repo}」的查询结果:\n\n`;
|
||||
if (list.length === 0) {
|
||||
answer += '知识库中未找到相关内容。';
|
||||
} else {
|
||||
list.forEach((item, index) => {
|
||||
const source = item.metadata?.path || item.metadata?.name || '未知来源';
|
||||
const type = item.metadata?.type === 'code' ? '〔代码〕' : '〔文档〕';
|
||||
const scorePercent = Math.round((item.score || 0) * 100);
|
||||
const url = item.metadata?.url || '无';
|
||||
answer += `【来源${index + 1}】${type} ${source} (相似度: ${scorePercent}%)\n${item.chunk}\n访问地址: ${url}\n\n`;
|
||||
});
|
||||
}
|
||||
ctx.body = { content: answer };
|
||||
}).addTo(app);
|
||||
1
agent/routes/knowledge/index.ts
Normal file
1
agent/routes/knowledge/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './ai.ts'
|
||||
@@ -2,6 +2,8 @@ import { createSkill } from '@kevisual/router';
|
||||
import { app, cnb } from '../../app.ts';
|
||||
import { tool } from "@opencode-ai/plugin/tool"
|
||||
|
||||
// "列出我的代码仓库,search blog"
|
||||
// 列出我的知识库的代码仓库
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'list-repos',
|
||||
@@ -11,18 +13,24 @@ app.route({
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'list-repos',
|
||||
title: '列出代码仓库',
|
||||
summary: '列出代码仓库',
|
||||
title: '列出cnb代码仓库',
|
||||
summary: '列出cnb代码仓库, 可选flags参数,如 KnowledgeBase',
|
||||
args: {
|
||||
search: tool.schema.string().optional().describe('搜索关键词'),
|
||||
pageSize: tool.schema.number().optional().describe('每页数量,默认999'),
|
||||
flags: tool.schema.string().optional().describe('仓库标记,如果是知识库则填写 KnowledgeBase'),
|
||||
},
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const search = ctx.query?.search;
|
||||
const pageSize = ctx.query?.pageSize || 9999;
|
||||
const res = await cnb.repo.getRepoList({ search, page_size: pageSize, role: 'developer' });
|
||||
const flags = ctx.query?.flags;
|
||||
const params: any = {};
|
||||
if (flags) {
|
||||
params.flags = flags;
|
||||
}
|
||||
const res = await cnb.repo.getRepoList({ search, page_size: pageSize, role: 'developer', ...params });
|
||||
if (res.code === 200) {
|
||||
const repos = res.data.map((item) => ({
|
||||
name: item.name,
|
||||
@@ -30,7 +38,7 @@ app.route({
|
||||
description: item.description,
|
||||
web_url: item.web_url,
|
||||
}));
|
||||
ctx.body = { content: JSON.stringify(repos) };
|
||||
ctx.body = { content: JSON.stringify(repos), list: res.data };
|
||||
} else {
|
||||
ctx.throw(500, '获取仓库列表失败');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { app, cnb } from '../../app.ts';
|
||||
import { createSkill, Skill } from '@kevisual/router'
|
||||
import { tool } from "@opencode-ai/plugin/tool"
|
||||
|
||||
// 创建一个仓库 kevisual/test-repo
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'create-repo',
|
||||
@@ -79,7 +81,7 @@ app.route({
|
||||
ctx.forward(res);
|
||||
}).addTo(app);
|
||||
|
||||
|
||||
// 删除一个仓库 kevisual/test-repo
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'delete-repo',
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
import { createSkill, tool } from '@kevisual/router';
|
||||
import { app, cnb } from '../../app.ts';
|
||||
import z from 'zod';
|
||||
import './skills.ts';
|
||||
|
||||
// 启动工作空间
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'start-workspace',
|
||||
description: '启动开发工作空间, 参数 repo',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode']
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'start-workspace',
|
||||
title: '启动cnb工作空间',
|
||||
summary: '启动cnb工作空间',
|
||||
args: {
|
||||
repo: tool.schema.string().describe('代码仓库路径,例如 user/repo'),
|
||||
branch: tool.schema.string().optional().describe('分支名称,默认主分支'),
|
||||
ref: tool.schema.string().optional().describe('提交引用,例如 commit sha'),
|
||||
},
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const repo = ctx.query?.repo;
|
||||
@@ -21,3 +35,133 @@ app.route({
|
||||
});
|
||||
ctx.forward(res);
|
||||
}).addTo(app);
|
||||
|
||||
// 获取cnb工作空间列表
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'list-workspace',
|
||||
description: '获取cnb开发工作空间列表,可选参数 status=running 获取运行中的环境',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'list-workspace',
|
||||
title: '列出cnb工作空间',
|
||||
summary: '列出cnb工作空间列表,支持按状态过滤, status 可选值 running 或 closed',
|
||||
args: {
|
||||
status: tool.schema.string().optional().describe('开发环境状态,running: 运行中,closed: 已关闭和停止的'),
|
||||
page: tool.schema.number().optional().describe('分页页码,默认 1'),
|
||||
pageSize: tool.schema.number().optional().describe('分页大小,默认 20,最大 100'),
|
||||
slug: tool.schema.string().optional().describe('仓库路径,例如 groupname/reponame'),
|
||||
branch: tool.schema.string().optional().describe('分支名称'),
|
||||
},
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const { status = 'running', page, pageSize, slug, branch } = ctx.query || {};
|
||||
const res = await cnb.workspace.list({
|
||||
status: status as 'running' | 'closed' | undefined,
|
||||
page: page ?? 1,
|
||||
pageSize: pageSize ?? 100,
|
||||
});
|
||||
ctx.forward({ code: 200, message: 'success', data: res });
|
||||
}).addTo(app);
|
||||
|
||||
// 获取工作空间详情
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'get-workspace',
|
||||
description: '获取工作空间详情,通过 repo 和 sn 获取',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'get-workspace',
|
||||
title: '获取工作空间详情',
|
||||
summary: '获取工作空间详细信息',
|
||||
args: {
|
||||
repo: tool.schema.string().describe('代码仓库路径,例如 user/repo'),
|
||||
sn: tool.schema.string().describe('工作空间流水线的 sn'),
|
||||
},
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const repo = ctx.query?.repo;
|
||||
const sn = ctx.query?.sn;
|
||||
if (!repo) {
|
||||
ctx.throw(400, '缺少参数 repo');
|
||||
}
|
||||
if (!sn) {
|
||||
ctx.throw(400, '缺少参数 sn');
|
||||
}
|
||||
const res = await cnb.workspace.getDetail(repo, sn);
|
||||
ctx.forward({ code: 200, message: 'success', data: res });
|
||||
}).addTo(app);
|
||||
|
||||
// 删除工作空间
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'delete-workspace',
|
||||
description: '删除工作空间,通过 pipelineId 或 sn',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'delete-workspace',
|
||||
title: '删除工作空间',
|
||||
summary: '删除工作空间,pipelineId 和 sn 二选一',
|
||||
args: {
|
||||
pipelineId: tool.schema.string().optional().describe('流水线 ID,优先使用'),
|
||||
sn: tool.schema.string().optional().describe('流水线构建号'),
|
||||
sns: tool.schema.array(z.string()).optional().describe('流水线构建号'),
|
||||
},
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const pipelineId = ctx.query?.pipelineId;
|
||||
const sn = ctx.query?.sn;
|
||||
const sns = ctx.query?.sns;
|
||||
if (!pipelineId && !sn && (!sns || sns.length === 0)) {
|
||||
ctx.throw(400, 'pipelineId 和 sn 必须提供其中一个');
|
||||
}
|
||||
if (sns && sns.length > 0) {
|
||||
const results = [];
|
||||
for (const snItem of sns) {
|
||||
const res = await cnb.workspace.deleteWorkspace({ sn: snItem });
|
||||
results.push(res);
|
||||
}
|
||||
ctx.forward({ code: 200, message: 'success', data: results });
|
||||
return;
|
||||
}
|
||||
const res = await cnb.workspace.deleteWorkspace({ pipelineId, sn });
|
||||
ctx.forward(res);
|
||||
}).addTo(app);
|
||||
|
||||
// 停止工作空间
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'stop-workspace',
|
||||
description: '停止工作空间,通过 pipelineId 或 sn',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'stop-workspace',
|
||||
title: '停止工作空间',
|
||||
summary: '停止运行中的工作空间',
|
||||
args: {
|
||||
pipelineId: tool.schema.string().optional().describe('流水线 ID,优先使用'),
|
||||
sn: tool.schema.string().optional().describe('流水线构建号'),
|
||||
},
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const pipelineId = ctx.query?.pipelineId;
|
||||
const sn = ctx.query?.sn;
|
||||
if (!pipelineId && !sn) {
|
||||
ctx.throw(400, 'pipelineId 和 sn 必须提供其中一个');
|
||||
}
|
||||
const res = await cnb.workspace.stopWorkspace({ pipelineId, sn });
|
||||
ctx.forward({ code: 200, message: 'success', data: res });
|
||||
}).addTo(app);
|
||||
|
||||
|
||||
64
agent/routes/workspace/skills.ts
Normal file
64
agent/routes/workspace/skills.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { createSkill, tool } from '@kevisual/router';
|
||||
import { app, cnb } from '../../app.ts';
|
||||
|
||||
// 批量删除已停止的cnb工作空间
|
||||
// app.route({
|
||||
// path: 'cnb',
|
||||
// key: 'clean-closed-workspace-skill',
|
||||
// description: '批量删除已停止的cnb工作空间',
|
||||
// middleware: ['auth'],
|
||||
// metadata: {
|
||||
// tags: ['opencode'],
|
||||
// ...createSkill({
|
||||
// skill: 'clean-closed-workspace-skill',
|
||||
// title: '清理已关闭的cnb工作空间',
|
||||
// summary: '批量删除已停止的cnb工作空间,释放资源',
|
||||
// args: {
|
||||
// question: tool.schema.string().optional().describe('具体的要求的信息'),
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }).define(async (ctx) => {
|
||||
// const question = ctx.query?.question || '';
|
||||
// let content = `这是一个技能任务, 批量删除已停止的cnb工作空间,释放资源
|
||||
// 执行步骤:
|
||||
// 1. 执行list-workspace,获取状态为 closed 的工作空间列表,提取sn
|
||||
// 2. 执行delete-workspace技能,传入sns列表的数组,批量删除工作空间`
|
||||
// if (question) {
|
||||
// content += `\n注意用户的具体要求是:${question}`;
|
||||
// }
|
||||
// ctx.body = { content }
|
||||
// }).addTo(app);
|
||||
|
||||
// 批量删除已停止的cnb工作空间
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'clean-closed-workspace',
|
||||
description: '批量删除已停止的cnb工作空间',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'clean-closed-workspace',
|
||||
title: '清理已关闭的cnb工作空间',
|
||||
summary: '批量删除已停止的cnb工作空间,释放资源',
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const closedWorkspaces = await cnb.workspace.list({ status: 'closed' });
|
||||
if (closedWorkspaces.code !== 200) {
|
||||
ctx.throw(500, '获取已关闭工作空间列表失败');
|
||||
}
|
||||
const list = closedWorkspaces.data?.list || [];
|
||||
if (list.length === 0) {
|
||||
ctx.forward({ code: 200, message: '没有已关闭的工作空间需要删除', data: [] });
|
||||
return;
|
||||
}
|
||||
const sns = list.map(ws => ws.sn);
|
||||
const results = [];
|
||||
for (const sn of sns) {
|
||||
const res = await cnb.workspace.deleteWorkspace({ sn });
|
||||
results.push(res);
|
||||
}
|
||||
ctx.forward({ code: 200, message: '已关闭的工作空间删除完成', data: results });
|
||||
}).addTo(app);
|
||||
@@ -0,0 +1,19 @@
|
||||
import { resolvePath } from '@kevisual/use-config';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
const entry = 'agent/opencode.ts';
|
||||
const naming = 'opencode';
|
||||
const external: string[] = ["bun"];
|
||||
await Bun.build({
|
||||
target: 'node',
|
||||
format: 'esm',
|
||||
entrypoints: [resolvePath(entry, { meta: import.meta })],
|
||||
outdir: resolvePath('./dist', { meta: import.meta }),
|
||||
naming: {
|
||||
entry: `${naming}.js`,
|
||||
},
|
||||
external,
|
||||
});
|
||||
|
||||
const cmd = 'dts -i agent/opencode.ts -o opencode.d.ts';
|
||||
execSync(cmd);
|
||||
28
package.json
28
package.json
@@ -1,38 +1,46 @@
|
||||
{
|
||||
"name": "@kevisual/cnb",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.3",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"build": "bun run bun.config.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"files": [
|
||||
"dist",
|
||||
"src",
|
||||
"mod.ts",
|
||||
"agent"
|
||||
],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.28.0",
|
||||
"packageManager": "pnpm@10.28.2",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@kevisual/ai": "^0.0.22",
|
||||
"@kevisual/context": "^0.0.4",
|
||||
"@kevisual/types": "^0.0.11",
|
||||
"@opencode-ai/plugin": "^1.1.23",
|
||||
"@kevisual/types": "^0.0.12",
|
||||
"@opencode-ai/plugin": "^1.1.36",
|
||||
"@types/bun": "^1.3.6",
|
||||
"@types/node": "^25.0.9",
|
||||
"@types/node": "^25.0.10",
|
||||
"dotenv": "^17.2.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kevisual/query": "^0.0.35",
|
||||
"@kevisual/router": "^0.0.55",
|
||||
"@kevisual/query": "^0.0.38",
|
||||
"@kevisual/router": "^0.0.62",
|
||||
"@kevisual/use-config": "^1.0.28",
|
||||
"es-toolkit": "^1.43.0",
|
||||
"es-toolkit": "^1.44.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"zod": "^4.3.5"
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"exports": {
|
||||
".": "./mod.ts",
|
||||
"./opencode": "./dist/opencode.js",
|
||||
"./src/*": "./src/*",
|
||||
"./agent/*": "./agent/*"
|
||||
}
|
||||
}
|
||||
110
pnpm-lock.yaml
generated
110
pnpm-lock.yaml
generated
@@ -9,76 +9,88 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@kevisual/query':
|
||||
specifier: ^0.0.35
|
||||
version: 0.0.35
|
||||
specifier: ^0.0.38
|
||||
version: 0.0.38
|
||||
'@kevisual/router':
|
||||
specifier: ^0.0.55
|
||||
version: 0.0.55
|
||||
specifier: ^0.0.62
|
||||
version: 0.0.62
|
||||
'@kevisual/use-config':
|
||||
specifier: ^1.0.28
|
||||
version: 1.0.28(dotenv@17.2.3)
|
||||
es-toolkit:
|
||||
specifier: ^1.43.0
|
||||
version: 1.43.0
|
||||
specifier: ^1.44.0
|
||||
version: 1.44.0
|
||||
nanoid:
|
||||
specifier: ^5.1.6
|
||||
version: 5.1.6
|
||||
zod:
|
||||
specifier: ^4.3.5
|
||||
version: 4.3.5
|
||||
specifier: ^4.3.6
|
||||
version: 4.3.6
|
||||
devDependencies:
|
||||
'@kevisual/ai':
|
||||
specifier: ^0.0.22
|
||||
version: 0.0.22
|
||||
'@kevisual/context':
|
||||
specifier: ^0.0.4
|
||||
version: 0.0.4
|
||||
'@kevisual/types':
|
||||
specifier: ^0.0.11
|
||||
version: 0.0.11
|
||||
specifier: ^0.0.12
|
||||
version: 0.0.12
|
||||
'@opencode-ai/plugin':
|
||||
specifier: ^1.1.23
|
||||
version: 1.1.23
|
||||
specifier: ^1.1.36
|
||||
version: 1.1.36
|
||||
'@types/bun':
|
||||
specifier: ^1.3.6
|
||||
version: 1.3.6
|
||||
'@types/node':
|
||||
specifier: ^25.0.9
|
||||
version: 25.0.9
|
||||
specifier: ^25.0.10
|
||||
version: 25.0.10
|
||||
dotenv:
|
||||
specifier: ^17.2.3
|
||||
version: 17.2.3
|
||||
|
||||
packages:
|
||||
|
||||
'@kevisual/ai@0.0.22':
|
||||
resolution: {integrity: sha512-9kth0pvPD4jT8c6rJ0DzSuvUY0s0pdw+CnO+YU8bwpMg04k1jFkd1RSZdtFrVbc60e2cpooQMUt/oimkfs082A==}
|
||||
|
||||
'@kevisual/context@0.0.4':
|
||||
resolution: {integrity: sha512-HJeLeZQLU+7tCluSfOyvkgKLs0HjCZrdJlZgEgKRSa8XTwZfMAUt6J7qZTbrZAHBlPtX68EPu/PI8JMCeu3WAQ==}
|
||||
|
||||
'@kevisual/load@0.0.6':
|
||||
resolution: {integrity: sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA==}
|
||||
|
||||
'@kevisual/query@0.0.35':
|
||||
resolution: {integrity: sha512-80dyy2LMCmEC72g+X4QWUKlZErhawQPgnGSBNR4yhrBcFgHIJQ14LR1Z+bS5S1I7db+1PDNpaxBTjIaoYoXunw==}
|
||||
'@kevisual/logger@0.0.4':
|
||||
resolution: {integrity: sha512-+fpr92eokSxoGOW1SIRl/27lPuO+zyY+feR5o2Q4YCNlAdt2x64NwC/w8r/3NEC5QenLgd4K0azyKTI2mHbARw==}
|
||||
|
||||
'@kevisual/router@0.0.55':
|
||||
resolution: {integrity: sha512-DVhXbbUCfSWWXsp1id1HBrkGiMZ6nFUBD1/C5E7IpLE5B32w7sv2xjKUt98OriFl0uyuneMEIZuZsAQaKplQ5g==}
|
||||
'@kevisual/permission@0.0.3':
|
||||
resolution: {integrity: sha512-8JsA/5O5Ax/z+M+MYpFYdlioHE6jNmWMuFSokBWYs9CCAHNiSKMR01YLkoVDoPvncfH/Y8F5K/IEXRCbptuMNA==}
|
||||
|
||||
'@kevisual/types@0.0.11':
|
||||
resolution: {integrity: sha512-idNLDTEKVdNXZHFQq8PTN62nflh94kvGtx+v8YDcMxt0Zo+HWVZTFElm+dMQxAs/vn4wo8F2r3VwzWNX/vcqwQ==}
|
||||
'@kevisual/query@0.0.38':
|
||||
resolution: {integrity: sha512-bfvbSodsZyMfwY+1T2SvDeOCKsT/AaIxlVe0+B1R/fNhlg2MDq2CP0L9HKiFkEm+OXrvXcYDMKPUituVUM5J6Q==}
|
||||
|
||||
'@kevisual/router@0.0.62':
|
||||
resolution: {integrity: sha512-kKQFYkJ/qemGAygGSKkM/TujvreoU9HxL7/2vx2RYDMRyRuZOYUuvrTF92XIffFnZHugFbs8uEt+Z8I11SrFUg==}
|
||||
|
||||
'@kevisual/types@0.0.12':
|
||||
resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==}
|
||||
|
||||
'@kevisual/use-config@1.0.28':
|
||||
resolution: {integrity: sha512-ngF+LDbjxpXWrZNmnShIKF/jPpAa+ezV+DcgoZIIzHlRnIjE+rr9sLkN/B7WJbiH9C/j1tQXOILY8ujBqILrow==}
|
||||
peerDependencies:
|
||||
dotenv: ^17
|
||||
|
||||
'@opencode-ai/plugin@1.1.23':
|
||||
resolution: {integrity: sha512-O/iLSKOUuzD95UWhj9y/tEuycPEBv36de0suHXXqeYLWZLZ16DAUSKR+YG7rvRjJS0sbn4biVMw+k7XXk/oxiQ==}
|
||||
'@opencode-ai/plugin@1.1.36':
|
||||
resolution: {integrity: sha512-b2XWeFZN7UzgwkkzTIi6qSntkpEA9En2zvpqakQzZAGQm6QBdGAlv6r1u5hEnmF12Gzyj5umTMWr5GzVbP/oAA==}
|
||||
|
||||
'@opencode-ai/sdk@1.1.23':
|
||||
resolution: {integrity: sha512-YjN9ogzkLol92s+/iARXRop9/5oFIezUkvWVay12u1IM6A/WJs50DeKl3oL0x4a68P1a5tI5gD98dLnk2+AlsA==}
|
||||
'@opencode-ai/sdk@1.1.36':
|
||||
resolution: {integrity: sha512-feNHWnbxhg03TI2QrWnw3Chc0eYrWSDSmHIy/ejpSVfcKlfXREw1Tpg0L4EjrpeSc4jB1eM673dh+WM/Ko2SFQ==}
|
||||
|
||||
'@types/bun@1.3.6':
|
||||
resolution: {integrity: sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA==}
|
||||
|
||||
'@types/node@25.0.9':
|
||||
resolution: {integrity: sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==}
|
||||
'@types/node@25.0.10':
|
||||
resolution: {integrity: sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==}
|
||||
|
||||
bun-types@1.3.6:
|
||||
resolution: {integrity: sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ==}
|
||||
@@ -87,12 +99,16 @@ packages:
|
||||
resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
es-toolkit@1.43.0:
|
||||
resolution: {integrity: sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==}
|
||||
es-toolkit@1.44.0:
|
||||
resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==}
|
||||
|
||||
eventemitter3@5.0.1:
|
||||
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
||||
|
||||
hono@4.11.6:
|
||||
resolution: {integrity: sha512-ofIiiHyl34SV6AuhE3YT2mhO5HRWokce+eUYE82TsP6z0/H3JeJcjVWEMSIAiw2QkjDOEpES/lYsg8eEbsLtdw==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
nanoid@5.1.6:
|
||||
resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==}
|
||||
engines: {node: ^18 || >=20}
|
||||
@@ -107,55 +123,69 @@ packages:
|
||||
zod@4.1.8:
|
||||
resolution: {integrity: sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==}
|
||||
|
||||
zod@4.3.5:
|
||||
resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==}
|
||||
zod@4.3.6:
|
||||
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@kevisual/ai@0.0.22':
|
||||
dependencies:
|
||||
'@kevisual/logger': 0.0.4
|
||||
'@kevisual/permission': 0.0.3
|
||||
'@kevisual/query': 0.0.38
|
||||
|
||||
'@kevisual/context@0.0.4': {}
|
||||
|
||||
'@kevisual/load@0.0.6':
|
||||
dependencies:
|
||||
eventemitter3: 5.0.1
|
||||
|
||||
'@kevisual/query@0.0.35':
|
||||
'@kevisual/logger@0.0.4': {}
|
||||
|
||||
'@kevisual/permission@0.0.3': {}
|
||||
|
||||
'@kevisual/query@0.0.38':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@kevisual/router@0.0.55': {}
|
||||
'@kevisual/router@0.0.62':
|
||||
dependencies:
|
||||
hono: 4.11.6
|
||||
|
||||
'@kevisual/types@0.0.11': {}
|
||||
'@kevisual/types@0.0.12': {}
|
||||
|
||||
'@kevisual/use-config@1.0.28(dotenv@17.2.3)':
|
||||
dependencies:
|
||||
'@kevisual/load': 0.0.6
|
||||
dotenv: 17.2.3
|
||||
|
||||
'@opencode-ai/plugin@1.1.23':
|
||||
'@opencode-ai/plugin@1.1.36':
|
||||
dependencies:
|
||||
'@opencode-ai/sdk': 1.1.23
|
||||
'@opencode-ai/sdk': 1.1.36
|
||||
zod: 4.1.8
|
||||
|
||||
'@opencode-ai/sdk@1.1.23': {}
|
||||
'@opencode-ai/sdk@1.1.36': {}
|
||||
|
||||
'@types/bun@1.3.6':
|
||||
dependencies:
|
||||
bun-types: 1.3.6
|
||||
|
||||
'@types/node@25.0.9':
|
||||
'@types/node@25.0.10':
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
bun-types@1.3.6:
|
||||
dependencies:
|
||||
'@types/node': 25.0.9
|
||||
'@types/node': 25.0.10
|
||||
|
||||
dotenv@17.2.3: {}
|
||||
|
||||
es-toolkit@1.43.0: {}
|
||||
es-toolkit@1.44.0: {}
|
||||
|
||||
eventemitter3@5.0.1: {}
|
||||
|
||||
hono@4.11.6: {}
|
||||
|
||||
nanoid@5.1.6: {}
|
||||
|
||||
tslib@2.8.1: {}
|
||||
@@ -164,4 +194,4 @@ snapshots:
|
||||
|
||||
zod@4.1.8: {}
|
||||
|
||||
zod@4.3.5: {}
|
||||
zod@4.3.6: {}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
对/agents 下的代码进行修改
|
||||
|
||||
需求1:
|
||||
|
||||
创建仓库, 需要仓库名字
|
||||
1. 自动添加.cnb.yml, 对.cnb.yml的内容进行修改。修改TO_REPO对应的内容, TO_REPO,是“group+repo”需要传入仓库名字,默认group为kevisual
|
||||
2. 自动添加opencode.json
|
||||
|
||||
需求2:
|
||||
获取当前启动的云开发环境。
|
||||
输出对应的请求的地址,token等信息
|
||||
输出打开云开发的vscode,buddy,cursor,trae,qcoder等工具的地址
|
||||
|
||||
需求3:
|
||||
|
||||
|
||||
@@ -7,41 +7,51 @@
|
||||
import { useKey } from "@kevisual/use-config"
|
||||
|
||||
export const CNB_ENV = {
|
||||
// 仓库相关配置
|
||||
/** 仓库的 HTTPS 地址,如 "https://cnb.cool/kevisual/cnb" */
|
||||
CNB_REPO_URL_HTTPS: useKey('CNB_REPO_URL_HTTPS'),
|
||||
// 仓库相关配置
|
||||
/** 仓库的 HTTPS 地址,如 "https://cnb.cool/kevisual/cnb" */
|
||||
CNB_REPO_URL_HTTPS: useKey('CNB_REPO_URL_HTTPS'),
|
||||
|
||||
// 构建相关配置
|
||||
/** 流水线 ID,唯一标识一次构建流水线,如 "cnb-108-1jer5qekq-001" */
|
||||
CNB_PIPELINE_ID: useKey('CNB_PIPELINE_ID'),
|
||||
/** 构建 ID,与流水线 ID 相关联,如 "cnb-108-1jer5qekq" */
|
||||
CNB_BUILD_ID: useKey('CNB_BUILD_ID'),
|
||||
/** 构建开始时间,ISO 8601 格式,如 "2026-01-13T07:58:41.825Z" */
|
||||
CNB_BUILD_START_TIME: useKey('CNB_BUILD_START_TIME'),
|
||||
/** 构建日志 Web 界面 URL,用于在浏览器中查看构建日志 */
|
||||
CNB_BUILD_WEB_URL: useKey('CNB_BUILD_WEB_URL'),
|
||||
/** 触发构建的事件类型,如 "vscode" 表示由 VS Code 触发 */
|
||||
CNB_EVENT: useKey('CNB_EVENT'),
|
||||
/** 当前构建对应的 Git 提交哈希值 */
|
||||
CNB_COMMIT: useKey('CNB_COMMIT'),
|
||||
// 构建相关配置
|
||||
/** 流水线 ID,唯一标识一次构建流水线,如 "cnb-upo-1jfth1771-001" */
|
||||
CNB_PIPELINE_ID: useKey('CNB_PIPELINE_ID'),
|
||||
/** 构建 ID,与流水线 ID 相关联,如 "cnb-upo-1jfth1771" */
|
||||
CNB_BUILD_ID: useKey('CNB_BUILD_ID'),
|
||||
/** 构建开始时间,ISO 8601 格式,如 "2026-01-13T07:58:41.825Z" */
|
||||
CNB_BUILD_START_TIME: useKey('CNB_BUILD_START_TIME'),
|
||||
/** 构建日志 Web 界面 URL,用于在浏览器中查看构建日志 */
|
||||
CNB_BUILD_WEB_URL: useKey('CNB_BUILD_WEB_URL'),
|
||||
/** 触发构建的事件类型,如 "vscode" 表示由 VS Code 触发 */
|
||||
CNB_EVENT: useKey('CNB_EVENT'),
|
||||
/** 当前构建对应的 Git 提交哈希值 */
|
||||
CNB_COMMIT: useKey('CNB_COMMIT'),
|
||||
|
||||
// VS Code 相关配置
|
||||
/** VS Code Web 界面的访问 URL,用于在浏览器中打开 VS Code */
|
||||
CNB_VSCODE_WEB_URL: useKey('CNB_VSCODE_WEB_URL'),
|
||||
/** VS Code 代理 URI,用于端口转发,{{port}} 会被替换为实际端口号, 例如: "https://1wnts22gq3-{{port}}.cnb.run"*/
|
||||
CNB_VSCODE_PROXY_URI: useKey('CNB_VSCODE_PROXY_URI'),
|
||||
// VS Code 相关配置
|
||||
/** VS Code Web 界面的访问 URL,用于在浏览器中打开 VS Code,例如:'https://cnb.cool/kevisual/cnb/-/workspace/vscode-web/cnb-upo-1jfth1771-001/'*/
|
||||
CNB_VSCODE_WEB_URL: useKey('CNB_VSCODE_WEB_URL'),
|
||||
/** VS Code 代理 URI,用于端口转发,{{port}} 会被替换为实际端口号, 例如: "https://1wnts22gq3-{{port}}.cnb.run"*/
|
||||
CNB_VSCODE_PROXY_URI: useKey('CNB_VSCODE_PROXY_URI'),
|
||||
/**
|
||||
* VS Code Remote SSH 连接字符串,例如: vscode://vscode-remote/ssh-remote+cnb-upo-1jfth1771-001.8939f3d1-de13-486e-921f-f07943fcfa28-qng@cnb.space/workspace/"
|
||||
* 是CNB_PIPELINE_ID和CNB_VSCODE_SSH_TOKEN的组合
|
||||
*/
|
||||
CNB_VSCODE_REMOTE_SSH_SCHEMA: useKey('CNB_VSCODE_REMOTE_SSH_SCHEMA'),
|
||||
/**
|
||||
* VS Code Remote SSH 连接的认证 Token 8939f3d1-de13-486e-921f-f07943fcfa28-qng
|
||||
* 组装为ssh的链接字符串是 ssh CNB_PIPELINE_ID + '.' + CNB_VSCODE_SSH_TOKEN@cnb.space
|
||||
*/
|
||||
CNB_VSCODE_SSH_TOKEN: useKey('CNB_VSCODE_SSH_TOKEN'),
|
||||
|
||||
// 仓库标识配置
|
||||
/** 仓库标识符,格式为 "组名/仓库名",如 "kevisual/cnb" */
|
||||
CNB_REPO_SLUG: useKey('CNB_REPO_SLUG'),
|
||||
/** 组名/命名空间标识符,如 "kevisual" */
|
||||
CNB_GROUP_SLUG: useKey('CNB_GROUP_SLUG'),
|
||||
// 仓库标识配置
|
||||
/** 仓库标识符,格式为 "组名/仓库名",如 "kevisual/cnb" */
|
||||
CNB_REPO_SLUG: useKey('CNB_REPO_SLUG'),
|
||||
/** 组名/命名空间标识符,如 "kevisual" */
|
||||
CNB_GROUP_SLUG: useKey('CNB_GROUP_SLUG'),
|
||||
|
||||
// 运行器资源配置
|
||||
/** 运行器分配的 CPU 核心数,单位为核, 例如: "8"*/
|
||||
CNB_CPUS: useKey('CNB_CPUS'),
|
||||
/** 运行器分配的内存大小,单位为 GB, 例如: "16"*/
|
||||
CNB_MEMORY: useKey('CNB_MEMORY'),
|
||||
/** 运行器的 IP 地址,用于网络连接和调试 */
|
||||
CNB_RUNNER_IP: useKey('CNB_RUNNER_IP'),
|
||||
// 运行器资源配置
|
||||
/** 运行器分配的 CPU 核心数,单位为核, 例如: "8"*/
|
||||
CNB_CPUS: useKey('CNB_CPUS'),
|
||||
/** 运行器分配的内存大小,单位为 GB, 例如: "16"*/
|
||||
CNB_MEMORY: useKey('CNB_MEMORY'),
|
||||
/** 运行器的 IP 地址,用于网络连接和调试 */
|
||||
CNB_RUNNER_IP: useKey('CNB_RUNNER_IP'),
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export class KnowledgeBase extends CNBCore {
|
||||
score_threshold?: number,
|
||||
top_k?: number,
|
||||
metadata_filtering_conditions?: MetadataFilteringConditions
|
||||
}): Promise<any> {
|
||||
}): Promise<Result<QueryRag[]>> {
|
||||
const url = `/${repo}/-/knowledge/base/query`;
|
||||
let postData = {
|
||||
query: data.query,
|
||||
@@ -43,4 +43,17 @@ type MetadataFilteringConditions = {
|
||||
value?: string
|
||||
}>
|
||||
logical_operator?: 'adn' | 'or'
|
||||
}
|
||||
|
||||
type QueryRag = {
|
||||
chunk: string;
|
||||
score: number;
|
||||
metadata: {
|
||||
hash: string;
|
||||
name: string;
|
||||
path: string;
|
||||
position: string;
|
||||
type: string; // code, text
|
||||
url: string;
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ export class Workspace extends CNBCore {
|
||||
return this.post({ url: '/workspace/delete', data });
|
||||
}
|
||||
/** 获取我的云原生开发环境列表 */
|
||||
async list(params?: ListParams): Promise<WorkspaceResult> {
|
||||
async list(params?: ListParams): Promise<Result<WorkspaceResult>> {
|
||||
return this.get({ url: '/workspace/list', params });
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { KnowledgeBase } from "../src/knowledge/index.ts";
|
||||
import { token, showMore, cookie } from "./common.ts";
|
||||
// group: "kevisual/test",
|
||||
const repo = new KnowledgeBase({ token: token, cookie: cookie });
|
||||
const repoName = "test-local-docs";
|
||||
const repoName = "kevisual/starred-auto";
|
||||
|
||||
// const queryRes = await repo.getEmbeddingModels(repoName);
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ import { token, showMore, cookie } from "./common.ts";
|
||||
const repo = new Repo({ token: token, cookie: cookie });
|
||||
|
||||
|
||||
const listRes = await repo.getRepoList({ page: 1, page_size: 999, role: 'developer' });
|
||||
const listRes = await repo.getRepoList({
|
||||
page: 1, page_size: 999, role: 'developer',
|
||||
flags: 'KnowledgeBase'
|
||||
});
|
||||
|
||||
console.log("listRes", showMore(listRes), listRes.data?.length);
|
||||
Reference in New Issue
Block a user