diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index b312728655..a125efaa1b 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -8,6 +8,7 @@
- [ ] π style
- [ ] π· build
- [ ] β‘οΈ perf
+- [ ] β
test
- [ ] π docs
- [ ] π¨ chore
diff --git a/packages/const/src/index.ts b/packages/const/src/index.ts
index bf422390ab..916b48205b 100644
--- a/packages/const/src/index.ts
+++ b/packages/const/src/index.ts
@@ -1,5 +1,4 @@
export * from './image';
-export * from './locale';
export * from './message';
export * from './settings';
export * from './version';
diff --git a/packages/const/src/meta.ts b/packages/const/src/meta.ts
index 4a6d7cc55b..97b1b7f1fc 100644
--- a/packages/const/src/meta.ts
+++ b/packages/const/src/meta.ts
@@ -1,5 +1,6 @@
-import { BRANDING_LOGO_URL } from '@/const/branding';
-import { MetaData } from '@/types/meta';
+import { MetaData } from '@lobechat/types';
+
+import { BRANDING_LOGO_URL } from './branding';
export const DEFAULT_AVATAR = 'π€';
export const DEFAULT_USER_AVATAR = 'π';
diff --git a/packages/const/src/settings/agent.ts b/packages/const/src/settings/agent.ts
index 2911fa9a84..8b5d6be3b6 100644
--- a/packages/const/src/settings/agent.ts
+++ b/packages/const/src/settings/agent.ts
@@ -1,7 +1,12 @@
-import { DEFAULT_AGENT_META } from '@/const/meta';
-import { DEFAULT_MODEL, DEFAULT_PROVIDER } from '@/const/settings/llm';
-import { LobeAgentChatConfig, LobeAgentConfig, LobeAgentTTSConfig } from '@/types/agent';
-import { UserDefaultAgent } from '@/types/user/settings';
+import {
+ LobeAgentChatConfig,
+ LobeAgentConfig,
+ LobeAgentTTSConfig,
+ UserDefaultAgent,
+} from '@lobechat/types';
+
+import { DEFAULT_AGENT_META } from '../meta';
+import { DEFAULT_MODEL, DEFAULT_PROVIDER } from './llm';
export const DEFAUTT_AGENT_TTS_CONFIG: LobeAgentTTSConfig = {
showAllLocaleVoice: false,
diff --git a/packages/const/src/settings/systemAgent.ts b/packages/const/src/settings/systemAgent.ts
index 043e8d21cc..56317ecfa1 100644
--- a/packages/const/src/settings/systemAgent.ts
+++ b/packages/const/src/settings/systemAgent.ts
@@ -6,9 +6,6 @@ import {
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from './llm';
-export const DEFAULT_REWRITE_QUERY =
- 'Given the following conversation and a follow-up question, rephrase the follow up question to be a standalone question, in its original language. Keep as much details as possible from previous messages. Keep entity names and all.';
-
export const DEFAULT_SYSTEM_AGENT_ITEM: SystemAgentItem = {
model: DEFAULT_MODEL,
provider: DEFAULT_PROVIDER,
diff --git a/packages/file-loaders/vitest.config.mts b/packages/file-loaders/vitest.config.mts
index 9e974995dc..940870a78e 100644
--- a/packages/file-loaders/vitest.config.mts
+++ b/packages/file-loaders/vitest.config.mts
@@ -2,13 +2,9 @@ import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
- // coverage: {
- // all: false,
- // provider: 'v8',
- // reporter: ['text', 'json', 'lcov', 'text-summary'],
- // reportsDirectory: './coverage/app',
- // },
+ coverage: {
+ reporter: ['text', 'json', 'lcov', 'text-summary'],
+ },
environment: 'happy-dom',
- // setupFiles: join(__dirname, './test/setup.ts'),
},
});
diff --git a/packages/model-runtime/vitest.config.mts b/packages/model-runtime/vitest.config.mts
index fd57324ecb..3eb8f5374c 100644
--- a/packages/model-runtime/vitest.config.mts
+++ b/packages/model-runtime/vitest.config.mts
@@ -13,6 +13,9 @@ export default defineConfig({
'@': resolve(__dirname, '../../src'),
/* eslint-enable */
},
+ coverage: {
+ reporter: ['text', 'json', 'lcov', 'text-summary'],
+ },
environment: 'happy-dom',
},
});
diff --git a/packages/prompts/package.json b/packages/prompts/package.json
index bcc2e958a6..a84aae07ad 100644
--- a/packages/prompts/package.json
+++ b/packages/prompts/package.json
@@ -8,7 +8,6 @@
"test:coverage": "vitest --coverage"
},
"dependencies": {
- "@lobechat/const": "workspace:*",
"@lobechat/types": "workspace:*"
}
}
diff --git a/packages/prompts/src/chains/__tests__/abstractChunk.test.ts b/packages/prompts/src/chains/__tests__/abstractChunk.test.ts
new file mode 100644
index 0000000000..77d4de24c6
--- /dev/null
+++ b/packages/prompts/src/chains/__tests__/abstractChunk.test.ts
@@ -0,0 +1,52 @@
+import { describe, expect, it, vi } from 'vitest';
+
+import { chainAbstractChunkText } from '../abstractChunk';
+
+describe('chainAbstractChunkText', () => {
+ it('should generate correct chat payload for chunk text', () => {
+ const testText = 'This is a sample chunk of text that needs to be summarized.';
+
+ const result = chainAbstractChunkText(testText);
+
+ expect(result).toEqual({
+ messages: [
+ {
+ content:
+ 'δ½ ζ―δΈεζ
ιΏδ» chunk δΈζεζθ¦ηε©ηοΌδ½ ιθ¦ε°η¨ζ·ηδΌθ―ζ»η»δΈΊ 1~2 ε₯θ―ηζθ¦οΌθΎεΊζ chunk ζδ½Ώη¨ηθ―η§',
+ role: 'system',
+ },
+ {
+ content: `chunk: ${testText}`,
+ role: 'user',
+ },
+ ],
+ });
+ });
+
+ it('should handle empty text', () => {
+ const result = chainAbstractChunkText('');
+
+ expect(result.messages).toHaveLength(2);
+ expect(result.messages![1].content).toBe('chunk: ');
+ });
+
+ it('should handle text with special characters', () => {
+ const testText = 'Text with special chars: @#$%^&*()';
+
+ const result = chainAbstractChunkText(testText);
+
+ expect(result.messages![1].content).toBe(`chunk: ${testText}`);
+ });
+
+ it('should always use system role for first message', () => {
+ const result = chainAbstractChunkText('test');
+
+ expect(result.messages![0].role).toBe('system');
+ });
+
+ it('should always use user role for second message', () => {
+ const result = chainAbstractChunkText('test');
+
+ expect(result.messages![1].role).toBe('user');
+ });
+});
diff --git a/packages/prompts/src/chains/__tests__/answerWithContext.test.ts b/packages/prompts/src/chains/__tests__/answerWithContext.test.ts
new file mode 100644
index 0000000000..000cc722fa
--- /dev/null
+++ b/packages/prompts/src/chains/__tests__/answerWithContext.test.ts
@@ -0,0 +1,100 @@
+import { describe, expect, it } from 'vitest';
+
+import { chainAnswerWithContext } from '../answerWithContext';
+
+describe('chainAnswerWithContext', () => {
+ it('should generate correct chat payload with context and knowledge', () => {
+ const testParams = {
+ context: ['Context passage 1', 'Context passage 2'],
+ knowledge: ['AI', 'Machine Learning'],
+ question: 'What is artificial intelligence?',
+ };
+
+ const result = chainAnswerWithContext(testParams);
+
+ expect(result.messages).toHaveLength(1);
+ expect(result.messages![0].role).toBe('user');
+ expect(result.messages![0].content).toContain('AI/Machine Learning');
+ expect(result.messages![0].content).toContain('Context passage 1');
+ expect(result.messages![0].content).toContain('Context passage 2');
+ expect(result.messages![0].content).toContain('What is artificial intelligence?');
+ });
+
+ it('should handle single knowledge area', () => {
+ const testParams = {
+ context: ['Single context'],
+ knowledge: ['Technology'],
+ question: 'How does it work?',
+ };
+
+ const result = chainAnswerWithContext(testParams);
+
+ expect(result.messages![0].content).toContain('Technology');
+ });
+
+ it('should handle multiple knowledge areas', () => {
+ const testParams = {
+ context: ['Context'],
+ knowledge: ['AI', 'ML', 'NLP', 'Computer Vision'],
+ question: 'Tell me about these fields',
+ };
+
+ const result = chainAnswerWithContext(testParams);
+
+ expect(result.messages![0].content).toContain('AI/ML/NLP/Computer Vision');
+ });
+
+ it('should handle empty context array', () => {
+ const testParams = {
+ context: [],
+ knowledge: ['AI'],
+ question: 'What is AI?',
+ };
+
+ const result = chainAnswerWithContext(testParams);
+
+ expect(result.messages![0].content).toContain('');
+ expect(result.messages![0].content).toContain('');
+ });
+
+ it('should include proper context formatting', () => {
+ const testParams = {
+ context: ['First passage', 'Second passage'],
+ knowledge: ['Test'],
+ question: 'Test question',
+ };
+
+ const result = chainAnswerWithContext(testParams);
+
+ expect(result.messages![0].content).toContain(
+ '\nFirst passage\nSecond passage\n',
+ );
+ });
+
+ it('should include proper instructions about using passages', () => {
+ const testParams = {
+ context: ['Context'],
+ knowledge: ['Knowledge'],
+ question: 'Question',
+ };
+
+ const result = chainAnswerWithContext(testParams);
+ const content = result.messages![0].content;
+
+ expect(content).toContain('passages might not be relevant');
+ expect(content).toContain('please only use the passages that are relevant');
+ expect(content).toContain('answer using your knowledge');
+ });
+
+ it('should include markdown formatting instruction', () => {
+ const testParams = {
+ context: ['Context'],
+ knowledge: ['Knowledge'],
+ question: 'Question',
+ };
+
+ const result = chainAnswerWithContext(testParams);
+
+ expect(result.messages![0].content).toContain('follow markdown syntax');
+ });
+});
diff --git a/packages/prompts/src/chains/__tests__/rewriteQuery.test.ts b/packages/prompts/src/chains/__tests__/rewriteQuery.test.ts
new file mode 100644
index 0000000000..bb4bd50348
--- /dev/null
+++ b/packages/prompts/src/chains/__tests__/rewriteQuery.test.ts
@@ -0,0 +1,88 @@
+import { describe, expect, it, vi } from 'vitest';
+
+// Mock DEFAULT_REWRITE_QUERY
+
+import { DEFAULT_REWRITE_QUERY, chainRewriteQuery } from '../rewriteQuery';
+
+describe('chainRewriteQuery', () => {
+ it('should generate correct chat payload with default instruction', () => {
+ const query = 'What about the weather?';
+ const context = ['Previous message 1', 'Previous message 2'];
+
+ const result = chainRewriteQuery(query, context);
+
+ expect(result.messages).toHaveLength(2);
+ expect(result.messages![0].role).toBe('system');
+ expect(result.messages![1].role).toBe('user');
+ expect(result.messages![0].content).toContain(DEFAULT_REWRITE_QUERY);
+ expect(result.messages![1].content).toContain(query);
+ });
+
+ it('should include chat history in system message', () => {
+ const query = 'Follow up question';
+ const context = ['User: Hello', 'Assistant: Hi there'];
+
+ const result = chainRewriteQuery(query, context);
+
+ expect(result.messages![0].content).toContain('');
+ expect(result.messages![0].content).toContain('User: Hello');
+ expect(result.messages![0].content).toContain('Assistant: Hi there');
+ expect(result.messages![0].content).toContain('');
+ });
+
+ it('should use custom instruction when provided', () => {
+ const query = 'Test query';
+ const context = ['Context'];
+ const customInstruction = 'Custom rewrite instruction';
+
+ const result = chainRewriteQuery(query, context, customInstruction);
+
+ expect(result.messages![0].content).toContain(customInstruction);
+ expect(result.messages![0].content).not.toContain(DEFAULT_REWRITE_QUERY);
+ });
+
+ it('should format user message correctly', () => {
+ const query = 'What is the status?';
+ const context = ['Previous context'];
+
+ const result = chainRewriteQuery(query, context);
+
+ expect(result.messages![1].content).toBe(`Follow Up Input: ${query}, it's standalone query:`);
+ });
+
+ it('should handle empty context array', () => {
+ const query = 'Empty context query';
+ const context: string[] = [];
+
+ const result = chainRewriteQuery(query, context);
+
+ expect(result.messages![0].content).toContain('\n\n');
+ });
+
+ it('should handle single context item', () => {
+ const query = 'Single context query';
+ const context = ['Only one message'];
+
+ const result = chainRewriteQuery(query, context);
+
+ expect(result.messages![0].content).toContain('Only one message');
+ });
+
+ it('should join multiple context items with newlines', () => {
+ const query = 'Multi context query';
+ const context = ['Message 1', 'Message 2', 'Message 3'];
+
+ const result = chainRewriteQuery(query, context);
+
+ expect(result.messages![0].content).toContain('Message 1\nMessage 2\nMessage 3');
+ });
+
+ it('should handle special characters in query', () => {
+ const query = 'Query with special chars: @#$%^&*()';
+ const context = ['Context'];
+
+ const result = chainRewriteQuery(query, context);
+
+ expect(result.messages![1].content).toContain(query);
+ });
+});
diff --git a/packages/prompts/src/chains/__tests__/summaryGenerationTitle.test.ts b/packages/prompts/src/chains/__tests__/summaryGenerationTitle.test.ts
new file mode 100644
index 0000000000..07f53b0641
--- /dev/null
+++ b/packages/prompts/src/chains/__tests__/summaryGenerationTitle.test.ts
@@ -0,0 +1,107 @@
+import { describe, expect, it } from 'vitest';
+
+import { chainSummaryGenerationTitle } from '../summaryGenerationTitle';
+
+describe('chainSummaryGenerationTitle', () => {
+ it('should generate correct chat payload for image modal', () => {
+ const prompts = ['A beautiful sunset', 'Mountain landscape'];
+ const modal = 'image' as const;
+ const locale = 'zh-CN';
+
+ const result = chainSummaryGenerationTitle(prompts, modal, locale);
+
+ expect(result.messages).toHaveLength(2);
+ expect(result.messages![0].role).toBe('system');
+ expect(result.messages![1].role).toBe('user');
+ expect(result.messages![0].content).toContain('AI image prompt');
+ expect(result.messages![0].content).toContain(locale);
+ });
+
+ it('should generate correct chat payload for video modal', () => {
+ const prompts = ['Dancing in the rain'];
+ const modal = 'video' as const;
+ const locale = 'en-US';
+
+ const result = chainSummaryGenerationTitle(prompts, modal, locale);
+
+ expect(result.messages![0].content).toContain('AI video prompt');
+ expect(result.messages![0].content).toContain(locale);
+ });
+
+ it('should format single prompt correctly', () => {
+ const prompts = ['Single prompt'];
+ const modal = 'image' as const;
+ const locale = 'zh-CN';
+
+ const result = chainSummaryGenerationTitle(prompts, modal, locale);
+
+ expect(result.messages![1].content).toContain('1. Single prompt');
+ });
+
+ it('should format multiple prompts with numbering', () => {
+ const prompts = ['First prompt', 'Second prompt', 'Third prompt'];
+ const modal = 'image' as const;
+ const locale = 'zh-CN';
+
+ const result = chainSummaryGenerationTitle(prompts, modal, locale);
+
+ const userMessage = result.messages![1].content;
+ expect(userMessage).toContain('1. First prompt');
+ expect(userMessage).toContain('2. Second prompt');
+ expect(userMessage).toContain('3. Third prompt');
+ });
+
+ it('should include system instructions about title requirements', () => {
+ const prompts = ['Test prompt'];
+ const modal = 'image' as const;
+ const locale = 'zh-CN';
+
+ const result = chainSummaryGenerationTitle(prompts, modal, locale);
+
+ const systemMessage = result.messages![0].content;
+ expect(systemMessage).toContain('θ΅ζ·±η AI θΊζ―εδ½θ
');
+ expect(systemMessage).toContain('10δΈͺεδ»₯ε
');
+ expect(systemMessage).toContain('δΈιθ¦ε
ε«ζ ηΉη¬¦ε·');
+ });
+
+ it('should handle empty prompts array', () => {
+ const prompts: string[] = [];
+ const modal = 'image' as const;
+ const locale = 'zh-CN';
+
+ const result = chainSummaryGenerationTitle(prompts, modal, locale);
+
+ expect(result.messages![1].content).toContain('ζη€Ίθ―οΌ\n');
+ });
+
+ it('should handle different locales', () => {
+ const prompts = ['Test'];
+ const modal = 'image' as const;
+ const customLocale = 'ja-JP';
+
+ const result = chainSummaryGenerationTitle(prompts, modal, customLocale);
+
+ expect(result.messages![0].content).toContain(customLocale);
+ });
+
+ it('should differentiate between image and video modals in instructions', () => {
+ const prompts = ['Test prompt'];
+ const locale = 'zh-CN';
+
+ const imageResult = chainSummaryGenerationTitle(prompts, 'image', locale);
+ const videoResult = chainSummaryGenerationTitle(prompts, 'video', locale);
+
+ expect(imageResult.messages![0].content).toContain('AI image prompt');
+ expect(videoResult.messages![0].content).toContain('AI video prompt');
+ });
+
+ it('should format prompts with newlines between them', () => {
+ const prompts = ['Prompt one', 'Prompt two'];
+ const modal = 'image' as const;
+ const locale = 'zh-CN';
+
+ const result = chainSummaryGenerationTitle(prompts, modal, locale);
+
+ expect(result.messages![1].content).toContain('1. Prompt one\n2. Prompt two');
+ });
+});
diff --git a/packages/prompts/src/chains/abstractChunk.ts b/packages/prompts/src/chains/abstractChunk.ts
index 73050738f1..4b0be07cd6 100644
--- a/packages/prompts/src/chains/abstractChunk.ts
+++ b/packages/prompts/src/chains/abstractChunk.ts
@@ -1,4 +1,3 @@
-import { DEFAULT_MODEL } from '@lobechat/const';
import { ChatStreamPayload } from '@lobechat/types';
export const chainAbstractChunkText = (text: string): Partial => {
@@ -14,6 +13,5 @@ export const chainAbstractChunkText = (text: string): Partial
role: 'user',
},
],
- model: DEFAULT_MODEL,
};
};
diff --git a/packages/prompts/src/chains/rewriteQuery.ts b/packages/prompts/src/chains/rewriteQuery.ts
index 11c341a565..f2f46dcb39 100644
--- a/packages/prompts/src/chains/rewriteQuery.ts
+++ b/packages/prompts/src/chains/rewriteQuery.ts
@@ -1,6 +1,8 @@
-import { DEFAULT_REWRITE_QUERY } from '@lobechat/const';
import { ChatStreamPayload } from '@lobechat/types';
+export const DEFAULT_REWRITE_QUERY =
+ 'Given the following conversation and a follow-up question, rephrase the follow up question to be a standalone question, in its original language. Keep as much details as possible from previous messages. Keep entity names and all.';
+
export const chainRewriteQuery = (
query: string,
context: string[],
diff --git a/packages/prompts/src/index.test.ts b/packages/prompts/src/index.test.ts
new file mode 100644
index 0000000000..92bd9cbe5d
--- /dev/null
+++ b/packages/prompts/src/index.test.ts
@@ -0,0 +1,41 @@
+import { describe, expect, it, vi } from 'vitest';
+
+import * as chains from './chains';
+import * as mainExports from './index';
+import * as prompts from './prompts';
+
+// Mock the problematic dependency
+vi.mock('@/locales/resources', () => ({
+ supportLocales: ['en-US', 'zh-CN'],
+}));
+
+describe('Main Index Export', () => {
+ it('should export all chains', () => {
+ expect(mainExports).toEqual(expect.objectContaining(chains));
+ });
+
+ it('should export all prompts', () => {
+ expect(mainExports).toEqual(expect.objectContaining(prompts));
+ });
+
+ it('should have all expected chain exports', () => {
+ const chainExports = [
+ 'chainAbstractChunkText',
+ 'chainAnswerWithContext',
+ 'chainLangDetect',
+ 'chainPickEmoji',
+ 'chainRewriteQuery',
+ 'chainSummaryAgentName',
+ 'chainSummaryDescription',
+ 'chainSummaryGenerationTitle',
+ 'chainSummaryHistory',
+ 'chainSummaryTags',
+ 'chainSummaryTitle',
+ 'chainTranslate',
+ ];
+
+ chainExports.forEach((exportName) => {
+ expect(mainExports).toHaveProperty(exportName);
+ });
+ });
+});
diff --git a/packages/prompts/src/prompts/systemRole/index.test.ts b/packages/prompts/src/prompts/systemRole/index.test.ts
new file mode 100644
index 0000000000..32cff26286
--- /dev/null
+++ b/packages/prompts/src/prompts/systemRole/index.test.ts
@@ -0,0 +1,136 @@
+import { describe, expect, it } from 'vitest';
+
+import { BuiltinSystemRolePrompts } from './index';
+
+describe('BuiltinSystemRolePrompts', () => {
+ it('should return welcome message only when only welcome is provided', () => {
+ const result = BuiltinSystemRolePrompts({
+ welcome: 'Welcome to the assistant!',
+ });
+
+ expect(result).toBe('Welcome to the assistant!');
+ });
+
+ it('should return plugins message only when only plugins is provided', () => {
+ const result = BuiltinSystemRolePrompts({
+ plugins: 'Available plugins: calculator, weather',
+ });
+
+ expect(result).toBe('Available plugins: calculator, weather');
+ });
+
+ it('should return history summary when only history is provided', () => {
+ const result = BuiltinSystemRolePrompts({
+ historySummary: 'User discussed AI topics previously',
+ });
+
+ expect(result).toContain('');
+ expect(result).toContain(
+ 'Users may have lots of chat messages, here is the summary of the history:',
+ );
+ expect(result).toContain('User discussed AI topics previously');
+ expect(result).toContain('');
+ expect(result.trim()).toMatch(/^[\s\S]*<\/chat_history_summary>$/);
+ });
+
+ it('should combine all three parts when all are provided', () => {
+ const result = BuiltinSystemRolePrompts({
+ welcome: 'Welcome!',
+ plugins: 'Plugins available',
+ historySummary: 'Previous conversation summary',
+ });
+
+ expect(result).toContain('Welcome!');
+ expect(result).toContain('Plugins available');
+ expect(result).toContain('');
+ expect(result).toContain('Previous conversation summary');
+ expect(result).toContain('');
+
+ // Should be joined with double newlines
+ const parts = result.split('\n\n');
+ expect(parts).toHaveLength(3);
+ });
+
+ it('should combine welcome and plugins when no history is provided', () => {
+ const result = BuiltinSystemRolePrompts({
+ welcome: 'Hello user!',
+ plugins: 'Available tools',
+ });
+
+ expect(result).toBe('Hello user!\n\nAvailable tools');
+ });
+
+ it('should combine welcome and history when no plugins provided', () => {
+ const result = BuiltinSystemRolePrompts({
+ welcome: 'Greetings!',
+ historySummary: 'Chat history here',
+ });
+
+ expect(result).toContain('Greetings!');
+ expect(result).toContain('');
+ expect(result).toContain('Chat history here');
+ });
+
+ it('should combine plugins and history when no welcome provided', () => {
+ const result = BuiltinSystemRolePrompts({
+ plugins: 'Tool list',
+ historySummary: 'Summary of previous chats',
+ });
+
+ expect(result).toContain('Tool list');
+ expect(result).toContain('');
+ expect(result).toContain('Summary of previous chats');
+ });
+
+ it('should return empty string when no parameters provided', () => {
+ const result = BuiltinSystemRolePrompts({});
+
+ expect(result).toBe('');
+ });
+
+ it('should filter out falsy values', () => {
+ const result = BuiltinSystemRolePrompts({
+ welcome: '',
+ plugins: 'Valid plugins',
+ historySummary: undefined,
+ });
+
+ expect(result).toBe('Valid plugins');
+ });
+
+ it('should handle null and undefined values gracefully', () => {
+ const result = BuiltinSystemRolePrompts({
+ welcome: undefined,
+ plugins: null as any,
+ historySummary: 'Valid history',
+ });
+
+ expect(result).toContain('');
+ expect(result).toContain('Valid history');
+ expect(result).toContain('');
+ });
+
+ it('should preserve whitespace in individual components', () => {
+ const result = BuiltinSystemRolePrompts({
+ welcome: 'Welcome\nwith newlines',
+ plugins: 'Plugins\twith tabs',
+ });
+
+ expect(result).toContain('Welcome\nwith newlines');
+ expect(result).toContain('Plugins\twith tabs');
+ });
+
+ it('should format history summary with proper XML structure', () => {
+ const historySummary = 'User asked about weather and traffic';
+ const result = BuiltinSystemRolePrompts({
+ historySummary,
+ });
+
+ expect(result).toContain('');
+ expect(result).toContain(
+ 'Users may have lots of chat messages, here is the summary of the history:',
+ );
+ expect(result).toContain('User asked about weather and traffic');
+ expect(result).toContain('');
+ });
+});
diff --git a/packages/prompts/vitest.config.mts b/packages/prompts/vitest.config.mts
index a9f5456ffd..940870a78e 100644
--- a/packages/prompts/vitest.config.mts
+++ b/packages/prompts/vitest.config.mts
@@ -2,6 +2,9 @@ import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
+ coverage: {
+ reporter: ['text', 'json', 'lcov', 'text-summary'],
+ },
environment: 'happy-dom',
},
});
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 723d925b18..44895d8e6d 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -1,3 +1,4 @@
+export * from './agent';
export * from './artifact';
export * from './chunk';
export * from './clientDB';
@@ -6,6 +7,7 @@ export * from './fetch';
export * from './knowledgeBase';
export * from './llm';
export * from './message';
+export * from './meta';
export * from './user';
export * from './user/settings';
// FIXME: I think we need a refactor for the "openai" types
diff --git a/packages/utils/package.json b/packages/utils/package.json
index a5eb0b6ea0..b1424eb836 100644
--- a/packages/utils/package.json
+++ b/packages/utils/package.json
@@ -2,7 +2,11 @@
"name": "@lobechat/utils",
"version": "1.0.0",
"private": true,
- "main": "./src/index.ts",
+ "exports": {
+ ".": "./src/index.ts",
+ "./server": "./src/server/index.ts",
+ "./client": "./src/client/index.ts"
+ },
"scripts": {
"test": "vitest",
"test:coverage": "vitest --coverage"
diff --git a/packages/utils/src/server/index.ts b/packages/utils/src/server/index.ts
new file mode 100644
index 0000000000..ad406733e8
--- /dev/null
+++ b/packages/utils/src/server/index.ts
@@ -0,0 +1,5 @@
+export * from './auth';
+export * from './correctOIDCUrl';
+export * from './geo';
+export * from './responsive';
+export * from './xor';
diff --git a/packages/utils/vitest.config.mts b/packages/utils/vitest.config.mts
index 8b33852d6e..89d01f885f 100644
--- a/packages/utils/vitest.config.mts
+++ b/packages/utils/vitest.config.mts
@@ -10,6 +10,9 @@ export default defineConfig({
'@': resolve(__dirname, '../../src'),
/* eslint-enable */
},
+ coverage: {
+ reporter: ['text', 'json', 'lcov', 'text-summary'],
+ },
environment: 'happy-dom',
setupFiles: join(__dirname, './tests/setup.ts'),
},
diff --git a/src/app/(backend)/middleware/auth/index.test.ts b/src/app/(backend)/middleware/auth/index.test.ts
index f031413f6b..e7dbeec24a 100644
--- a/src/app/(backend)/middleware/auth/index.test.ts
+++ b/src/app/(backend)/middleware/auth/index.test.ts
@@ -1,9 +1,9 @@
import { AgentRuntimeError } from '@lobechat/model-runtime';
import { ChatErrorType } from '@lobechat/types';
+import { getXorPayload } from '@lobechat/utils/server';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { createErrorResponse } from '@/utils/errorResponse';
-import { getXorPayload } from '@/utils/server/xor';
import { RequestHandler, checkAuth } from './index';
import { checkAuthMethod } from './utils';
@@ -20,7 +20,7 @@ vi.mock('./utils', () => ({
checkAuthMethod: vi.fn(),
}));
-vi.mock('@/utils/server/xor', () => ({
+vi.mock('@lobechat/utils/server', () => ({
getXorPayload: vi.fn(),
}));
diff --git a/src/app/(backend)/middleware/auth/index.ts b/src/app/(backend)/middleware/auth/index.ts
index 424393b6c4..efc3a3b986 100644
--- a/src/app/(backend)/middleware/auth/index.ts
+++ b/src/app/(backend)/middleware/auth/index.ts
@@ -5,6 +5,7 @@ import {
ModelRuntime,
} from '@lobechat/model-runtime';
import { ChatErrorType } from '@lobechat/types';
+import { getXorPayload } from '@lobechat/utils/server';
import { NextRequest } from 'next/server';
import {
@@ -17,7 +18,6 @@ import {
import { ClerkAuth } from '@/libs/clerk-auth';
import { validateOIDCJWT } from '@/libs/oidc-provider/jwt';
import { createErrorResponse } from '@/utils/errorResponse';
-import { getXorPayload } from '@/utils/server/xor';
import { checkAuthMethod } from './utils';
diff --git a/src/app/(backend)/oidc/consent/route.ts b/src/app/(backend)/oidc/consent/route.ts
index 21e5ca85ed..4166f27932 100644
--- a/src/app/(backend)/oidc/consent/route.ts
+++ b/src/app/(backend)/oidc/consent/route.ts
@@ -1,9 +1,8 @@
+import { correctOIDCUrl, getUserAuth } from '@lobechat/utils/server';
import debug from 'debug';
import { NextRequest, NextResponse } from 'next/server';
import { OIDCService } from '@/server/services/oidc';
-import { getUserAuth } from '@/utils/server/auth';
-import { correctOIDCUrl } from '@/utils/server/correctOIDCUrl';
const log = debug('lobe-oidc:consent');
diff --git a/src/app/(backend)/webapi/chat/[provider]/route.test.ts b/src/app/(backend)/webapi/chat/[provider]/route.test.ts
index f0fdfe33b6..31d6cda8d1 100644
--- a/src/app/(backend)/webapi/chat/[provider]/route.test.ts
+++ b/src/app/(backend)/webapi/chat/[provider]/route.test.ts
@@ -2,11 +2,11 @@
import { getAuth } from '@clerk/nextjs/server';
import { LobeRuntimeAI, ModelRuntime } from '@lobechat/model-runtime';
import { ChatErrorType } from '@lobechat/types';
+import { getXorPayload } from '@lobechat/utils/server';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { checkAuthMethod } from '@/app/(backend)/middleware/auth/utils';
import { LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED } from '@/const/auth';
-import { getXorPayload } from '@/utils/server/xor';
import { POST } from './route';
@@ -18,7 +18,7 @@ vi.mock('@/app/(backend)/middleware/auth/utils', () => ({
checkAuthMethod: vi.fn(),
}));
-vi.mock('@/utils/server/xor', () => ({
+vi.mock('@lobechat/utils/server', () => ({
getXorPayload: vi.fn(),
}));
diff --git a/src/app/(backend)/webapi/plugin/gateway/route.ts b/src/app/(backend)/webapi/plugin/gateway/route.ts
index 8b23693b8b..23ba98f3e0 100644
--- a/src/app/(backend)/webapi/plugin/gateway/route.ts
+++ b/src/app/(backend)/webapi/plugin/gateway/route.ts
@@ -1,5 +1,6 @@
import { AgentRuntimeError } from '@lobechat/model-runtime';
import { ChatErrorType, ErrorType, TraceNameMap } from '@lobechat/types';
+import { getXorPayload } from '@lobechat/utils/server';
import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
import { createGatewayOnEdgeRuntime } from '@lobehub/chat-plugins-gateway';
@@ -8,7 +9,6 @@ import { LOBE_CHAT_TRACE_ID } from '@/const/trace';
import { getAppConfig } from '@/envs/app';
import { TraceClient } from '@/libs/traces';
import { createErrorResponse } from '@/utils/errorResponse';
-import { getXorPayload } from '@/utils/server/xor';
import { getTracePayload } from '@/utils/trace';
import { parserPluginSettings } from './settings';
diff --git a/src/app/[variants]/(main)/files/[id]/page.tsx b/src/app/[variants]/(main)/files/[id]/page.tsx
index 3fdfc0f449..75b6cb24e2 100644
--- a/src/app/[variants]/(main)/files/[id]/page.tsx
+++ b/src/app/[variants]/(main)/files/[id]/page.tsx
@@ -1,3 +1,4 @@
+import { getUserAuth } from '@lobechat/utils/server';
import { notFound } from 'next/navigation';
import { Flexbox } from 'react-layout-kit';
@@ -5,7 +6,6 @@ import FileViewer from '@/features/FileViewer';
import { createCallerFactory } from '@/libs/trpc/lambda';
import { lambdaRouter } from '@/server/routers/lambda';
import { PagePropsWithId } from '@/types/next';
-import { getUserAuth } from '@/utils/server/auth';
import FileDetail from '../features/FileDetail';
import Header from './Header';
diff --git a/src/app/[variants]/(main)/settings/sync/page.tsx b/src/app/[variants]/(main)/settings/sync/page.tsx
index 72ab30e11d..705a062241 100644
--- a/src/app/[variants]/(main)/settings/sync/page.tsx
+++ b/src/app/[variants]/(main)/settings/sync/page.tsx
@@ -1,10 +1,10 @@
+import { gerServerDeviceInfo } from '@lobechat/utils/server';
import { notFound } from 'next/navigation';
import { serverFeatureFlags } from '@/config/featureFlags';
import { metadataModule } from '@/server/metadata';
import { translation } from '@/server/translation';
import { DynamicLayoutProps } from '@/types/next';
-import { gerServerDeviceInfo } from '@/utils/server/responsive';
import { RouteVariants } from '@/utils/server/routeVariants';
import Page from './index';
diff --git a/src/app/[variants]/(main)/settings/system-agent/index.tsx b/src/app/[variants]/(main)/settings/system-agent/index.tsx
index 5ffe9e34fe..71dabb41df 100644
--- a/src/app/[variants]/(main)/settings/system-agent/index.tsx
+++ b/src/app/[variants]/(main)/settings/system-agent/index.tsx
@@ -1,6 +1,7 @@
'use client';
-import { DEFAULT_REWRITE_QUERY } from '@/const/settings';
+import { DEFAULT_REWRITE_QUERY } from '@lobechat/prompts';
+
import { isServerMode } from '@/const/version';
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
diff --git a/packages/const/src/locale.ts b/src/const/locale.ts
similarity index 100%
rename from packages/const/src/locale.ts
rename to src/const/locale.ts
diff --git a/src/libs/oidc-provider/adapter.ts b/src/libs/oidc-provider/adapter.ts
index 165b8bf0c3..9a52b57b67 100644
--- a/src/libs/oidc-provider/adapter.ts
+++ b/src/libs/oidc-provider/adapter.ts
@@ -164,7 +164,7 @@ class OIDCAdapter {
log('[%s] Setting userId: %s', this.name, payload.accountId);
} else {
try {
- const { getUserAuth } = await import('@/utils/server/auth');
+ const { getUserAuth } = await import('@lobechat/utils/server');
try {
const { userId } = await getUserAuth();
if (userId) {
diff --git a/src/libs/trpc/edge/middleware/jwtPayload.test.ts b/src/libs/trpc/edge/middleware/jwtPayload.test.ts
index 432dda1497..41a83100bb 100644
--- a/src/libs/trpc/edge/middleware/jwtPayload.test.ts
+++ b/src/libs/trpc/edge/middleware/jwtPayload.test.ts
@@ -1,11 +1,11 @@
// @vitest-environment node
+import * as utils from '@lobechat/utils/server';
import { TRPCError } from '@trpc/server';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createCallerFactory } from '@/libs/trpc/edge';
import { AuthContext, createContextInner } from '@/libs/trpc/edge/context';
import { edgeTrpc as trpc } from '@/libs/trpc/edge/init';
-import * as utils from '@/utils/server/xor';
import { jwtPayloadChecker } from './jwtPayload';
diff --git a/src/libs/trpc/edge/middleware/jwtPayload.ts b/src/libs/trpc/edge/middleware/jwtPayload.ts
index 8c7a847500..82b3c4b27f 100644
--- a/src/libs/trpc/edge/middleware/jwtPayload.ts
+++ b/src/libs/trpc/edge/middleware/jwtPayload.ts
@@ -1,7 +1,6 @@
+import { getXorPayload } from '@lobechat/utils/server';
import { TRPCError } from '@trpc/server';
-import { getXorPayload } from '@/utils/server/xor';
-
import { edgeTrpc } from '../init';
export const jwtPayloadChecker = edgeTrpc.middleware(async (opts) => {
diff --git a/src/libs/trpc/lambda/middleware/keyVaults.ts b/src/libs/trpc/lambda/middleware/keyVaults.ts
index a928ffda86..54f0714564 100644
--- a/src/libs/trpc/lambda/middleware/keyVaults.ts
+++ b/src/libs/trpc/lambda/middleware/keyVaults.ts
@@ -1,7 +1,6 @@
+import { getXorPayload } from '@lobechat/utils/server';
import { TRPCError } from '@trpc/server';
-import { getXorPayload } from '@/utils/server/xor';
-
import { trpc } from '../init';
export const keyVaults = trpc.middleware(async (opts) => {
diff --git a/src/middleware.ts b/src/middleware.ts
index 88a85f5dcc..854f058da9 100644
--- a/src/middleware.ts
+++ b/src/middleware.ts
@@ -1,4 +1,5 @@
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
+import { parseDefaultThemeFromCountry } from '@lobechat/utils/server';
import debug from 'debug';
import { NextRequest, NextResponse } from 'next/server';
import { UAParser } from 'ua-parser-js';
@@ -11,11 +12,10 @@ import { LOBE_THEME_APPEARANCE } from '@/const/theme';
import { appEnv } from '@/envs/app';
import NextAuthEdge from '@/libs/next-auth/edge';
import { Locales } from '@/locales/resources';
-import { parseBrowserLanguage } from '@/utils/locale';
-import { parseDefaultThemeFromCountry } from '@/utils/server/geo';
-import { RouteVariants } from '@/utils/server/routeVariants';
import { oidcEnv } from './envs/oidc';
+import { parseBrowserLanguage } from './utils/locale';
+import { RouteVariants } from './utils/server/routeVariants';
// Create debug logger instances
const logDefault = debug('middleware:default');
diff --git a/src/server/routers/tools/search.test.ts b/src/server/routers/tools/search.test.ts
index 8c96a7428d..d61110b019 100644
--- a/src/server/routers/tools/search.test.ts
+++ b/src/server/routers/tools/search.test.ts
@@ -9,7 +9,7 @@ import { SEARCH_SEARXNG_NOT_CONFIG } from '@/types/tool/search';
import { searchRouter } from './search';
// Mock JWT verification
-vi.mock('@/utils/server/xor', () => ({
+vi.mock('@lobechat/utils/server', () => ({
getXorPayload: vi.fn().mockReturnValue({ userId: '1' }),
}));
diff --git a/src/utils/client/switchLang.ts b/src/utils/client/switchLang.ts
index 34e7739adb..2a8f1377cf 100644
--- a/src/utils/client/switchLang.ts
+++ b/src/utils/client/switchLang.ts
@@ -1,7 +1,7 @@
-import { LOBE_LOCALE_COOKIE } from '@lobechat/const';
import { setCookie } from '@lobechat/utils';
import { changeLanguage } from 'i18next';
+import { LOBE_LOCALE_COOKIE } from '@/const/locale';
import { LocaleMode } from '@/types/locale';
export const switchLang = (locale: LocaleMode) => {
diff --git a/packages/utils/src/locale.test.ts b/src/utils/locale.test.ts
similarity index 100%
rename from packages/utils/src/locale.test.ts
rename to src/utils/locale.test.ts
diff --git a/packages/utils/src/locale.ts b/src/utils/locale.ts
similarity index 100%
rename from packages/utils/src/locale.ts
rename to src/utils/locale.ts
diff --git a/packages/utils/src/server/pageProps.ts b/src/utils/server/pageProps.ts
similarity index 85%
rename from packages/utils/src/server/pageProps.ts
rename to src/utils/server/pageProps.ts
index a8a0813afc..a751c4c2ae 100644
--- a/packages/utils/src/server/pageProps.ts
+++ b/src/utils/server/pageProps.ts
@@ -1,6 +1,7 @@
import { translation } from '@/server/translation';
import { DynamicLayoutProps } from '@/types/next';
-import { RouteVariants } from '@/utils/server/routeVariants';
+
+import { RouteVariants } from './routeVariants';
export const parsePageMetaProps = async (props: DynamicLayoutProps) => {
const { locale: hl, isMobile } = await RouteVariants.getVariantsFromProps(props);
diff --git a/packages/utils/src/server/routeVariants.ts b/src/utils/server/routeVariants.ts
similarity index 100%
rename from packages/utils/src/server/routeVariants.ts
rename to src/utils/server/routeVariants.ts
diff --git a/tsconfig.json b/tsconfig.json
index d7acc0a795..982f275d52 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -21,7 +21,7 @@
"@/libs/model-runtime": ["./packages/model-runtime/src/index.ts"],
"@/libs/model-runtime/*": ["./packages/model-runtime/src/*"],
"@/database/*": ["./packages/database/src/*", "./src/database/*"],
- "@/const/*": ["./packages/const/src/*"],
+ "@/const/*": ["./packages/const/src/*", "./src/const/*"],
"@/utils/*": ["./packages/utils/src/*", "./src/utils/*"],
"@/types/*": ["./packages/types/src/*", "./src/types/*"],
"@/*": ["./src/*"],
diff --git a/vitest.config.mts b/vitest.config.mts
index e66fc00a9a..5870fbc037 100644
--- a/vitest.config.mts
+++ b/vitest.config.mts
@@ -13,6 +13,7 @@ export default defineConfig({
'@/database/_deprecated': resolve(__dirname, './src/database/_deprecated'),
'@/database': resolve(__dirname, './packages/database/src'),
'@/utils/client/switchLang': resolve(__dirname, './src/utils/client/switchLang'),
+ '@/const/locale': resolve(__dirname, './src/const/locale'),
// TODO: after refactor the errorResponse, we can remove it
'@/utils/errorResponse': resolve(__dirname, './src/utils/errorResponse'),
'@/utils': resolve(__dirname, './packages/utils/src'),