@lobehub/lobehub 2.0.0-next.220 → 2.0.0-next.222

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.
Files changed (123) hide show
  1. package/.github/workflows/claude-auto-testing.yml +6 -3
  2. package/.github/workflows/claude-dedupe-issues.yml +8 -1
  3. package/.github/workflows/claude-issue-triage.yml +8 -14
  4. package/.github/workflows/claude-translate-comments.yml +6 -3
  5. package/.github/workflows/claude-translator.yml +12 -14
  6. package/.github/workflows/claude.yml +10 -20
  7. package/.github/workflows/test.yml +26 -0
  8. package/.i18nrc.js +4 -2
  9. package/CHANGELOG.md +66 -0
  10. package/apps/desktop/src/main/core/browser/Browser.ts +48 -20
  11. package/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts +1 -0
  12. package/changelog/v1.json +21 -0
  13. package/docs/glossary.md +11 -0
  14. package/locales/zh-CN/components.json +1 -0
  15. package/locales/zh-CN/topic.json +5 -5
  16. package/package.json +3 -3
  17. package/packages/const/src/index.ts +0 -1
  18. package/packages/memory-user-memory/package.json +1 -0
  19. package/packages/memory-user-memory/src/extractors/context.test.ts +3 -2
  20. package/packages/memory-user-memory/src/extractors/experience.test.ts +3 -2
  21. package/packages/memory-user-memory/src/extractors/identity.test.ts +23 -6
  22. package/packages/memory-user-memory/src/extractors/preference.test.ts +3 -2
  23. package/packages/memory-user-memory/vitest.config.ts +4 -0
  24. package/packages/model-runtime/src/providers/replicate/index.ts +1 -1
  25. package/packages/ssrf-safe-fetch/index.test.ts +2 -2
  26. package/packages/ssrf-safe-fetch/package.json +3 -2
  27. package/packages/types/src/serverConfig.ts +2 -0
  28. package/packages/utils/package.json +1 -1
  29. package/packages/utils/src/client/xor-obfuscation.test.ts +32 -32
  30. package/packages/utils/src/client/xor-obfuscation.ts +3 -4
  31. package/packages/utils/src/imageToBase64.ts +1 -1
  32. package/packages/utils/src/server/__tests__/auth.test.ts +1 -1
  33. package/packages/utils/src/server/auth.ts +1 -1
  34. package/packages/utils/src/server/xor.test.ts +9 -7
  35. package/packages/utils/src/server/xor.ts +1 -1
  36. package/packages/web-crawler/package.json +1 -1
  37. package/packages/web-crawler/src/crawImpl/__tests__/naive.test.ts +1 -1
  38. package/packages/web-crawler/src/crawImpl/naive.ts +1 -1
  39. package/scripts/prebuild.mts +58 -1
  40. package/src/app/(backend)/api/auth/[...all]/route.ts +2 -1
  41. package/src/app/(backend)/middleware/auth/index.ts +3 -3
  42. package/src/app/(backend)/middleware/auth/utils.test.ts +1 -1
  43. package/src/app/(backend)/middleware/auth/utils.ts +1 -1
  44. package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +2 -2
  45. package/src/app/(backend)/webapi/models/[provider]/route.test.ts +1 -1
  46. package/src/app/(backend)/webapi/plugin/gateway/route.ts +1 -1
  47. package/src/app/(backend)/webapi/proxy/route.ts +1 -1
  48. package/src/app/[variants]/(auth)/login/[[...login]]/page.tsx +1 -1
  49. package/src/app/[variants]/(auth)/reset-password/layout.tsx +1 -1
  50. package/src/app/[variants]/(auth)/signin/layout.tsx +1 -1
  51. package/src/app/[variants]/(auth)/signin/useSignIn.ts +2 -2
  52. package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -1
  53. package/src/app/[variants]/(auth)/signup/[[...signup]]/useSignUp.tsx +12 -6
  54. package/src/app/[variants]/(auth)/verify-email/layout.tsx +1 -1
  55. package/src/app/[variants]/(main)/settings/profile/features/AvatarRow.tsx +1 -1
  56. package/src/app/[variants]/(main)/settings/security/index.tsx +1 -1
  57. package/src/app/[variants]/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +1 -1
  58. package/src/app/[variants]/(mobile)/me/(home)/__tests__/useCategory.test.tsx +1 -1
  59. package/src/app/[variants]/(mobile)/me/(home)/features/UserBanner.tsx +1 -1
  60. package/src/app/[variants]/(mobile)/settings/_layout/Header.tsx +1 -1
  61. package/src/components/ModelSelect/index.tsx +103 -72
  62. package/src/envs/auth.ts +30 -9
  63. package/src/features/Conversation/Messages/AssistantGroup/components/EditState.tsx +15 -32
  64. package/src/features/Conversation/Messages/AssistantGroup/index.tsx +9 -7
  65. package/src/features/EditorModal/EditorCanvas.tsx +31 -50
  66. package/src/features/EditorModal/TextareCanvas.tsx +3 -1
  67. package/src/features/EditorModal/index.tsx +14 -4
  68. package/src/features/ModelSwitchPanel/components/Footer.tsx +42 -0
  69. package/src/features/ModelSwitchPanel/components/List/MultipleProvidersModelItem.tsx +103 -0
  70. package/src/features/ModelSwitchPanel/components/List/SingleProviderModelItem.tsx +24 -0
  71. package/src/features/ModelSwitchPanel/components/List/VirtualItemRenderer.tsx +180 -0
  72. package/src/features/ModelSwitchPanel/components/List/index.tsx +99 -0
  73. package/src/features/ModelSwitchPanel/components/PanelContent.tsx +77 -0
  74. package/src/features/ModelSwitchPanel/components/Toolbar.tsx +54 -0
  75. package/src/features/ModelSwitchPanel/const.ts +29 -0
  76. package/src/features/ModelSwitchPanel/hooks/useBuildVirtualItems.ts +122 -0
  77. package/src/features/ModelSwitchPanel/hooks/useCurrentModelName.ts +18 -0
  78. package/src/features/ModelSwitchPanel/hooks/useDelayedRender.ts +18 -0
  79. package/src/features/ModelSwitchPanel/hooks/useModelAndProvider.ts +14 -0
  80. package/src/features/ModelSwitchPanel/hooks/usePanelHandlers.ts +33 -0
  81. package/src/features/ModelSwitchPanel/hooks/usePanelSize.ts +33 -0
  82. package/src/features/ModelSwitchPanel/hooks/usePanelState.ts +20 -0
  83. package/src/features/ModelSwitchPanel/index.tsx +25 -706
  84. package/src/features/ModelSwitchPanel/styles.ts +58 -0
  85. package/src/features/ModelSwitchPanel/types.ts +73 -0
  86. package/src/features/ModelSwitchPanel/utils.ts +24 -0
  87. package/src/features/User/UserPanel/PanelContent.tsx +1 -1
  88. package/src/features/User/__tests__/PanelContent.test.tsx +1 -1
  89. package/src/features/User/__tests__/UserAvatar.test.tsx +1 -1
  90. package/src/features/User/__tests__/useMenu.test.tsx +1 -1
  91. package/src/layout/GlobalProvider/StoreInitialization.tsx +2 -1
  92. package/src/libs/better-auth/auth-client.ts +7 -3
  93. package/src/libs/better-auth/define-config.ts +2 -2
  94. package/src/libs/next/proxy/define-config.ts +1 -2
  95. package/src/libs/oidc-provider/provider.test.ts +1 -1
  96. package/src/libs/trpc/async/context.ts +1 -1
  97. package/src/libs/trpc/lambda/context.ts +7 -8
  98. package/src/libs/trpc/middleware/userAuth.ts +1 -1
  99. package/src/libs/trusted-client/getSessionUser.ts +1 -1
  100. package/src/locales/default/components.ts +1 -0
  101. package/src/server/globalConfig/index.ts +2 -0
  102. package/src/server/routers/async/caller.ts +1 -1
  103. package/src/server/routers/lambda/__tests__/user.test.ts +2 -2
  104. package/src/server/routers/lambda/notebook.ts +4 -2
  105. package/src/server/routers/lambda/user.ts +2 -1
  106. package/src/services/_auth.ts +3 -3
  107. package/src/services/chat/index.ts +1 -1
  108. package/src/services/chat/mecha/contextEngineering.ts +1 -1
  109. package/src/services/notebook.ts +2 -0
  110. package/src/store/global/initialState.ts +10 -0
  111. package/src/store/global/selectors/systemStatus.ts +5 -0
  112. package/src/store/serverConfig/selectors.ts +5 -1
  113. package/src/store/tool/slices/builtin/executors/lobe-web-browsing.ts +2 -0
  114. package/src/store/tool/slices/mcpStore/action.ts +74 -75
  115. package/src/store/user/slices/auth/action.test.ts +1 -1
  116. package/src/store/user/slices/auth/action.ts +1 -1
  117. package/src/store/user/slices/auth/initialState.ts +1 -1
  118. package/src/store/user/slices/auth/selectors.test.ts +1 -1
  119. package/src/store/user/slices/auth/selectors.ts +1 -1
  120. package/src/store/user/slices/common/action.ts +1 -1
  121. package/src/store/userMemory/slices/context/action.ts +6 -6
  122. package/glossary.json +0 -8
  123. package/packages/const/src/auth.ts +0 -14
