Skip to content

Commit

Permalink
✨ feat: 完成插件网关所有功能
Browse files Browse the repository at this point in the history
  • Loading branch information
arvinxx committed Aug 19, 2023
1 parent 989470e commit 62e26c7
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 16 deletions.
1 change: 1 addition & 0 deletions api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export default async (req: VercelRequest, response: VercelResponse) => {
response.json({
status: 'ok',
});
return 'hello';
};
8 changes: 8 additions & 0 deletions api/v1/_validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { z } from 'zod';

export const payloadSchema = z.object({
arguments: z.string().optional(),
name: z.string(),
});

export type PluginPayload = z.infer<typeof payloadSchema>;
147 changes: 133 additions & 14 deletions api/v1/runner.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { LobeChatPlugin, LobeChatPluginsMarketIndex } from '@lobehub/chat-plugin-sdk';
import {
ErrorType,
LobeChatPlugin,
LobeChatPluginsMarketIndex,
createErrorResponse,
marketIndexSchema,
pluginManifestSchema,
pluginMetaSchema,
} from '@lobehub/chat-plugin-sdk';
import { Validator } from 'jsonschema';

import { OpenAIPluginPayload } from '../../types/plugins';
import { PluginPayload, payloadSchema } from './_validator';

export const config = {
runtime: 'edge',
Expand All @@ -9,28 +18,138 @@ export const config = {
const INDEX_URL = `https://registry.npmmirror.com/@lobehub/lobe-chat-plugins/latest/files`;

export default async (req: Request) => {
if (req.method !== 'POST') return new Response('Method Not Allowed', { status: 405 });
// ========== 1. 校验请求方法 ========== //
if (req.method !== 'POST')
return createErrorResponse(ErrorType.MethodNotAllowed, {
message: '[gateway] only allow POST method',
});

const indexRes = await fetch(INDEX_URL);
const manifest: LobeChatPluginsMarketIndex = await indexRes.json();
console.info('manifest:', manifest);
// ========== 2. 校验请求入参基础格式 ========== //
const requestPayload = (await req.json()) as PluginPayload;

const { name, arguments: args } = (await req.json()) as OpenAIPluginPayload;
const payloadParseResult = payloadSchema.safeParse(requestPayload);

if (!payloadParseResult.success)
return createErrorResponse(ErrorType.BadRequest, payloadParseResult.error);

const { name, arguments: args } = requestPayload;

console.info(`plugin call: ${name}`);

const item = manifest.plugins.find((i) => i.name === name);
// ========== 3. 获取插件市场索引 ========== //

let marketIndex: LobeChatPluginsMarketIndex | undefined;
try {
const indexRes = await fetch(INDEX_URL);
marketIndex = await indexRes.json();
} catch (error) {
console.error(error);
marketIndex = undefined;
}

// 插件市场索引不存在
if (!marketIndex)
return createErrorResponse(ErrorType.PluginMarketIndexNotFound, {
indexUrl: INDEX_URL,
message: '[gateway] plugin market index not found',
});

// 插件市场索引解析失败
const indexParseResult = marketIndexSchema.safeParse(marketIndex);

if (!indexParseResult.success)
return createErrorResponse(ErrorType.PluginMarketIndexInvalid, {
error: indexParseResult.error,
indexUrl: INDEX_URL,
marketIndex,
message: '[gateway] plugin market index is invalid',
});

console.info('marketIndex:', marketIndex);

// ========== 4. 校验插件 meta 完备性 ========== //

const pluginMeta = marketIndex.plugins.find((i) => i.name === name);

// 一个不规范的插件示例
// const pluginMeta = {
// createAt: '2023-08-12',
// homepage: 'https://github.com/lobehub/chat-plugin-real-time-weather',
// manifest: 'https://registry.npmmirror.com/@lobehub/lobe-chat-plugins/latest/files',
// meta: {
// avatar: '☂️',
// tags: ['weather', 'realtime'],
// },
// name: 'realtimeWeather',
// schemaVersion: 'v1',
// };

// 校验插件是否存在
if (!pluginMeta)
return createErrorResponse(ErrorType.PluginMetaNotFound, {
message: `[gateway] plugin '${name}' is not found,please check the plugin list in ${INDEX_URL}, or create an issue to [lobe-chat-plugins](https://github.com/lobehub/lobe-chat-plugins/issues)`,
name,
});

if (!item) return;
const metaParseResult = pluginMetaSchema.safeParse(pluginMeta);

if (!item.manifest) return;
if (!metaParseResult.success)
return createErrorResponse(ErrorType.PluginMetaInvalid, {
error: metaParseResult.error,
message: '[plugin] plugin meta is invalid',
pluginMeta,
});

// ========== 5. 校验插件 manifest 完备性 ========== //

// 获取插件的 manifest
const pluginRes = await fetch(item.manifest);
const chatPlugin = (await pluginRes.json()) as LobeChatPlugin;
console.log(`[${name}] plugin manifest:`, chatPlugin);
let manifest: LobeChatPlugin | undefined;
try {
const pluginRes = await fetch(pluginMeta.manifest);
manifest = (await pluginRes.json()) as LobeChatPlugin;
} catch (error) {
console.error(error);
manifest = undefined;
}

if (!manifest)
return createErrorResponse(ErrorType.PluginManifestNotFound, {
manifestUrl: pluginMeta.manifest,
message: '[plugin] plugin manifest not found',
});

const manifestParseResult = pluginManifestSchema.safeParse(manifest);

if (!manifestParseResult.success)
return createErrorResponse(ErrorType.PluginManifestInvalid, {
error: manifestParseResult.error,
manifest: manifest,
message: '[plugin] plugin manifest is invalid',
});

console.log(`[${name}] plugin manifest:`, manifest);

// ========== 6. 校验请求入参与 manifest 要求一致性 ========== //
const v = new Validator();

if (args) {
const validator = v.validate(JSON.parse(args!), manifest.schema.parameters as any);

if (!validator.valid)
return createErrorResponse(ErrorType.BadRequest, {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
error: validator.errors.map(({ schema: _, ...res }) => res),
manifest,
message: '[plugin] args is invalid with plugin manifest schema',
});
}

// ========== 7. 发送请求 ========== //

const response = await fetch(manifest.server.url, { body: args, method: 'post' });

const response = await fetch(chatPlugin.server.url, { body: args, method: 'post' });
// 不正常的错误,直接返回请求
if (!response.ok) return response;

const data = await response.text();

Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
"not ie <= 10"
],
"dependencies": {
"@lobehub/chat-plugin-sdk": "^1.0.1"
"@lobehub/chat-plugin-sdk": "latest",
"jsonschema": "^1.4.1",
"zod": "^3"
},
"devDependencies": {
"@lobehub/lint": "latest",
Expand All @@ -45,7 +47,7 @@
"prettier": "^2",
"semantic-release": "^21",
"typescript": "^5",
"vercel": "^31.2.3",
"vercel": "^29",
"vitest": "latest"
}
}

0 comments on commit 62e26c7

Please sign in to comment.