├── .bunfig.toml
├── .changelogrc.js
├── .commitlintrc.js
├── .editorconfig
├── .env.example
├── .eslintignore
├── .eslintrc.js
├── .fatherrc.ts
├── .github
├── ISSUE_TEMPLATE
│ ├── 1_bug_report.yml
│ ├── 2_feature_request.yml
│ ├── 3_question.yml
│ └── other.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── issue-auto-comments.yml
│ ├── issue-check-inactive.yml
│ ├── issue-close-require.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .husky
├── commit-msg
├── pre-commit
└── prepare-commit-msg
├── .npmrc
├── .prettierignore
├── .prettierrc.js
├── .releaserc.js
├── .remarkrc.js
├── CHANGELOG.md
├── LICENSE
├── README.md
├── README.zh-CN.md
├── api
├── index.ts
└── v1
│ └── runner.ts
├── docs
└── api.md
├── package.json
├── public
└── index.html
├── src
├── cors.ts
├── edge.ts
├── gateway.ts
├── index.ts
└── node.ts
├── tests
├── __snapshots__
│ └── edge.test.ts.snap
├── edge.test.ts
└── gateway.test.ts
├── tsconfig.json
├── types
├── pluginItem.ts
├── plugins.ts
└── schema.ts
└── vitest.config.ts
/.bunfig.toml:
--------------------------------------------------------------------------------
1 | [install.lockfile]
2 |
3 | save = false
4 |
--------------------------------------------------------------------------------
/.changelogrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@lobehub/lint').changelog;
2 |
--------------------------------------------------------------------------------
/.commitlintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@lobehub/lint').commitlint;
2 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # relpace url with your internal plugins index url
2 | PLUGINS_INDEX_URL=https://example.com/index.json
3 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Eslintignore for LobeHub
2 | ################################################################
3 |
4 | # dependencies
5 | node_modules
6 |
7 | # ci
8 | coverage
9 | .coverage
10 |
11 | # test
12 | jest*
13 | _test_
14 | __test__
15 |
16 | # umi
17 | .umi
18 | .umi-production
19 | .umi-test
20 | .dumi/tmp*
21 | !.dumirc.ts
22 |
23 | # production
24 | dist
25 | es
26 | lib
27 | logs
28 |
29 | # misc
30 | # add other ignore file below
31 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const config = require('@lobehub/lint').eslint;
2 |
3 | config.rules['no-extra-boolean-cast'] = 0;
4 | config.rules['unicorn/no-array-for-each'] = 0;
5 |
6 | module.exports = config;
7 |
--------------------------------------------------------------------------------
/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'father';
2 |
3 | export default defineConfig({
4 | cjs: { output: 'dist' },
5 | });
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/1_bug_report.yml:
--------------------------------------------------------------------------------
1 | name: '🐛 反馈缺陷 Bug Report'
2 | description: '反馈一个问题缺陷 | Report an bug'
3 | title: '[Bug] '
4 | labels: '🐛 Bug'
5 | body:
6 | - type: dropdown
7 | attributes:
8 | label: '💻 系统环境 | Operating System'
9 | options:
10 | - Windows
11 | - macOS
12 | - Ubuntu
13 | - Other Linux
14 | - Other
15 | validations:
16 | required: true
17 | - type: dropdown
18 | attributes:
19 | label: '🌐 浏览器 | Browser'
20 | options:
21 | - Chrome
22 | - Edge
23 | - Safari
24 | - Firefox
25 | - Other
26 | validations:
27 | required: true
28 | - type: textarea
29 | attributes:
30 | label: '🐛 问题描述 | Bug Description'
31 | description: A clear and concise description of the bug.
32 | validations:
33 | required: true
34 | - type: textarea
35 | attributes:
36 | label: '🚦 期望结果 | Expected Behavior'
37 | description: A clear and concise description of what you expected to happen.
38 | - type: textarea
39 | attributes:
40 | label: '📷 复现步骤 | Recurrence Steps'
41 | description: A clear and concise description of how to recurrence.
42 | - type: textarea
43 | attributes:
44 | label: '📝 补充信息 | Additional Information'
45 | description: If your problem needs further explanation, or if the issue you're seeing cannot be reproduced in a gist, please add more information here.
46 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/2_feature_request.yml:
--------------------------------------------------------------------------------
1 | name: '🌠 功能需求 Feature Request'
2 | description: '需求或建议 | Suggest an idea'
3 | title: '[Request] '
4 | labels: '🌠 Feature Request'
5 | body:
6 | - type: textarea
7 | attributes:
8 | label: '🥰 需求描述 | Feature Description'
9 | description: Please add a clear and concise description of the problem you are seeking to solve with this feature request.
10 | validations:
11 | required: true
12 | - type: textarea
13 | attributes:
14 | label: '🧐 解决方案 | Proposed Solution'
15 | description: Describe the solution you'd like in a clear and concise manner.
16 | validations:
17 | required: true
18 | - type: textarea
19 | attributes:
20 | label: '📝 补充信息 | Additional Information'
21 | description: Add any other context about the problem here.
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/3_question.yml:
--------------------------------------------------------------------------------
1 | name: '😇 疑问或帮助 Help Wanted'
2 | description: '疑问或需要帮助 | Need help'
3 | title: '[Question] '
4 | labels: '😇 Help Wanted'
5 | body:
6 | - type: textarea
7 | attributes:
8 | label: '🧐 问题描述 | Proposed Solution'
9 | description: A clear and concise description of the proplem.
10 | validations:
11 | required: true
12 | - type: textarea
13 | attributes:
14 | label: '📝 补充信息 | Additional Information'
15 | description: Add any other context about the problem here.
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/other.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: '📝 其他 Other'
3 | about: '其他问题 | Other issues'
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | #### 💻 变更类型 | Change Type
2 |
3 |
4 |
5 | - [ ] ✨ feat
6 | - [ ] 🐛 fix
7 | - [ ] ♻️ refactor
8 | - [ ] 💄 style
9 | - [ ] 🔨 chore
10 | - [ ] 📝 docs
11 |
12 | #### 🔀 变更说明 | Description of Change
13 |
14 |
15 |
16 | #### 📝 补充信息 | Additional Information
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.github/workflows/issue-auto-comments.yml:
--------------------------------------------------------------------------------
1 | name: Issue Auto Comment
2 | on:
3 | issues:
4 | types:
5 | - opened
6 | - closed
7 | - assigned
8 | pull_request_target:
9 | types:
10 | - opened
11 | - closed
12 |
13 | permissions:
14 | contents: read
15 |
16 | jobs:
17 | run:
18 | permissions:
19 | issues: write # for actions-cool/issues-helper to update issues
20 | pull-requests: write # for actions-cool/issues-helper to update PRs
21 | runs-on: ubuntu-latest
22 | steps:
23 | - name: Auto Comment on Issues Opened
24 | uses: wow-actions/auto-comment@v1
25 | with:
26 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN}}
27 | issuesOpened: |
28 | 👀 @{{ author }}
29 | Thank you for raising an issue. We will investigate into the matter and get back to you as soon as possible.
30 | Please make sure you have given us as much context as possible.\
31 | 非常感谢您提交 issue。我们会尽快调查此事,并尽快回复您。 请确保您已经提供了尽可能多的背景信息。
32 | - name: Auto Comment on Issues Closed
33 | uses: wow-actions/auto-comment@v1
34 | with:
35 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN}}
36 | issuesClosed: |
37 | ✅ @{{ author }}
38 |
39 | This issue is closed, If you have any questions, you can comment and reply.\
40 | 此问题已经关闭。如果您有任何问题,可以留言并回复。
41 | - name: Auto Comment on Pull Request Opened
42 | uses: wow-actions/auto-comment@v1
43 | with:
44 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN}}
45 | pullRequestOpened: |
46 | 👍 @{{ author }}
47 |
48 | Thank you for raising your pull request and contributing to our Community
49 | Please make sure you have followed our contributing guidelines. We will review it as soon as possible.
50 | If you encounter any problems, please feel free to connect with us.\
51 | 非常感谢您提出拉取请求并为我们的社区做出贡献,请确保您已经遵循了我们的贡献指南,我们会尽快审查它。
52 | 如果您遇到任何问题,请随时与我们联系。
53 | - name: Auto Comment on Pull Request Merged
54 | uses: actions-cool/pr-welcome@main
55 | if: github.event.pull_request.merged == true
56 | with:
57 | token: ${{ secrets.GH_TOKEN }}
58 | comment: |
59 | ❤️ Great PR @${{ github.event.pull_request.user.login }} ❤️
60 |
61 | The growth of project is inseparable from user feedback and contribution, thanks for your contribution!\
62 | 项目的成长离不开用户反馈和贡献,感谢您的贡献!
63 | emoji: 'hooray'
64 | pr-emoji: '+1, heart'
65 | - name: Remove inactive
66 | if: github.event.issue.state == 'open' && github.actor == github.event.issue.user.login
67 | uses: actions-cool/issues-helper@v3
68 | with:
69 | actions: 'remove-labels'
70 | token: ${{ secrets.GH_TOKEN }}
71 | issue-number: ${{ github.event.issue.number }}
72 | labels: 'Inactive'
73 |
--------------------------------------------------------------------------------
/.github/workflows/issue-check-inactive.yml:
--------------------------------------------------------------------------------
1 | name: Issue Check Inactive
2 |
3 | on:
4 | schedule:
5 | - cron: '0 0 */15 * *'
6 |
7 | permissions:
8 | contents: read
9 |
10 | jobs:
11 | issue-check-inactive:
12 | permissions:
13 | issues: write # for actions-cool/issues-helper to update issues
14 | pull-requests: write # for actions-cool/issues-helper to update PRs
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: check-inactive
18 | uses: actions-cool/issues-helper@v3
19 | with:
20 | actions: 'check-inactive'
21 | token: ${{ secrets.GH_TOKEN }}
22 | inactive-label: 'Inactive'
23 | inactive-day: 30
24 |
--------------------------------------------------------------------------------
/.github/workflows/issue-close-require.yml:
--------------------------------------------------------------------------------
1 | name: Issue Close Require
2 |
3 | on:
4 | schedule:
5 | - cron: '0 0 * * *'
6 |
7 | permissions:
8 | contents: read
9 |
10 | jobs:
11 | issue-close-require:
12 | permissions:
13 | issues: write # for actions-cool/issues-helper to update issues
14 | pull-requests: write # for actions-cool/issues-helper to update PRs
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: need reproduce
18 | uses: actions-cool/issues-helper@v3
19 | with:
20 | actions: 'close-issues'
21 | token: ${{ secrets.GH_TOKEN }}
22 | labels: '✅ Fixed'
23 | inactive-day: 3
24 | body: |
25 | 👋 @{{ github.event.issue.user.login }}
26 |
27 | Since the issue was labeled with `✅ Fixed`, but no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.\
28 | 由于该 issue 被标记为已修复,同时 3 天未收到回应。现关闭 issue,若有任何问题,可评论回复。
29 | - name: need reproduce
30 | uses: actions-cool/issues-helper@v3
31 | with:
32 | actions: 'close-issues'
33 | token: ${{ secrets.GH_TOKEN }}
34 | labels: '🤔 Need Reproduce'
35 | inactive-day: 3
36 | body: |
37 | 👋 @{{ github.event.issue.user.login }}
38 |
39 | Since the issue was labeled with `🤔 Need Reproduce`, but no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.\
40 | 由于该 issue 被标记为需要更多信息,却 3 天未收到回应。现关闭 issue,若有任何问题,可评论回复。
41 | - name: need reproduce
42 | uses: actions-cool/issues-helper@v3
43 | with:
44 | actions: 'close-issues'
45 | token: ${{ secrets.GH_TOKEN }}
46 | labels: "🙅🏻♀️ WON'T DO"
47 | inactive-day: 3
48 | body: |
49 | 👋 @{{ github.event.issue.user.login }}
50 |
51 | Since the issue was labeled with `🙅🏻♀️ WON'T DO`, and no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.\
52 | 由于该 issue 被标记为暂不处理,同时 3 天未收到回应。现关闭 issue,若有任何问题,可评论回复。
53 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release CI
2 | on:
3 | push:
4 | branches:
5 | - main
6 | - alpha
7 | - beta
8 | - rc
9 |
10 | jobs:
11 | release:
12 | name: Release
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v3
16 |
17 | - name: Install bun
18 | uses: oven-sh/setup-bun@v1
19 |
20 | - name: Install deps
21 | run: bun i
22 |
23 | - name: CI
24 | run: bun run ci
25 |
26 | - name: Test
27 | run: bun run test
28 |
29 | - name: build
30 | run: bun run build
31 |
32 | - name: release
33 | run: bun run release
34 | env:
35 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
36 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
37 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test CI
2 | on:
3 | pull_request:
4 | push:
5 | branches:
6 | - '!main'
7 | jobs:
8 | test:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v3
13 |
14 | - name: Install bun
15 | uses: oven-sh/setup-bun@v1
16 |
17 | - name: Install deps
18 | run: bun i
19 |
20 | - name: CI
21 | run: bun run ci
22 |
23 | - name: Test and coverage
24 | run: bun run test:coverage
25 |
26 | - name: Upload coverage to Codecov
27 | uses: codecov/codecov-action@v3
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Gitignore for LobeHub
2 | ################################################################
3 |
4 | # general
5 | .DS_Store
6 | .idea
7 | .vscode
8 | .history
9 | .temp
10 | .env.local
11 | venv
12 | temp
13 | tmp
14 |
15 | # dependencies
16 | node_modules
17 | *.log
18 | *.lock
19 | package-lock.json
20 |
21 | # ci
22 | coverage
23 | .coverage
24 | .eslintcache
25 | .stylelintcache
26 |
27 | # production
28 | dist
29 | es
30 | lib
31 | logs
32 | test-output
33 |
34 | # umi
35 | .umi
36 | .umi-production
37 | .umi-test
38 | .dumi/tmp*
39 |
40 | # husky
41 | .husky/prepare-commit-msg
42 |
43 | # misc
44 | # add other ignore file below
45 | bun.lockb
46 | build
47 | .env
48 | .vercel
49 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx --no -- commitlint --edit ${1}
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx --no-install lint-staged
5 |
--------------------------------------------------------------------------------
/.husky/prepare-commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | # lobe-commit as a commit hook
3 | if npx -v >&/dev/null
4 | then
5 | exec < /dev/tty
6 | npx -c "lobe-commit --hook $1 $2"
7 | else
8 | exec < /dev/tty
9 | lobe-commit --hook $1 $2
10 | fi
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | lockfile=false
2 | resolution-mode=highest
3 | public-hoist-pattern[]=*@umijs/lint*
4 | public-hoist-pattern[]=*changelog*
5 | public-hoist-pattern[]=*commitlint*
6 | public-hoist-pattern[]=*eslint*
7 | public-hoist-pattern[]=*postcss*
8 | public-hoist-pattern[]=*prettier*
9 | public-hoist-pattern[]=*remark*
10 | public-hoist-pattern[]=*semantic-release*
11 | public-hoist-pattern[]=*stylelint*
12 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Prettierignore for LobeHub
2 | ################################################################
3 |
4 | # general
5 | .DS_Store
6 | .editorconfig
7 | .idea
8 | .vscode
9 | .history
10 | .temp
11 | .env.local
12 | .husky
13 | .npmrc
14 | .gitkeep
15 | venv
16 | temp
17 | tmp
18 | LICENSE
19 |
20 | # dependencies
21 | node_modules
22 | *.log
23 | *.lock
24 | package-lock.json
25 |
26 | # ci
27 | coverage
28 | .coverage
29 | .eslintcache
30 | .stylelintcache
31 | test-output
32 | __snapshots__
33 | *.snap
34 |
35 | # production
36 | dist
37 | es
38 | lib
39 | logs
40 |
41 | # umi
42 | .umi
43 | .umi-production
44 | .umi-test
45 | .dumi/tmp*
46 |
47 | # ignore files
48 | .*ignore
49 |
50 | # docker
51 | docker
52 | Dockerfile*
53 |
54 | # image
55 | *.webp
56 | *.gif
57 | *.png
58 | *.jpg
59 |
60 | # misc
61 | # add other ignore file below
62 | .env.example
63 | .env
64 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@lobehub/lint').prettier;
2 |
--------------------------------------------------------------------------------
/.releaserc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@lobehub/lint').semanticRelease;
2 |
--------------------------------------------------------------------------------
/.remarkrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@lobehub/lint').remarklint;
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Changelog
4 |
5 | ## [Version 1.9.0](https://github.com/lobehub/chat-plugins-gateway/compare/v1.8.2...v1.9.0)
6 |
7 | Released on **2023-12-26**
8 |
9 | #### ✨ Features
10 |
11 | - **misc**: Support defaultPluginSettings options on Gateway.
12 |
13 |
14 |
15 |
16 | Improvements and Fixes
17 |
18 | #### What's improved
19 |
20 | - **misc**: Support defaultPluginSettings options on Gateway ([fea957e](https://github.com/lobehub/chat-plugins-gateway/commit/fea957e))
21 |
22 |
23 |
24 |
25 |
26 | [](#readme-top)
27 |
28 |
29 |
30 | ### [Version 1.8.2](https://github.com/lobehub/chat-plugins-gateway/compare/v1.8.1...v1.8.2)
31 |
32 | Released on **2023-12-15**
33 |
34 | #### 🐛 Bug Fixes
35 |
36 | - **misc**: Fix openapi requestBody.
37 |
38 |
39 |
40 |
41 | Improvements and Fixes
42 |
43 | #### What's fixed
44 |
45 | - **misc**: Fix openapi requestBody ([4385080](https://github.com/lobehub/chat-plugins-gateway/commit/4385080))
46 |
47 |
48 |
49 |
50 |
51 | [](#readme-top)
52 |
53 |
54 |
55 | ### [Version 1.8.1](https://github.com/lobehub/chat-plugins-gateway/compare/v1.8.0...v1.8.1)
56 |
57 | Released on **2023-12-14**
58 |
59 | #### 🐛 Bug Fixes
60 |
61 | - **misc**: Handling unknown error.
62 |
63 |
64 |
65 |
66 | Improvements and Fixes
67 |
68 | #### What's fixed
69 |
70 | - **misc**: Handling unknown error ([e503d1f](https://github.com/lobehub/chat-plugins-gateway/commit/e503d1f))
71 |
72 |
73 |
74 |
75 |
76 | [](#readme-top)
77 |
78 |
79 |
80 | ## [Version 1.8.0](https://github.com/lobehub/chat-plugins-gateway/compare/v1.7.2...v1.8.0)
81 |
82 | Released on **2023-12-13**
83 |
84 | #### ✨ Features
85 |
86 | - **misc**: Support node runtime.
87 |
88 |
89 |
90 |
91 | Improvements and Fixes
92 |
93 | #### What's improved
94 |
95 | - **misc**: Support node runtime, closes [#9](https://github.com/lobehub/chat-plugins-gateway/issues/9) ([28ef918](https://github.com/lobehub/chat-plugins-gateway/commit/28ef918))
96 |
97 |
98 |
99 |
100 |
101 | [](#readme-top)
102 |
103 |
104 |
105 | ## [Version 1.8.0-beta.4](https://github.com/lobehub/chat-plugins-gateway/compare/v1.8.0-beta.3...v1.8.0-beta.4)
106 |
107 | Released on **2023-12-13**
108 |
109 | #### 🐛 Bug Fixes
110 |
111 | - **misc**: Close ajv strict mode.
112 |
113 |
114 |
115 |
116 | Improvements and Fixes
117 |
118 | #### What's fixed
119 |
120 | - **misc**: Close ajv strict mode ([a6eb5b6](https://github.com/lobehub/chat-plugins-gateway/commit/a6eb5b6))
121 |
122 |
123 |
124 |
125 |
126 | [](#readme-top)
127 |
128 |
129 |
130 | ## [Version 1.8.0-beta.3](https://github.com/lobehub/chat-plugins-gateway/compare/v1.8.0-beta.2...v1.8.0-beta.3)
131 |
132 | Released on **2023-12-13**
133 |
134 | #### 🐛 Bug Fixes
135 |
136 | - **misc**: Fix ajv schema.
137 |
138 |
139 |
140 |
141 | Improvements and Fixes
142 |
143 | #### What's fixed
144 |
145 | - **misc**: Fix ajv schema ([40f22c5](https://github.com/lobehub/chat-plugins-gateway/commit/40f22c5))
146 |
147 |
148 |
149 |
150 |
151 | [](#readme-top)
152 |
153 |
154 |
155 | ## [Version 1.8.0-beta.2](https://github.com/lobehub/chat-plugins-gateway/compare/v1.8.0-beta.1...v1.8.0-beta.2)
156 |
157 | Released on **2023-12-13**
158 |
159 | #### 🐛 Bug Fixes
160 |
161 | - **misc**: Fix request input.
162 |
163 |
164 |
165 |
166 | Improvements and Fixes
167 |
168 | #### What's fixed
169 |
170 | - **misc**: Fix request input ([1b896de](https://github.com/lobehub/chat-plugins-gateway/commit/1b896de))
171 |
172 |
173 |
174 |
175 |
176 | [](#readme-top)
177 |
178 |
179 |
180 | ## [Version 1.8.0-beta.1](https://github.com/lobehub/chat-plugins-gateway/compare/v1.7.2...v1.8.0-beta.1)
181 |
182 | Released on **2023-12-13**
183 |
184 | #### ♻ Code Refactoring
185 |
186 | - **misc**: Refactor to the Gateway Class and add tests.
187 |
188 | #### ✨ Features
189 |
190 | - **misc**: Support node runtime.
191 |
192 |
193 |
194 |
195 | Improvements and Fixes
196 |
197 | #### Code refactoring
198 |
199 | - **misc**: Refactor to the Gateway Class and add tests ([e8c8cb5](https://github.com/lobehub/chat-plugins-gateway/commit/e8c8cb5))
200 |
201 | #### What's improved
202 |
203 | - **misc**: Support node runtime ([a8e2933](https://github.com/lobehub/chat-plugins-gateway/commit/a8e2933))
204 |
205 |
206 |
207 |
208 |
209 | [](#readme-top)
210 |
211 |
212 |
213 | ### [Version 1.7.2](https://github.com/lobehub/chat-plugins-gateway/compare/v1.7.1...v1.7.2)
214 |
215 | Released on **2023-12-12**
216 |
217 | #### 🐛 Bug Fixes
218 |
219 | - **misc**: Improve PluginGatewayError error response.
220 |
221 |
222 |
223 |
224 | Improvements and Fixes
225 |
226 | #### What's fixed
227 |
228 | - **misc**: Improve PluginGatewayError error response ([18b1c43](https://github.com/lobehub/chat-plugins-gateway/commit/18b1c43))
229 |
230 |
231 |
232 |
233 |
234 | [](#readme-top)
235 |
236 |
237 |
238 | ### [Version 1.7.1](https://github.com/lobehub/chat-plugins-gateway/compare/v1.7.0...v1.7.1)
239 |
240 | Released on **2023-12-12**
241 |
242 | #### 🐛 Bug Fixes
243 |
244 | - **misc**: Fix error with openapi client init.
245 |
246 |
247 |
248 |
249 | Improvements and Fixes
250 |
251 | #### What's fixed
252 |
253 | - **misc**: Fix error with openapi client init ([b652cb5](https://github.com/lobehub/chat-plugins-gateway/commit/b652cb5))
254 |
255 |
256 |
257 |
258 |
259 | [](#readme-top)
260 |
261 |
262 |
263 | ## [Version 1.7.0](https://github.com/lobehub/chat-plugins-gateway/compare/v1.6.0...v1.7.0)
264 |
265 | Released on **2023-12-11**
266 |
267 | #### ✨ Features
268 |
269 | - **misc**: Support basic auth for OpenAPI.
270 |
271 |
272 |
273 |
274 | Improvements and Fixes
275 |
276 | #### What's improved
277 |
278 | - **misc**: Support basic auth for OpenAPI ([d3fe874](https://github.com/lobehub/chat-plugins-gateway/commit/d3fe874))
279 |
280 |
281 |
282 |
283 |
284 | [](#readme-top)
285 |
286 |
287 |
288 | ## [Version 1.6.0](https://github.com/lobehub/chat-plugins-gateway/compare/v1.5.3...v1.6.0)
289 |
290 | Released on **2023-12-11**
291 |
292 | #### ✨ Features
293 |
294 | - **misc**: Support OpenAPI request mode.
295 |
296 | #### 🐛 Bug Fixes
297 |
298 | - **misc**: Fix types.
299 |
300 |
301 |
302 |
303 | Improvements and Fixes
304 |
305 | #### What's improved
306 |
307 | - **misc**: Support OpenAPI request mode ([605d947](https://github.com/lobehub/chat-plugins-gateway/commit/605d947))
308 |
309 | #### What's fixed
310 |
311 | - **misc**: Fix types ([8e2dde4](https://github.com/lobehub/chat-plugins-gateway/commit/8e2dde4))
312 |
313 |
314 |
315 |
316 |
317 | [](#readme-top)
318 |
319 |
320 |
321 | ### [Version 1.5.3](https://github.com/lobehub/chat-plugins-gateway/compare/v1.5.2...v1.5.3)
322 |
323 | Released on **2023-09-12**
324 |
325 | #### 🐛 Bug Fixes
326 |
327 | - **misc**: Fix ci, Fix father version, Fix lint.
328 |
329 |
330 |
331 |
332 | Improvements and Fixes
333 |
334 | #### What's fixed
335 |
336 | - **misc**: Fix ci ([9099c61](https://github.com/lobehub/chat-plugins-gateway/commit/9099c61))
337 | - **misc**: Fix father version ([9668073](https://github.com/lobehub/chat-plugins-gateway/commit/9668073))
338 | - **misc**: Fix lint ([3e47791](https://github.com/lobehub/chat-plugins-gateway/commit/3e47791))
339 |
340 |
341 |
342 |
343 |
344 | [](#readme-top)
345 |
346 |
347 |
348 | ### [Version 1.5.2](https://github.com/lobehub/chat-plugins-gateway/compare/v1.5.1...v1.5.2)
349 |
350 | Released on **2023-09-08**
351 |
352 | #### 💄 Styles
353 |
354 | - **misc**: Update lint.
355 |
356 |
357 |
358 |
359 | Improvements and Fixes
360 |
361 | #### Styles
362 |
363 | - **misc**: Update lint ([6385312](https://github.com/lobehub/chat-plugins-gateway/commit/6385312))
364 |
365 |
366 |
367 |
368 |
369 | [](#readme-top)
370 |
371 |
372 |
373 | ### [Version 1.5.1](https://github.com/lobehub/chat-plugins-gateway/compare/v1.5.0...v1.5.1)
374 |
375 | Released on **2023-09-05**
376 |
377 | #### 🐛 Bug Fixes
378 |
379 | - **misc**: Fix getPluginSettingsFromRequest.
380 |
381 |
382 |
383 |
384 | Improvements and Fixes
385 |
386 | #### What's fixed
387 |
388 | - **misc**: Fix getPluginSettingsFromRequest ([aad3c7a](https://github.com/lobehub/chat-plugins-gateway/commit/aad3c7a))
389 |
390 |
391 |
392 |
393 |
394 | [](#readme-top)
395 |
396 |
397 |
398 | ## [Version 1.5.0](https://github.com/lobehub/chat-plugins-gateway/compare/v1.4.3...v1.5.0)
399 |
400 | Released on **2023-08-29**
401 |
402 | #### ✨ Features
403 |
404 | - **misc**: 更新入参配置,解决跨域兼容性的问题.
405 |
406 |
407 |
408 |
409 | Improvements and Fixes
410 |
411 | #### What's improved
412 |
413 | - **misc**: 更新入参配置,解决跨域兼容性的问题 ([c02451c](https://github.com/lobehub/chat-plugins-gateway/commit/c02451c))
414 |
415 |
416 |
417 |
418 |
419 | [](#readme-top)
420 |
421 |
422 |
423 | ### [Version 1.4.3](https://github.com/lobehub/chat-plugins-gateway/compare/v1.4.2...v1.4.3)
424 |
425 | Released on **2023-08-29**
426 |
427 | #### 🐛 Bug Fixes
428 |
429 | - **misc**: 移除跨域限制,解决插件复用的问题.
430 |
431 |
432 |
433 |
434 | Improvements and Fixes
435 |
436 | #### What's fixed
437 |
438 | - **misc**: 移除跨域限制,解决插件复用的问题 ([975b705](https://github.com/lobehub/chat-plugins-gateway/commit/975b705))
439 |
440 |
441 |
442 |
443 |
444 | [](#readme-top)
445 |
446 |
447 |
448 | ### [Version 1.4.2](https://github.com/lobehub/chat-plugins-gateway/compare/v1.4.1...v1.4.2)
449 |
450 | Released on **2023-08-29**
451 |
452 | #### 🐛 Bug Fixes
453 |
454 | - **misc**: 移除跨域限制,解决插件复用的问题.
455 |
456 |
457 |
458 |
459 | Improvements and Fixes
460 |
461 | #### What's fixed
462 |
463 | - **misc**: 移除跨域限制,解决插件复用的问题 ([1de6837](https://github.com/lobehub/chat-plugins-gateway/commit/1de6837))
464 |
465 |
466 |
467 |
468 |
469 | [](#readme-top)
470 |
471 |
472 |
473 | ### [Version 1.4.1](https://github.com/lobehub/chat-plugins-gateway/compare/v1.4.0...v1.4.1)
474 |
475 | Released on **2023-08-29**
476 |
477 | #### 🐛 Bug Fixes
478 |
479 | - **misc**: 内联默认的 索引 URL,解决外部使用问题.
480 |
481 |
482 |
483 |
484 | Improvements and Fixes
485 |
486 | #### What's fixed
487 |
488 | - **misc**: 内联默认的 索引 URL,解决外部使用问题 ([54b93d2](https://github.com/lobehub/chat-plugins-gateway/commit/54b93d2))
489 |
490 |
491 |
492 |
493 |
494 | [](#readme-top)
495 |
496 |
497 |
498 | ## [Version 1.4.0](https://github.com/lobehub/chat-plugins-gateway/compare/v1.3.2...v1.4.0)
499 |
500 | Released on **2023-08-28**
501 |
502 | #### ✨ Features
503 |
504 | - **misc**: 支持入参包含 manifest.
505 |
506 |
507 |
508 |
509 | Improvements and Fixes
510 |
511 | #### What's improved
512 |
513 | - **misc**: 支持入参包含 manifest ([b209fba](https://github.com/lobehub/chat-plugins-gateway/commit/b209fba))
514 |
515 |
516 |
517 |
518 |
519 | [](#readme-top)
520 |
521 |
522 |
523 | ### [Version 1.3.2](https://github.com/lobehub/chat-plugins-gateway/compare/v1.3.1...v1.3.2)
524 |
525 | Released on **2023-08-26**
526 |
527 | #### 🐛 Bug Fixes
528 |
529 | - **misc**: Change master to main.
530 |
531 |
532 |
533 |
534 | Improvements and Fixes
535 |
536 | #### What's fixed
537 |
538 | - **misc**: Change master to main ([134c77f](https://github.com/lobehub/chat-plugins-gateway/commit/134c77f))
539 |
540 |
541 |
542 |
543 |
544 | [](#readme-top)
545 |
546 |
547 |
548 | ### [Version 1.3.1](https://github.com/lobehub/chat-plugins-gateway/compare/v1.3.0...v1.3.1)
549 |
550 | Released on **2023-08-26**
551 |
552 | #### 🐛 Bug Fixes
553 |
554 | - **misc**: Fix an error when plugin settings is undefined.
555 |
556 |
557 |
558 |
559 | Improvements and Fixes
560 |
561 | #### What's fixed
562 |
563 | - **misc**: Fix an error when plugin settings is undefined ([666e503](https://github.com/lobehub/chat-plugins-gateway/commit/666e503))
564 |
565 |
566 |
567 |
568 |
569 | [](#readme-top)
570 |
571 |
572 |
573 | ## [Version 1.3.0](https://github.com/lobehub/chat-plugins-gateway/compare/v1.2.5...v1.3.0)
574 |
575 | Released on **2023-08-25**
576 |
577 | #### ✨ Features
578 |
579 | - **misc**: 补充插件网关的校验逻辑.
580 |
581 |
582 |
583 |
584 | Improvements and Fixes
585 |
586 | #### What's improved
587 |
588 | - **misc**: 补充插件网关的校验逻辑 ([c6f5286](https://github.com/lobehub/chat-plugins-gateway/commit/c6f5286))
589 |
590 |
591 |
592 |
593 |
594 | [](#readme-top)
595 |
596 |
597 |
598 | ### [Version 1.2.5](https://github.com/lobehub/chat-plugins-gateway/compare/v1.2.4...v1.2.5)
599 |
600 | Released on **2023-08-25**
601 |
602 | #### 🐛 Bug Fixes
603 |
604 | - **misc**: 修正没有呈现索引 url 的问题.
605 |
606 |
607 |
608 |
609 | Improvements and Fixes
610 |
611 | #### What's fixed
612 |
613 | - **misc**: 修正没有呈现索引 url 的问题 ([75f94dd](https://github.com/lobehub/chat-plugins-gateway/commit/75f94dd))
614 |
615 |
616 |
617 |
618 |
619 | [](#readme-top)
620 |
621 |
622 |
623 | ### [Version 1.2.4](https://github.com/lobehub/chat-plugins-gateway/compare/v1.2.3...v1.2.4)
624 |
625 | Released on **2023-08-25**
626 |
627 | #### 🐛 Bug Fixes
628 |
629 | - **misc**: 兼容 sdk 变更.
630 |
631 |
632 |
633 |
634 | Improvements and Fixes
635 |
636 | #### What's fixed
637 |
638 | - **misc**: 兼容 sdk 变更 ([7ff08c8](https://github.com/lobehub/chat-plugins-gateway/commit/7ff08c8))
639 |
640 |
641 |
642 |
643 |
644 | [](#readme-top)
645 |
646 |
647 |
648 | ### [Version 1.2.3](https://github.com/lobehub/chat-plugins-gateway/compare/v1.2.2...v1.2.3)
649 |
650 | Released on **2023-08-24**
651 |
652 | #### ♻ Code Refactoring
653 |
654 | - **misc**: Refactor with new manifest schema.
655 |
656 |
657 |
658 |
659 | Improvements and Fixes
660 |
661 | #### Code refactoring
662 |
663 | - **misc**: Refactor with new manifest schema, closes [#5](https://github.com/lobehub/chat-plugins-gateway/issues/5) ([583f834](https://github.com/lobehub/chat-plugins-gateway/commit/583f834))
664 |
665 |
666 |
667 |
668 |
669 | [](#readme-top)
670 |
671 |
672 |
673 | ### [Version 1.2.2](https://github.com/lobehub/chat-plugins-gateway/compare/v1.2.1...v1.2.2)
674 |
675 | Released on **2023-08-22**
676 |
677 | #### 🐛 Bug Fixes
678 |
679 | - **misc**: Fix missing types.
680 |
681 |
682 |
683 |
684 | Improvements and Fixes
685 |
686 | #### What's fixed
687 |
688 | - **misc**: Fix missing types ([4d8029d](https://github.com/lobehub/chat-plugins-gateway/commit/4d8029d))
689 |
690 |
691 |
692 |
693 |
694 | [](#readme-top)
695 |
696 |
697 |
698 | ### [Version 1.2.1](https://github.com/lobehub/chat-plugins-gateway/compare/v1.2.0...v1.2.1)
699 |
700 | Released on **2023-08-22**
701 |
702 | #### 🐛 Bug Fixes
703 |
704 | - **misc**: Fix not release.
705 |
706 |
707 |
708 |
709 | Improvements and Fixes
710 |
711 | #### What's fixed
712 |
713 | - **misc**: Fix not release ([3a0d30f](https://github.com/lobehub/chat-plugins-gateway/commit/3a0d30f))
714 |
715 |
716 |
717 |
718 |
719 | [](#readme-top)
720 |
721 |
722 |
723 | ## [Version 1.2.0](https://github.com/lobehub/chat-plugins-gateway/compare/v1.1.0...v1.2.0)
724 |
725 | Released on **2023-08-22**
726 |
727 | #### ✨ Features
728 |
729 | - **misc**: Add reusable gateway function.
730 |
731 | #### 🐛 Bug Fixes
732 |
733 | - **misc**: Fix config error, fix config error.
734 |
735 |
736 |
737 |
738 | Improvements and Fixes
739 |
740 | #### What's improved
741 |
742 | - **misc**: Add reusable gateway function ([eba16ab](https://github.com/lobehub/chat-plugins-gateway/commit/eba16ab))
743 |
744 | #### What's fixed
745 |
746 | - **misc**: Fix config error ([96ed006](https://github.com/lobehub/chat-plugins-gateway/commit/96ed006))
747 | - **misc**: Fix config error ([25fbd18](https://github.com/lobehub/chat-plugins-gateway/commit/25fbd18))
748 |
749 |
750 |
751 |
752 |
753 | [](#readme-top)
754 |
755 |
756 |
757 | ## [Version 1.1.0](https://github.com/lobehub/chat-plugin-market/compare/v1.0.0...v1.1.0)
758 |
759 | Released on **2023-08-20**
760 |
761 | #### ✨ Features
762 |
763 | - **misc**: Custom market index url.
764 |
765 |
766 |
767 |
768 | Improvements and Fixes
769 |
770 | #### What's improved
771 |
772 | - **misc**: Custom market index url, closes [#3](https://github.com/lobehub/chat-plugin-market/issues/3) ([14cd4b2](https://github.com/lobehub/chat-plugin-market/commit/14cd4b2))
773 |
774 |
775 |
776 |
777 |
778 | [](#readme-top)
779 |
780 |
781 |
782 | ## Version 1.0.0
783 |
784 | Released on **2023-08-19**
785 |
786 | #### ♻ Code Refactoring
787 |
788 | - **misc**: Remove searchEngine, remove webCrawler.
789 |
790 | #### ✨ Features
791 |
792 | - **misc**: Chat plugin gateway 1.0, prod 生产锁定 indexURL 版本,先使用最简单的方式迁移插件服务端,初步剥离天气插件,支持 V1 manifest 版本的方案.
793 |
794 | #### 🐛 Bug Fixes
795 |
796 | - **misc**: Fix console time error, fix import error, fix request error, 修正动态链路的命中逻辑,修正动态链路的命中逻辑.
797 |
798 |
799 |
800 |
801 | Improvements and Fixes
802 |
803 | #### Code refactoring
804 |
805 | - **misc**: Remove searchEngine ([d0fc888](https://github.com/lobehub/chat-plugin-market/commit/d0fc888))
806 | - **misc**: Remove webCrawler ([91663ea](https://github.com/lobehub/chat-plugin-market/commit/91663ea))
807 |
808 | #### What's improved
809 |
810 | - **misc**: Chat plugin gateway 1.0, closes [#2](https://github.com/lobehub/chat-plugin-market/issues/2) ([788a30a](https://github.com/lobehub/chat-plugin-market/commit/788a30a))
811 | - **misc**: Prod 生产锁定 indexURL 版本 ([80cdafb](https://github.com/lobehub/chat-plugin-market/commit/80cdafb))
812 | - **misc**: 先使用最简单的方式迁移插件服务端 ([eaf26bd](https://github.com/lobehub/chat-plugin-market/commit/eaf26bd))
813 | - **misc**: 初步剥离天气插件,closes [#1](https://github.com/lobehub/chat-plugin-market/issues/1) ([ecf1c4c](https://github.com/lobehub/chat-plugin-market/commit/ecf1c4c))
814 | - **misc**: 支持 V1 manifest 版本的方案 ([8f26a6c](https://github.com/lobehub/chat-plugin-market/commit/8f26a6c))
815 |
816 | #### What's fixed
817 |
818 | - **misc**: Fix console time error ([1cd882c](https://github.com/lobehub/chat-plugin-market/commit/1cd882c))
819 | - **misc**: Fix import error ([a9fa133](https://github.com/lobehub/chat-plugin-market/commit/a9fa133))
820 | - **misc**: Fix request error ([d8bb922](https://github.com/lobehub/chat-plugin-market/commit/d8bb922))
821 | - **misc**: 修正动态链路的命中逻辑 ([beb14eb](https://github.com/lobehub/chat-plugin-market/commit/beb14eb))
822 | - **misc**: 修正动态链路的命中逻辑 ([525654f](https://github.com/lobehub/chat-plugin-market/commit/525654f))
823 |
824 |
825 |
826 |
827 |
828 | [](#readme-top)
829 |
830 |
831 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 LobeHub
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |

6 |

7 |

8 |
9 |
LobeChat Plugins Gateway
10 |
11 | Plugin Gateway Service for Lobe Chat and Lobe Web
12 |
13 | [![][🤯-🧩-lobehub-shield]][🤯-🧩-lobehub-link]
14 | [![][npm-release-shield]][npm-release-link]
15 | [![][github-releasedate-shield]][github-releasedate-link]
16 | [![][github-action-test-shield]][github-action-test-link]
17 | [![][github-action-release-shield]][github-action-release-link]
18 | [![][github-contributors-shield]][github-contributors-link]
19 | [![][github-forks-shield]][github-forks-link]
20 | [![][github-stars-shield]][github-stars-link]
21 | [![][github-issues-shield]][github-issues-link]
22 | [![][github-license-shield]][github-license-link]
23 |
24 | **English** · [简体中文](./README.zh-CN.md) · [Changelog](./CHANGELOG.md) · [Report Bug][github-issues-link] · [Request Feature][github-issues-link]
25 |
26 | 
27 |
28 |
29 |
30 |
31 | Table of contents
32 |
33 | #### TOC
34 |
35 | - [👋 Intro](#-intro)
36 | - [🤯 Usage](#-usage)
37 | - [Base URLs](#base-urls)
38 | - [POST Plugin Gateway](#post-plugin-gateway)
39 | - [🛳 Self Hosting](#-self-hosting)
40 | - [Deploy to Vercel](#deploy-to-vercel)
41 | - [📦 Plugin Ecosystem](#-plugin-ecosystem)
42 | - [⌨️ Local Development](#️-local-development)
43 | - [🤝 Contributing](#-contributing)
44 | - [🔗 Links](#-links)
45 |
46 | ####
47 |
48 |
49 |
50 | ## 👋 Intro
51 |
52 | LobeChat Plugins Gateway is a backend service that provides a gateway for LobeChat plugins. We use [vercel](https://vercel.com/) to deploy this service. The main API `POST /api/v1/runner` is deployed as an [Edge Function](https://vercel.com/docs/functions/edge-functions).
53 |
54 | The gateway service fetches lobe plugins index from the [LobeChat Plugins](https://github.com/lobehub/lobe-chat-plugins), if you want to add your plugin to the index, please [submit a PR](https://github.com/lobehub/lobe-chat-plugins/pulls) to the LobeChat Plugins repository.
55 |
56 |
57 |
58 | [![][back-to-top]](#readme-top)
59 |
60 |
61 |
62 | ## 🤯 Usage
63 |
64 | ### Base URLs
65 |
66 | | Environment | URL |
67 | | ----------- | ---------------------------------------------- |
68 | | `PROD` | |
69 | | `DEV` | |
70 |
71 | ### POST Plugin Gateway
72 |
73 | > **Note**\
74 | > **POST** `/api/v1/runner`\
75 | > Interface to communicate with the LobeChat plugin. This interface describes how to use the LobeChat plugin gateway API to send requests and get responses
76 |
77 | #### Body Request Parameters
78 |
79 | ```json
80 | {
81 | "arguments": "{\n \"city\": \"杭州\"\n}",
82 | "name": "realtimeWeather"
83 | }
84 | ```
85 |
86 | #### Response
87 |
88 | ```json
89 | [
90 | {
91 | "city": "杭州市",
92 | "adcode": "330100",
93 | "province": "浙江",
94 | "reporttime": "2023-08-17 23:32:22",
95 | "casts": [
96 | {
97 | "date": "2023-08-17",
98 | "week": "4",
99 | "dayweather": "小雨",
100 | "nightweather": "小雨",
101 | "daytemp": "33",
102 | "nighttemp": "24",
103 | "daywind": "东",
104 | "nightwind": "东",
105 | "daypower": "≤3",
106 | "nightpower": "≤3",
107 | "daytemp_float": "33.0",
108 | "nighttemp_float": "24.0"
109 | },
110 | {
111 | "date": "2023-08-18",
112 | "week": "5",
113 | "dayweather": "小雨",
114 | "nightweather": "小雨",
115 | "daytemp": "32",
116 | "nighttemp": "23",
117 | "daywind": "东北",
118 | "nightwind": "东北",
119 | "daypower": "4",
120 | "nightpower": "4",
121 | "daytemp_float": "32.0",
122 | "nighttemp_float": "23.0"
123 | },
124 | {
125 | "date": "2023-08-19",
126 | "week": "6",
127 | "dayweather": "小雨",
128 | "nightweather": "雷阵雨",
129 | "daytemp": "32",
130 | "nighttemp": "24",
131 | "daywind": "东",
132 | "nightwind": "东",
133 | "daypower": "4",
134 | "nightpower": "4",
135 | "daytemp_float": "32.0",
136 | "nighttemp_float": "24.0"
137 | },
138 | {
139 | "date": "2023-08-20",
140 | "week": "7",
141 | "dayweather": "雷阵雨",
142 | "nightweather": "多云",
143 | "daytemp": "33",
144 | "nighttemp": "25",
145 | "daywind": "东",
146 | "nightwind": "东",
147 | "daypower": "≤3",
148 | "nightpower": "≤3",
149 | "daytemp_float": "33.0",
150 | "nighttemp_float": "25.0"
151 | }
152 | ]
153 | }
154 | ]
155 | ```
156 |
157 | See [API Document](https://apifox.com/apidoc/shared-c574e77f-4230-4727-9c05-c5c9988eed06) for more information.
158 |
159 |
160 |
161 | [![][back-to-top]](#readme-top)
162 |
163 |
164 |
165 | ## 🛳 Self Hosting
166 |
167 | If you want to deploy this service by yourself, you can follow the steps below.
168 |
169 | ### Deploy to Vercel
170 |
171 | Click button below to deploy your private plugins' gateway.
172 |
173 | [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Flobehub%2Fchat-plugins-gateway&project-name=chat-plugins-gateway&repository-name=chat-plugins-gateway)
174 |
175 | If you want to make some customization, you can add environment variable:
176 |
177 | - `PLUGINS_INDEX_URL`: You can change the default plugins index url as your need.
178 |
179 |
180 |
181 | [![][back-to-top]](#readme-top)
182 |
183 |
184 |
185 | ## 📦 Plugin Ecosystem
186 |
187 | Plugins provide a means to extend the Function Calling capabilities of LobeChat. They can be used to introduce new function calls and even new ways to render message results. If you are interested in plugin development, please refer to our [📘 Plugin Development Guide](https://github.com/lobehub/lobe-chat/wiki/Plugin-Development) in the Wiki.
188 |
189 | - [lobe-chat-plugins][lobe-chat-plugins]: This is the plugin index for LobeChat. It accesses index.json from this repository to display a list of available plugins for LobeChat to the user.
190 | - [chat-plugin-template][chat-plugin-template]: This is the plugin template for LobeChat plugin development.
191 | - [@lobehub/chat-plugin-sdk][chat-plugin-sdk]: The LobeChat Plugin SDK assists you in creating exceptional chat plugins for Lobe Chat.
192 | - [@lobehub/chat-plugins-gateway][chat-plugins-gateway]: The LobeChat Plugins Gateway is a backend service that provides a gateway for LobeChat plugins. We deploy this service using Vercel. The primary API POST /api/v1/runner is deployed as an Edge Function.
193 |
194 |
195 |
196 | [![][back-to-top]](#readme-top)
197 |
198 |
199 |
200 | ## ⌨️ Local Development
201 |
202 | You can use Github Codespaces for online development:
203 |
204 | [![][github-codespace-shield]][github-codespace-link]
205 |
206 | Or clone it for local development:
207 |
208 | [![][bun-shield]][bun-link]
209 |
210 | ```bash
211 | $ git clone https://github.com/lobehub/chat-plugins-gateway.git
212 | $ cd chat-plugins-gateway
213 | $ bun install
214 | $ bun dev
215 | ```
216 |
217 |
218 |
219 | [![][back-to-top]](#readme-top)
220 |
221 |
222 |
223 | ## 🤝 Contributing
224 |
225 | Contributions of all types are more than welcome, if you are interested in contributing code, feel free to check out our GitHub [Issues][github-issues-link] to get stuck in to show us what you’re made of.
226 |
227 | [![][pr-welcome-shield]][pr-welcome-link]
228 |
229 | [![][github-contrib-shield]][github-contrib-link]
230 |
231 |
232 |
233 | [![][back-to-top]](#readme-top)
234 |
235 |
236 |
237 | ## 🔗 Links
238 |
239 | - **[🤖 Lobe Chat](https://github.com/lobehub/lobe-chat)** - An open-source, extensible (Function Calling), high-performance chatbot framework. It supports one-click free deployment of your private ChatGPT/LLM web application.
240 | - **[🧩 / 🏪 Plugin Index](https://github.com/lobehub/lobe-chat-plugins)** - This is the plugin index for LobeChat. It accesses index.json from this repository to display a list of available plugins for Function Calling to the user.
241 |
242 |
243 |
244 | [![][back-to-top]](#readme-top)
245 |
246 |
247 |
248 | ---
249 |
250 | #### 📝 License
251 |
252 | Copyright © 2023 [LobeHub][profile-link].
253 | This project is [MIT](./LICENSE) licensed.
254 |
255 |
256 |
257 | [🤯-🧩-lobehub-link]: https://github.com/lobehub/lobe-chat-plugins
258 | [🤯-🧩-lobehub-shield]: https://img.shields.io/badge/%F0%9F%A4%AF%20%26%20%F0%9F%A7%A9%20LobeHub-Plugin-95f3d9?labelColor=black&style=flat-square
259 | [back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-black?style=flat-square
260 | [bun-link]: https://bun.sh
261 | [bun-shield]: https://img.shields.io/badge/-speedup%20with%20bun-black?logo=bun&style=for-the-badge
262 | [chat-plugin-sdk]: https://github.com/lobehub/chat-plugin-sdk
263 | [chat-plugin-template]: https://github.com/lobehub/chat-plugin-
264 | [chat-plugins-gateway]: https://github.com/lobehub/chat-plugins-gateway
265 | [github-action-release-link]: https://github.com/lobehub/chat-plugins-gateway/actions/workflows/release.yml
266 | [github-action-release-shield]: https://img.shields.io/github/actions/workflow/status/lobehub/chat-plugins-gateway/release.yml?label=release&labelColor=black&logo=githubactions&logoColor=white&style=flat-square
267 | [github-action-test-link]: https://github.com/lobehub/chat-plugins-gateway/actions/workflows/test.yml
268 | [github-action-test-shield]: https://img.shields.io/github/actions/workflow/status/lobehub/chat-plugins-gateway/test.yml?label=test&labelColor=black&logo=githubactions&logoColor=white&style=flat-square
269 | [github-codespace-link]: https://codespaces.new/lobehub/chat-plugins-gateway
270 | [github-codespace-shield]: https://github.com/codespaces/badge.svg
271 | [github-contrib-link]: https://github.com/lobehub/chat-plugins-gateway/graphs/contributors
272 | [github-contrib-shield]: https://contrib.rocks/image?repo=lobehub%2Fchat-plugins-gateway
273 | [github-contributors-link]: https://github.com/lobehub/chat-plugins-gateway/graphs/contributors
274 | [github-contributors-shield]: https://img.shields.io/github/contributors/lobehub/chat-plugins-gateway?color=c4f042&labelColor=black&style=flat-square
275 | [github-forks-link]: https://github.com/lobehub/chat-plugins-gateway/network/members
276 | [github-forks-shield]: https://img.shields.io/github/forks/lobehub/chat-plugins-gateway?color=8ae8ff&labelColor=black&style=flat-square
277 | [github-issues-link]: https://github.com/lobehub/chat-plugins-gateway/issues
278 | [github-issues-shield]: https://img.shields.io/github/issues/lobehub/chat-plugins-gateway?color=ff80eb&labelColor=black&style=flat-square
279 | [github-license-link]: https://github.com/lobehub/chat-plugins-gateway/blob/main/LICENSE
280 | [github-license-shield]: https://img.shields.io/github/license/lobehub/chat-plugins-gateway?color=white&labelColor=black&style=flat-square
281 | [github-releasedate-link]: https://github.com/lobehub/chat-plugins-gateway/releases
282 | [github-releasedate-shield]: https://img.shields.io/github/release-date/lobehub/chat-plugins-gateway?labelColor=black&style=flat-square
283 | [github-stars-link]: https://github.com/lobehub/chat-plugins-gateway/network/stargazers
284 | [github-stars-shield]: https://img.shields.io/github/stars/lobehub/chat-plugins-gateway?color=ffcb47&labelColor=black&style=flat-square
285 | [lobe-chat-plugins]: https://github.com/lobehub/lobe-chat-plugins
286 | [npm-release-link]: https://www.npmjs.com/package/@lobehub/chat-plugins-gateway
287 | [npm-release-shield]: https://img.shields.io/npm/v/@lobehub/chat-plugins-gateway?color=369eff&labelColor=black&logo=npm&logoColor=white&style=flat-square
288 | [pr-welcome-link]: https://github.com/lobehub/chat-plugins-gateway/pulls
289 | [pr-welcome-shield]: https://img.shields.io/badge/%F0%9F%A4%AF%20PR%20WELCOME-%E2%86%92-ffcb47?labelColor=black&style=for-the-badge
290 | [profile-link]: https://github.com/lobehub
291 |
--------------------------------------------------------------------------------
/README.zh-CN.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |

6 |

7 |

8 |
9 |
LobeChat 插件网关
10 |
11 | LobeChat Plugin Gateway 是一个为 LobeChat 和 LobeHub 提供 Chat 插件网关的后端服务。
12 |
13 | [![][🤯-🧩-lobehub-shield]][🤯-🧩-lobehub-link]
14 | [![][npm-release-shield]][npm-release-link]
15 | [![][github-releasedate-shield]][github-releasedate-link]
16 | [![][github-action-test-shield]][github-action-test-link]
17 | [![][github-action-release-shield]][github-action-release-link]
18 | [![][github-contributors-shield]][github-contributors-link]
19 | [![][github-forks-shield]][github-forks-link]
20 | [![][github-stars-shield]][github-stars-link]
21 | [![][github-issues-shield]][github-issues-link]
22 | [![][github-license-shield]][github-license-link]
23 |
24 | [English](./README.md) · **简体中文** · [更新日志](./CHANGELOG.md) · [报告 Bug][github-issues-link] · [请求功能][github-issues-link]
25 |
26 |
27 |
28 |
29 | 目录
30 |
31 | #### TOC
32 |
33 | - [👋 简介](#-简介)
34 | - [🤯 使用方法](#-使用方法)
35 | - [基本 URL](#基本-url)
36 | - [POST 插件网关](#post-插件网关)
37 | - [🛳 自托管](#-自托管)
38 | - [部署到 Vercel](#部署到-vercel)
39 | - [📦 插件生态](#-插件生态)
40 | - [⌨️ Local Development](#️-local-development)
41 | - [🤝 Contributing](#-contributing)
42 | - [🔗 Links](#-links)
43 |
44 | ####
45 |
46 |
47 |
48 | ## 👋 简介
49 |
50 | LobeChat 插件网关是一个后端服务,为 LobeChat 插件提供网关。我们使用 [vercel](https://vercel.com/) 来部署此服务。主要 API `POST /api/v1/runner` 部署为[Edge Function](https://vercel.com/docs/functions/edge-functions)。
51 |
52 | 网关服务从 [LobeChat 插件](https://github.com/lobehub/lobe-chat-plugins) 获取 Lobe 插件索引,如果您想将您的插件添加到索引中,请在 LobeChat 插件仓库中[提交 PR](https://github.com/lobehub/lobe-chat-plugins/pulls)。
53 |
54 |
55 |
56 | [![][back-to-top]](#readme-top)
57 |
58 |
59 |
60 | ## 🤯 使用方法
61 |
62 | ### 基本 URL
63 |
64 | | 环境 | URL |
65 | | ------ | ---------------------------------------------- |
66 | | `PROD` | |
67 | | `DEV` | |
68 |
69 | ### POST 插件网关
70 |
71 | > **Note**\
72 | > **POST** `/api/v1/runner`\
73 | > 与 LobeChat 插件进行通信的接口。此接口描述了如何使用 LobeChat 插件网关 API 发送请求和获取响应。
74 |
75 | #### Body Request Parameters 请求体参数
76 |
77 | ```json
78 | {
79 | "arguments": "{\n \"city\": \"杭州\"\n}",
80 | "name": "realtimeWeather"
81 | }
82 | ```
83 |
84 | #### Response 响应
85 |
86 | ```json
87 | [
88 | {
89 | "city": "杭州市",
90 | "adcode": "330100",
91 | "province": "浙江",
92 | "reporttime": "2023-08-17 23:32:22",
93 | "casts": [
94 | {
95 | "date": "2023-08-17",
96 | "week": "4",
97 | "dayweather": "小雨",
98 | "nightweather": "小雨",
99 | "daytemp": "33",
100 | "nighttemp": "24",
101 | "daywind": "东",
102 | "nightwind": "东",
103 | "daypower": "≤3",
104 | "nightpower": "≤3",
105 | "daytemp_float": "33.0",
106 | "nighttemp_float": "24.0"
107 | },
108 | {
109 | "date": "2023-08-18",
110 | "week": "5",
111 | "dayweather": "小雨",
112 | "nightweather": "小雨",
113 | "daytemp": "32",
114 | "nighttemp": "23",
115 | "daywind": "东北",
116 | "nightwind": "东北",
117 | "daypower": "4",
118 | "nightpower": "4",
119 | "daytemp_float": "32.0",
120 | "nighttemp_float": "23.0"
121 | },
122 | {
123 | "date": "2023-08-19",
124 | "week": "6",
125 | "dayweather": "小雨",
126 | "nightweather": "雷阵雨",
127 | "daytemp": "32",
128 | "nighttemp": "24",
129 | "daywind": "东",
130 | "nightwind": "东",
131 | "daypower": "4",
132 | "nightpower": "4",
133 | "daytemp_float": "32.0",
134 | "nighttemp_float": "24.0"
135 | },
136 | {
137 | "date": "2023-08-20",
138 | "week": "7",
139 | "dayweather": "雷阵雨",
140 | "nightweather": "多云",
141 | "daytemp": "33",
142 | "nighttemp": "25",
143 | "daywind": "东",
144 | "nightwind": "东",
145 | "daypower": "≤3",
146 | "nightpower": "≤3",
147 | "daytemp_float": "33.0",
148 | "nighttemp_float": "25.0"
149 | }
150 | ]
151 | }
152 | ]
153 | ```
154 |
155 | 更多信息请参见[API 文档](https://apifox.com/apidoc/shared-c574e77f-4230-4727-9c05-c5c9988eed06)。
156 |
157 |
158 |
159 | [![][back-to-top]](#readme-top)
160 |
161 |
162 |
163 | ## 🛳 自托管
164 |
165 | 如果您想自己部署此服务,可以按照以下步骤进行操作。
166 |
167 | ### 部署到 Vercel
168 |
169 | 点击下方按钮来部署您的私有插件网关。
170 |
171 | [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Flobehub%2Fchat-plugins-gateway&project-name=chat-plugins-gateway&repository-name=chat-plugins-gateway)
172 |
173 | 如果您想进行一些自定义设置,可以在部署时添加环境变量(Environment Variable):
174 |
175 | - `PLUGINS_INDEX_URL`:你可以通过该变量指定插件市场的索引地址
176 |
177 |
178 |
179 | [![][back-to-top]](#readme-top)
180 |
181 |
182 |
183 | ## 📦 插件生态
184 |
185 | 插件提供了扩展 LobeChat Function Calling 能力的方法。可以用于引入新的 Function Calling,甚至是新的消息结果渲染方式。如果你对插件开发感兴趣,请在 Wiki 中查阅我们的 [📘 插件开发指引](https://github.com/lobehub/lobe-chat/wiki/Plugin-Development.zh-CN) 。
186 |
187 | - [lobe-chat-plugins][lobe-chat-plugins]:这是 LobeChat 的插件索引。它从该仓库的 index.json 中获取插件列表并显示给用户。
188 | - [chat-plugin-template][chat-plugin-template]: Chat Plugin 插件开发模版,你可以通过项目模版快速新建插件项目。
189 | - [@lobehub/chat-plugin-sdk][chat-plugin-sdk]:LobeChat 插件 SDK 可帮助您创建出色的 Lobe Chat 插件。
190 | - [@lobehub/chat-plugins-gateway][chat-plugins-gateway]:LobeChat 插件网关是一个后端服务,作为 LobeChat 插件的网关。我们使用 Vercel 部署此服务。主要的 API POST /api/v1/runner 被部署为 Edge Function。
191 |
192 |
193 |
194 | [![][back-to-top]](#readme-top)
195 |
196 |
197 |
198 | ## ⌨️ Local Development
199 |
200 | 可以使用 GitHub Codespaces 进行在线开发:
201 |
202 | [![][github-codespace-shield]][github-codespace-link]
203 |
204 | 或者使用以下命令进行本地开发:
205 |
206 | [![][bun-shield]][bun-link]
207 |
208 | ```bash
209 | $ git clone https://github.com/lobehub/chat-plugins-gateway.git
210 | $ cd chat-plugins-gateway
211 | $ bun install
212 | $ bun dev
213 | ```
214 |
215 |
216 |
217 | [![][back-to-top]](#readme-top)
218 |
219 |
220 |
221 | ## 🤝 Contributing
222 |
223 | 我们非常欢迎各种形式的贡献。如果你对贡献代码感兴趣,可以查看我们的 GitHub [Issues][github-issues-link],大展身手,向我们展示你的奇思妙想。
224 |
225 | [![][pr-welcome-shield]][pr-welcome-link]
226 |
227 | [![][github-contrib-shield]][github-contrib-link]
228 |
229 |
230 |
231 | [![][back-to-top]](#readme-top)
232 |
233 |
234 |
235 | ## 🔗 Links
236 |
237 | - **[🤖 Lobe Chat](https://github.com/lobehub/lobe-chat)** - An open-source, extensible (Function Calling), high-performance chatbot framework. It supports one-click free deployment of your private ChatGPT/LLM web application.
238 | - **[🧩 / 🏪 Plugin Index](https://github.com/lobehub/lobe-chat-plugins)** - This is the plugin index for LobeChat. It accesses index.json from this repository to display a list of available plugins for Function Calling to the user.
239 |
240 |
241 |
242 | [![][back-to-top]](#readme-top)
243 |
244 |
245 |
246 | ---
247 |
248 | #### 📝 License
249 |
250 | Copyright © 2023 [LobeHub][profile-link].
251 | This project is [MIT](./LICENSE) licensed.
252 |
253 |
254 |
255 | [🤯-🧩-lobehub-link]: https://github.com/lobehub/lobe-chat-plugins
256 | [🤯-🧩-lobehub-shield]: https://img.shields.io/badge/%F0%9F%A4%AF%20%26%20%F0%9F%A7%A9%20LobeHub-Plugin-95f3d9?labelColor=black&style=flat-square
257 | [back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-black?style=flat-square
258 | [bun-link]: https://bun.sh
259 | [bun-shield]: https://img.shields.io/badge/-speedup%20with%20bun-black?logo=bun&style=for-the-badge
260 | [chat-plugin-sdk]: https://github.com/lobehub/chat-plugin-sdk
261 | [chat-plugin-template]: https://github.com/lobehub/chat-plugin-
262 | [chat-plugins-gateway]: https://github.com/lobehub/chat-plugins-gateway
263 | [github-action-release-link]: https://github.com/lobehub/chat-plugins-gateway/actions/workflows/release.yml
264 | [github-action-release-shield]: https://img.shields.io/github/actions/workflow/status/lobehub/chat-plugins-gateway/release.yml?label=release&labelColor=black&logo=githubactions&logoColor=white&style=flat-square
265 | [github-action-test-link]: https://github.com/lobehub/chat-plugins-gateway/actions/workflows/test.yml
266 | [github-action-test-shield]: https://img.shields.io/github/actions/workflow/status/lobehub/chat-plugins-gateway/test.yml?label=test&labelColor=black&logo=githubactions&logoColor=white&style=flat-square
267 | [github-codespace-link]: https://codespaces.new/lobehub/chat-plugins-gateway
268 | [github-codespace-shield]: https://github.com/codespaces/badge.svg
269 | [github-contrib-link]: https://github.com/lobehub/chat-plugins-gateway/graphs/contributors
270 | [github-contrib-shield]: https://contrib.rocks/image?repo=lobehub%2Fchat-plugins-gateway
271 | [github-contributors-link]: https://github.com/lobehub/chat-plugins-gateway/graphs/contributors
272 | [github-contributors-shield]: https://img.shields.io/github/contributors/lobehub/chat-plugins-gateway?color=c4f042&labelColor=black&style=flat-square
273 | [github-forks-link]: https://github.com/lobehub/chat-plugins-gateway/network/members
274 | [github-forks-shield]: https://img.shields.io/github/forks/lobehub/chat-plugins-gateway?color=8ae8ff&labelColor=black&style=flat-square
275 | [github-issues-link]: https://github.com/lobehub/chat-plugins-gateway/issues
276 | [github-issues-shield]: https://img.shields.io/github/issues/lobehub/chat-plugins-gateway?color=ff80eb&labelColor=black&style=flat-square
277 | [github-license-link]: https://github.com/lobehub/chat-plugins-gateway/blob/main/LICENSE
278 | [github-license-shield]: https://img.shields.io/github/license/lobehub/chat-plugins-gateway?color=white&labelColor=black&style=flat-square
279 | [github-releasedate-link]: https://github.com/lobehub/chat-plugins-gateway/releases
280 | [github-releasedate-shield]: https://img.shields.io/github/release-date/lobehub/chat-plugins-gateway?labelColor=black&style=flat-square
281 | [github-stars-link]: https://github.com/lobehub/chat-plugins-gateway/network/stargazers
282 | [github-stars-shield]: https://img.shields.io/github/stars/lobehub/chat-plugins-gateway?color=ffcb47&labelColor=black&style=flat-square
283 | [lobe-chat-plugins]: https://github.com/lobehub/lobe-chat-plugins
284 | [npm-release-link]: https://www.npmjs.com/package/@lobehub/chat-plugins-gateway
285 | [npm-release-shield]: https://img.shields.io/npm/v/@lobehub/chat-plugins-gateway?color=369eff&labelColor=black&logo=npm&logoColor=white&style=flat-square
286 | [pr-welcome-link]: https://github.com/lobehub/chat-plugins-gateway/pulls
287 | [pr-welcome-shield]: https://img.shields.io/badge/%F0%9F%A4%AF%20PR%20WELCOME-%E2%86%92-ffcb47?labelColor=black&style=for-the-badge
288 | [profile-link]: https://github.com/lobehub
289 |
--------------------------------------------------------------------------------
/api/index.ts:
--------------------------------------------------------------------------------
1 | import type { VercelRequest, VercelResponse } from '@vercel/node';
2 |
3 | export default async (req: VercelRequest, response: VercelResponse) => {
4 | response.json({
5 | status: 'ok',
6 | });
7 | return 'hello';
8 | };
9 |
--------------------------------------------------------------------------------
/api/v1/runner.ts:
--------------------------------------------------------------------------------
1 | import { createGatewayOnNodeRuntime } from '../../src';
2 |
3 | export default createGatewayOnNodeRuntime();
4 |
--------------------------------------------------------------------------------
/docs/api.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: LobeChat Plugins v1.0.0
3 | language_tabs:
4 | - shell: Shell
5 | - http: HTTP
6 | - javascript: JavaScript
7 | - ruby: Ruby
8 | - python: Python
9 | - php: PHP
10 | - java: Java
11 | - go: Go
12 | toc_footers: []
13 | includes: []
14 | search: true
15 | code_clipboard: true
16 | highlight_theme: darkula
17 | headingLevel: 2
18 | generator: '@tarslib/widdershins v4.0.17'
19 | ---
20 |
21 | # LobeChat Plugins
22 |
23 | > v1.0.0
24 |
25 | Base URLs:
26 |
27 | - Prod URL:
28 |
29 | # Default
30 |
31 | ## POST 插件网关
32 |
33 | POST /api/v1/runner
34 |
35 | 与 LobeChat 插件进行通信的接口。本接口描述了如何使用 LobeChat 插件网关 API 来发送请求和获取响应。
36 |
37 | > Body 请求参数
38 |
39 | ```json
40 | {
41 | "arguments": "{\n \"city\": \"杭州\"\n}",
42 | "name": "realtimeWeather"
43 | }
44 | ```
45 |
46 | ### 请求参数
47 |
48 | | 名称 | 位置 | 类型 | 必选 | 说明 |
49 | | ----------- | ---- | ------ | ---- | ---- |
50 | | body | body | object | 否 | none |
51 | | » name | body | string | 是 | none |
52 | | » arguments | body | string | 是 | none |
53 | | » indexUrl | body | string | 否 | none |
54 |
55 | > 返回示例
56 |
57 | > 成功
58 |
59 | > 请求入参校验失败
60 |
61 | ```json
62 | {
63 | "body": {
64 | "issues": [
65 | {
66 | "code": "invalid_type",
67 | "expected": "string",
68 | "received": "undefined",
69 | "path": ["name"],
70 | "message": "Required"
71 | }
72 | ],
73 | "name": "ZodError"
74 | },
75 | "errorType": 400
76 | }
77 | ```
78 |
79 | ```json
80 | {
81 | "body": {
82 | "error": [
83 | {
84 | "path": ["city"],
85 | "property": "instance.city",
86 | "message": "is not of a type(s) string",
87 | "instance": 123,
88 | "name": "type",
89 | "argument": ["string"],
90 | "stack": "instance.city is not of a type(s) string"
91 | }
92 | ],
93 | "manifest": {
94 | "version": "1",
95 | "name": "realtimeWeather",
96 | "schema": {
97 | "description": "获取当前天气情况",
98 | "name": "realtimeWeather",
99 | "parameters": {
100 | "properties": {
101 | "city": {
102 | "description": "城市名称",
103 | "type": "string"
104 | }
105 | },
106 | "required": ["city"],
107 | "type": "object"
108 | }
109 | },
110 | "server": {
111 | "url": "https://realtime-weather.chat-plugin.lobehub.com/api/v1"
112 | }
113 | },
114 | "message": "[plugin] args is invalid with plugin manifest schema"
115 | },
116 | "errorType": 400
117 | }
118 | ```
119 |
120 | > 插件信息不存在
121 |
122 | ```json
123 | {
124 | "body": {
125 | "message": "[gateway] plugin is not found",
126 | "name": "abcccccc"
127 | },
128 | "errorType": "pluginMetaNotFound"
129 | }
130 | ```
131 |
132 | ```json
133 | {
134 | "body": {
135 | "manifestUrl": "https://web-crawler.chat-plugin.lobehub.com/manifest.json",
136 | "message": "[plugin] plugin manifest not found"
137 | },
138 | "errorType": "pluginManifestNotFound"
139 | }
140 | ```
141 |
142 | > 插件元数据错误
143 |
144 | ```json
145 | {
146 | "body": {
147 | "error": {
148 | "issues": [
149 | {
150 | "code": "invalid_type",
151 | "expected": "string",
152 | "received": "undefined",
153 | "path": ["manifest"],
154 | "message": "Required"
155 | }
156 | ],
157 | "name": "ZodError"
158 | },
159 | "message": "[plugin] plugin meta is invalid",
160 | "pluginMeta": {
161 | "createAt": "2023-08-12",
162 | "homepage": "https://github.com/lobehub/chat-plugin-real-time-weather",
163 | "meta": {
164 | "avatar": "☂️",
165 | "tags": ["weather", "realtime"]
166 | },
167 | "name": "realtimeWeather",
168 | "schemaVersion": "v1"
169 | }
170 | },
171 | "errorType": "pluginMetaInvalid"
172 | }
173 | ```
174 |
175 | > 491 Response
176 |
177 | ```json
178 | {}
179 | ```
180 |
181 | > 插件市场索引错误
182 |
183 | ```json
184 | {
185 | "body": {
186 | "error": {
187 | "issues": [
188 | {
189 | "code": "invalid_type",
190 | "expected": "array",
191 | "received": "undefined",
192 | "path": ["plugins"],
193 | "message": "Required"
194 | },
195 | {
196 | "code": "invalid_type",
197 | "expected": "number",
198 | "received": "undefined",
199 | "path": ["version"],
200 | "message": "Required"
201 | }
202 | ],
203 | "name": "ZodError"
204 | },
205 | "indexUrl": "https://registry.npmmirror.com",
206 | "marketIndex": {
207 | "last_package": "@c2pkg/storage-upload",
208 | "last_package_version": "@c2pkg/storage-upload@3.10.0",
209 | "doc_count": 3561547,
210 | "doc_version_count": 34943697,
211 | "download": {
212 | "today": 25587449,
213 | "yesterday": 128550602,
214 | "samedayLastweek": 35493324,
215 | "thisweek": 776453391,
216 | "thismonth": 2190677962,
217 | "thisyear": 27479998339,
218 | "lastweek": 792798408,
219 | "lastmonth": 4224663889,
220 | "lastyear": 35082329272
221 | },
222 | "update_seq": 63479086,
223 | "sync_model": "all",
224 | "sync_changes_steam": {
225 | "since": "35627379",
226 | "registryId": "6306496ddd636c97816f37d4",
227 | "taskWorker": "npmmirror-x86-20220823002:3275935",
228 | "task_count": 1597861518,
229 | "last_package": "gridcreator",
230 | "last_package_created": "2023-08-09T14:38:06.068Z"
231 | },
232 | "sync_binary": true,
233 | "instance_start_time": "2023-08-17T14:05:53.874Z",
234 | "node_version": "v16.20.1",
235 | "app_version": "3.41.0",
236 | "engine": "mysql",
237 | "source_registry": "https://r.cnpmjs.org",
238 | "changes_stream_registry": "https://r.cnpmjs.org",
239 | "cache_time": "2023-08-19T07:05:14.181Z",
240 | "upstream_registries": [
241 | {
242 | "since": "35627379",
243 | "registryId": "6306496ddd636c97816f37d4",
244 | "taskWorker": "npmmirror-x86-20220823002:3275935",
245 | "task_count": 1597861518,
246 | "last_package": "gridcreator",
247 | "last_package_created": "2023-08-09T14:38:06.068Z",
248 | "source_registry": "https://r.cnpmjs.org",
249 | "changes_stream_url": "https://r.cnpmjs.org/_changes",
250 | "registry_name": "default"
251 | }
252 | ]
253 | },
254 | "message": "[gateway] plugin market index is invalid"
255 | },
256 | "errorType": "pluginMarketIndexInvalid"
257 | }
258 | ```
259 |
260 | ```json
261 | {
262 | "body": {
263 | "indexUrl": "https://baidu.com",
264 | "message": "[gateway] plugin market index not found"
265 | },
266 | "errorType": "pluginMarketIndexNotFound"
267 | }
268 | ```
269 |
270 | ### 返回结果
271 |
272 | | 状态码 | 状态码含义 | 说明 | 数据模型 |
273 | | ------ | ---------------------------------------------------------------- | -------------------- | -------- |
274 | | 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | 成功 | Inline |
275 | | 400 | [Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1) | 请求入参校验失败 | Inline |
276 | | 404 | [Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4) | 插件信息不存在 | Inline |
277 | | 490 | Unknown | 插件元数据错误 | Inline |
278 | | 491 | Unknown | 插件描述文件校验失败 | Inline |
279 | | 590 | Unknown | 插件市场索引错误 | Inline |
280 |
281 | ### 返回数据结构
282 |
283 | 状态码 **490**
284 |
285 | | 名称 | 类型 | 必选 | 约束 | 中文名 | 说明 |
286 | | ------------- | ------ | ---- | ---- | ------ | ---- |
287 | | » body | object | true | none | | none |
288 | | »» error | object | true | none | | none |
289 | | »» message | string | true | none | | none |
290 | | »» pluginMeta | object | true | none | | none |
291 | | » errorType | string | true | none | | none |
292 |
293 | 状态码 **590**
294 |
295 | | 名称 | 类型 | 必选 | 约束 | 中文名 | 说明 |
296 | | -------------- | ------ | ----- | ---- | ------ | ---- |
297 | | » body | object | true | none | | none |
298 | | »» error | object | false | none | | none |
299 | | »» indexUrl | string | true | none | | none |
300 | | »» marketIndex | object | false | none | | none |
301 | | »» message | string | true | none | | none |
302 | | » errorType | string | true | none | | none |
303 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@lobehub/chat-plugins-gateway",
3 | "version": "1.9.0",
4 | "description": "Lobe Chat Plugin Market",
5 | "repository": "https://github.com/lobehub/chat-plugins-gateway.git",
6 | "author": "LobeHub ",
7 | "sideEffects": false,
8 | "main": "dist/index.js",
9 | "types": "dist/index.d.ts",
10 | "files": [
11 | "dist"
12 | ],
13 | "scripts": {
14 | "build": "father build",
15 | "build:watch": "father dev",
16 | "ci": "npm run lint && npm run type-check && npm run doctor",
17 | "doctor": "father doctor",
18 | "lint": "eslint \"{src,api,docs}/**/*.{js,jsx,ts,tsx}\" --fix",
19 | "lint:md": "remark . --quiet --frail --output",
20 | "prepare": "husky install",
21 | "prepublishOnly": "npm run build",
22 | "prettier": "prettier -c --write \"**/**\"",
23 | "release": "semantic-release",
24 | "start": "vercel dev",
25 | "test": "vitest",
26 | "test:coverage": "vitest --coverage",
27 | "test:update": "vitest -u",
28 | "type-check": "tsc --noEmit"
29 | },
30 | "lint-staged": {
31 | "*.md": [
32 | "remark --quiet --output --",
33 | "prettier --write --no-error-on-unmatched-pattern"
34 | ],
35 | "*.json": [
36 | "prettier --write --no-error-on-unmatched-pattern"
37 | ],
38 | "*.{js,jsx}": [
39 | "prettier --write",
40 | "eslint --fix"
41 | ],
42 | "*.{ts,tsx}": [
43 | "prettier --parser=typescript --write",
44 | "eslint --fix"
45 | ]
46 | },
47 | "browserslist": [
48 | "> 1%",
49 | "last 2 versions",
50 | "not ie <= 10"
51 | ],
52 | "dependencies": {
53 | "@babel/runtime": "^7",
54 | "@cfworker/json-schema": "^1",
55 | "@lobehub/chat-plugin-sdk": "^1",
56 | "ajv": "^8",
57 | "swagger-client": "^3",
58 | "zod": "^3"
59 | },
60 | "devDependencies": {
61 | "@commitlint/cli": "^17",
62 | "@lobehub/lint": "latest",
63 | "@vercel/node": "^2",
64 | "@vitest/coverage-v8": "0.34.6",
65 | "commitlint": "^17",
66 | "cross-env": "^7",
67 | "eslint": "^8",
68 | "father": "4.3.1",
69 | "husky": "^8",
70 | "lint-staged": "^13",
71 | "prettier": "^3",
72 | "remark": "^14",
73 | "remark-cli": "^11",
74 | "semantic-release": "^21",
75 | "typescript": "^5",
76 | "vercel": "^32.7.1",
77 | "vitest": "0.34.6"
78 | },
79 | "publishConfig": {
80 | "access": "public",
81 | "registry": "https://registry.npmjs.org"
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Lobe Chat Plugin Market
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/cors.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Multi purpose CORS lib.
3 | * Note: Based on the `cors` package in npm but using only
4 | * web APIs. Feel free to use it in your own projects.
5 | */
6 |
7 | export type StaticOrigin = boolean | string | RegExp | (boolean | string | RegExp)[];
8 |
9 | export type OriginFn = (
10 | origin: string | undefined,
11 | req: Request,
12 | ) => StaticOrigin | Promise;
13 |
14 | export interface CorsOptions {
15 | allowedHeaders?: string | string[];
16 | credentials?: boolean;
17 | exposedHeaders?: string | string[];
18 | maxAge?: number;
19 | methods?: string | string[];
20 | optionsSuccessStatus?: number;
21 | origin?: StaticOrigin | OriginFn;
22 | preflightContinue?: boolean;
23 | }
24 |
25 | const defaultOptions: CorsOptions = {
26 | methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
27 | optionsSuccessStatus: 204,
28 | origin: '*',
29 | preflightContinue: false,
30 | };
31 |
32 | function isOriginAllowed(origin: string, allowed: StaticOrigin): boolean {
33 | return Array.isArray(allowed)
34 | ? allowed.some((o) => isOriginAllowed(origin, o))
35 | : typeof allowed === 'string'
36 | ? origin === allowed
37 | : allowed instanceof RegExp
38 | ? allowed.test(origin)
39 | : !!allowed;
40 | }
41 |
42 | function getOriginHeaders(reqOrigin: string | undefined, origin: StaticOrigin) {
43 | const headers = new Headers();
44 |
45 | if (origin === '*') {
46 | // Allow any origin
47 | headers.set('Access-Control-Allow-Origin', '*');
48 | } else if (typeof origin === 'string') {
49 | // Fixed origin
50 | headers.set('Access-Control-Allow-Origin', origin);
51 | headers.append('Vary', 'Origin');
52 | } else {
53 | const allowed = isOriginAllowed(reqOrigin ?? '', origin);
54 |
55 | if (allowed && reqOrigin) {
56 | headers.set('Access-Control-Allow-Origin', reqOrigin);
57 | }
58 | headers.append('Vary', 'Origin');
59 | }
60 |
61 | return headers;
62 | }
63 |
64 | // originHeadersFromReq
65 |
66 | async function originHeadersFromReq(req: Request, origin: StaticOrigin | OriginFn) {
67 | const reqOrigin = req.headers.get('Origin') || undefined;
68 | const value = typeof origin === 'function' ? await origin(reqOrigin, req) : origin;
69 |
70 | if (!value) return;
71 | return getOriginHeaders(reqOrigin, value);
72 | }
73 |
74 | function getAllowedHeaders(req: Request, allowed?: string | string[]) {
75 | const headers = new Headers();
76 |
77 | if (!allowed) {
78 | // eslint-disable-next-line no-param-reassign
79 | allowed = req.headers.get('Access-Control-Request-Headers')!;
80 | headers.append('Vary', 'Access-Control-Request-Headers');
81 | } else if (Array.isArray(allowed)) {
82 | // If the allowed headers is an array, turn it into a string
83 | // eslint-disable-next-line no-param-reassign
84 | allowed = allowed.join(',');
85 | }
86 | if (allowed) {
87 | headers.set('Access-Control-Allow-Headers', allowed);
88 | }
89 |
90 | return headers;
91 | }
92 |
93 | export default async function cors(req: Request, res: Response, options?: CorsOptions) {
94 | const opts = { ...defaultOptions, ...options };
95 | const { headers } = res;
96 | const originHeaders = await originHeadersFromReq(req, opts.origin ?? false);
97 | const mergeHeaders = (v: string, k: string) => {
98 | if (k === 'Vary') headers.append(k, v);
99 | else headers.set(k, v);
100 | };
101 |
102 | // If there's no origin we won't touch the response
103 | if (!originHeaders) return res;
104 |
105 | originHeaders.forEach((element, index) => {
106 | mergeHeaders(element, index);
107 | });
108 |
109 | if (opts.credentials) {
110 | headers.set('Access-Control-Allow-Credentials', 'true');
111 | }
112 |
113 | const exposed = Array.isArray(opts.exposedHeaders)
114 | ? opts.exposedHeaders.join(',')
115 | : opts.exposedHeaders;
116 |
117 | if (exposed) {
118 | headers.set('Access-Control-Expose-Headers', exposed);
119 | }
120 |
121 | // Handle the preflight request
122 | if (req.method === 'OPTIONS') {
123 | if (opts.methods) {
124 | const methods = Array.isArray(opts.methods) ? opts.methods.join(',') : opts.methods;
125 |
126 | headers.set('Access-Control-Allow-Methods', methods);
127 | }
128 |
129 | getAllowedHeaders(req, opts.allowedHeaders).forEach((element, index) => {
130 | mergeHeaders(element, index);
131 | });
132 |
133 | if (typeof opts.maxAge === 'number') {
134 | headers.set('Access-Control-Max-Age', String(opts.maxAge));
135 | }
136 |
137 | if (opts.preflightContinue) return res;
138 |
139 | headers.set('Content-Length', '0');
140 | return new Response(null, { headers, status: opts.optionsSuccessStatus });
141 | }
142 |
143 | // If we got here, it's a normal request
144 | return res;
145 | }
146 |
147 | export function initCors(options?: CorsOptions) {
148 | return (req: Request, res: Response) => cors(req, res, options);
149 | }
150 |
--------------------------------------------------------------------------------
/src/edge.ts:
--------------------------------------------------------------------------------
1 | import {
2 | PluginErrorType,
3 | PluginRequestPayload,
4 | createErrorResponse,
5 | getPluginSettingsFromRequest,
6 | } from '@lobehub/chat-plugin-sdk';
7 |
8 | import cors, { CorsOptions } from './cors';
9 | import { Gateway, GatewayErrorResponse, GatewayOptions } from './gateway';
10 |
11 | export interface EdgeRuntimeGatewayOptions extends GatewayOptions {
12 | cors?: CorsOptions;
13 | }
14 |
15 | /**
16 | * create Gateway Edge Function with plugins index url
17 | * @param options {EdgeRuntimeGatewayOptions}
18 | */
19 | export const createGatewayOnEdgeRuntime = (options: EdgeRuntimeGatewayOptions = {}) => {
20 | const gateway = new Gateway(options);
21 |
22 | const handler = async (req: Request): Promise => {
23 | // ========== 1. 校验请求方法 ========== //
24 | if (req.method !== 'POST')
25 | return createErrorResponse(PluginErrorType.MethodNotAllowed, {
26 | message: '[gateway] only allow POST method',
27 | });
28 |
29 | const requestPayload = (await req.json()) as PluginRequestPayload;
30 | const settings = getPluginSettingsFromRequest(req);
31 |
32 | try {
33 | const res = await gateway.execute(requestPayload, settings);
34 | return new Response(res.data);
35 | } catch (error) {
36 | const { errorType, body } = error as GatewayErrorResponse;
37 |
38 | if (!errorType) {
39 | const err = error as Error;
40 | console.error(err.stack);
41 | return createErrorResponse(PluginErrorType.PluginGatewayError, {
42 | stack: err.stack,
43 | });
44 | }
45 | return createErrorResponse(errorType, body);
46 | }
47 | };
48 |
49 | return async (req: Request) => cors(req, await handler(req), options.cors);
50 | };
51 |
--------------------------------------------------------------------------------
/src/gateway.ts:
--------------------------------------------------------------------------------
1 | // reason to use cfworker json schema:
2 | // https://github.com/vercel/next.js/discussions/47063#discussioncomment-5303951
3 | import { Schema } from '@cfworker/json-schema';
4 | import {
5 | IPluginErrorType,
6 | LobeChatPluginApi,
7 | LobeChatPluginManifest,
8 | LobeChatPluginsMarketIndex,
9 | PluginErrorType,
10 | PluginRequestPayload,
11 | createHeadersWithPluginSettings,
12 | marketIndexSchema,
13 | pluginManifestSchema,
14 | pluginMetaSchema,
15 | pluginRequestPayloadSchema,
16 | } from '@lobehub/chat-plugin-sdk';
17 | import { OPENAPI_REQUEST_BODY_KEY } from '@lobehub/chat-plugin-sdk/openapi';
18 | // @ts-ignore
19 | import SwaggerClient from 'swagger-client';
20 |
21 | export const DEFAULT_PLUGINS_INDEX_URL = 'https://chat-plugins.lobehub.com';
22 |
23 | type IValidator = (schema: Schema, value: any) => { errors?: any; valid: boolean };
24 |
25 | export interface GatewayOptions {
26 | Validator?: IValidator;
27 | defaultPluginSettings?: Record>;
28 | /**
29 | * @default https://chat-plugins.lobehub.com
30 | */
31 | pluginsIndexUrl?: string;
32 | }
33 |
34 | export interface GatewaySuccessResponse {
35 | data: string;
36 | success: true;
37 | }
38 | export interface GatewayErrorResponse {
39 | body: string | object;
40 | errorType: IPluginErrorType;
41 | success: false;
42 | }
43 |
44 | export class Gateway {
45 | private pluginIndexUrl = DEFAULT_PLUGINS_INDEX_URL;
46 | private _validator: IValidator | undefined;
47 | private defaultPluginSettings: Record> = {};
48 |
49 | constructor(options?: GatewayOptions) {
50 | if (options?.pluginsIndexUrl) {
51 | this.pluginIndexUrl = options.pluginsIndexUrl;
52 | }
53 |
54 | if (options?.defaultPluginSettings) {
55 | this.defaultPluginSettings = options.defaultPluginSettings;
56 | }
57 |
58 | if (options?.Validator) {
59 | this._validator = options.Validator;
60 | }
61 | }
62 |
63 | private createSuccessResponse = (data: string) => {
64 | return { data, success: true } as const;
65 | };
66 |
67 | private createErrorResponse = (errorType: IPluginErrorType | string, body?: string | object) => {
68 | throw { body, errorType, success: false };
69 | };
70 |
71 | private async validate(schema: Schema, value: any) {
72 | if (this._validator) return this._validator(schema, value);
73 |
74 | // reason to use cfworker json schema:
75 | // https://github.com/vercel/next.js/discussions/47063#discussioncomment-5303951
76 | const { Validator } = await import('@cfworker/json-schema');
77 | const v = new Validator(schema);
78 | const validator = v.validate(value);
79 | if (!validator.valid) return { errors: validator.errors, valid: false };
80 | return { valid: true };
81 | }
82 |
83 | execute = async (
84 | payload: PluginRequestPayload,
85 | settings?: any,
86 | ): Promise => {
87 | // ========== 2. 校验请求入参基础格式 ========== //
88 | const payloadParseResult = pluginRequestPayloadSchema.safeParse(payload);
89 | if (!payloadParseResult.success)
90 | return this.createErrorResponse(PluginErrorType.BadRequest, payloadParseResult.error);
91 |
92 | const { identifier, arguments: args, indexUrl, apiName } = payload;
93 |
94 | let manifest = payload.manifest as LobeChatPluginManifest | undefined;
95 | console.info(`[${identifier}] - ${apiName} `);
96 |
97 | // 入参中如果没有 manifest,则从插件市场索引中获取
98 | if (!manifest) {
99 | const marketIndexUrl = indexUrl ?? this.pluginIndexUrl;
100 | // ========== 3. 获取插件市场索引 ========== //
101 |
102 | let marketIndex: LobeChatPluginsMarketIndex | undefined;
103 | try {
104 | const indexRes = await fetch(marketIndexUrl);
105 | marketIndex = await indexRes.json();
106 | } catch (error) {
107 | console.error(error);
108 | marketIndex = undefined;
109 | }
110 |
111 | // 插件市场索引不存在
112 | if (!marketIndex)
113 | return this.createErrorResponse(PluginErrorType.PluginMarketIndexNotFound, {
114 | indexUrl: marketIndexUrl,
115 | message: '[gateway] plugin market index not found',
116 | });
117 |
118 | // 插件市场索引解析失败
119 | const indexParseResult = marketIndexSchema.safeParse(marketIndex);
120 |
121 | if (!indexParseResult.success)
122 | return this.createErrorResponse(PluginErrorType.PluginMarketIndexInvalid, {
123 | error: indexParseResult.error,
124 | indexUrl: marketIndexUrl,
125 | marketIndex,
126 | message: '[gateway] plugin market index is invalid',
127 | });
128 |
129 | console.info(
130 | `[marketIndex V${marketIndex.schemaVersion}] total ${marketIndex.plugins.length} plugins`,
131 | );
132 |
133 | // ========== 4. 校验插件 meta 完备性 ========== //
134 |
135 | const pluginMeta = marketIndex.plugins.find((i) => i.identifier === identifier);
136 |
137 | // 一个不规范的插件示例
138 | // const pluginMeta = {
139 | // createAt: '2023-08-12',
140 | // homepage: 'https://github.com/lobehub/chat-plugin-real-time-weather',
141 | // manifest: 'https://registry.npmmirror.com/@lobehub/lobe-chat-plugins/latest/files',
142 | // meta: {
143 | // avatar: '☂️',
144 | // tags: ['weather', 'realtime'],
145 | // },
146 | // name: 'realtimeWeather',
147 | // schemaVersion: 'v1',
148 | // };
149 |
150 | // 校验插件是否存在
151 | if (!pluginMeta)
152 | return this.createErrorResponse(PluginErrorType.PluginMetaNotFound, {
153 | identifier,
154 | message: `[gateway] plugin '${identifier}' is not found,please check the plugin list in ${marketIndexUrl}, or create an issue to [lobe-chat-plugins](https://github.com/lobehub/lobe-chat-plugins/issues)`,
155 | });
156 |
157 | const metaParseResult = pluginMetaSchema.safeParse(pluginMeta);
158 |
159 | if (!metaParseResult.success)
160 | return this.createErrorResponse(PluginErrorType.PluginMetaInvalid, {
161 | error: metaParseResult.error,
162 | message: '[plugin] plugin meta is invalid',
163 | pluginMeta,
164 | });
165 | // ========== 5. 校验插件 manifest 完备性 ========== //
166 |
167 | // 获取插件的 manifest
168 | try {
169 | const pluginRes = await fetch(pluginMeta.manifest);
170 | manifest = (await pluginRes.json()) as LobeChatPluginManifest;
171 | } catch (error) {
172 | console.error(error);
173 | manifest = undefined;
174 | }
175 |
176 | if (!manifest)
177 | return this.createErrorResponse(PluginErrorType.PluginManifestNotFound, {
178 | manifestUrl: pluginMeta.manifest,
179 | message: '[plugin] plugin manifest not found',
180 | });
181 | }
182 |
183 | const manifestParseResult = pluginManifestSchema.safeParse(manifest);
184 |
185 | if (!manifestParseResult.success)
186 | return this.createErrorResponse(PluginErrorType.PluginManifestInvalid, {
187 | error: manifestParseResult.error,
188 | manifest: manifest,
189 | message: '[plugin] plugin manifest is invalid',
190 | });
191 |
192 | // console.log(`[${identifier}] plugin manifest:`, manifest);
193 |
194 | const defaultSettings = this.defaultPluginSettings[identifier] || {};
195 | const finalSettings = { ...defaultSettings, ...settings };
196 | // ========== 6. 校验是否按照 manifest 包含了 settings 配置 ========== //
197 |
198 | if (manifest.settings) {
199 | const { valid, errors } = await this.validate(manifest.settings as any, finalSettings);
200 |
201 | if (!valid)
202 | return this.createErrorResponse(PluginErrorType.PluginSettingsInvalid, {
203 | error: errors,
204 | message: '[plugin] your settings is invalid with plugin manifest setting schema',
205 | settings,
206 | });
207 | }
208 |
209 | // ========== 7. 校验请求入参与 manifest 要求一致性 ========== //
210 | const api = manifest.api.find((i) => i.name === apiName);
211 |
212 | if (!api)
213 | return this.createErrorResponse(PluginErrorType.PluginApiNotFound, {
214 | manifest,
215 | message: '[plugin] api not found',
216 | request: {
217 | apiName,
218 | identifier,
219 | },
220 | });
221 |
222 | if (args) {
223 | const params = JSON.parse(args!);
224 | const { valid, errors } = await this.validate(api.parameters as any, params);
225 |
226 | if (!valid)
227 | return this.createErrorResponse(PluginErrorType.PluginApiParamsError, {
228 | api,
229 | error: errors,
230 | message: '[plugin] args is invalid with plugin manifest api schema',
231 | request: params,
232 | });
233 | }
234 |
235 | // ========== 8. 兼容 OpenAPI 请求模式 ========== //
236 | if (manifest.openapi) {
237 | return await this.callOpenAPI(payload, finalSettings, manifest);
238 | }
239 |
240 | return await this.callApi(api, args, finalSettings);
241 | };
242 |
243 | private async callApi(
244 | api: LobeChatPluginApi,
245 | args: string | undefined,
246 | settings: any,
247 | ): Promise {
248 | if (!api.url)
249 | return this.createErrorResponse(PluginErrorType.PluginApiParamsError, {
250 | api,
251 | message: '[plugin] missing api url',
252 | });
253 |
254 | const response = await fetch(api.url, {
255 | body: args,
256 | headers: createHeadersWithPluginSettings(settings),
257 | method: 'POST',
258 | });
259 |
260 | // ========== 9. 发送请求 ========== //
261 |
262 | // 不正常的错误,直接返回请求
263 | if (!response.ok)
264 | return this.createErrorResponse(response.status as IPluginErrorType, await response.text());
265 |
266 | const data = await response.text();
267 |
268 | // console.log(`[${identifier}]`, args, `result:`, JSON.stringify(data).slice(0, 1000));
269 |
270 | return this.createSuccessResponse(data);
271 | }
272 |
273 | private async callOpenAPI(
274 | payload: PluginRequestPayload,
275 | settings: any = {},
276 | manifest: LobeChatPluginManifest,
277 | ): Promise {
278 | const { arguments: args, apiName } = payload;
279 |
280 | // @ts-ignore
281 | // const { default: SwaggerClient } = await import('swagger-client');
282 |
283 | const authorizations = {} as {
284 | [key: string]: any;
285 | basicAuth?: any;
286 | oauth2?: {
287 | accessToken: string;
288 | clientId: string;
289 | clientSecret: string;
290 | };
291 | };
292 |
293 | // 根据 settings 中的每个属性来构建 authorizations 对象
294 | for (const [key, value] of Object.entries(settings)) {
295 | // 处理 API Key 和 Bearer Token
296 | authorizations[key] = value as string;
297 |
298 | // TODO: Basic Auth and OAuth2
299 | // if (key.endsWith('_username') && key.endsWith('_password')) {
300 | // // 处理 HTTP Basic Authentication
301 | // const username = settings[key];
302 | // const password = settings[key.replace('_username', '_password')];
303 | // authorizations.basicAuth = new SwaggerClient.PasswordAuthorization(username, password);
304 | // console.log(authorizations.basicAuth);
305 | // } else if (
306 | // key.endsWith('_clientId') &&
307 | // key.endsWith('_clientSecret') &&
308 | // key.endsWith('_accessToken')
309 | // ) {
310 | // // 处理 OAuth2
311 | // const clientId = settings[key];
312 | // const clientSecret = settings[key.replace('_clientId', '_clientSecret')];
313 | // const accessToken = settings[key.replace('_clientId', '_accessToken')];
314 | // authorizations.oauth2 = { accessToken, clientId, clientSecret };
315 | // }
316 | }
317 |
318 | let client;
319 | try {
320 | client = await SwaggerClient({ authorizations, url: manifest.openapi });
321 | } catch (error) {
322 | return this.createErrorResponse(PluginErrorType.PluginOpenApiInitError, {
323 | error: error,
324 | message: '[plugin] openapi client init error',
325 | openapi: manifest.openapi,
326 | });
327 | }
328 |
329 | const requestParams = JSON.parse(args || '{}');
330 | const { [OPENAPI_REQUEST_BODY_KEY]: requestBody, ...parameters } = requestParams;
331 |
332 | try {
333 | const res = await client.execute({ operationId: apiName, parameters, requestBody });
334 |
335 | return this.createSuccessResponse(res.text);
336 | } catch (error) {
337 | // 如果没有 status,说明没有发送请求,可能是 openapi 相关调用实现的问题
338 | if (!(error as any).status) {
339 | console.error(error);
340 |
341 | return this.createErrorResponse(PluginErrorType.PluginGatewayError, {
342 | apiName,
343 | authorizations,
344 | error: (error as Error).message,
345 | message:
346 | '[plugin] there are problem with sending openapi request, please contact with LobeHub Team',
347 | openapi: manifest.openapi,
348 | parameters,
349 | requestBody,
350 | });
351 | }
352 |
353 | // 如果是 401 则说明是鉴权问题
354 | if ((error as Response).status === 401)
355 | return this.createErrorResponse(PluginErrorType.PluginSettingsInvalid);
356 |
357 | return this.createErrorResponse(PluginErrorType.PluginServerError, { error });
358 | }
359 | }
360 | }
361 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | createGatewayOnEdgeRuntime,
3 | /**
4 | * please use `createGatewayOnEdgeRuntime` instead
5 | * @deprecated
6 | */
7 | createGatewayOnEdgeRuntime as createLobeChatPluginGateway,
8 | } from './edge';
9 | export * from './gateway';
10 | export * from './node';
11 |
--------------------------------------------------------------------------------
/src/node.ts:
--------------------------------------------------------------------------------
1 | import {
2 | PluginErrorType,
3 | PluginRequestPayload,
4 | getPluginErrorStatus,
5 | getPluginSettingsFromHeaders,
6 | } from '@lobehub/chat-plugin-sdk';
7 | import { VercelRequest, VercelResponse } from '@vercel/node';
8 | import Ajv from 'ajv';
9 |
10 | import { Gateway, GatewayErrorResponse, GatewayOptions } from './gateway';
11 |
12 | export type NodeRuntimeGatewayOptions = Pick;
13 |
14 | export const createGatewayOnNodeRuntime = (options: NodeRuntimeGatewayOptions = {}) => {
15 | const gateway = new Gateway({
16 | ...options,
17 | Validator: (schema, value) => {
18 | const ajv = new Ajv({ strict: false });
19 | const validate = ajv.compile(schema);
20 |
21 | const valid = validate(value);
22 | return {
23 | errors: validate.errors,
24 | valid,
25 | };
26 | },
27 | });
28 |
29 | return async (req: VercelRequest, res: VercelResponse) => {
30 | // ========== 1. 校验请求方法 ========== //
31 | if (req.method !== 'POST') {
32 | res.status(PluginErrorType.MethodNotAllowed).send({
33 | message: '[gateway] only allow POST method',
34 | });
35 |
36 | return;
37 | }
38 |
39 | let requestPayload = req.body as PluginRequestPayload | string;
40 | if (typeof requestPayload === 'string') {
41 | requestPayload = JSON.parse(requestPayload) as PluginRequestPayload;
42 | }
43 |
44 | const settings = getPluginSettingsFromHeaders(req.headers as any);
45 |
46 | try {
47 | const { data } = await gateway.execute(requestPayload, settings);
48 |
49 | res.send(data);
50 | } catch (error) {
51 | const { errorType, body } = error as GatewayErrorResponse;
52 |
53 | res
54 | .status(getPluginErrorStatus(errorType))
55 | .send(
56 | errorType
57 | ? { body, errorType }
58 | : { body: error, errorType: PluginErrorType.PluginGatewayError },
59 | );
60 | }
61 | };
62 | };
63 |
--------------------------------------------------------------------------------
/tests/__snapshots__/edge.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`createGatewayOnEdgeRuntime > Error > should return PluginApiNotFound error when apiName is not found in manifest 1`] = `
4 | {
5 | "body": {
6 | "manifest": {
7 | "api": [
8 | {
9 | "description": "",
10 | "name": "test-api",
11 | "parameters": {
12 | "properties": {},
13 | "type": "object",
14 | },
15 | "url": "https://test-api-url.com",
16 | },
17 | ],
18 | "identifier": "abc",
19 | "meta": {},
20 | },
21 | "message": "[plugin] api not found",
22 | "request": {
23 | "apiName": "unknown-api",
24 | "identifier": "test-plugin",
25 | },
26 | },
27 | "errorType": "PluginApiNotFound",
28 | }
29 | `;
30 |
31 | exports[`createGatewayOnEdgeRuntime > Error > should return PluginApiParamsError when api parameters are invalid 1`] = `
32 | {
33 | "body": {
34 | "api": {
35 | "description": "",
36 | "name": "test-api",
37 | "parameters": {
38 | "properties": {
39 | "a": {
40 | "type": "string",
41 | },
42 | },
43 | "required": [
44 | "a",
45 | ],
46 | "type": "object",
47 | },
48 | "url": "https://test-api-url.com",
49 | },
50 | "error": [
51 | {
52 | "error": "Instance does not have required property \\"a\\".",
53 | "instanceLocation": "#",
54 | "keyword": "required",
55 | "keywordLocation": "#/required",
56 | },
57 | ],
58 | "message": "[plugin] args is invalid with plugin manifest api schema",
59 | "request": {
60 | "invalid": "params",
61 | },
62 | },
63 | "errorType": "PluginApiParamsError",
64 | }
65 | `;
66 |
67 | exports[`createGatewayOnEdgeRuntime > Error > should return PluginApiParamsError when api url is missing 1`] = `
68 | {
69 | "body": {
70 | "api": {
71 | "description": "",
72 | "name": "test-api",
73 | "parameters": {
74 | "properties": {},
75 | "type": "object",
76 | },
77 | },
78 | "message": "[plugin] missing api url",
79 | },
80 | "errorType": "PluginApiParamsError",
81 | }
82 | `;
83 |
84 | exports[`createGatewayOnEdgeRuntime > Error > should return PluginManifestInvalid error when plugin manifest is invalid 1`] = `
85 | {
86 | "body": {
87 | "error": {
88 | "issues": [
89 | {
90 | "code": "invalid_type",
91 | "expected": "array",
92 | "message": "Required",
93 | "path": [
94 | "api",
95 | ],
96 | "received": "undefined",
97 | },
98 | {
99 | "code": "invalid_type",
100 | "expected": "string",
101 | "message": "Required",
102 | "path": [
103 | "identifier",
104 | ],
105 | "received": "undefined",
106 | },
107 | {
108 | "code": "invalid_type",
109 | "expected": "object",
110 | "message": "Required",
111 | "path": [
112 | "meta",
113 | ],
114 | "received": "undefined",
115 | },
116 | ],
117 | "name": "ZodError",
118 | },
119 | "manifest": {
120 | "invalid": "manifest",
121 | },
122 | "message": "[plugin] plugin manifest is invalid",
123 | },
124 | "errorType": "PluginManifestInvalid",
125 | }
126 | `;
127 |
128 | exports[`createGatewayOnEdgeRuntime > Error > should return PluginManifestNotFound error when plugin manifest is unreachable 1`] = `
129 | {
130 | "body": {
131 | "manifestUrl": "https://test-plugin-url.com/manifest.json",
132 | "message": "[plugin] plugin manifest not found",
133 | },
134 | "errorType": "PluginManifestNotFound",
135 | }
136 | `;
137 |
138 | exports[`createGatewayOnEdgeRuntime > Error > should return PluginMarketIndexInvalid error when market index is invalid 1`] = `
139 | {
140 | "body": {
141 | "error": {
142 | "issues": [
143 | {
144 | "code": "invalid_type",
145 | "expected": "array",
146 | "message": "Required",
147 | "path": [
148 | "plugins",
149 | ],
150 | "received": "undefined",
151 | },
152 | {
153 | "code": "invalid_type",
154 | "expected": "number",
155 | "message": "Required",
156 | "path": [
157 | "schemaVersion",
158 | ],
159 | "received": "undefined",
160 | },
161 | ],
162 | "name": "ZodError",
163 | },
164 | "indexUrl": "https://test-market-index-url.com",
165 | "marketIndex": {
166 | "invalid": "index",
167 | },
168 | "message": "[gateway] plugin market index is invalid",
169 | },
170 | "errorType": "PluginMarketIndexInvalid",
171 | }
172 | `;
173 |
174 | exports[`createGatewayOnEdgeRuntime > Error > should return PluginMarketIndexNotFound error when market index is unreachable 1`] = `
175 | {
176 | "body": {
177 | "indexUrl": "https://chat-plugins.lobehub.com",
178 | "message": "[gateway] plugin market index not found",
179 | },
180 | "errorType": "PluginMarketIndexNotFound",
181 | }
182 | `;
183 |
184 | exports[`createGatewayOnEdgeRuntime > Error > should return PluginMetaInvalid error when plugin meta is invalid 1`] = `
185 | {
186 | "body": {
187 | "error": {
188 | "issues": [
189 | {
190 | "code": "invalid_type",
191 | "expected": "string",
192 | "message": "Required",
193 | "path": [
194 | "author",
195 | ],
196 | "received": "undefined",
197 | },
198 | {
199 | "code": "invalid_type",
200 | "expected": "string",
201 | "message": "Required",
202 | "path": [
203 | "createdAt",
204 | ],
205 | "received": "undefined",
206 | },
207 | {
208 | "code": "invalid_type",
209 | "expected": "string",
210 | "message": "Required",
211 | "path": [
212 | "homepage",
213 | ],
214 | "received": "undefined",
215 | },
216 | {
217 | "code": "invalid_type",
218 | "expected": "string",
219 | "message": "Required",
220 | "path": [
221 | "manifest",
222 | ],
223 | "received": "undefined",
224 | },
225 | {
226 | "code": "invalid_type",
227 | "expected": "object",
228 | "message": "Required",
229 | "path": [
230 | "meta",
231 | ],
232 | "received": "undefined",
233 | },
234 | {
235 | "code": "invalid_type",
236 | "expected": "number",
237 | "message": "Required",
238 | "path": [
239 | "schemaVersion",
240 | ],
241 | "received": "undefined",
242 | },
243 | ],
244 | "name": "ZodError",
245 | },
246 | "message": "[plugin] plugin meta is invalid",
247 | "pluginMeta": {
248 | "identifier": "test-plugin",
249 | "invalidMeta": true,
250 | },
251 | },
252 | "errorType": "PluginMetaInvalid",
253 | }
254 | `;
255 |
256 | exports[`createGatewayOnEdgeRuntime > Error > should return PluginSettingsInvalid error when provided empty settings 1`] = `
257 | {
258 | "body": {
259 | "error": [
260 | {
261 | "error": "Instance does not have required property \\"abc\\".",
262 | "instanceLocation": "#",
263 | "keyword": "required",
264 | "keywordLocation": "#/required",
265 | },
266 | ],
267 | "message": "[plugin] your settings is invalid with plugin manifest setting schema",
268 | },
269 | "errorType": "PluginSettingsInvalid",
270 | }
271 | `;
272 |
273 | exports[`createGatewayOnEdgeRuntime > Error > should return PluginSettingsInvalid error when provided settings are invalid 1`] = `
274 | {
275 | "body": {
276 | "error": [
277 | {
278 | "error": "Instance does not have required property \\"abc\\".",
279 | "instanceLocation": "#",
280 | "keyword": "required",
281 | "keywordLocation": "#/required",
282 | },
283 | ],
284 | "message": "[plugin] your settings is invalid with plugin manifest setting schema",
285 | "settings": {
286 | "invalid": "settings",
287 | },
288 | },
289 | "errorType": "PluginSettingsInvalid",
290 | }
291 | `;
292 |
293 | exports[`createGatewayOnEdgeRuntime > OpenAPI Auth > should return PluginGatewayError when not return status 1`] = `
294 | {
295 | "body": {
296 | "apiName": "test-api",
297 | "authorizations": {},
298 | "message": "[plugin] there are problem with sending openapi request, please contact with LobeHub Team",
299 | "openapi": "https://test-openapi-url.com",
300 | "parameters": {},
301 | },
302 | "errorType": "PluginGatewayError",
303 | }
304 | `;
305 |
306 | exports[`createGatewayOnEdgeRuntime > OpenAPI Auth > should return PluginServerError when OpenAPI request returns non-200 status 1`] = `
307 | {
308 | "body": {
309 | "error": {
310 | "status": 400,
311 | "text": "Bad Request",
312 | },
313 | },
314 | "errorType": "PluginServerError",
315 | }
316 | `;
317 |
318 | exports[`createGatewayOnEdgeRuntime > OpenAPI Auth > should return PluginSettingsInvalid when OpenAPI request fails with 401 1`] = `
319 | {
320 | "errorType": "PluginSettingsInvalid",
321 | }
322 | `;
323 |
324 | exports[`createGatewayOnEdgeRuntime > OpenAPI Error > should return PluginOpenApiInitError error when openapi client init fails 1`] = `
325 | {
326 | "body": {
327 | "error": {},
328 | "message": "[plugin] openapi client init error",
329 | "openapi": "https://test-openapi-url.com",
330 | },
331 | "errorType": "PluginOpenApiInitError",
332 | }
333 | `;
334 |
335 | exports[`createGatewayOnEdgeRuntime > OpenAPI Error > should return error response when OpenAPI request fails 1`] = `
336 | {
337 | "body": {
338 | "error": {
339 | "status": 500,
340 | "text": "Internal Server Error",
341 | },
342 | },
343 | "errorType": "PluginServerError",
344 | }
345 | `;
346 |
--------------------------------------------------------------------------------
/tests/edge.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | LobeChatPluginManifest,
3 | PluginErrorType,
4 | PluginRequestPayload,
5 | createHeadersWithPluginSettings,
6 | } from '@lobehub/chat-plugin-sdk';
7 | import { LOBE_PLUGIN_SETTINGS } from '@lobehub/chat-plugin-sdk/lib/request';
8 | import { createGatewayOnEdgeRuntime } from '@lobehub/chat-plugins-gateway';
9 | // @ts-ignore
10 | import SwaggerClient from 'swagger-client';
11 | import { Mock, beforeEach, describe, expect, it, vi } from 'vitest';
12 |
13 | vi.stubGlobal('fetch', vi.fn());
14 |
15 | vi.mock('swagger-client', () => ({
16 | default: vi.fn(),
17 | }));
18 |
19 | // 模拟响应数据
20 | const mockMarketIndex = {
21 | plugins: [
22 | {
23 | identifier: 'test-plugin',
24 | manifest: 'https://test-plugin-url.com/manifest.json',
25 | },
26 | ],
27 | schemaVersion: 1,
28 | };
29 |
30 | const mockManifest = {
31 | api: [
32 | {
33 | description: '',
34 | name: 'test-api',
35 | parameters: { properties: {}, type: 'object' },
36 | url: 'https://test-api-url.com',
37 | },
38 | ],
39 | identifier: 'abc',
40 | meta: {},
41 | } as LobeChatPluginManifest;
42 |
43 | const mockManifestWithOpenAPI = {
44 | ...mockManifest,
45 | openapi: 'https://test-openapi-url.com',
46 | };
47 |
48 | const mockPluginRequestPayload: PluginRequestPayload = {
49 | apiName: 'test-api',
50 | arguments: '{}',
51 | identifier: 'test-plugin',
52 | indexUrl: 'https://test-market-index-url.com',
53 | manifest: mockManifest,
54 | };
55 |
56 | const mockRequest: Request = new Request('https://test-url.com', {
57 | body: JSON.stringify(mockPluginRequestPayload),
58 | method: 'POST',
59 | });
60 |
61 | let gateway: ReturnType;
62 |
63 | beforeEach(() => {
64 | // Reset the mocked fetch before each test
65 | vi.resetAllMocks();
66 | gateway = createGatewayOnEdgeRuntime();
67 | });
68 |
69 | describe('createGatewayOnEdgeRuntime', () => {
70 | it('should execute successfully when provided with correct payload and settings', async () => {
71 | (fetch as Mock).mockImplementation(async (url) => {
72 | if (url === 'https://test-market-index-url.com')
73 | return {
74 | json: async () => mockMarketIndex,
75 | ok: true,
76 | };
77 |
78 | return new Response(JSON.stringify({ success: true }), { status: 200 });
79 | });
80 |
81 | const response = await gateway(mockRequest);
82 | const data = await response.json();
83 | expect(response.status).toBe(200);
84 | expect(data).toEqual({ success: true });
85 | });
86 |
87 | it('should return correct response when OpenAPI request is successful', async () => {
88 | const mockSettings = {
89 | apiKey: 'mock-api-key',
90 | };
91 |
92 | vi.mocked(SwaggerClient).mockResolvedValue({
93 | execute: vi.fn().mockResolvedValue({
94 | status: 200,
95 | text: JSON.stringify({ success: true }),
96 | }),
97 | });
98 |
99 | const mockRequest: Request = new Request('https://test-url.com', {
100 | body: JSON.stringify({ ...mockPluginRequestPayload, manifest: mockManifestWithOpenAPI }),
101 | headers: createHeadersWithPluginSettings(mockSettings),
102 | method: 'POST',
103 | });
104 |
105 | const response = await gateway(mockRequest);
106 | const data = await response.json();
107 | expect(response.status).toBe(200);
108 | expect(data).toEqual({ success: true });
109 | });
110 |
111 | describe('with defaultPluginSettings', () => {
112 | it('should execute successfully when provided with defaultPluginSettings payload', async () => {
113 | (fetch as Mock).mockImplementation(async (url, { headers }) => {
114 | if (url === 'https://test-market-index-url.com')
115 | return {
116 | json: async () => mockMarketIndex,
117 | ok: true,
118 | };
119 |
120 | return new Response(JSON.stringify({ headers }), { status: 200 });
121 | });
122 |
123 | const config = { abc: '123' };
124 |
125 | gateway = createGatewayOnEdgeRuntime({ defaultPluginSettings: { 'test-plugin': config } });
126 |
127 | const request: Request = new Request('https://test-url.com', {
128 | body: JSON.stringify(mockPluginRequestPayload),
129 | method: 'POST',
130 | });
131 |
132 | const response = await gateway(request);
133 | const data = await response.json();
134 | expect(response.status).toBe(200);
135 | expect(data).toEqual({
136 | headers: { [LOBE_PLUGIN_SETTINGS]: JSON.stringify(config) },
137 | });
138 | });
139 | });
140 |
141 | describe('Error', () => {
142 | it('only allow post method', async () => {
143 | const invalidPayload = { ...mockPluginRequestPayload, identifier: null }; // Invalid payload
144 | const mockRequest: Request = new Request('https://test-url.com', {
145 | body: JSON.stringify(invalidPayload),
146 | method: 'PUT',
147 | });
148 | const response = await gateway(mockRequest);
149 | expect(response.status).toBe(PluginErrorType.MethodNotAllowed);
150 | });
151 | it('should return BadRequest error when payload is invalid', async () => {
152 | const invalidPayload = { ...mockPluginRequestPayload, identifier: null }; // Invalid payload
153 | const mockRequest: Request = new Request('https://test-url.com', {
154 | body: JSON.stringify(invalidPayload),
155 | method: 'POST',
156 | });
157 | const response = await gateway(mockRequest);
158 | expect(response.status).toBe(PluginErrorType.BadRequest);
159 | });
160 |
161 | it('should return PluginMarketIndexNotFound error when market index is unreachable', async () => {
162 | (fetch as Mock).mockRejectedValue(new Error('Network error'));
163 | const mockRequest: Request = new Request('https://test-url.com', {
164 | body: JSON.stringify({
165 | ...mockPluginRequestPayload,
166 | indexUrl: undefined,
167 | manifest: undefined,
168 | }),
169 | method: 'POST',
170 | });
171 | const response = await gateway(mockRequest);
172 | expect(response.status).toBe(590);
173 | expect(await response.json()).toMatchSnapshot();
174 | });
175 |
176 | it('should return PluginMarketIndexInvalid error when market index is invalid', async () => {
177 | (fetch as Mock).mockResolvedValueOnce({
178 | json: async () => ({ invalid: 'index' }),
179 | ok: true, // Invalid market index
180 | });
181 | const mockRequest: Request = new Request('https://test-url.com', {
182 | body: JSON.stringify({ ...mockPluginRequestPayload, manifest: undefined }),
183 | method: 'POST',
184 | });
185 | const response = await gateway(mockRequest);
186 | expect(response.status).toBe(490);
187 | expect(await response.json()).toMatchSnapshot();
188 | });
189 |
190 | it('should return PluginMetaNotFound error when plugin meta does not exist', async () => {
191 | (fetch as Mock).mockResolvedValueOnce({
192 | json: async () => mockMarketIndex,
193 | ok: true,
194 | });
195 | const invalidPayload = {
196 | ...mockPluginRequestPayload,
197 | identifier: 'unknown-plugin',
198 | manifest: undefined,
199 | };
200 | const mockRequest: Request = new Request('https://test-url.com', {
201 | body: JSON.stringify(invalidPayload),
202 | method: 'POST',
203 | });
204 |
205 | const response = await gateway(mockRequest);
206 |
207 | expect(response.status).toEqual(404);
208 | expect(await response.json()).toEqual({
209 | body: {
210 | identifier: 'unknown-plugin',
211 | message:
212 | "[gateway] plugin 'unknown-plugin' is not found,please check the plugin list in https://test-market-index-url.com, or create an issue to [lobe-chat-plugins](https://github.com/lobehub/lobe-chat-plugins/issues)",
213 | },
214 | errorType: 'PluginMetaNotFound',
215 | });
216 | });
217 |
218 | it('should return PluginMetaInvalid error when plugin meta is invalid', async () => {
219 | (fetch as Mock).mockResolvedValueOnce({
220 | json: async () => ({
221 | ...mockMarketIndex,
222 | plugins: [{ identifier: 'test-plugin', invalidMeta: true }],
223 | }),
224 | ok: true,
225 | });
226 | const mockRequest: Request = new Request('https://test-url.com', {
227 | body: JSON.stringify({ ...mockPluginRequestPayload, manifest: undefined }),
228 | method: 'POST',
229 | });
230 | const response = await gateway(mockRequest);
231 | expect(response.status).toBe(490);
232 | expect(await response.json()).toMatchSnapshot();
233 | });
234 |
235 | it('should return PluginManifestNotFound error when plugin manifest is unreachable', async () => {
236 | (fetch as Mock).mockImplementation(async (url) => {
237 | if (url === mockPluginRequestPayload.indexUrl)
238 | return {
239 | json: async () => ({
240 | ...mockMarketIndex,
241 | plugins: [
242 | {
243 | author: 'test-plugin',
244 | createdAt: '2023-08-12',
245 | homepage: 'https://github.com/lobehub/chat-plugin-real-time-weather',
246 | identifier: 'test-plugin',
247 | manifest: 'https://test-plugin-url.com/manifest.json',
248 | meta: {
249 | avatar: '☂️',
250 | tags: ['weather', 'realtime'],
251 | title: 'realtimeWeather',
252 | },
253 | schemaVersion: 1,
254 | },
255 | ],
256 | }),
257 | ok: true,
258 | };
259 |
260 | if (url === 'https://test-plugin-url.com/manifest.json')
261 | return {
262 | json: async () => {
263 | throw new Error('Network error');
264 | },
265 | ok: true,
266 | };
267 |
268 | throw new Error('Network error');
269 | });
270 |
271 | const mockRequest: Request = new Request('https://test-url.com', {
272 | body: JSON.stringify({ ...mockPluginRequestPayload, manifest: undefined }),
273 | method: 'POST',
274 | });
275 | const response = await gateway(mockRequest);
276 |
277 | expect(response.status).toBe(404);
278 | expect(await response.json()).toMatchSnapshot();
279 | });
280 |
281 | it('should return PluginManifestInvalid error when plugin manifest is invalid', async () => {
282 | (fetch as Mock).mockImplementation(async (url) => {
283 | if (url === mockPluginRequestPayload.indexUrl)
284 | return {
285 | json: async () => ({
286 | ...mockMarketIndex,
287 | plugins: [
288 | {
289 | author: 'test-plugin',
290 | createdAt: '2023-08-12',
291 | homepage: 'https://github.com/lobehub/chat-plugin-real-time-weather',
292 | identifier: 'test-plugin',
293 | manifest: 'https://test-plugin-url.com/manifest.json',
294 | meta: {
295 | avatar: '☂️',
296 | tags: ['weather', 'realtime'],
297 | title: 'realtimeWeather',
298 | },
299 | schemaVersion: 1,
300 | },
301 | ],
302 | }),
303 | ok: true,
304 | };
305 |
306 | if (url === 'https://test-plugin-url.com/manifest.json')
307 | return {
308 | json: async () => ({ invalid: 'manifest' }),
309 | ok: true,
310 | };
311 |
312 | throw new Error('Network error');
313 | });
314 |
315 | const mockRequest: Request = new Request('https://test-url.com', {
316 | body: JSON.stringify({ ...mockPluginRequestPayload, manifest: undefined }),
317 | method: 'POST',
318 | });
319 | const response = await gateway(mockRequest);
320 | expect(response.status).toBe(490);
321 | expect(await response.json()).toMatchSnapshot();
322 | });
323 |
324 | it('should return PluginSettingsInvalid error when provided settings are invalid', async () => {
325 | const settings = { invalid: 'settings' };
326 |
327 | const payload = {
328 | ...mockPluginRequestPayload,
329 | manifest: {
330 | ...mockManifest,
331 | settings: { properties: { abc: { type: 'string' } }, required: ['abc'], type: 'object' },
332 | } as LobeChatPluginManifest,
333 | };
334 | const mockRequest: Request = new Request('https://test-url.com', {
335 | body: JSON.stringify(payload),
336 | headers: createHeadersWithPluginSettings(settings),
337 | method: 'POST',
338 | });
339 |
340 | const response = await gateway(mockRequest);
341 | expect(response.status).toBe(422);
342 | expect(await response.json()).toMatchSnapshot();
343 | });
344 |
345 | it('should return PluginSettingsInvalid error when provided empty settings', async () => {
346 | const payload = {
347 | ...mockPluginRequestPayload,
348 | manifest: {
349 | ...mockManifest,
350 | settings: { properties: { abc: { type: 'string' } }, required: ['abc'], type: 'object' },
351 | } as LobeChatPluginManifest,
352 | };
353 |
354 | const mockRequest: Request = new Request('https://test-url.com', {
355 | body: JSON.stringify(payload),
356 | method: 'POST',
357 | });
358 | const response = await gateway(mockRequest);
359 | expect(response.status).toBe(422);
360 | expect(await response.json()).toMatchSnapshot();
361 | });
362 |
363 | it('should return PluginApiNotFound error when apiName is not found in manifest', async () => {
364 | const payload = { ...mockPluginRequestPayload, apiName: 'unknown-api' };
365 | const mockRequest: Request = new Request('https://test-url.com', {
366 | body: JSON.stringify(payload),
367 | method: 'POST',
368 | });
369 | const response = await gateway(mockRequest);
370 | expect(response.status).toBe(404);
371 | expect(await response.json()).toMatchSnapshot();
372 | });
373 |
374 | it('should return PluginApiParamsError when api parameters are invalid', async () => {
375 | const payload: PluginRequestPayload = {
376 | ...mockPluginRequestPayload,
377 | // Invalid parameters
378 | arguments: '{"invalid": "params"}',
379 | manifest: {
380 | ...mockManifest,
381 | api: [
382 | {
383 | description: '',
384 | name: 'test-api',
385 | parameters: {
386 | properties: { a: { type: 'string' } },
387 | required: ['a'],
388 | type: 'object',
389 | },
390 | url: 'https://test-api-url.com',
391 | },
392 | ],
393 | } as LobeChatPluginManifest,
394 | };
395 | const mockRequest: Request = new Request('https://test-url.com', {
396 | body: JSON.stringify(payload),
397 | method: 'POST',
398 | });
399 | const response = await gateway(mockRequest);
400 | expect(response.status).toBe(422);
401 | expect(await response.json()).toMatchSnapshot();
402 | });
403 |
404 | it('should return PluginApiParamsError when api url is missing', async () => {
405 | const manifestWithoutUrl = {
406 | ...mockManifest,
407 | api: [{ ...mockManifest.api[0], url: undefined }],
408 | };
409 | const mockRequest: Request = new Request('https://test-url.com', {
410 | body: JSON.stringify({ ...mockPluginRequestPayload, manifest: manifestWithoutUrl }),
411 | method: 'POST',
412 | });
413 |
414 | const response = await gateway(mockRequest);
415 | expect(response.status).toBe(422);
416 | expect(await response.json()).toMatchSnapshot();
417 | });
418 |
419 | it('should return correct response when API request is successful', async () => {
420 | (fetch as Mock).mockResolvedValueOnce(
421 | new Response(JSON.stringify({ success: true }), { status: 200 }),
422 | );
423 |
424 | const mockRequest: Request = new Request('https://test-url.com', {
425 | body: JSON.stringify(mockPluginRequestPayload),
426 | method: 'POST',
427 | });
428 |
429 | const response = await gateway(mockRequest);
430 | const data = await response.json();
431 | expect(response.status).toBe(200);
432 | expect(data).toEqual({ success: true });
433 | });
434 |
435 | it('should return error response when API request fails', async () => {
436 | (fetch as Mock).mockResolvedValueOnce(new Response('Internal Server Error', { status: 500 }));
437 |
438 | const mockRequest: Request = new Request('https://test-url.com', {
439 | body: JSON.stringify(mockPluginRequestPayload),
440 | method: 'POST',
441 | });
442 |
443 | const response = await gateway(mockRequest);
444 | expect(response.status).toBe(500);
445 | expect(await response.text()).toBe(
446 | JSON.stringify({ body: 'Internal Server Error', errorType: 500 }),
447 | );
448 | });
449 | });
450 |
451 | describe('OpenAPI Error', () => {
452 | const mockSettings = {
453 | apiKey: 'mock-api-key',
454 | };
455 |
456 | it('should return PluginOpenApiInitError error when openapi client init fails', async () => {
457 | vi.mocked(SwaggerClient).mockImplementationOnce(() => {
458 | throw new Error('Initialization failed');
459 | });
460 |
461 | const mockRequest: Request = new Request('https://test-url.com', {
462 | body: JSON.stringify({ ...mockPluginRequestPayload, manifest: mockManifestWithOpenAPI }),
463 | headers: createHeadersWithPluginSettings(mockSettings),
464 | method: 'POST',
465 | });
466 |
467 | const response = await gateway(mockRequest);
468 | expect(response.status).toBe(500);
469 | expect(await response.json()).toMatchSnapshot();
470 | });
471 |
472 | it('should return error response when OpenAPI request fails', async () => {
473 | vi.mocked(SwaggerClient).mockResolvedValue({
474 | execute: vi.fn().mockRejectedValueOnce({
475 | status: 500,
476 | text: 'Internal Server Error',
477 | }),
478 | });
479 |
480 | const mockRequest: Request = new Request('https://test-url.com', {
481 | body: JSON.stringify({ ...mockPluginRequestPayload, manifest: mockManifestWithOpenAPI }),
482 | headers: createHeadersWithPluginSettings(mockSettings),
483 | method: 'POST',
484 | });
485 |
486 | const response = await gateway(mockRequest);
487 | expect(response.status).toBe(500);
488 | expect(await response.json()).toMatchSnapshot();
489 | });
490 | });
491 |
492 | describe('OpenAPI Auth', () => {
493 | it.skip('should handle authorization correctly for basicAuth', async () => {
494 | const settingsWithBasicAuth = {
495 | basic_auth_password: 'testpass',
496 | basic_auth_username: 'testuser',
497 | };
498 | const mockSwaggerExecute = vi.fn().mockResolvedValue({
499 | status: 200,
500 | text: JSON.stringify({ success: true }),
501 | });
502 | vi.mocked(SwaggerClient).mockResolvedValue({
503 | execute: mockSwaggerExecute,
504 | });
505 | const PasswordAuthorization = vi
506 | .fn()
507 | .mockImplementation((u: string, p: string) => [u, p].join('-'));
508 |
509 | SwaggerClient['PasswordAuthorization'] = PasswordAuthorization;
510 |
511 | const mockRequest: Request = new Request('https://test-url.com', {
512 | body: JSON.stringify({ ...mockPluginRequestPayload, manifest: mockManifestWithOpenAPI }),
513 | headers: createHeadersWithPluginSettings(settingsWithBasicAuth),
514 | method: 'POST',
515 | });
516 |
517 | await gateway(mockRequest);
518 |
519 | expect(SwaggerClient).toHaveBeenCalledWith(
520 | expect.objectContaining({
521 | authorizations: expect.objectContaining({
522 | basicAuth: expect.anything(),
523 | }),
524 | url: 'https://test-openapi-url.com',
525 | }),
526 | );
527 | expect(mockSwaggerExecute).toHaveBeenCalled();
528 | });
529 |
530 | it.skip('should handle authorization correctly for OAuth2', async () => {
531 | const settingsWithOAuth2 = {
532 | oauth2_accessToken: 'testtoken',
533 | oauth2_clientId: 'testclient',
534 | oauth2_clientSecret: 'testsecret',
535 | };
536 | const mockSwaggerExecute = vi.fn().mockResolvedValue({
537 | status: 200,
538 | text: JSON.stringify({ success: true }),
539 | });
540 | vi.mocked(SwaggerClient).mockResolvedValue({
541 | execute: mockSwaggerExecute,
542 | });
543 |
544 | const mockRequest: Request = new Request('https://test-url.com', {
545 | body: JSON.stringify({ ...mockPluginRequestPayload, manifest: mockManifestWithOpenAPI }),
546 | headers: createHeadersWithPluginSettings(settingsWithOAuth2),
547 | method: 'POST',
548 | });
549 |
550 | await gateway(mockRequest);
551 |
552 | expect(SwaggerClient).toHaveBeenCalledWith({
553 | authorizations: expect.objectContaining({
554 | oauth2: expect.objectContaining({
555 | accessToken: 'testtoken',
556 | clientId: 'testclient',
557 | clientSecret: 'testsecret',
558 | }),
559 | }),
560 | url: 'https://test-openapi-url.com',
561 | });
562 | expect(mockSwaggerExecute).toHaveBeenCalled();
563 | });
564 |
565 | it('should return PluginServerError when OpenAPI request returns non-200 status', async () => {
566 | const mockSwaggerExecute = vi.fn().mockRejectedValue({
567 | status: 400,
568 | text: 'Bad Request',
569 | });
570 | vi.mocked(SwaggerClient).mockResolvedValue({
571 | execute: mockSwaggerExecute,
572 | });
573 |
574 | const mockRequest: Request = new Request('https://test-url.com', {
575 | body: JSON.stringify({
576 | ...mockPluginRequestPayload,
577 | manifest: mockManifestWithOpenAPI,
578 | }),
579 | headers: createHeadersWithPluginSettings({}),
580 | method: 'POST',
581 | });
582 |
583 | const response = await gateway(mockRequest);
584 |
585 | expect(response.status).toBe(500);
586 | expect(await response.json()).toMatchSnapshot();
587 | });
588 |
589 | it('should return PluginGatewayError when not return status', async () => {
590 | const mockSwaggerExecute = vi.fn().mockRejectedValue({
591 | text: 'Bad Request',
592 | });
593 | vi.mocked(SwaggerClient).mockResolvedValue({
594 | execute: mockSwaggerExecute,
595 | });
596 | const mockRequest: Request = new Request('https://test-url.com', {
597 | body: JSON.stringify({
598 | ...mockPluginRequestPayload,
599 | arguments: undefined,
600 | manifest: mockManifestWithOpenAPI,
601 | }),
602 | headers: createHeadersWithPluginSettings({}),
603 | method: 'POST',
604 | });
605 |
606 | const response = await gateway(mockRequest);
607 |
608 | expect(response.status).toBe(500);
609 | expect(await response.json()).toMatchSnapshot();
610 | });
611 |
612 | it('should return PluginSettingsInvalid when OpenAPI request fails with 401', async () => {
613 | const mockSwaggerExecute = vi.fn().mockRejectedValue({
614 | status: 401,
615 | text: 'Unauthorized',
616 | });
617 | vi.mocked(SwaggerClient).mockResolvedValue({
618 | execute: mockSwaggerExecute,
619 | });
620 |
621 | const mockRequest: Request = new Request('https://test-url.com', {
622 | body: JSON.stringify({ ...mockPluginRequestPayload, manifest: mockManifestWithOpenAPI }),
623 | headers: createHeadersWithPluginSettings({}),
624 | method: 'POST',
625 | });
626 | const response = await gateway(mockRequest);
627 | expect(response.status).toBe(422);
628 | expect(await response.json()).toMatchSnapshot();
629 | });
630 | });
631 | });
632 |
--------------------------------------------------------------------------------
/tests/gateway.test.ts:
--------------------------------------------------------------------------------
1 | import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
2 | import { Gateway } from '@lobehub/chat-plugins-gateway';
3 | import Ajv from 'ajv';
4 | // @ts-ignore
5 | import SwaggerClient from 'swagger-client';
6 | import { describe, expect, it, vi } from 'vitest';
7 |
8 | vi.mock('swagger-client', () => ({
9 | default: vi.fn(),
10 | }));
11 |
12 | describe('Gateway', () => {
13 | it('should init with pluginIndexUrl', () => {
14 | const gateway = new Gateway({ pluginsIndexUrl: 'https://test-market-index-url.com' });
15 |
16 | expect(gateway['pluginIndexUrl']).toBe('https://test-market-index-url.com');
17 | });
18 |
19 | describe('using AJV Validator', () => {
20 | const gateway = new Gateway({
21 | Validator: (schema, value) => {
22 | const ajv = new Ajv({ strict: false });
23 | const validate = ajv.compile(schema);
24 |
25 | const valid = validate(value);
26 | return {
27 | errors: validate.errors,
28 | valid,
29 | };
30 | },
31 | });
32 |
33 | it('run with ajv validator', async () => {
34 | const mockResult = JSON.stringify({ success: true });
35 | vi.mocked(SwaggerClient).mockResolvedValue({
36 | execute: vi.fn().mockResolvedValue({
37 | status: 200,
38 | text: mockResult,
39 | }),
40 | });
41 |
42 | const payload = {
43 | apiName: 'getSupportedVendors',
44 | arguments: '{}',
45 | identifier: 'mock-credit-card',
46 | manifest: {
47 | $schema: '../node_modules/@lobehub/chat-plugin-sdk/schema.json',
48 | api: [
49 | {
50 | description: 'It is API to get list of supported vendors',
51 | name: 'getSupportedVendors',
52 | parameters: {
53 | properties: {},
54 | type: 'object',
55 | },
56 | },
57 | ],
58 | author: 'arvinxx',
59 | createdAt: '2023-12-11',
60 | identifier: 'mock-credit-card',
61 | meta: {
62 | avatar: '💳',
63 | description: 'Credit Card Number Generator',
64 | tags: ['credit-card', 'mockup', 'generator'],
65 | title: 'Credit Card Generator',
66 | },
67 | openapi:
68 | 'https://lobe-plugin-mock-credit-card-arvinxx.vercel.app/credit-card-number-generator.json',
69 | settings: {
70 | properties: {
71 | apiKeyAuth: {
72 | description: 'apiKeyAuth API Key',
73 | format: 'password',
74 | title: 'X-OpenAPIHub-Key',
75 | type: 'string',
76 | },
77 | },
78 | required: ['apiKeyAuth'],
79 | type: 'object',
80 | },
81 | version: '1',
82 | },
83 | type: 'default',
84 | } as PluginRequestPayload;
85 |
86 | const res = await gateway.execute(payload, { apiKeyAuth: 'abc' });
87 |
88 | expect(res.success).toBeTruthy();
89 | expect(res.data).toEqual(mockResult);
90 | });
91 |
92 | it('run with ajv with $$refs', async () => {
93 | const mockResult = JSON.stringify({ success: true });
94 | vi.mocked(SwaggerClient).mockResolvedValue({
95 | execute: vi.fn().mockResolvedValue({
96 | status: 200,
97 | text: mockResult,
98 | }),
99 | });
100 |
101 | const payload = {
102 | apiName: 'getRandomCardNumber',
103 | arguments: '{"vendor":"visa"}',
104 | identifier: 'mock-credit-card',
105 | manifest: {
106 | $schema: '../node_modules/@lobehub/chat-plugin-sdk/schema.json',
107 | api: [
108 | {
109 | description: 'It is API to generate random credit card numbers',
110 | name: 'getRandomCardNumber',
111 | parameters: {
112 | properties: {
113 | vendor: {
114 | $$ref: 'https://chat-dev.lobehub.com/chat#/components/schemas/Vendor',
115 | enum: [
116 | 'visa',
117 | 'master-card',
118 | 'diners-club',
119 | 'american-express',
120 | 'discover',
121 | 'jcb',
122 | ],
123 | type: 'string',
124 | },
125 | },
126 | required: ['vendor'],
127 | type: 'object',
128 | },
129 | },
130 | {
131 | description: 'It is API to get list of supported vendors',
132 | name: 'getSupportedVendors',
133 | parameters: { properties: {}, type: 'object' },
134 | },
135 | ],
136 | author: 'arvinxx',
137 | createdAt: '2023-12-11',
138 | identifier: 'mock-credit-card',
139 | meta: {
140 | avatar: '💳',
141 | description: 'Credit Card Number Generator',
142 | tags: ['credit-card', 'mockup', 'generator'],
143 | title: 'Credit Card Generator',
144 | },
145 | openapi:
146 | 'https://lobe-plugin-mock-credit-card-arvinxx.vercel.app/credit-card-number-generator.json',
147 | settings: {
148 | properties: {
149 | apiKeyAuth: {
150 | description: 'apiKeyAuth API Key',
151 | format: 'password',
152 | title: 'X-OpenAPIHub-Key',
153 | type: 'string',
154 | },
155 | },
156 | required: ['apiKeyAuth'],
157 | type: 'object',
158 | },
159 | version: '1',
160 | },
161 | type: 'default',
162 | } as PluginRequestPayload;
163 |
164 | const res = await gateway.execute(payload, { apiKeyAuth: 'abc' });
165 |
166 | expect(res.success).toBeTruthy();
167 | expect(res.data).toEqual(mockResult);
168 | });
169 | });
170 | });
171 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "CommonJS",
4 | "target": "ES5",
5 | "lib": ["dom", "esnext"],
6 | "declaration": true,
7 | "sourceMap": true,
8 | "skipDefaultLibCheck": true,
9 | "baseUrl": ".",
10 | "allowSyntheticDefaultImports": true,
11 | "moduleResolution": "node",
12 | "forceConsistentCasingInFileNames": true,
13 | "noImplicitReturns": true,
14 | "noUnusedLocals": true,
15 | "resolveJsonModule": true,
16 | "skipLibCheck": true,
17 | "strict": true,
18 | "paths": {
19 | "@lobehub/chat-plugins-gateway": ["src"]
20 | }
21 | },
22 | "include": ["types", "api", "src", "*.ts", "tests"]
23 | }
24 |
--------------------------------------------------------------------------------
/types/pluginItem.ts:
--------------------------------------------------------------------------------
1 | import { ChatCompletionFunctions } from './schema';
2 |
3 | /**
4 | * 插件项
5 | * @template Result - 结果类型,默认为 any
6 | * @template RunnerParams - 运行参数类型,默认为 any
7 | */
8 | export interface PluginItem {
9 | /**
10 | * 头像
11 | */
12 | avatar: string;
13 | /**
14 | * 名称
15 | */
16 | name: string;
17 | /**
18 | * 运行器
19 | * @param params - 运行参数
20 | * @returns 运行结果的 Promise
21 | */
22 | runner: PluginRunner;
23 | /**
24 | * 聊天完成函数的模式
25 | */
26 | schema: ChatCompletionFunctions;
27 | }
28 |
29 | /**
30 | * 插件运行器
31 | * @template Params - 参数类型,默认为 object
32 | * @template Result - 结果类型,默认为 any
33 | * @param params - 运行参数
34 | * @returns 运行结果的 Promise
35 | */
36 | export type PluginRunner = (params: Params) => Promise;
37 |
--------------------------------------------------------------------------------
/types/plugins.ts:
--------------------------------------------------------------------------------
1 | export interface OpenAIPluginPayload {
2 | arguments: string;
3 | name: string;
4 | }
5 |
--------------------------------------------------------------------------------
/types/schema.ts:
--------------------------------------------------------------------------------
1 | export interface ChatCompletionFunctions {
2 | /**
3 | * The description of what the function does.
4 | * @type {string}
5 | * @memberof ChatCompletionFunctions
6 | */
7 | description?: string;
8 | /**
9 | * The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.
10 | * @type {string}
11 | * @memberof ChatCompletionFunctions
12 | */
13 | name: string;
14 | /**
15 | * The parameters the functions accepts, described as a JSON Schema object. See the [guide](/docs/guides/gpt/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format.
16 | * @type {{ [key: string]: any }}
17 | * @memberof ChatCompletionFunctions
18 | */
19 | parameters?: {
20 | [key: string]: any;
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config';
2 |
3 | export default defineConfig({
4 | test: {
5 | alias: {
6 | '@': 'src',
7 | '@lobehub/chat-plugins-gateway': 'src',
8 | },
9 | coverage: {
10 | provider: 'v8',
11 | reporter: ['text', 'json', 'lcov', 'text-summary'],
12 | },
13 | environment: 'node',
14 | globals: true,
15 | },
16 | });
17 |
--------------------------------------------------------------------------------