refactor: remove pnpm workspace configuration and update opencode functionality
- Deleted pnpm-workspace.yaml file. - Modified opencode.ts to enhance skill creation and execution: - Updated skill title and summary for clarity. - Introduced a delay for router loading. - Improved route filtering logic. - Added extractArgs function to handle argument extraction from z.object types. - Updated route.ts to ensure 'opencode' tag is added to skills if not present and improved JSON schema handling for args.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { app, createSkill, tool } from '../app.ts';
|
import { app, createSkill, tool } from '../app.ts';
|
||||||
import * as docs from '../gen/index.ts'
|
import * as docs from '../gen/index.ts'
|
||||||
import * as pkgs from '../../package.json' assert { type: 'json' };
|
import * as pkgs from '../../package.json' assert { type: 'json' };
|
||||||
|
import z from 'zod';
|
||||||
app.route({
|
app.route({
|
||||||
path: 'router-skill',
|
path: 'router-skill',
|
||||||
key: 'create-route',
|
key: 'create-route',
|
||||||
@@ -32,14 +33,47 @@ app.route({
|
|||||||
}
|
}
|
||||||
}).addTo(app);
|
}).addTo(app);
|
||||||
|
|
||||||
// 调用router应用 path router-skill key version
|
// 获取最新router版本号
|
||||||
app.route({
|
app.route({
|
||||||
path: 'router-skill',
|
path: 'router-skill',
|
||||||
key: 'version',
|
key: 'version',
|
||||||
description: '获取路由技能版本',
|
description: '获取最新router版本号',
|
||||||
middleware: ['auth'],
|
middleware: ['auth'],
|
||||||
|
metadata: {
|
||||||
|
tags: ['opencode'],
|
||||||
|
...createSkill({
|
||||||
|
skill: 'router-skill-version',
|
||||||
|
title: '获取最新router版本号',
|
||||||
|
summary: '获取最新router版本号',
|
||||||
|
args: {}
|
||||||
|
})
|
||||||
|
},
|
||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
content: pkgs.version || 'unknown'
|
content: pkgs.version || 'unknown'
|
||||||
}
|
}
|
||||||
}).addTo(app);
|
}).addTo(app);
|
||||||
|
|
||||||
|
// 执行技能test-route-skill,name为abearxiong
|
||||||
|
app.route({
|
||||||
|
path: 'route-skill',
|
||||||
|
key: 'test',
|
||||||
|
description: '测试路由技能',
|
||||||
|
middleware: ['auth'],
|
||||||
|
metadata: {
|
||||||
|
tags: ['opencode'],
|
||||||
|
...createSkill({
|
||||||
|
skill: 'test-route-skill',
|
||||||
|
title: '测试路由技能',
|
||||||
|
summary: '测试路由技能是否正常工作',
|
||||||
|
args: z.object({
|
||||||
|
name: z.string().describe('名字'),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const name = ctx.query.name || 'unknown';
|
||||||
|
ctx.body = {
|
||||||
|
content: '测试成功,你好 ' + name
|
||||||
|
}
|
||||||
|
}).addTo(app)
|
||||||
4
bun.lock
4
bun.lock
@@ -13,7 +13,7 @@
|
|||||||
"@kevisual/dts": "^0.0.4",
|
"@kevisual/dts": "^0.0.4",
|
||||||
"@kevisual/js-filter": "^0.0.5",
|
"@kevisual/js-filter": "^0.0.5",
|
||||||
"@kevisual/local-proxy": "^0.0.8",
|
"@kevisual/local-proxy": "^0.0.8",
|
||||||
"@kevisual/query": "^0.0.40",
|
"@kevisual/query": "^0.0.46",
|
||||||
"@kevisual/use-config": "^1.0.30",
|
"@kevisual/use-config": "^1.0.30",
|
||||||
"@opencode-ai/plugin": "^1.2.6",
|
"@opencode-ai/plugin": "^1.2.6",
|
||||||
"@types/bun": "^1.3.9",
|
"@types/bun": "^1.3.9",
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
|
|
||||||
"@kevisual/local-proxy": ["@kevisual/local-proxy@0.0.8", "", {}, "sha512-VX/P+6/Cc8ruqp34ag6gVX073BchUmf5VNZcTV/6MJtjrNE76G8V6TLpBE8bywLnrqyRtFLIspk4QlH8up9B5Q=="],
|
"@kevisual/local-proxy": ["@kevisual/local-proxy@0.0.8", "", {}, "sha512-VX/P+6/Cc8ruqp34ag6gVX073BchUmf5VNZcTV/6MJtjrNE76G8V6TLpBE8bywLnrqyRtFLIspk4QlH8up9B5Q=="],
|
||||||
|
|
||||||
"@kevisual/query": ["@kevisual/query@0.0.40", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-7m5BgDzd01m51hCHUId6ugQHdwgrLTb6fI7DSuMY17VjWb0+zGnkYmvRBqkTXzoIjjYbP5iwtRnrooEoToQfhg=="],
|
"@kevisual/query": ["@kevisual/query@0.0.46", "", {}, "sha512-JwHV16ehk8JWM5wiWW5kz9yTg4HrOmmnci5QvwQYdhXYXDzGpUrOxeoz3wloMs4kX3bkowz97iLLW6uQdgUoTw=="],
|
||||||
|
|
||||||
"@kevisual/use-config": ["@kevisual/use-config@1.0.30", "", { "dependencies": { "@kevisual/load": "^0.0.6" }, "peerDependencies": { "dotenv": "^17" } }, "sha512-kPdna0FW/X7D600aMdiZ5UTjbCo6d8d4jjauSc8RMmBwUU6WliFDSPUNKVpzm2BsDX5Nth1IXFPYMqH+wxqAmw=="],
|
"@kevisual/use-config": ["@kevisual/use-config@1.0.30", "", { "dependencies": { "@kevisual/load": "^0.0.6" }, "peerDependencies": { "dotenv": "^17" } }, "sha512-kPdna0FW/X7D600aMdiZ5UTjbCo6d8d4jjauSc8RMmBwUU6WliFDSPUNKVpzm2BsDX5Nth1IXFPYMqH+wxqAmw=="],
|
||||||
|
|
||||||
|
|||||||
2461
package-lock.json
generated
2461
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package",
|
"$schema": "https://json.schemastore.org/package",
|
||||||
"name": "@kevisual/router",
|
"name": "@kevisual/router",
|
||||||
"version": "0.0.74",
|
"version": "0.0.75",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/router.js",
|
"main": "./dist/router.js",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"@kevisual/dts": "^0.0.4",
|
"@kevisual/dts": "^0.0.4",
|
||||||
"@kevisual/js-filter": "^0.0.5",
|
"@kevisual/js-filter": "^0.0.5",
|
||||||
"@kevisual/local-proxy": "^0.0.8",
|
"@kevisual/local-proxy": "^0.0.8",
|
||||||
"@kevisual/query": "^0.0.43",
|
"@kevisual/query": "^0.0.46",
|
||||||
"@kevisual/use-config": "^1.0.30",
|
"@kevisual/use-config": "^1.0.30",
|
||||||
"@opencode-ai/plugin": "^1.2.6",
|
"@opencode-ai/plugin": "^1.2.6",
|
||||||
"@types/bun": "^1.3.9",
|
"@types/bun": "^1.3.9",
|
||||||
|
|||||||
1191
pnpm-lock.yaml
generated
1191
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
|||||||
onlyBuiltDependencies:
|
|
||||||
- esbuild
|
|
||||||
packages:
|
|
||||||
- 'demo/**/*'
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useContextKey } from '@kevisual/context'
|
import { useContextKey } from '@kevisual/context'
|
||||||
import { createSkill, type QueryRouterServer, tool, type QueryRouter, type Skill } from './route.ts'
|
import { createSkill, type QueryRouterServer, tool, type Skill } from './route.ts'
|
||||||
import { type App } from './app.ts'
|
import { type App } from './app.ts'
|
||||||
import { PluginInput, type Plugin, Hooks } from "@opencode-ai/plugin"
|
import { PluginInput, type Plugin, Hooks } from "@opencode-ai/plugin"
|
||||||
|
|
||||||
@@ -15,8 +15,12 @@ export const addCallFn = (app: App) => {
|
|||||||
tags: ['opencode'],
|
tags: ['opencode'],
|
||||||
...createSkill({
|
...createSkill({
|
||||||
skill: 'call-app',
|
skill: 'call-app',
|
||||||
title: '调用app应用',
|
title: '调用app应用,非技能模块',
|
||||||
summary: '调用router的应用, 参数path, key, payload',
|
summary: `调用router的应用(非技能模块),适用于需要直接调用应用而不是技能的场景
|
||||||
|
条件1: 参数path(string), key(string), payload(object)
|
||||||
|
条件2: 当前的应用调用的模式不是技能
|
||||||
|
|
||||||
|
`,
|
||||||
args: {
|
args: {
|
||||||
path: tool.schema.string().describe('应用路径,例如 cnb'),
|
path: tool.schema.string().describe('应用路径,例如 cnb'),
|
||||||
key: tool.schema.string().optional().describe('应用key,例如 list-repos'),
|
key: tool.schema.string().optional().describe('应用key,例如 list-repos'),
|
||||||
@@ -42,7 +46,9 @@ export const createRouterAgentPluginFn = (opts?: {
|
|||||||
query?: string,
|
query?: string,
|
||||||
hooks?: (plugin: PluginInput) => Promise<Hooks>
|
hooks?: (plugin: PluginInput) => Promise<Hooks>
|
||||||
}) => {
|
}) => {
|
||||||
|
new Promise(resolve => setTimeout(resolve, 100)) // 等待路由加载
|
||||||
let router = opts?.router
|
let router = opts?.router
|
||||||
|
|
||||||
if (!router) {
|
if (!router) {
|
||||||
const app = useContextKey<App>('app')
|
const app = useContextKey<App>('app')
|
||||||
router = app
|
router = app
|
||||||
@@ -59,13 +65,15 @@ export const createRouterAgentPluginFn = (opts?: {
|
|||||||
const _routes = filter(router.routes, opts?.query || '')
|
const _routes = filter(router.routes, opts?.query || '')
|
||||||
const routes = _routes.filter(r => {
|
const routes = _routes.filter(r => {
|
||||||
const metadata = r.metadata as Skill
|
const metadata = r.metadata as Skill
|
||||||
|
if (metadata && metadata.skill && metadata.summary) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if (metadata && metadata.tags && metadata.tags.includes('opencode')) {
|
if (metadata && metadata.tags && metadata.tags.includes('opencode')) {
|
||||||
return !!metadata.skill
|
return !!metadata.skill
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
});
|
});
|
||||||
|
// opencode run "使用技能查看系统信息"
|
||||||
// opencode run "查看系统信息"
|
|
||||||
const AgentPlugin: Plugin = async (pluginInput) => {
|
const AgentPlugin: Plugin = async (pluginInput) => {
|
||||||
useContextKey<PluginInput>('plugin-input', () => pluginInput, true)
|
useContextKey<PluginInput>('plugin-input', () => pluginInput, true)
|
||||||
const hooks = opts?.hooks ? await opts.hooks(pluginInput) : {}
|
const hooks = opts?.hooks ? await opts.hooks(pluginInput) : {}
|
||||||
@@ -74,10 +82,11 @@ export const createRouterAgentPluginFn = (opts?: {
|
|||||||
'tool': {
|
'tool': {
|
||||||
...routes.reduce((acc, route) => {
|
...routes.reduce((acc, route) => {
|
||||||
const metadata = route.metadata as Skill
|
const metadata = route.metadata as Skill
|
||||||
|
let args = extractArgs(metadata?.args)
|
||||||
acc[metadata.skill!] = {
|
acc[metadata.skill!] = {
|
||||||
name: metadata.title || metadata.skill,
|
name: metadata.title || metadata.skill,
|
||||||
description: metadata.summary || '',
|
description: metadata.summary || '',
|
||||||
args: metadata.args || {},
|
args: args,
|
||||||
async execute(args: Record<string, any>) {
|
async execute(args: Record<string, any>) {
|
||||||
const res = await router.run({
|
const res = await router.run({
|
||||||
path: route.path,
|
path: route.path,
|
||||||
@@ -117,4 +126,17 @@ export const createRouterAgentPluginFn = (opts?: {
|
|||||||
|
|
||||||
export const usePluginInput = (): PluginInput => {
|
export const usePluginInput = (): PluginInput => {
|
||||||
return useContextKey<PluginInput>('plugin-input')
|
return useContextKey<PluginInput>('plugin-input')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果args是z.object类型,拆分第一个Object的属性,比如z.object({ name: z.string(), age: z.number() }),拆分成{name: z.string(), age: z.number()}
|
||||||
|
* 如果args是普通对象,直接返回
|
||||||
|
* @param args
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const extractArgs = (args: any) => {
|
||||||
|
if (args && typeof args === 'object' && typeof args.shape === 'object') {
|
||||||
|
return args.shape
|
||||||
|
}
|
||||||
|
return args || {}
|
||||||
}
|
}
|
||||||
16
src/route.ts
16
src/route.ts
@@ -97,6 +97,7 @@ export type Skill<T = SimpleObject> = {
|
|||||||
skill: string;
|
skill: string;
|
||||||
title: string;
|
title: string;
|
||||||
summary?: string;
|
summary?: string;
|
||||||
|
tags?: string[];
|
||||||
args?: {
|
args?: {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
};
|
};
|
||||||
@@ -106,6 +107,12 @@ export const tool = {
|
|||||||
}
|
}
|
||||||
/** */
|
/** */
|
||||||
export const createSkill = <T = SimpleObject>(skill: Skill<T>): Skill<T> => {
|
export const createSkill = <T = SimpleObject>(skill: Skill<T>): Skill<T> => {
|
||||||
|
if (skill.tags) {
|
||||||
|
const hasOpencode = skill.tags.includes('opencode');
|
||||||
|
if (!hasOpencode) {
|
||||||
|
skill.tags.push('opencode');
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
args: {},
|
args: {},
|
||||||
...skill
|
...skill
|
||||||
@@ -247,6 +254,10 @@ export const toJSONSchema = (route: RouteInfo) => {
|
|||||||
const pickValues = pick(route, pickValue as any);
|
const pickValues = pick(route, pickValue as any);
|
||||||
if (pickValues?.metadata?.args) {
|
if (pickValues?.metadata?.args) {
|
||||||
const args = pickValues.metadata.args;
|
const args = pickValues.metadata.args;
|
||||||
|
if (args && typeof args === 'object' && args.toJSONSchema && typeof args.toJSONSchema === 'function') {
|
||||||
|
pickValues.metadata.args = args.toJSONSchema();
|
||||||
|
return pickValues;
|
||||||
|
}
|
||||||
const keys = Object.keys(args);
|
const keys = Object.keys(args);
|
||||||
const newArgs: { [key: string]: any } = {};
|
const newArgs: { [key: string]: any } = {};
|
||||||
for (let key of keys) {
|
for (let key of keys) {
|
||||||
@@ -265,6 +276,11 @@ export const toJSONSchema = (route: RouteInfo) => {
|
|||||||
export const fromJSONSchema = (route: RouteInfo): RouteInfo => {
|
export const fromJSONSchema = (route: RouteInfo): RouteInfo => {
|
||||||
const args = route?.metadata?.args;
|
const args = route?.metadata?.args;
|
||||||
if (!args) return route;
|
if (!args) return route;
|
||||||
|
if (args["$schema"] || (args.type === 'object' && args.properties && typeof args.properties === 'object')) {
|
||||||
|
// 可能是整个schema
|
||||||
|
route.metadata.args = z.fromJSONSchema(args);
|
||||||
|
return route;
|
||||||
|
}
|
||||||
const keys = Object.keys(args);
|
const keys = Object.keys(args);
|
||||||
const newArgs: { [key: string]: any } = {};
|
const newArgs: { [key: string]: any } = {};
|
||||||
for (let key of keys) {
|
for (let key of keys) {
|
||||||
|
|||||||
Reference in New Issue
Block a user