@@ -40,14 +40,31 @@ describe('IdentityExtractor', () => {
40
40
 
41
41
  it('uses structuredCall to invoke the runtime and parse structured results', async () => {
42
42
  const extractor = new IdentityExtractor(extractorConfig);
43
+ // Mock data matching IdentityActionsSchema structure
43
44
  const structuredResult = {
44
- withIdentities: {
45
- actions: {
46
- add: [{ description: 'New identity', extractedLabels: ['tag'], type: 'personal' }],
47
- remove: null,
48
- update: null,
45
+ add: [
46
+ {
47
+ details: null,
48
+ memoryCategory: 'personal',
49
+ memoryLayer: 'identity',
50
+ memoryType: 'fact',
51
+ summary: 'New identity summary',
52
+ tags: ['tag'],
53
+ title: 'New identity',
54
+ withIdentity: {
55
+ description: 'New identity description',
56
+ episodicDate: null,
57
+ extractedLabels: ['tag'],
58
+ relationship: 'self',
59
+ role: 'developer',
60
+ scoreConfidence: 0.8,
61
+ sourceEvidence: null,
62
+ type: 'personal',
63
+ },
49
64
  },
50
- },
65
+ ],
66
+ remove: null,
67
+ update: null,
51
68
  };
52
69
  (runtimeMock.generateObject as any) = vi.fn().mockResolvedValue(structuredResult);
53
70
 
@@ -3,7 +3,7 @@ import type { ModelRuntime } from '@lobechat/model-runtime';
3
3
  import { readFile } from 'node:fs/promises';
4
4
  import { beforeEach, describe, expect, it, vi } from 'vitest';
5
5
 
6
- import { MEMORY_CATEGORIES, memoryTypeValues } from '../schemas';
6
+ import { memoryTypeValues } from '../schemas';
7
7
  import type { ExtractorTemplateProps } from '../types';
8
8
  import { PreferenceExtractor } from './preference';
9
9
 
@@ -40,7 +40,8 @@ describe('PreferenceExtractor', () => {
40
40
 
41
41
  expect(memories.type).toBe('array');
42
42
  expect(memoryItem.properties.memoryLayer.const).toBe('preference');
43
- expect(memoryItem.properties.memoryCategory.enum).toEqual(MEMORY_CATEGORIES);
43
+ // memoryCategory is a plain string in schema, not an enum
44
+ expect(memoryItem.properties.memoryCategory.type).toBe('string');
44
45
  expect(memoryItem.properties.memoryType.enum).toEqual(memoryTypeValues);
45
46
  expect((schema?.schema as any).additionalProperties).toBe(false);
46
47
  });
@@ -1,7 +1,11 @@
1
+ import { resolve } from 'node:path';
1
2
  import { defineConfig } from 'vitest/config';
2
3
 
3
4
  export default defineConfig({
4
5
  test: {
6
+ alias: {
7
+ '@': resolve(__dirname, '../../src'),
8
+ },
5
9
  coverage: {
6
10
  reporter: ['text', 'json', 'lcov', 'text-summary'],
7
11
  },
@@ -160,7 +160,7 @@ export class LobeReplicateAI implements LobeRuntimeAI {
160
160
  '[Replicate createImage] Local URL detected, will fetch and upload as data',
161
161
  );
162
162
  try {
163
- const { ssrfSafeFetch } = await import('ssrf-safe-fetch');
163
+ const { ssrfSafeFetch } = await import('@lobechat/ssrf-safe-fetch');
164
164
  const imageResponse = await ssrfSafeFetch(imageUrl);
165
165
  if (!imageResponse.ok) {
166
166
  throw new Error(
@@ -117,7 +117,7 @@ describe('ssrfSafeFetch', () => {
117
117
  });
118
118
 
119
119
  it('should allow private IPs when SSRF_ALLOW_PRIVATE_IP_ADDRESS is true', async () => {
120
- process.env.SSRF_ALLOW_PRIVATE_IP_ADDRESS = 'true';
120
+ process.env.SSRF_ALLOW_PRIVATE_IP_ADDRESS = '1';
121
121
 
122
122
  const mockResponse = createMockResponse();
123
123
  mockFetch.mockResolvedValue(mockResponse);
@@ -277,7 +277,7 @@ describe('ssrfSafeFetch', () => {
277
277
  describe('integration scenarios', () => {
278
278
  it('should work with complex request configurations', async () => {
279
279
  process.env.SSRF_ALLOW_IP_ADDRESS_LIST = '127.0.0.1';
280
- process.env.SSRF_ALLOW_PRIVATE_IP_ADDRESS = 'true';
280
+ process.env.SSRF_ALLOW_PRIVATE_IP_ADDRESS = '1';
281
281
 
282
282
  const mockResponse = createMockResponse({
283
283
  // @ts-ignore
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "ssrf-safe-fetch",
2
+ "name": "@lobechat/ssrf-safe-fetch",
3
3
  "version": "1.0.0",
4
4
  "private": true,
5
5
  "description": "SSRF-safe fetch implementation with browser/node conditional exports",
@@ -12,7 +12,8 @@
12
12
  },
13
13
  "main": "index.ts",
14
14
  "scripts": {
15
- "test": "vitest run"
15
+ "test": "vitest run",
16
+ "test:coverage": "vitest --coverage --silent='passed-only'"
16
17
  },
17
18
  "dependencies": {
18
19
  "node-fetch": "^3.3.2",
@@ -49,7 +49,9 @@ export type ServerLanguageModel = Partial<Record<GlobalLLMProviderKey, ServerMod
49
49
  export interface GlobalServerConfig {
50
50
  aiProvider: ServerLanguageModel;
51
51
  defaultAgent?: PartialDeep<UserDefaultAgent>;
52
+ enableEmailVerification?: boolean;
52
53
  enableKlavis?: boolean;
54
+ enableMagicLink?: boolean;
53
55
  enableMarketTrustedClient?: boolean;
54
56
  enableUploadFileToServer?: boolean;
55
57
  enabledAccessCode?: boolean;
@@ -34,7 +34,7 @@
34
34
  "remark": "^15.0.1",
35
35
  "remark-gfm": "^4.0.1",
36
36
  "remark-html": "^16.0.1",
37
- "ssrf-safe-fetch": "workspace:*",
37
+ "@lobechat/ssrf-safe-fetch": "workspace:*",
38
38
  "tokenx": "^1.2.1",
39
39
  "ua-parser-js": "^1.0.41",
40
40
  "uuid": "^11.1.0",
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
- import { SECRET_XOR_KEY } from '@/const/auth';
3
+ import { SECRET_XOR_KEY } from '@/envs/auth';
4
4
 
5
5
  import { obfuscatePayloadWithXOR } from './xor-obfuscation';
6
6
 
@@ -8,7 +8,7 @@ describe('xor-obfuscation', () => {
8
8
  describe('obfuscatePayloadWithXOR', () => {
9
9
  it('åŗ”čÆ„åÆ¹ē®€å•å­—ē¬¦äø²čæ›č”Œę··ę·†å¹¶čæ”å›žBase64字符串', () => {
10
10
  const payload = 'hello world';
11
- const result = obfuscatePayloadWithXOR(payload);
11
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
12
12
 
13
13
  // éŖŒčÆčæ”å›žå€¼ę˜Æå­—ē¬¦äø²
14
14
  expect(typeof result).toBe('string');
@@ -22,7 +22,7 @@ describe('xor-obfuscation', () => {
22
22
 
23
23
  it('应评对JSONåÆ¹č±”čæ›č”Œę··ę·†', () => {
24
24
  const payload = { name: 'test', value: 123, active: true };
25
- const result = obfuscatePayloadWithXOR(payload);
25
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
26
26
 
27
27
  // éŖŒčÆčæ”å›žå€¼ę˜Æå­—ē¬¦äø²
28
28
  expect(typeof result).toBe('string');
@@ -33,7 +33,7 @@ describe('xor-obfuscation', () => {
33
33
 
34
34
  it('åŗ”čÆ„åÆ¹ę•°ē»„čæ›č”Œę··ę·†', () => {
35
35
  const payload = [1, 2, 3, 'test', { nested: true }];
36
- const result = obfuscatePayloadWithXOR(payload);
36
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
37
37
 
38
38
  // éŖŒčÆčæ”å›žå€¼ę˜Æå­—ē¬¦äø²
39
39
  expect(typeof result).toBe('string');
@@ -58,7 +58,7 @@ describe('xor-obfuscation', () => {
58
58
  tokens: ['abc123', 'def456'],
59
59
  metadata: null,
60
60
  };
61
- const result = obfuscatePayloadWithXOR(payload);
61
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
62
62
 
63
63
  // éŖŒčÆčæ”å›žå€¼ę˜Æå­—ē¬¦äø²
64
64
  expect(typeof result).toBe('string');
@@ -69,8 +69,8 @@ describe('xor-obfuscation', () => {
69
69
 
70
70
  it('ē›øåŒēš„č¾“å…„åŗ”čÆ„äŗ§ē”Ÿē›øåŒēš„č¾“å‡ŗ', () => {
71
71
  const payload = { test: 'consistent' };
72
- const result1 = obfuscatePayloadWithXOR(payload);
73
- const result2 = obfuscatePayloadWithXOR(payload);
72
+ const result1 = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
73
+ const result2 = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
74
74
 
75
75
  expect(result1).toBe(result2);
76
76
  });
@@ -79,15 +79,15 @@ describe('xor-obfuscation', () => {
79
79
  const payload1 = { test: 'value1' };
80
80
  const payload2 = { test: 'value2' };
81
81
 
82
- const result1 = obfuscatePayloadWithXOR(payload1);
83
- const result2 = obfuscatePayloadWithXOR(payload2);
82
+ const result1 = obfuscatePayloadWithXOR(payload1, SECRET_XOR_KEY);
83
+ const result2 = obfuscatePayloadWithXOR(payload2, SECRET_XOR_KEY);
84
84
 
85
85
  expect(result1).not.toBe(result2);
86
86
  });
87
87
 
88
88
  it('åŗ”čÆ„å¤„ē†åŒ…å«ē‰¹ę®Šå­—ē¬¦ēš„å­—ē¬¦äø²', () => {
89
89
  const payload = 'Hello! @#$%^&*()_+-=[]{}|;:,.<>?/~`"\'\\';
90
- const result = obfuscatePayloadWithXOR(payload);
90
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
91
91
 
92
92
  // éŖŒčÆčæ”å›žå€¼ę˜Æå­—ē¬¦äø²
93
93
  expect(typeof result).toBe('string');
@@ -98,7 +98,7 @@ describe('xor-obfuscation', () => {
98
98
 
99
99
  it('åŗ”čÆ„å¤„ē†åŒ…å«Unicodeå­—ē¬¦ēš„å­—ē¬¦äø²', () => {
100
100
  const payload = 'ä½ å„½äø–ē•Œ šŸŒ Ć©mojis ę—„ęœ¬čŖž ķ•œźµ­ģ–“';
101
- const result = obfuscatePayloadWithXOR(payload);
101
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
102
102
 
103
103
  // éŖŒčÆčæ”å›žå€¼ę˜Æå­—ē¬¦äø²
104
104
  expect(typeof result).toBe('string');
@@ -109,7 +109,7 @@ describe('xor-obfuscation', () => {
109
109
 
110
110
  it('应评处理空字符串', () => {
111
111
  const payload = '';
112
- const result = obfuscatePayloadWithXOR(payload);
112
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
113
113
 
114
114
  // éŖŒčÆčæ”å›žå€¼ę˜Æå­—ē¬¦äø²
115
115
  expect(typeof result).toBe('string');
@@ -120,7 +120,7 @@ describe('xor-obfuscation', () => {
120
120
 
121
121
  it('应评处理空对豔', () => {
122
122
  const payload = {};
123
- const result = obfuscatePayloadWithXOR(payload);
123
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
124
124
 
125
125
  // éŖŒčÆčæ”å›žå€¼ę˜Æå­—ē¬¦äø²
126
126
  expect(typeof result).toBe('string');
@@ -130,7 +130,7 @@ describe('xor-obfuscation', () => {
130
130
  });
131
131
 
132
132
  it('应评处理空数组', () => {
133
- const result = obfuscatePayloadWithXOR([]);
133
+ const result = obfuscatePayloadWithXOR([], SECRET_XOR_KEY);
134
134
 
135
135
  // éŖŒčÆčæ”å›žå€¼ę˜Æå­—ē¬¦äø²
136
136
  expect(typeof result).toBe('string');
@@ -141,7 +141,7 @@ describe('xor-obfuscation', () => {
141
141
 
142
142
  it('应评处理null值', () => {
143
143
  const payload = null;
144
- const result = obfuscatePayloadWithXOR(payload);
144
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
145
145
 
146
146
  // éŖŒčÆčæ”å›žå€¼ę˜Æå­—ē¬¦äø²
147
147
  expect(typeof result).toBe('string');
@@ -152,7 +152,7 @@ describe('xor-obfuscation', () => {
152
152
 
153
153
  it('应评处理数字', () => {
154
154
  const payload = 42;
155
- const result = obfuscatePayloadWithXOR(payload);
155
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
156
156
 
157
157
  // éŖŒčÆčæ”å›žå€¼ę˜Æå­—ē¬¦äø²
158
158
  expect(typeof result).toBe('string');
@@ -165,8 +165,8 @@ describe('xor-obfuscation', () => {
165
165
  const payloadTrue = true;
166
166
  const payloadFalse = false;
167
167
 
168
- const resultTrue = obfuscatePayloadWithXOR(payloadTrue);
169
- const resultFalse = obfuscatePayloadWithXOR(payloadFalse);
168
+ const resultTrue = obfuscatePayloadWithXOR(payloadTrue, SECRET_XOR_KEY);
169
+ const resultFalse = obfuscatePayloadWithXOR(payloadFalse, SECRET_XOR_KEY);
170
170
 
171
171
  // éŖŒčÆčæ”å›žå€¼ę˜Æå­—ē¬¦äø²
172
172
  expect(typeof resultTrue).toBe('string');
@@ -189,7 +189,7 @@ describe('xor-obfuscation', () => {
189
189
  tab: 'col1\tcol2',
190
190
  unicode: '\u0041\u0042\u0043',
191
191
  };
192
- const result = obfuscatePayloadWithXOR(payload);
192
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
193
193
 
194
194
  // éŖŒčÆčæ”å›žå€¼ę˜Æå­—ē¬¦äø²
195
195
  expect(typeof result).toBe('string');
@@ -200,7 +200,7 @@ describe('xor-obfuscation', () => {
200
200
 
201
201
  it('åŗ”čÆ„å¤„ē†å¾ˆé•æēš„å­—ē¬¦äø²', () => {
202
202
  const payload = 'a'.repeat(10000);
203
- const result = obfuscatePayloadWithXOR(payload);
203
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
204
204
 
205
205
  // éŖŒčÆčæ”å›žå€¼ę˜Æå­—ē¬¦äø²
206
206
  expect(typeof result).toBe('string');
@@ -216,8 +216,8 @@ describe('xor-obfuscation', () => {
216
216
  const shortPayload = 'short';
217
217
  const longPayload = 'this is a much longer string that should produce different output';
218
218
 
219
- const shortResult = obfuscatePayloadWithXOR(shortPayload);
220
- const longResult = obfuscatePayloadWithXOR(longPayload);
219
+ const shortResult = obfuscatePayloadWithXOR(shortPayload, SECRET_XOR_KEY);
220
+ const longResult = obfuscatePayloadWithXOR(longPayload, SECRET_XOR_KEY);
221
221
 
222
222
  // č¾ƒé•æēš„č¾“å…„åŗ”čÆ„äŗ§ē”Ÿč¾ƒé•æēš„č¾“å‡ŗ
223
223
  expect(longResult.length).toBeGreaterThan(shortResult.length);
@@ -225,7 +225,7 @@ describe('xor-obfuscation', () => {
225
225
 
226
226
  it('åŗ”čÆ„éŖŒčÆč¾“å‡ŗę˜Æęœ‰ę•ˆēš„Base64ę ¼å¼', () => {
227
227
  const payload = { test: 'base64 validation' };
228
- const result = obfuscatePayloadWithXOR(payload);
228
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
229
229
 
230
230
  // 验证Base64ę ¼å¼ēš„ę­£åˆ™č”Øč¾¾å¼
231
231
  const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
@@ -242,14 +242,14 @@ describe('xor-obfuscation', () => {
242
242
  },
243
243
  };
244
244
 
245
- const result = obfuscatePayloadWithXOR(payload);
245
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
246
246
  expect(typeof result).toBe('string');
247
247
  expect(() => atob(result)).not.toThrow();
248
248
  });
249
249
 
250
250
  it('应评对undefinedå€¼čæ›č”Œå¤„ē†', () => {
251
251
  const payload = undefined;
252
- const result = obfuscatePayloadWithXOR(payload);
252
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
253
253
 
254
254
  // éŖŒčÆčæ”å›žå€¼ę˜Æå­—ē¬¦äø²
255
255
  expect(typeof result).toBe('string');
@@ -268,7 +268,7 @@ describe('xor-obfuscation', () => {
268
268
  value: 123,
269
269
  };
270
270
 
271
- const result = obfuscatePayloadWithXOR(payload);
271
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
272
272
  expect(typeof result).toBe('string');
273
273
  expect(() => atob(result)).not.toThrow();
274
274
  });
@@ -279,7 +279,7 @@ describe('xor-obfuscation', () => {
279
279
 
280
280
  // å¤šę¬”čæč”Œē›øåŒč¾“å…„
281
281
  for (let i = 0; i < 10; i++) {
282
- results.push(obfuscatePayloadWithXOR(payload));
282
+ results.push(obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY));
283
283
  }
284
284
 
285
285
  // ę‰€ęœ‰ē»“ęžœåŗ”čÆ„ē›øåŒ
@@ -293,7 +293,7 @@ describe('xor-obfuscation', () => {
293
293
  name: 'date test',
294
294
  };
295
295
 
296
- const result = obfuscatePayloadWithXOR(payload);
296
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
297
297
  expect(typeof result).toBe('string');
298
298
  expect(() => atob(result)).not.toThrow();
299
299
  });
@@ -306,7 +306,7 @@ describe('xor-obfuscation', () => {
306
306
  normalKey: 'normal value',
307
307
  };
308
308
 
309
- const result = obfuscatePayloadWithXOR(payload);
309
+ const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
310
310
  expect(typeof result).toBe('string');
311
311
  expect(() => atob(result)).not.toThrow();
312
312
  });
@@ -314,7 +314,7 @@ describe('xor-obfuscation', () => {
314
314
  it('åŗ”čÆ„éŖŒčÆę··ę·†åŽēš„ę•°ę®é•æåŗ¦åˆē†ę€§', () => {
315
315
  const originalPayload = { test: 'length check' };
316
316
  const originalJSON = JSON.stringify(originalPayload);
317
- const result = obfuscatePayloadWithXOR(originalPayload);
317
+ const result = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
318
318
 
319
319
  // Base64 ē¼–ē åŽēš„é•æåŗ¦é€šåøøę˜ÆåŽŸå§‹é•æåŗ¦ēš„ 4/3 å€ļ¼ˆå‘äøŠå–ę•“åˆ°4ēš„å€ę•°ļ¼‰
320
320
  const expectedMinLength = Math.ceil((originalJSON.length * 4) / 3 / 4) * 4;
@@ -323,7 +323,7 @@ describe('xor-obfuscation', () => {
323
323
 
324
324
  it('åŗ”čÆ„éŖŒčÆXORę“ä½œēš„ę­£ē”®ę€§ļ¼ˆé€ščæ‡é€†å‘ę“ä½œļ¼‰', () => {
325
325
  const originalPayload = { message: 'XOR test', value: 42 };
326
- const obfuscatedResult = obfuscatePayloadWithXOR(originalPayload);
326
+ const obfuscatedResult = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
327
327
 
328
328
  // ę‰‹åŠØå®žēŽ°é€†å‘ę“ä½œę„éŖŒčÆ XOR ę“ä½œēš„ę­£ē”®ę€§
329
329
  const base64Decoded = atob(obfuscatedResult);
@@ -357,7 +357,7 @@ describe('xor-obfuscation', () => {
357
357
  [4, 5, 6],
358
358
  ];
359
359
 
360
- const results = payloads.map((payload) => obfuscatePayloadWithXOR(payload));
360
+ const results = payloads.map((payload) => obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY));
361
361
 
362
362
  // éŖŒčÆę‰€ęœ‰ē»“ęžœéƒ½äøē›øåŒ
363
363
  for (let i = 0; i < results.length; i++) {
@@ -1,5 +1,3 @@
1
- import { SECRET_XOR_KEY } from '@/const/auth';
2
-
3
1
  /**
4
2
  * Convert string to Uint8Array (UTF-8 encoding)
5
3
  */
@@ -24,12 +22,13 @@ const xorProcess = (data: Uint8Array, key: Uint8Array): Uint8Array => {
24
22
  /**
25
23
  * Obfuscate payload with XOR and encode to Base64
26
24
  * @param payload The JSON object to obfuscate
25
+ * @param secretKey The key used for XOR obfuscation
27
26
  * @returns The obfuscated string encoded in Base64
28
27
  */
29
- export const obfuscatePayloadWithXOR = <T>(payload: T): string => {
28
+ export const obfuscatePayloadWithXOR = <T>(payload: T, secretKey: string): string => {
30
29
  const jsonString = JSON.stringify(payload);
31
30
  const dataBytes = stringToUint8Array(jsonString);
32
- const keyBytes = stringToUint8Array(SECRET_XOR_KEY);
31
+ const keyBytes = stringToUint8Array(secretKey);
33
32
 
34
33
  const xoredBytes = xorProcess(dataBytes, keyBytes);
35
34
 
@@ -48,7 +48,7 @@ export const imageUrlToBase64 = async (
48
48
 
49
49
  // Use SSRF-safe fetch on server-side to prevent SSRF attacks
50
50
  const res = isServer
51
- ? await import('ssrf-safe-fetch').then((m) => m.ssrfSafeFetch(imageUrl))
51
+ ? await import('@lobechat/ssrf-safe-fetch').then((m) => m.ssrfSafeFetch(imageUrl))
52
52
  : await fetch(imageUrl);
53
53
 
54
54
  const blob = await res.blob();
@@ -7,7 +7,7 @@ let mockEnableBetterAuth = false;
7
7
  let mockEnableClerk = false;
8
8
  let mockEnableNextAuth = false;
9
9
 
10
- vi.mock('@/const/auth', () => ({
10
+ vi.mock('@/envs/auth', () => ({
11
11
  get enableBetterAuth() {
12
12
  return mockEnableBetterAuth;
13
13
  },
@@ -1,6 +1,6 @@
1
1
  import { headers } from 'next/headers';
2
2
 
3
- import { enableBetterAuth, enableClerk, enableNextAuth } from '@/const/auth';
3
+ import { enableBetterAuth, enableClerk, enableNextAuth } from '@/envs/auth';
4
4
 
5
5
  export const getUserAuth = async () => {
6
6
  if (enableClerk) {
@@ -1,5 +1,7 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
+ import { SECRET_XOR_KEY } from '@/envs/auth';
4
+
3
5
  import { obfuscatePayloadWithXOR } from '../client/xor-obfuscation';
4
6
  import { getXorPayload } from './xor';
5
7
 
@@ -12,7 +14,7 @@ describe('getXorPayload', () => {
12
14
  };
13
15
 
14
16
  // ä½æē”Øå®¢ęˆ·ē«Æēš„ę··ę·†å‡½ę•°ē”Ÿęˆtoken
15
- const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
17
+ const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
16
18
 
17
19
  // ä½æē”ØęœåŠ”ē«Æēš„č§£ē å‡½ę•°č§£ē 
18
20
  const decodedPayload = getXorPayload(obfuscatedToken);
@@ -25,7 +27,7 @@ describe('getXorPayload', () => {
25
27
  userId: '12345',
26
28
  };
27
29
 
28
- const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
30
+ const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
29
31
  const decodedPayload = getXorPayload(obfuscatedToken);
30
32
 
31
33
  expect(decodedPayload).toEqual(originalPayload);
@@ -40,7 +42,7 @@ describe('getXorPayload', () => {
40
42
  awsSessionToken: 'session-token-example',
41
43
  };
42
44
 
43
- const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
45
+ const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
44
46
  const decodedPayload = getXorPayload(obfuscatedToken);
45
47
 
46
48
  expect(decodedPayload).toEqual(originalPayload);
@@ -54,7 +56,7 @@ describe('getXorPayload', () => {
54
56
  azureApiVersion: '2024-02-15-preview',
55
57
  };
56
58
 
57
- const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
59
+ const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
58
60
  const decodedPayload = getXorPayload(obfuscatedToken);
59
61
 
60
62
  expect(decodedPayload).toEqual(originalPayload);
@@ -67,7 +69,7 @@ describe('getXorPayload', () => {
67
69
  cloudflareBaseURLOrAccountID: 'account-id-example',
68
70
  };
69
71
 
70
- const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
72
+ const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
71
73
  const decodedPayload = getXorPayload(obfuscatedToken);
72
74
 
73
75
  expect(decodedPayload).toEqual(originalPayload);
@@ -76,7 +78,7 @@ describe('getXorPayload', () => {
76
78
  it('should handle empty payload correctly', () => {
77
79
  const originalPayload = {};
78
80
 
79
- const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
81
+ const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
80
82
  const decodedPayload = getXorPayload(obfuscatedToken);
81
83
 
82
84
  expect(decodedPayload).toEqual(originalPayload);
@@ -89,7 +91,7 @@ describe('getXorPayload', () => {
89
91
  apiKey: 'test-key',
90
92
  };
91
93
 
92
- const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
94
+ const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
93
95
  const decodedPayload = getXorPayload(obfuscatedToken);
94
96
 
95
97
  expect(decodedPayload).toEqual(originalPayload);
@@ -1,6 +1,6 @@
1
1
  import { ClientSecretPayload } from '@lobechat/types';
2
2
 
3
- import { SECRET_XOR_KEY } from '@/const/auth';
3
+ import { SECRET_XOR_KEY } from '@/envs/auth';
4
4
 
5
5
  /**
6
6
  * Convert Base64 string to Uint8Array
@@ -13,7 +13,7 @@
13
13
  "happy-dom": "^20.0.11",
14
14
  "node-html-markdown": "^1.3.0",
15
15
  "query-string": "^9.3.1",
16
- "ssrf-safe-fetch": "workspace:*",
16
+ "@lobechat/ssrf-safe-fetch": "workspace:*",
17
17
  "url-join": "^5"
18
18
  }
19
19
  }
@@ -13,7 +13,7 @@ vi.mock('../../utils/withTimeout', () => ({
13
13
  withTimeout: vi.fn(),
14
14
  }));
15
15
 
16
- vi.mock('ssrf-safe-fetch', () => ({
16
+ vi.mock('@lobechat/ssrf-safe-fetch', () => ({
17
17
  ssrfSafeFetch: vi.fn(),
18
18
  }));
19
19
 
@@ -1,4 +1,4 @@
1
- import { ssrfSafeFetch } from 'ssrf-safe-fetch';
1
+ import { ssrfSafeFetch } from '@lobechat/ssrf-safe-fetch';
2
2
 
3
3
  import { CrawlImpl, CrawlSuccessResult } from '../type';
4
4
  import { NetworkConnectionError, PageNotFoundError, TimeoutError } from '../utils/errorType';
@@ -1,3 +1,4 @@
1
+ import { execSync } from 'node:child_process';
1
2
  import * as dotenv from 'dotenv';
2
3
  import dotenvExpand from 'dotenv-expand';
3
4
  import { existsSync } from 'node:fs';
@@ -14,6 +15,61 @@ if (isDesktop) {
14
15
  } else {
15
16
  dotenvExpand.expand(dotenv.config());
16
17
  }
18
+
19
+ // Auth flags - use process.env directly for build-time dead code elimination
20
+ const enableClerk =
21
+ process.env.NEXT_PUBLIC_ENABLE_CLERK_AUTH === '1'
22
+ ? true
23
+ : !!process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
24
+ const enableBetterAuth = process.env.NEXT_PUBLIC_ENABLE_BETTER_AUTH === '1';
25
+ const enableNextAuth = process.env.NEXT_PUBLIC_ENABLE_NEXT_AUTH === '1';
26
+ const enableAuth = enableClerk || enableBetterAuth || enableNextAuth || false;
27
+
28
+ const getCommandVersion = (command: string): string | null => {
29
+ try {
30
+ return execSync(`${command} --version`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
31
+ .trim()
32
+ .split('\n')[0];
33
+ } catch {
34
+ return null;
35
+ }
36
+ };
37
+
38
+ const printEnvInfo = () => {
39
+ console.log('\nšŸ“‹ Build Environment Info:');
40
+ console.log('─'.repeat(50));
41
+
42
+ // Runtime versions
43
+ console.log(` Node.js: ${process.version}`);
44
+ console.log(` npm: ${getCommandVersion('npm') ?? 'not installed'}`);
45
+
46
+ const bunVersion = getCommandVersion('bun');
47
+ if (bunVersion) console.log(` bun: ${bunVersion}`);
48
+
49
+ const pnpmVersion = getCommandVersion('pnpm');
50
+ if (pnpmVersion) console.log(` pnpm: ${pnpmVersion}`);
51
+
52
+ // Auth-related env vars
53
+ console.log('\n Auth Environment Variables:');
54
+ console.log(` NEXT_PUBLIC_AUTH_URL: ${process.env.NEXT_PUBLIC_AUTH_URL ?? '(not set)'}`);
55
+ console.log(` NEXTAUTH_URL: ${process.env.NEXTAUTH_URL ?? '(not set)'}`);
56
+ console.log(` APP_URL: ${process.env.APP_URL ?? '(not set)'}`);
57
+ console.log(` VERCEL_URL: ${process.env.VERCEL_URL ?? '(not set)'}`);
58
+ console.log(` AUTH_EMAIL_VERIFICATION: ${process.env.AUTH_EMAIL_VERIFICATION ?? '(not set)'}`);
59
+ console.log(` ENABLE_MAGIC_LINK: ${process.env.ENABLE_MAGIC_LINK ?? '(not set)'}`);
60
+ console.log(` AUTH_SECRET: ${process.env.AUTH_SECRET ? 'āœ“ set' : 'āœ— not set'}`);
61
+ console.log(` KEY_VAULTS_SECRET: ${process.env.KEY_VAULTS_SECRET ? 'āœ“ set' : 'āœ— not set'}`);
62
+
63
+ // Auth flags
64
+ console.log('\n Auth Flags:');
65
+ console.log(` enableClerk: ${enableClerk}`);
66
+ console.log(` enableBetterAuth: ${enableBetterAuth}`);
67
+ console.log(` enableNextAuth: ${enableNextAuth}`);
68
+ console.log(` enableAuth: ${enableAuth}`);
69
+
70
+ console.log('─'.repeat(50));
71
+ };
72
+
17
73
  // åˆ›å»ŗéœ€č¦ęŽ’é™¤ēš„ē‰¹ę€§ę˜ å°„
18
74
  /* eslint-disable sort-keys-fix/sort-keys-fix */
19
75
  const partialBuildPages = [
@@ -105,8 +161,9 @@ export const runPrebuild = async (targetDir: string = 'src') => {
105
161
  const isMainModule = process.argv[1] === fileURLToPath(import.meta.url);
106
162
 
107
163
  if (isMainModule) {
164
+ printEnvInfo();
108
165
  // ę‰§č”Œåˆ é™¤ę“ä½œ
109
- console.log('Starting prebuild cleanup...');
166
+ console.log('\nStarting prebuild cleanup...');
110
167
  await runPrebuild();
111
168
  console.log('Prebuild cleanup completed.');
112
169
  }
@@ -1,6 +1,7 @@
1
- import { enableBetterAuth, enableNextAuth } from '@lobechat/const';
2
1
  import type { NextRequest } from 'next/server';
3
2
 
3
+ import { enableBetterAuth, enableNextAuth } from '@/envs/auth';
4
+
4
5
  const createHandler = async () => {
5
6
  if (enableBetterAuth) {
6
7
  const [{ toNextJsHandler }, { auth }] = await Promise.all([
@@ -8,15 +8,15 @@ import { ChatErrorType, type ClientSecretPayload } from '@lobechat/types';
8
8
  import { getXorPayload } from '@lobechat/utils/server';
9
9
  import { type NextRequest } from 'next/server';
10
10
 
11
+ import { getServerDB } from '@/database/core/db-adaptor';
12
+ import { type LobeChatDatabase } from '@/database/type';
11
13
  import {
12
14
  LOBE_CHAT_AUTH_HEADER,
13
15
  LOBE_CHAT_OIDC_AUTH_HEADER,
14
16
  OAUTH_AUTHORIZED,
15
17
  enableBetterAuth,
16
18
  enableClerk,
17
- } from '@/const/auth';
18
- import { getServerDB } from '@/database/core/db-adaptor';
19
- import { type LobeChatDatabase } from '@/database/type';
19
+ } from '@/envs/auth';
20
20
  import { ClerkAuth } from '@/libs/clerk-auth';
21
21
  import { validateOIDCJWT } from '@/libs/oidc-provider/jwt';
22
22
  import { createErrorResponse } from '@/utils/errorResponse';
@@ -7,7 +7,7 @@ let enableClerkMock = false;
7
7
  let enableNextAuthMock = false;
8
8
  let enableBetterAuthMock = false;
9
9
 
10
- vi.mock('@/const/auth', async (importOriginal) => {
10
+ vi.mock('@/envs/auth', async (importOriginal) => {
11
11
  const data = await importOriginal();
12
12
 
13
13
  return {