Skip to content

Commit 788a30a

Browse files
authored
✨ feat: chat plugin gateway 1.0 (#2)
* ✨ feat: update to latest * 🔥 chore: clean code * 🧑‍💻 chore: add console * ✨ feat: 完成插件网关所有功能 * 🐛 fix: replace jsonschema with edge runtime version
1 parent 80cdafb commit 788a30a

File tree

4 files changed

+149
-33
lines changed

4 files changed

+149
-33
lines changed

api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export default async (req: VercelRequest, response: VercelResponse) => {
44
response.json({
55
status: 'ok',
66
});
7+
return 'hello';
78
};

api/v1/_validator.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { z } from 'zod';
2+
3+
export const payloadSchema = z.object({
4+
arguments: z.string().optional(),
5+
name: z.string(),
6+
});
7+
8+
export type PluginPayload = z.infer<typeof payloadSchema>;

api/v1/runner.ts

Lines changed: 136 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,160 @@
1-
import { LobeChatPlugin, LobeChatPluginsMarketIndex } from '@lobehub/chat-plugin-sdk';
2-
3-
import { OpenAIPluginPayload } from '../../types/plugins';
1+
// reason to use cfworker json schema:
2+
// https://github.com/vercel/next.js/discussions/47063#discussioncomment-5303951
3+
import { Validator } from '@cfworker/json-schema';
4+
import {
5+
ErrorType,
6+
LobeChatPlugin,
7+
LobeChatPluginsMarketIndex,
8+
createErrorResponse,
9+
marketIndexSchema,
10+
pluginManifestSchema,
11+
pluginMetaSchema,
12+
} from '@lobehub/chat-plugin-sdk';
13+
14+
import { PluginPayload, payloadSchema } from './_validator';
415

516
export const config = {
617
runtime: 'edge',
718
};
819

9-
const INDEX_URL = `https://registry.npmmirror.com/@lobehub/lobe-chat-plugins/~1.4/files`;
20+
const INDEX_URL = `https://registry.npmmirror.com/@lobehub/lobe-chat-plugins/latest/files`;
1021

1122
export default async (req: Request) => {
12-
if (req.method !== 'POST') return new Response('Method Not Allowed', { status: 405 });
23+
// ========== 1. 校验请求方法 ========== //
24+
if (req.method !== 'POST')
25+
return createErrorResponse(ErrorType.MethodNotAllowed, {
26+
message: '[gateway] only allow POST method',
27+
});
28+
29+
// ========== 2. 校验请求入参基础格式 ========== //
30+
const requestPayload = (await req.json()) as PluginPayload;
1331

14-
const indexRes = await fetch(INDEX_URL);
15-
const manifest: LobeChatPluginsMarketIndex = await indexRes.json();
16-
console.log('manifest:', manifest);
32+
const payloadParseResult = payloadSchema.safeParse(requestPayload);
1733

18-
const { name, arguments: args } = (await req.json()) as OpenAIPluginPayload;
34+
if (!payloadParseResult.success)
35+
return createErrorResponse(ErrorType.BadRequest, payloadParseResult.error);
1936

20-
console.log(`检测到 functionCall: ${name}`);
37+
const { name, arguments: args } = requestPayload;
2138

22-
const item = manifest.plugins.find((i) => i.name === name);
39+
console.info(`plugin call: ${name}`);
2340

24-
if (!item) return;
41+
// ========== 3. 获取插件市场索引 ========== //
2542

26-
// 兼容 V0 版本的代码
27-
if ((manifest.version as number) === 0) {
28-
// 先通过插件资产 endpoint 路径查询
29-
const res = await fetch((item as any).runtime.endpoint, { body: args, method: 'post' });
30-
const data = await res.text();
31-
console.log(`[${name}]`, args, `result:`, data.slice(0, 3600));
32-
return new Response(data);
43+
let marketIndex: LobeChatPluginsMarketIndex | undefined;
44+
try {
45+
const indexRes = await fetch(INDEX_URL);
46+
marketIndex = await indexRes.json();
47+
} catch (error) {
48+
console.error(error);
49+
marketIndex = undefined;
3350
}
3451

35-
// 新版 V1 的代码
36-
else if (manifest.version === 1) {
37-
// 先通过插件资产 endpoint 路径查询
52+
// 插件市场索引不存在
53+
if (!marketIndex)
54+
return createErrorResponse(ErrorType.PluginMarketIndexNotFound, {
55+
indexUrl: INDEX_URL,
56+
message: '[gateway] plugin market index not found',
57+
});
58+
59+
// 插件市场索引解析失败
60+
const indexParseResult = marketIndexSchema.safeParse(marketIndex);
61+
62+
if (!indexParseResult.success)
63+
return createErrorResponse(ErrorType.PluginMarketIndexInvalid, {
64+
error: indexParseResult.error,
65+
indexUrl: INDEX_URL,
66+
marketIndex,
67+
message: '[gateway] plugin market index is invalid',
68+
});
69+
70+
console.info('marketIndex:', marketIndex);
71+
72+
// ========== 4. 校验插件 meta 完备性 ========== //
73+
74+
const pluginMeta = marketIndex.plugins.find((i) => i.name === name);
75+
76+
// 一个不规范的插件示例
77+
// const pluginMeta = {
78+
// createAt: '2023-08-12',
79+
// homepage: 'https://github.com/lobehub/chat-plugin-real-time-weather',
80+
// manifest: 'https://registry.npmmirror.com/@lobehub/lobe-chat-plugins/latest/files',
81+
// meta: {
82+
// avatar: '☂️',
83+
// tags: ['weather', 'realtime'],
84+
// },
85+
// name: 'realtimeWeather',
86+
// schemaVersion: 'v1',
87+
// };
88+
89+
// 校验插件是否存在
90+
if (!pluginMeta)
91+
return createErrorResponse(ErrorType.PluginMetaNotFound, {
92+
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)`,
93+
name,
94+
});
95+
96+
const metaParseResult = pluginMetaSchema.safeParse(pluginMeta);
97+
98+
if (!metaParseResult.success)
99+
return createErrorResponse(ErrorType.PluginMetaInvalid, {
100+
error: metaParseResult.error,
101+
message: '[plugin] plugin meta is invalid',
102+
pluginMeta,
103+
});
104+
105+
// ========== 5. 校验插件 manifest 完备性 ========== //
106+
107+
// 获取插件的 manifest
108+
let manifest: LobeChatPlugin | undefined;
109+
try {
110+
const pluginRes = await fetch(pluginMeta.manifest);
111+
manifest = (await pluginRes.json()) as LobeChatPlugin;
112+
} catch (error) {
113+
console.error(error);
114+
manifest = undefined;
115+
}
38116

39-
if (!item.manifest) return;
117+
if (!manifest)
118+
return createErrorResponse(ErrorType.PluginManifestNotFound, {
119+
manifestUrl: pluginMeta.manifest,
120+
message: '[plugin] plugin manifest not found',
121+
});
40122

41-
// 获取插件的 manifest
42-
const pluginRes = await fetch(item.manifest);
43-
const chatPlugin = (await pluginRes.json()) as LobeChatPlugin;
123+
const manifestParseResult = pluginManifestSchema.safeParse(manifest);
44124

45-
const response = await fetch(chatPlugin.server.url, { body: args, method: 'post' });
125+
if (!manifestParseResult.success)
126+
return createErrorResponse(ErrorType.PluginManifestInvalid, {
127+
error: manifestParseResult.error,
128+
manifest: manifest,
129+
message: '[plugin] plugin manifest is invalid',
130+
});
46131

47-
const data = await response.text();
132+
console.log(`[${name}] plugin manifest:`, manifest);
48133

49-
console.log(`[${name}]`, args, `result:`, data.slice(0, 3600));
134+
// ========== 6. 校验请求入参与 manifest 要求一致性 ========== //
50135

51-
return new Response(data);
136+
if (args) {
137+
const v = new Validator(manifest.schema.parameters as any);
138+
const validator = v.validate(JSON.parse(args!));
139+
140+
if (!validator.valid)
141+
return createErrorResponse(ErrorType.BadRequest, {
142+
error: validator.errors,
143+
manifest,
144+
message: '[plugin] args is invalid with plugin manifest schema',
145+
});
52146
}
53147

54-
return;
148+
// ========== 7. 发送请求 ========== //
149+
150+
const response = await fetch(manifest.server.url, { body: args, method: 'post' });
151+
152+
// 不正常的错误,直接返回请求
153+
if (!response.ok) return response;
154+
155+
const data = await response.text();
156+
157+
console.log(`[${name}]`, args, `result:`, data.slice(0, 3600));
158+
159+
return new Response(data);
55160
};

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@
3131
"not ie <= 10"
3232
],
3333
"dependencies": {
34-
"@lobehub/chat-plugin-sdk": "^1.0.1"
34+
"@cfworker/json-schema": "^1",
35+
"@lobehub/chat-plugin-sdk": "latest",
36+
"zod": "^3"
3537
},
3638
"devDependencies": {
3739
"@lobehub/lint": "latest",
@@ -45,7 +47,7 @@
4547
"prettier": "^2",
4648
"semantic-release": "^21",
4749
"typescript": "^5",
48-
"vercel": "^31.2.3",
50+
"vercel": "^29",
4951
"vitest": "latest"
5052
}
5153
}

0 commit comments

Comments
 (0)