@lobehub/lobehub 2.0.5 → 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +1 -0
- package/CHANGELOG.md +50 -0
- package/changelog/v2.json +14 -0
- package/docs/self-hosting/advanced/auth/clerk-to-betterauth.mdx +2 -0
- package/docs/self-hosting/advanced/auth/clerk-to-betterauth.zh-CN.mdx +2 -0
- package/docs/self-hosting/advanced/auth/nextauth-to-betterauth.mdx +2 -0
- package/docs/self-hosting/advanced/auth/nextauth-to-betterauth.zh-CN.mdx +2 -0
- package/docs/self-hosting/platform/docker.mdx +6 -0
- package/docs/self-hosting/platform/docker.zh-CN.mdx +6 -0
- package/docs/self-hosting/platform/vercel.mdx +0 -49
- package/docs/self-hosting/platform/vercel.zh-CN.mdx +0 -47
- package/docs/self-hosting/start.mdx +0 -20
- package/docs/self-hosting/start.zh-CN.mdx +0 -18
- package/package.json +1 -1
- package/packages/database/src/repositories/aiInfra/index.test.ts +52 -0
- package/packages/database/src/repositories/aiInfra/index.ts +103 -0
- package/packages/model-runtime/src/core/streams/protocol.ts +3 -1
- package/src/app/(backend)/api/workflows/memory-user-memory/pipelines/persona/update-writing/route.ts +19 -19
- package/src/app/[variants]/(main)/agent/features/Conversation/AgentWelcome/ToolAuthAlert.tsx +3 -2
- package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +3 -3
- package/src/app/[variants]/onboarding/components/KlavisServerList/hooks/useKlavisOAuth.ts +12 -5
- package/src/components/DebugNode.tsx +21 -0
- package/src/features/ChatInput/ActionBar/Tools/KlavisServerItem.tsx +3 -3
- package/src/features/ChatInput/ActionBar/Tools/ToolItem.tsx +16 -13
- package/src/features/ChatInput/ActionBar/Tools/ToolsList.tsx +51 -40
- package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +7 -1
- package/src/features/ChatInput/ActionBar/components/ActionPopover.tsx +14 -11
- package/src/layout/GlobalProvider/useUserStateRedirect.ts +6 -2
- package/src/libs/observability/traceparent.test.ts +46 -7
- package/src/libs/observability/traceparent.ts +12 -10
- package/src/server/routers/lambda/klavis.ts +38 -10
- package/src/server/services/memory/userMemory/__tests__/extract.runtime.test.ts +181 -26
- package/src/server/services/memory/userMemory/extract.ts +120 -96
- package/src/server/services/memory/userMemory/persona/__tests__/service.test.ts +46 -0
- package/src/server/services/memory/userMemory/persona/service.ts +47 -6
- package/src/store/tool/slices/klavisStore/action.ts +20 -0
- package/docs/self-hosting/server-database.mdx +0 -157
- package/docs/self-hosting/server-database.zh-CN.mdx +0 -146
package/.eslintrc.js
CHANGED
|
@@ -20,6 +20,7 @@ config.rules['unicorn/no-array-for-each'] = 0;
|
|
|
20
20
|
config.rules['unicorn/prefer-number-properties'] = 0;
|
|
21
21
|
config.rules['unicorn/prefer-query-selector'] = 0;
|
|
22
22
|
config.rules['unicorn/no-array-callback-reference'] = 0;
|
|
23
|
+
config.rules['@typescript-eslint/no-use-before-define'] = 0;
|
|
23
24
|
// FIXME: Linting error in src/app/[variants]/(main)/chat/features/Migration/DBReader.ts, the fundamental solution should be upgrading typescript-eslint
|
|
24
25
|
config.rules['@typescript-eslint/no-useless-constructor'] = 0;
|
|
25
26
|
config.rules['@next/next/no-img-element'] = 0;
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 2.0.7](https://github.com/lobehub/lobe-chat/compare/v2.0.6...v2.0.7)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-28**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **model-runtime**: Include tool_calls in speed metrics & add getActiveTraceId.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's fixed
|
|
19
|
+
|
|
20
|
+
- **model-runtime**: Include tool_calls in speed metrics & add getActiveTraceId, closes [#11927](https://github.com/lobehub/lobe-chat/issues/11927) ([b24da44](https://github.com/lobehub/lobe-chat/commit/b24da44))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
### [Version 2.0.6](https://github.com/lobehub/lobe-chat/compare/v2.0.5...v2.0.6)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2026-01-27**</sup>
|
|
33
|
+
|
|
34
|
+
#### 🐛 Bug Fixes
|
|
35
|
+
|
|
36
|
+
- **misc**: The klavis in onboarding connect timeout fixed.
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### What's fixed
|
|
44
|
+
|
|
45
|
+
- **misc**: The klavis in onboarding connect timeout fixed, closes [#11918](https://github.com/lobehub/lobe-chat/issues/11918) ([bc165be](https://github.com/lobehub/lobe-chat/commit/bc165be))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
### [Version 2.0.5](https://github.com/lobehub/lobe-chat/compare/v2.0.4...v2.0.5)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2026-01-27**</sup>
|
package/changelog/v2.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"children": {},
|
|
4
|
+
"date": "2026-01-28",
|
|
5
|
+
"version": "2.0.7"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"children": {
|
|
9
|
+
"fixes": [
|
|
10
|
+
"The klavis in onboarding connect timeout fixed."
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"date": "2026-01-27",
|
|
14
|
+
"version": "2.0.6"
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"children": {
|
|
4
18
|
"fixes": [
|
|
@@ -63,6 +63,8 @@ For small self-hosted deployments, the simplest approach is to let users reset t
|
|
|
63
63
|
|
|
64
64
|
Remove Clerk variables and add Better Auth variables:
|
|
65
65
|
|
|
66
|
+
<GenerateSecret envName="AUTH_SECRET" />
|
|
67
|
+
|
|
66
68
|
```bash
|
|
67
69
|
# Remove these
|
|
68
70
|
# NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=xxx
|
|
@@ -116,6 +116,8 @@ For small self-hosted deployments, the simplest approach is to let users re-logi
|
|
|
116
116
|
|
|
117
117
|
Remove NextAuth variables and add Better Auth variables:
|
|
118
118
|
|
|
119
|
+
<GenerateSecret envName="AUTH_SECRET" />
|
|
120
|
+
|
|
119
121
|
```bash
|
|
120
122
|
# Remove these
|
|
121
123
|
# NEXT_AUTH_SECRET=xxx
|
|
@@ -58,6 +58,12 @@ Here is the process for deploying the LobeHub server database version on a Linux
|
|
|
58
58
|
|
|
59
59
|
### Create a file named `lobe-chat.env` to store environment variables:
|
|
60
60
|
|
|
61
|
+
Click the buttons below to generate required secrets:
|
|
62
|
+
|
|
63
|
+
<GenerateSecret envName="KEY_VAULTS_SECRET" />
|
|
64
|
+
|
|
65
|
+
<GenerateSecret envName="AUTH_SECRET" />
|
|
66
|
+
|
|
61
67
|
```shell
|
|
62
68
|
# Website domain
|
|
63
69
|
APP_URL=https://your-prod-domain.com
|
|
@@ -114,55 +114,6 @@ The server-side database needs to be paired with a user authentication service t
|
|
|
114
114
|
<Callout type={'info'}>
|
|
115
115
|
For advanced features like SSO providers, magic link login, and email verification, see [Authentication Service](/docs/self-hosting/advanced/auth).
|
|
116
116
|
</Callout>
|
|
117
|
-
|
|
118
|
-
### Add Public and Private Key Environment Variables in Vercel
|
|
119
|
-
|
|
120
|
-
In Vercel's deployment environment variables, add the `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` environment variables. You can click on "API Keys" in the menu, then copy the corresponding values and paste them into Vercel's environment variables.
|
|
121
|
-
|
|
122
|
-
<Image alt={'Find the corresponding public and private key environment variables in Clerk'} src={'/blog/assets28616219/89883703-7a1a-4a11-b944-5d804544e57c.webp'} />
|
|
123
|
-
|
|
124
|
-
The environment variables required for this step are as follows:
|
|
125
|
-
|
|
126
|
-
```shell
|
|
127
|
-
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_xxxxxxxxxxx
|
|
128
|
-
CLERK_SECRET_KEY=sk_live_xxxxxxxxxxxxxxxxxxxxxx
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
Add the above variables to Vercel:
|
|
132
|
-
|
|
133
|
-
<Image alt={'Add Clerk public and private key environment variables in Vercel'} src={'/blog/assets28616219/2bfa13df-6e20-4768-97c0-4dad06c85a2f.webp'} />
|
|
134
|
-
|
|
135
|
-
### Create and Configure Webhook in Clerk
|
|
136
|
-
|
|
137
|
-
Since we let Clerk fully handle user authentication and management, we need Clerk to notify our application and store data in the database when there are changes in the user's lifecycle (create, update, delete). We achieve this requirement through the Webhook provided by Clerk.
|
|
138
|
-
|
|
139
|
-
We need to add an endpoint in Clerk's Webhooks to inform Clerk to send notifications to this endpoint when a user's information changes.
|
|
140
|
-
|
|
141
|
-
<Image alt={'Add Webhooks endpoint in Clerk'} src={'/blog/assets28616219/f50f47fb-5e8e-4930-bf4e-8cf6f5b8afb9.webp'} />
|
|
142
|
-
|
|
143
|
-
Fill in the endpoint with the URL of your Vercel project, such as `https://your-project.vercel.app/api/webhooks/clerk`. Then, subscribe to events by checking the three user events (`user.created`, `user.deleted`, `user.updated`), and click create.
|
|
144
|
-
|
|
145
|
-
<Callout type={'warning'}>
|
|
146
|
-
The `https://` in the URL is essential to maintain the integrity of the URL.
|
|
147
|
-
</Callout>
|
|
148
|
-
|
|
149
|
-
<Image alt={'Configure URL and user events when adding Clerk Webhooks'} src={'/blog/assets28616219/0249ea56-ab17-4aa9-a56c-9ebd556c2645.webp'} />
|
|
150
|
-
|
|
151
|
-
### Add Webhook Secret to Vercel Environment Variables
|
|
152
|
-
|
|
153
|
-
After creation, you can find the secret of this Webhook in the bottom right corner:
|
|
154
|
-
|
|
155
|
-
<Image alt={'View Clerk Webhooks secret'} src={'/blog/assets28616219/fab4abb2-584b-49de-9340-813382951635.webp'} />
|
|
156
|
-
|
|
157
|
-
The environment variable corresponding to this secret is `CLERK_WEBHOOK_SECRET`:
|
|
158
|
-
|
|
159
|
-
```shell
|
|
160
|
-
CLERK_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxx
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Add it to Vercel's environment variables:
|
|
164
|
-
|
|
165
|
-
<Image alt={'Add Clerk Webhooks secret in Vercel'} src={'/blog/assets28616219/5fdc9479-007f-46ab-9d6e-a9603e949116.webp'} />
|
|
166
117
|
</Steps>
|
|
167
118
|
|
|
168
119
|
By completing these steps, you have successfully configured the authentication service. Next, we will configure the S3 storage service.
|
|
@@ -114,53 +114,6 @@ tags:
|
|
|
114
114
|
<Callout type={'info'}>
|
|
115
115
|
如需 SSO 登录、魔法链接登录、邮箱验证等高级功能,请参阅 [身份验证服务](/zh/docs/self-hosting/advanced/auth)。
|
|
116
116
|
</Callout>
|
|
117
|
-
|
|
118
|
-
### 在 Vercel 中添加公、私钥环境变量
|
|
119
|
-
|
|
120
|
-
在 Vercel 的部署环境变量中,添加 `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` 和 `CLERK_SECRET_KEY` 环境变量。你可以在菜单中点击「API Keys」,然后复制对应的值填入 Vercel 的环境变量中。
|
|
121
|
-
|
|
122
|
-
<Image alt={'在 Clerk 中找到对应的公私钥环境变量'} src={'/blog/assets28616219/89883703-7a1a-4a11-b944-5d804544e57c.webp'} />
|
|
123
|
-
|
|
124
|
-
此步骤所需的环境变量如下:
|
|
125
|
-
|
|
126
|
-
```shell
|
|
127
|
-
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_xxxxxxxxxxx
|
|
128
|
-
CLERK_SECRET_KEY=sk_live_xxxxxxxxxxxxxxxxxxxxxx
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
添加上述变量到 Vercel 中:
|
|
132
|
-
|
|
133
|
-
<Image alt={'在 Vercel 中添加 Clerk 公私钥环境变量'} src={'/blog/assets28616219/2bfa13df-6e20-4768-97c0-4dad06c85a2f.webp'} />
|
|
134
|
-
|
|
135
|
-
### 在 Clerk 中创建并配置 Webhook
|
|
136
|
-
|
|
137
|
-
由于我们让 Clerk 完全接管用户鉴权与管理,因此我们需要在 Clerk 用户生命周期变更时(创建、更新、删除)中通知我们的应用并存储落库。我们通过 Clerk 提供的 Webhook 来实现这一诉求。
|
|
138
|
-
|
|
139
|
-
我们需要在 Clerk 的 Webhooks 中添加一个端点(Endpoint),告诉 Clerk 当用户发生变更时,向这个端点发送通知。
|
|
140
|
-
|
|
141
|
-
<Image alt={'Clerk 添加 Webhooks 端点'} src={'/blog/assets28616219/f50f47fb-5e8e-4930-bf4e-8cf6f5b8afb9.webp'} />
|
|
142
|
-
|
|
143
|
-
在 endpoint 中填写你的 Vercel 项目的 URL,如 `https://your-project.vercel.app/api/webhooks/clerk`。然后在订阅事件(Subscribe to events)中,勾选 user 的三个事件(`user.created` 、`user.deleted`、`user.updated`),然后点击创建。
|
|
144
|
-
|
|
145
|
-
<Callout type={'warning'}>URL 的`https://`不可缺失,须保持 URL 的完整性</Callout>
|
|
146
|
-
|
|
147
|
-
<Image alt={'添加 Clerk Webhooks 时,配置 URL 和用户事件'} src={'/blog/assets28616219/0249ea56-ab17-4aa9-a56c-9ebd556c2645.webp'} />
|
|
148
|
-
|
|
149
|
-
### 将 Webhook 秘钥添加到 Vercel 环境变量
|
|
150
|
-
|
|
151
|
-
创建完毕后,可以在右下角找到该 Webhook 的秘钥:
|
|
152
|
-
|
|
153
|
-
<Image alt={'查看 Clerk Webhooks 秘钥'} src={'/blog/assets28616219/fab4abb2-584b-49de-9340-813382951635.webp'} />
|
|
154
|
-
|
|
155
|
-
这个秘钥所对应的环境变量名为 `CLERK_WEBHOOK_SECRET`:
|
|
156
|
-
|
|
157
|
-
```shell
|
|
158
|
-
CLERK_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxx
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
将其添加到 Vercel 的环境变量中:
|
|
162
|
-
|
|
163
|
-
<Image alt={'在 Vercel 中 添加 Clerk Webhooks 秘钥'} src={'/blog/assets28616219/5fdc9479-007f-46ab-9d6e-a9603e949116.webp'} />
|
|
164
117
|
</Steps>
|
|
165
118
|
|
|
166
119
|
这样,你已经成功配置了身份验证服务。接下来我们将配置 S3 存储服务。
|
|
@@ -16,24 +16,4 @@ tags:
|
|
|
16
16
|
|
|
17
17
|
LobeHub supports various deployment platforms, including Vercel, Docker, and Docker Compose. You can choose a deployment platform that suits you to build your own Lobe Chat.
|
|
18
18
|
|
|
19
|
-
## Quick Deployment
|
|
20
|
-
|
|
21
|
-
For users who are new to LobeHub, we recommend using the client-side database mode for quick deployment. The advantage of this mode is that deployment can be quickly completed with just one command/button, making it easy for you to quickly get started and experience LobeHub.
|
|
22
|
-
|
|
23
|
-
You can follow the guide below for quick deployment of LobeHub:
|
|
24
|
-
|
|
25
19
|
<PlatformCards urlPrefix={'platform'} />
|
|
26
|
-
|
|
27
|
-
<Callout>
|
|
28
|
-
In the client-side database mode, data is stored locally on the user's device, without
|
|
29
|
-
cross-device synchronization, and does not support advanced features such as file uploads and
|
|
30
|
-
knowledge base.
|
|
31
|
-
</Callout>
|
|
32
|
-
|
|
33
|
-
## Advanced Mode: Server-Side Database
|
|
34
|
-
|
|
35
|
-
For users who are already familiar with LobeHub or need cross-device synchronization, you can deploy a version with a server-side database to access a more complete and powerful LobeHub.
|
|
36
|
-
|
|
37
|
-
<Cards>
|
|
38
|
-
<Card href={'/docs/self-hosting/server-database'} title={'Server-Side Database Deployment Guide'} />
|
|
39
|
-
</Cards>
|
|
@@ -20,22 +20,4 @@ tags:
|
|
|
20
20
|
|
|
21
21
|
LobeHub 支持多种部署平台,包括 Vercel、Docker、 Docker Compose 、阿里云计算巢 和腾讯轻量云 等,你可以选择适合自己的部署平台进行部署,构建属于自己的 Lobe Chat。
|
|
22
22
|
|
|
23
|
-
## 快速部署
|
|
24
|
-
|
|
25
|
-
对于第一次了解 LobeHub 的用户,我们推荐使用客户端数据库的模式快速部署,该模式的优势是一行指令 / 一个按钮即可快捷完成部署,便于你快速上手与体验 LobeHub。
|
|
26
|
-
|
|
27
|
-
你可以通过以下指南快速部署 LobeHub:
|
|
28
|
-
|
|
29
23
|
<PlatformCards urlPrefix={'platform'} />
|
|
30
|
-
|
|
31
|
-
<Callout>
|
|
32
|
-
客户端数据库模式下数据均保留在用户本地,不会跨多端同步,也不支持文件上传、知识库等进阶功能。
|
|
33
|
-
</Callout>
|
|
34
|
-
|
|
35
|
-
## 进阶模式:服务端数据库
|
|
36
|
-
|
|
37
|
-
针对已经了解 LobeHub 的用户,或需要多端同步的用户,可以自行部署带有服务端数据库的版本,进而获得更完整、功能更强大的 LobeHub。
|
|
38
|
-
|
|
39
|
-
<Cards rows={1}>
|
|
40
|
-
<Card href={'/zh/docs/self-hosting/server-database'} title={'服务端数据库部署指南'} />
|
|
41
|
-
</Cards>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
AiProviderDetailItem,
|
|
3
3
|
AiProviderListItem,
|
|
4
4
|
AiProviderRuntimeConfig,
|
|
5
|
+
AiProviderRuntimeState,
|
|
5
6
|
EnabledProvider,
|
|
6
7
|
} from '@lobechat/types';
|
|
7
8
|
import { AiProviderModelListItem, EnabledAiModel, ExtendParamsType } from 'model-bank';
|
|
@@ -1774,4 +1775,55 @@ describe('AiInfraRepos', () => {
|
|
|
1774
1775
|
});
|
|
1775
1776
|
});
|
|
1776
1777
|
});
|
|
1778
|
+
|
|
1779
|
+
describe('AiInfraRepos.tryMatchingProviderFrom', () => {
|
|
1780
|
+
const createRuntimeState = (models: EnabledAiModel[]): AiProviderRuntimeState => ({
|
|
1781
|
+
enabledAiModels: models,
|
|
1782
|
+
enabledAiProviders: [],
|
|
1783
|
+
enabledChatAiProviders: [],
|
|
1784
|
+
enabledImageAiProviders: [],
|
|
1785
|
+
runtimeConfig: {},
|
|
1786
|
+
});
|
|
1787
|
+
|
|
1788
|
+
it('prefers provider order when multiple providers have model', async () => {
|
|
1789
|
+
const runtimeState = createRuntimeState([
|
|
1790
|
+
{ abilities: {}, enabled: true, id: 'm-1', type: 'chat', providerId: 'provider-b' },
|
|
1791
|
+
{ abilities: {}, enabled: true, id: 'm-1', type: 'chat', providerId: 'provider-a' },
|
|
1792
|
+
]);
|
|
1793
|
+
|
|
1794
|
+
const providerId = await AiInfraRepos.tryMatchingProviderFrom(runtimeState, {
|
|
1795
|
+
modelId: 'm-1',
|
|
1796
|
+
preferredProviders: ['provider-b', 'provider-a'],
|
|
1797
|
+
});
|
|
1798
|
+
|
|
1799
|
+
expect(providerId).toBe('provider-b');
|
|
1800
|
+
});
|
|
1801
|
+
|
|
1802
|
+
it('ignores disabled models when matching', async () => {
|
|
1803
|
+
const runtimeState = createRuntimeState([
|
|
1804
|
+
{ abilities: {}, enabled: false, id: 'm-1', type: 'chat', providerId: 'provider-disabled' },
|
|
1805
|
+
{ abilities: {}, enabled: true, id: 'm-1', type: 'chat', providerId: 'provider-a' },
|
|
1806
|
+
]);
|
|
1807
|
+
|
|
1808
|
+
const providerId = await AiInfraRepos.tryMatchingProviderFrom(runtimeState, {
|
|
1809
|
+
modelId: 'm-1',
|
|
1810
|
+
preferredProviders: ['provider-disabled', 'provider-a'],
|
|
1811
|
+
});
|
|
1812
|
+
|
|
1813
|
+
expect(providerId).toBe('provider-a');
|
|
1814
|
+
});
|
|
1815
|
+
|
|
1816
|
+
it('falls back to provided fallback provider when no match', async () => {
|
|
1817
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1818
|
+
const runtimeState = createRuntimeState([]);
|
|
1819
|
+
|
|
1820
|
+
const providerId = await AiInfraRepos.tryMatchingProviderFrom(runtimeState, {
|
|
1821
|
+
modelId: 'm-1',
|
|
1822
|
+
fallbackProvider: 'provider-fallback',
|
|
1823
|
+
});
|
|
1824
|
+
|
|
1825
|
+
expect(providerId).toBe('provider-fallback');
|
|
1826
|
+
warnSpy.mockRestore();
|
|
1827
|
+
});
|
|
1828
|
+
});
|
|
1777
1829
|
});
|
|
@@ -24,6 +24,8 @@ import { LobeChatDatabase } from '../../type';
|
|
|
24
24
|
|
|
25
25
|
type DecryptUserKeyVaults = (encryptKeyVaultsStr: string | null) => Promise<any>;
|
|
26
26
|
|
|
27
|
+
const normalizeProvider = (provider: string) => provider.toLowerCase();
|
|
28
|
+
|
|
27
29
|
/**
|
|
28
30
|
* Provider-level search defaults (only used when built-in models don't provide settings.searchImpl and settings.searchProvider)
|
|
29
31
|
* Note: Not stored in DB, only injected during read
|
|
@@ -282,6 +284,107 @@ export class AiInfraRepos {
|
|
|
282
284
|
};
|
|
283
285
|
};
|
|
284
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Resolve the best provider for a given model.
|
|
289
|
+
*
|
|
290
|
+
* Matching pipeline:
|
|
291
|
+
* 1) Build a map of provider -> enabled model ids (disabled models are ignored).
|
|
292
|
+
* 2) Walk providers in priority order: preferred providers (if any) -> explicit fallback provider -> remaining providers that have enabled models.
|
|
293
|
+
* 3) For each provider, look for an exact modelId match or any preferred model alias.
|
|
294
|
+
* 4) If nothing matches, fall back to the configured provider (with a warning) or throw when no fallback exists.
|
|
295
|
+
*
|
|
296
|
+
* Handles:
|
|
297
|
+
* - Preferred provider ordering (case-insensitive).
|
|
298
|
+
* - Preferred model aliases.
|
|
299
|
+
* - Disabled models are skipped.
|
|
300
|
+
* - Missing matches: falls back when possible, otherwise surfaces an error.
|
|
301
|
+
*
|
|
302
|
+
* Edge cases to note:
|
|
303
|
+
* - If preferredProviders are set, non-preferred providers are skipped unless they are also the explicit fallback.
|
|
304
|
+
* - If fallbackProvider lacks enabled models, it is still returned (caller should ensure runtimeConfig has credentials).
|
|
305
|
+
*/
|
|
306
|
+
static async tryMatchingProviderFrom(
|
|
307
|
+
runtimeState: AiProviderRuntimeState,
|
|
308
|
+
options: {
|
|
309
|
+
fallbackProvider?: string;
|
|
310
|
+
label?: string;
|
|
311
|
+
modelId: string;
|
|
312
|
+
preferredModels?: string[];
|
|
313
|
+
preferredProviders?: string[];
|
|
314
|
+
},
|
|
315
|
+
): Promise<string> {
|
|
316
|
+
const { modelId, fallbackProvider, preferredModels, preferredProviders, label } = options;
|
|
317
|
+
|
|
318
|
+
// Build a map of provider -> enabled model ids for quick membership checks; skip disabled models entirely
|
|
319
|
+
const providerModels = runtimeState.enabledAiModels.reduce<Record<string, Set<string>>>(
|
|
320
|
+
(acc, model) => {
|
|
321
|
+
if (model.enabled === false) return acc;
|
|
322
|
+
|
|
323
|
+
const providerId = normalizeProvider(model.providerId);
|
|
324
|
+
acc[providerId] = acc[providerId] || new Set<string>();
|
|
325
|
+
acc[providerId].add(model.id);
|
|
326
|
+
|
|
327
|
+
return acc;
|
|
328
|
+
},
|
|
329
|
+
{},
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// Normalize preferred providers so ordering is stable and comparisons are case-insensitive
|
|
333
|
+
const normalizedPreferredProviders = (preferredProviders || [])
|
|
334
|
+
.map(normalizeProvider)
|
|
335
|
+
.filter(Boolean);
|
|
336
|
+
|
|
337
|
+
// Provider search pipeline:
|
|
338
|
+
// 1) iterate preferred providers (if given)
|
|
339
|
+
// 2) fall back to the explicitly configured fallback provider
|
|
340
|
+
// 3) consider any provider that has enabled models
|
|
341
|
+
const providerOrder = Array.from(
|
|
342
|
+
new Set(
|
|
343
|
+
[
|
|
344
|
+
...normalizedPreferredProviders,
|
|
345
|
+
fallbackProvider ? normalizeProvider(fallbackProvider) : undefined,
|
|
346
|
+
...Object.keys(providerModels),
|
|
347
|
+
].filter(Boolean) as string[],
|
|
348
|
+
),
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
// Candidate models include the requested modelId plus any preferred model aliases
|
|
352
|
+
const modelTargets = new Set([modelId, ...(preferredModels || [])]);
|
|
353
|
+
|
|
354
|
+
for (const providerId of providerOrder) {
|
|
355
|
+
// If preferred providers are specified, skip non-preferred providers unless they are the explicit fallback
|
|
356
|
+
if (
|
|
357
|
+
normalizedPreferredProviders.length > 0 &&
|
|
358
|
+
providerId !== normalizeProvider(fallbackProvider || '') &&
|
|
359
|
+
!normalizedPreferredProviders.includes(providerId)
|
|
360
|
+
) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const models = providerModels[providerId];
|
|
365
|
+
if (!models) {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Accept the first provider in order whose enabled models contain either the requested id or any preferred alias
|
|
370
|
+
const match = Array.from(modelTargets).find((target) => models.has(target));
|
|
371
|
+
if (match) {
|
|
372
|
+
return providerId;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (fallbackProvider) {
|
|
377
|
+
console.warn(
|
|
378
|
+
`[ai-infra] no enabled provider found for ${label || 'model'} "${modelId}" (preferred ${preferredProviders}), falling back to server-configured provider "${fallbackProvider}".`,
|
|
379
|
+
);
|
|
380
|
+
return normalizeProvider(fallbackProvider);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
throw new Error(
|
|
384
|
+
`Unable to resolve provider for ${label || 'model'} "${modelId}". Check preferred providers/models configuration.`,
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
285
388
|
getAiProviderModelList = async (
|
|
286
389
|
providerId: string,
|
|
287
390
|
options?: {
|
|
@@ -472,12 +472,14 @@ export const createTokenSpeedCalculator = (
|
|
|
472
472
|
// - text/reasoning: standard text output events
|
|
473
473
|
// - content_part/reasoning_part: multimodal output events used by Gemini 3+ models
|
|
474
474
|
// which emit structured parts instead of plain text events
|
|
475
|
+
// - tool_calls: function calling output events
|
|
475
476
|
if (
|
|
476
477
|
!outputStartAt &&
|
|
477
478
|
(chunk.type === 'text' ||
|
|
478
479
|
chunk.type === 'reasoning' ||
|
|
479
480
|
chunk.type === 'content_part' ||
|
|
480
|
-
chunk.type === 'reasoning_part'
|
|
481
|
+
chunk.type === 'reasoning_part' ||
|
|
482
|
+
chunk.type === 'tool_calls')
|
|
481
483
|
) {
|
|
482
484
|
outputStartAt = Date.now();
|
|
483
485
|
}
|
package/src/app/(backend)/api/workflows/memory-user-memory/pipelines/persona/update-writing/route.ts
CHANGED
|
@@ -8,39 +8,39 @@ import {
|
|
|
8
8
|
} from '@/server/services/memory/userMemory/persona/service';
|
|
9
9
|
|
|
10
10
|
const workflowPayloadSchema = z.object({
|
|
11
|
-
userId: z.string().optional(),
|
|
12
11
|
userIds: z.array(z.string()).optional(),
|
|
13
12
|
});
|
|
14
13
|
|
|
15
14
|
export const { POST } = serve(async (context) => {
|
|
16
|
-
const payload =
|
|
15
|
+
const payload = await context.run('memory:pipelines:persona:update-writing:parse-payload', () =>
|
|
16
|
+
workflowPayloadSchema.parse(context.requestPayload || {}),
|
|
17
|
+
);
|
|
17
18
|
const db = await getServerDB();
|
|
18
19
|
|
|
19
|
-
const userIds = Array.from(
|
|
20
|
-
new Set([...(payload.userIds || []), ...(payload.userId ? [payload.userId] : [])]),
|
|
21
|
-
).filter(Boolean);
|
|
22
|
-
|
|
20
|
+
const userIds = Array.from(new Set(payload.userIds || [])).filter(Boolean);
|
|
23
21
|
if (userIds.length === 0) {
|
|
24
|
-
|
|
22
|
+
throw new Error('No user IDs provided for persona update.');
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
const service = new UserPersonaService(db);
|
|
28
|
-
const results = [];
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
27
|
+
await Promise.all(
|
|
28
|
+
userIds.map(async (userId) =>
|
|
29
|
+
context.run(`memory:pipelines:persona:update-writing:users:${userId}`, async () => {
|
|
30
|
+
const context = await buildUserPersonaJobInput(db, userId);
|
|
31
|
+
const result = await service.composeWriting({ ...context, userId });
|
|
32
|
+
return {
|
|
33
|
+
diffId: result.diff?.id,
|
|
34
|
+
documentId: result.document.id,
|
|
35
|
+
userId,
|
|
36
|
+
version: result.document.version,
|
|
37
|
+
};
|
|
38
|
+
}),
|
|
39
|
+
),
|
|
40
|
+
);
|
|
40
41
|
|
|
41
42
|
return {
|
|
42
43
|
message: 'User persona processed via workflow.',
|
|
43
44
|
processedUsers: userIds.length,
|
|
44
|
-
results,
|
|
45
45
|
};
|
|
46
46
|
});
|
package/src/app/[variants]/(main)/agent/features/Conversation/AgentWelcome/ToolAuthAlert.tsx
CHANGED
|
@@ -104,7 +104,7 @@ const KlavisToolAuthItem = memo<KlavisToolAuthItemProps>(({ tool, onAuthComplete
|
|
|
104
104
|
try {
|
|
105
105
|
await refreshKlavisServerTools(identifier);
|
|
106
106
|
} catch (error) {
|
|
107
|
-
console.
|
|
107
|
+
console.debug('[Klavis] Polling check (expected during auth):', error);
|
|
108
108
|
}
|
|
109
109
|
}, POLL_INTERVAL_MS);
|
|
110
110
|
|
|
@@ -129,7 +129,8 @@ const KlavisToolAuthItem = memo<KlavisToolAuthItemProps>(({ tool, onAuthComplete
|
|
|
129
129
|
windowCheckIntervalRef.current = null;
|
|
130
130
|
}
|
|
131
131
|
oauthWindowRef.current = null;
|
|
132
|
-
|
|
132
|
+
// Start polling after window closes
|
|
133
|
+
startFallbackPolling(identifier);
|
|
133
134
|
}
|
|
134
135
|
} catch {
|
|
135
136
|
if (windowCheckIntervalRef.current) {
|
|
@@ -120,7 +120,7 @@ const KlavisSkillItem = memo<KlavisSkillItemProps>(({ serverType, server }) => {
|
|
|
120
120
|
try {
|
|
121
121
|
await refreshKlavisServerTools(serverName);
|
|
122
122
|
} catch (error) {
|
|
123
|
-
console.
|
|
123
|
+
console.debug('[Klavis] Polling check (expected during auth):', error);
|
|
124
124
|
}
|
|
125
125
|
}, POLL_INTERVAL_MS);
|
|
126
126
|
|
|
@@ -145,8 +145,8 @@ const KlavisSkillItem = memo<KlavisSkillItemProps>(({ serverType, server }) => {
|
|
|
145
145
|
windowCheckIntervalRef.current = null;
|
|
146
146
|
}
|
|
147
147
|
oauthWindowRef.current = null;
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
// Start polling after window closes
|
|
149
|
+
startFallbackPolling(serverName);
|
|
150
150
|
}
|
|
151
151
|
} catch {
|
|
152
152
|
console.log('[Klavis] COOP blocked window.closed access, falling back to polling');
|