@lobehub/lobehub 2.0.7 → 2.0.8

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/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 2.0.8](https://github.com/lobehub/lobe-chat/compare/v2.0.7...v2.0.8)
6
+
7
+ <sup>Released on **2026-01-28**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix inbox agent in mobile.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix inbox agent in mobile, closes [#11929](https://github.com/lobehub/lobe-chat/issues/11929) ([42f5c0b](https://github.com/lobehub/lobe-chat/commit/42f5c0b))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 2.0.7](https://github.com/lobehub/lobe-chat/compare/v2.0.6...v2.0.7)
6
31
 
7
32
  <sup>Released on **2026-01-28**</sup>
package/changelog/v2.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix inbox agent in mobile."
6
+ ]
7
+ },
8
+ "date": "2026-01-28",
9
+ "version": "2.0.8"
10
+ },
2
11
  {
3
12
  "children": {},
4
13
  "date": "2026-01-28",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.7",
3
+ "version": "2.0.8",
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",
@@ -18,6 +18,7 @@ export { LobeComfyUI } from './providers/comfyui';
18
18
  export { LobeDeepSeekAI } from './providers/deepseek';
19
19
  export { LobeGoogleAI } from './providers/google';
20
20
  export { LobeGroq } from './providers/groq';
21
+ export { LobeHubAI } from './providers/lobehub';
21
22
  export { LobeMinimaxAI } from './providers/minimax';
22
23
  export { LobeMistralAI } from './providers/mistral';
23
24
  export { LobeMoonshotAI } from './providers/moonshot';
@@ -36,7 +37,6 @@ export { LobeXiaomiMiMoAI } from './providers/xiaomimimo';
36
37
  export { LobeZenMuxAI } from './providers/zenmux';
37
38
  export { LobeZeroOneAI } from './providers/zeroone';
38
39
  export { LobeZhipuAI } from './providers/zhipu';
39
- export { LobeHubAI } from './providers/lobehub';
40
40
  export * from './types';
41
41
  export * from './types/error';
42
42
  export { consumeStreamUntilDone } from './utils/consumeStream';
@@ -88,7 +88,6 @@ export const providerRuntimeMap = {
88
88
  deepseek: LobeDeepSeekAI,
89
89
  fal: LobeFalAI,
90
90
  fireworksai: LobeFireworksAI,
91
- lobehub: LobeHubAI,
92
91
  giteeai: LobeGiteeAI,
93
92
  github: LobeGithubAI,
94
93
  google: LobeGoogleAI,
@@ -100,6 +99,7 @@ export const providerRuntimeMap = {
100
99
  internlm: LobeInternLMAI,
101
100
  jina: LobeJinaAI,
102
101
  lmstudio: LobeLMStudioAI,
102
+ lobehub: LobeHubAI,
103
103
  minimax: LobeMinimaxAI,
104
104
  mistral: LobeMistralAI,
105
105
  modelscope: LobeModelScopeAI,
@@ -2,45 +2,35 @@ import { memo } from 'react';
2
2
  import { Link } from 'react-router-dom';
3
3
 
4
4
  import { DEFAULT_INBOX_AVATAR } from '@/const/meta';
5
- import { INBOX_SESSION_ID } from '@/const/session';
6
5
  import { SESSION_CHAT_URL } from '@/const/url';
7
6
  import { useNavigateToAgent } from '@/hooks/useNavigateToAgent';
8
- import { getChatStoreState, useChatStore } from '@/store/chat';
9
- import { chatSelectors } from '@/store/chat/selectors';
7
+ import { useAgentStore } from '@/store/agent';
8
+ import { builtinAgentSelectors } from '@/store/agent/selectors';
10
9
  import { useServerConfigStore } from '@/store/serverConfig';
11
10
  import { useSessionStore } from '@/store/session';
11
+ import { sessionSelectors } from '@/store/session/selectors';
12
12
 
13
13
  import ListItem from '../ListItem';
14
14
 
15
15
  const Inbox = memo(() => {
16
16
  const mobile = useServerConfigStore((s) => s.isMobile);
17
- const activeId = useSessionStore((s) => s.activeId);
17
+ const isInboxActive = useSessionStore(sessionSelectors.isInboxSession);
18
18
  const navigateToAgent = useNavigateToAgent();
19
-
20
- const openNewTopicOrSaveTopic = useChatStore((s) => s.openNewTopicOrSaveTopic);
19
+ const inboxAgentId = useAgentStore(builtinAgentSelectors.inboxAgentId);
21
20
 
22
21
  return (
23
22
  <Link
24
23
  aria-label={'Lobe AI'}
25
- onClick={async (e) => {
24
+ onClick={(e) => {
26
25
  e.preventDefault();
27
- if (activeId === INBOX_SESSION_ID && !mobile) {
28
- // If user tap the inbox again, open a new topic.
29
- // Only for desktop.
30
- const inboxMessages = chatSelectors.inboxActiveTopicMessages(getChatStoreState());
31
- if (inboxMessages.length > 0) {
32
- await openNewTopicOrSaveTopic();
33
- }
34
- } else {
35
- navigateToAgent(INBOX_SESSION_ID);
36
- }
26
+ navigateToAgent(inboxAgentId);
37
27
  }}
38
- to={SESSION_CHAT_URL(INBOX_SESSION_ID, mobile)}
28
+ to={SESSION_CHAT_URL(inboxAgentId, mobile)}
39
29
  >
40
30
  <ListItem
41
- active={activeId === INBOX_SESSION_ID}
31
+ active={isInboxActive}
42
32
  avatar={DEFAULT_INBOX_AVATAR}
43
- key={INBOX_SESSION_ID}
33
+ key={'inbox'}
44
34
  styles={{
45
35
  container: {
46
36
  gap: 12,
@@ -227,7 +227,7 @@ export interface TopicBatchWorkflowPayload extends MemoryExtractionPayloadInput
227
227
  userId: string;
228
228
  }
229
229
 
230
- type ProviderKeyVaultMap = Record<
230
+ export type ProviderKeyVaultMap = Record<
231
231
  string,
232
232
  AiProviderRuntimeState['runtimeConfig'][string]['keyVaults'] | undefined
233
233
  >;
@@ -296,16 +296,16 @@ const maskSecret = (value?: string) => {
296
296
  return `${value.slice(0, 6)}***${value.slice(-4)}`;
297
297
  };
298
298
 
299
- type ProviderCredential = { apiKey?: string; baseURL?: string };
299
+ export type ProviderCredential = { apiKey?: string; baseURL?: string };
300
300
 
301
- type RuntimeResolveOptions = {
301
+ export type RuntimeResolveOptions = {
302
302
  fallback?: ProviderCredential;
303
303
  preferred?: {
304
304
  providerIds?: string[];
305
305
  };
306
306
  };
307
307
 
308
- const resolveRuntimeAgentConfig = (
308
+ export const resolveRuntimeAgentConfig = (
309
309
  agent: MemoryAgentConfig,
310
310
  keyVaults?: ProviderKeyVaultMap,
311
311
  options?: RuntimeResolveOptions,
@@ -323,34 +323,63 @@ const resolveRuntimeAgentConfig = (
323
323
  );
324
324
 
325
325
  for (const provider of providerOrder) {
326
+ if (provider === 'lobehub') {
327
+ debugRuntimeInit(agent, {
328
+ provider,
329
+ source: 'user-vault' as const,
330
+ });
331
+
332
+ return ModelRuntime.initializeWithProvider(provider, {});
333
+ }
334
+
326
335
  const { apiKey: userApiKey, baseURL: userBaseURL } = extractCredentialsFromVault(
327
336
  keyVaults?.[provider],
328
337
  );
329
- if (!userApiKey) continue;
338
+ if (!userApiKey) {
339
+ console.warn(
340
+ `[memory-extraction] skipping provider ${provider} due to missing API key in user vault`,
341
+ );
342
+ continue;
343
+ }
344
+
345
+ debugRuntimeInit(agent, {
346
+ apiKey: userApiKey,
347
+ baseURL: userBaseURL,
348
+ provider,
349
+ source: 'user-vault' as const,
350
+ });
330
351
 
331
352
  // Only use the user baseURL if we are also using their API key; otherwise fall back entirely
332
353
  // to system config to avoid mixing credentials.
333
- return {
354
+ return ModelRuntime.initializeWithProvider(provider, {
334
355
  apiKey: userApiKey,
335
- baseURL: userBaseURL || agent.baseURL || options?.fallback?.baseURL,
336
- provider,
337
- source: 'user-keyvault' as const,
338
- };
356
+ baseURL: userBaseURL,
357
+ });
339
358
  }
340
359
 
341
- return {
360
+ debugRuntimeInit(agent, {
342
361
  apiKey: agent.apiKey || options?.fallback?.apiKey,
343
362
  baseURL: agent.baseURL || options?.fallback?.baseURL,
344
363
  provider: agent.provider || 'openai',
345
364
  source: 'system-config' as const,
346
- };
365
+ });
366
+
367
+ return ModelRuntime.initializeWithProvider(agent.provider || 'openai', {
368
+ apiKey: agent.apiKey || options?.fallback?.apiKey,
369
+ baseURL: agent.baseURL || options?.fallback?.baseURL,
370
+ });
347
371
  };
348
372
 
349
373
  const logRuntime = debug('lobe-server:memory:user-memory:runtime');
350
374
 
351
375
  const debugRuntimeInit = (
352
376
  agent: MemoryAgentConfig,
353
- resolved: ReturnType<typeof resolveRuntimeAgentConfig>,
377
+ resolved: {
378
+ apiKey?: string;
379
+ baseURL?: string;
380
+ provider: string;
381
+ source: 'user-vault' | 'system-config';
382
+ },
354
383
  ) => {
355
384
  if (!logRuntime.enabled) return;
356
385
  logRuntime('init runtime', {
@@ -363,24 +392,6 @@ const debugRuntimeInit = (
363
392
  });
364
393
  };
365
394
 
366
- const initRuntimeForAgent = async (
367
- agent: MemoryAgentConfig,
368
- keyVaults?: ProviderKeyVaultMap,
369
- options?: RuntimeResolveOptions,
370
- ) => {
371
- const resolved = resolveRuntimeAgentConfig(agent, keyVaults, options);
372
- debugRuntimeInit(agent, resolved);
373
-
374
- if (!resolved.apiKey) {
375
- throw new Error(`Missing API key for ${resolved.provider} memory extraction runtime`);
376
- }
377
-
378
- return ModelRuntime.initializeWithProvider(resolved.provider, {
379
- apiKey: resolved.apiKey,
380
- baseURL: resolved.baseURL,
381
- });
382
- };
383
-
384
395
  const isTopicExtracted = (metadata?: ChatTopicMetadata | null): boolean => {
385
396
  const extractStatus = metadata?.userMemoryExtractStatus;
386
397
  if (extractStatus) return extractStatus === 'completed';
@@ -1957,17 +1968,17 @@ export class MemoryExtractionExecutor {
1957
1968
  };
1958
1969
 
1959
1970
  const runtimes: RuntimeBundle = {
1960
- embeddings: await initRuntimeForAgent(
1971
+ embeddings: await resolveRuntimeAgentConfig(
1961
1972
  { ...this.privateConfig.embedding },
1962
1973
  keyVaults,
1963
1974
  embeddingOptions,
1964
1975
  ),
1965
- gatekeeper: await initRuntimeForAgent(
1976
+ gatekeeper: await resolveRuntimeAgentConfig(
1966
1977
  { ...this.privateConfig.agentGateKeeper },
1967
1978
  keyVaults,
1968
1979
  gatekeeperOptions,
1969
1980
  ),
1970
- layerExtractor: await initRuntimeForAgent(
1981
+ layerExtractor: await resolveRuntimeAgentConfig(
1971
1982
  { ...this.privateConfig.agentLayerExtractor },
1972
1983
  keyVaults,
1973
1984
  layerExtractorOptions,
@@ -75,10 +75,8 @@ vi.mock('@lobechat/memory-user-memory', () => ({
75
75
  })),
76
76
  }));
77
77
 
78
- vi.mock('@lobechat/model-runtime', () => ({
79
- ModelRuntime: {
80
- initializeWithProvider: vi.fn().mockResolvedValue({}),
81
- },
78
+ vi.mock('@/server/services/memory/userMemory/extract', () => ({
79
+ resolveRuntimeAgentConfig: vi.fn().mockResolvedValue({}),
82
80
  }));
83
81
 
84
82
  let db: LobeChatDatabase;
@@ -9,7 +9,6 @@ import {
9
9
  type UserPersonaExtractionResult,
10
10
  UserPersonaExtractor,
11
11
  } from '@lobechat/memory-user-memory';
12
- import { ModelRuntime } from '@lobechat/model-runtime';
13
12
  import { desc, eq } from 'drizzle-orm';
14
13
 
15
14
  import { UserMemoryModel } from '@/database/models/userMemory';
@@ -21,26 +20,14 @@ import {
21
20
  parseMemoryExtractionConfig,
22
21
  } from '@/server/globalConfig/parseMemoryExtractionConfig';
23
22
  import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
23
+ import {
24
+ type ProviderKeyVaultMap,
25
+ type RuntimeResolveOptions,
26
+ resolveRuntimeAgentConfig,
27
+ } from '@/server/services/memory/userMemory/extract';
24
28
  import { LayersEnum } from '@/types/userMemory';
25
29
  import { trimBasedOnBatchProbe } from '@/utils/chunkers';
26
30
 
27
- const extractCredentialsFromVault = (
28
- vault?: Record<string, unknown>,
29
- ): { apiKey?: string; baseURL?: string } => {
30
- if (!vault || typeof vault !== 'object') return {};
31
-
32
- const apiKey =
33
- 'apiKey' in vault && typeof (vault as any).apiKey === 'string'
34
- ? (vault as any).apiKey
35
- : undefined;
36
- const baseURL =
37
- 'baseURL' in vault && typeof (vault as any).baseURL === 'string'
38
- ? (vault as any).baseURL
39
- : undefined;
40
-
41
- return { apiKey, baseURL };
42
- };
43
-
44
31
  interface UserPersonaAgentPayload {
45
32
  existingPersona?: string | null;
46
33
  language?: string;
@@ -79,28 +66,27 @@ export class UserPersonaService {
79
66
  const runtimeState = await aiInfraRepos.getAiProviderRuntimeState(
80
67
  KeyVaultsGateKeeper.getUserKeyVaults,
81
68
  );
82
-
83
69
  const providerId = await AiInfraRepos.tryMatchingProviderFrom(runtimeState, {
84
70
  fallbackProvider: this.agentConfig.provider,
85
71
  label: 'persona writer',
86
72
  modelId: this.agentConfig.model,
87
73
  });
88
74
 
89
- const normalizedProvider = providerId.toLowerCase();
90
- const { apiKey: vaultApiKey, baseURL: vaultBaseURL } = extractCredentialsFromVault(
91
- runtimeState.runtimeConfig?.[normalizedProvider]?.keyVaults,
75
+ const keyVaults: ProviderKeyVaultMap = Object.entries(runtimeState.runtimeConfig || {}).reduce(
76
+ (acc, [provider, config]) => {
77
+ acc[provider.toLowerCase()] = config?.keyVaults;
78
+ return acc;
79
+ },
80
+ {} as ProviderKeyVaultMap,
92
81
  );
93
82
 
94
- const useVaultCredential = !!vaultApiKey;
95
- const apiKey = useVaultCredential ? vaultApiKey : this.agentConfig.apiKey;
96
- const baseURL = useVaultCredential
97
- ? vaultBaseURL || this.agentConfig.baseURL
98
- : this.agentConfig.baseURL;
99
-
100
- const runtime = await ModelRuntime.initializeWithProvider(normalizedProvider, {
101
- apiKey,
102
- baseURL,
103
- });
83
+ const runtime = await resolveRuntimeAgentConfig({ ...this.agentConfig }, keyVaults, {
84
+ fallback: {
85
+ apiKey: this.agentConfig.apiKey,
86
+ baseURL: this.agentConfig.baseURL,
87
+ },
88
+ preferred: { providerIds: [providerId] },
89
+ } satisfies RuntimeResolveOptions);
104
90
 
105
91
  const personaModel = new UserPersonaModel(this.db, payload.userId);
106
92
  const lastDocument = await personaModel.getLatestPersonaDocument();