@lobehub/lobehub 2.0.0-next.360 → 2.0.0-next.362
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 +50 -0
- package/Dockerfile +2 -1
- package/changelog/v1.json +14 -0
- package/locales/en-US/chat.json +3 -1
- package/locales/zh-CN/chat.json +2 -0
- package/package.json +1 -1
- package/packages/const/src/userMemory.ts +1 -0
- package/packages/context-engine/src/base/BaseEveryUserContentProvider.ts +204 -0
- package/packages/context-engine/src/base/BaseLastUserContentProvider.ts +1 -8
- package/packages/context-engine/src/base/__tests__/BaseEveryUserContentProvider.test.ts +354 -0
- package/packages/context-engine/src/base/constants.ts +20 -0
- package/packages/context-engine/src/engine/messages/MessagesEngine.ts +27 -23
- package/packages/context-engine/src/engine/messages/__tests__/MessagesEngine.test.ts +364 -0
- package/packages/context-engine/src/providers/PageEditorContextInjector.ts +17 -13
- package/packages/context-engine/src/providers/PageSelectionsInjector.ts +65 -0
- package/packages/context-engine/src/providers/__tests__/PageSelectionsInjector.test.ts +333 -0
- package/packages/context-engine/src/providers/index.ts +3 -1
- package/packages/database/src/models/userMemory/model.ts +178 -3
- package/packages/database/src/models/userMemory/sources/benchmarkLoCoMo.ts +1 -1
- package/packages/memory-user-memory/package.json +2 -1
- package/packages/memory-user-memory/promptfoo/evals/activity/basic/buildMessages.ts +40 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/basic/eval.yaml +13 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/basic/prompt.ts +5 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/basic/tests/cases.ts +106 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/locomo/buildMessages.ts +104 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/locomo/eval.yaml +13 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/locomo/prompt.ts +5 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/locomo/tests/benchmark-locomo-payload-conv-26.json +149 -0
- package/packages/memory-user-memory/promptfoo/evals/activity/locomo/tests/cases.ts +72 -0
- package/packages/memory-user-memory/promptfoo/response-formats/activity.json +370 -0
- package/packages/memory-user-memory/promptfoo/response-formats/experience.json +14 -0
- package/packages/memory-user-memory/promptfoo/response-formats/identity.json +281 -255
- package/packages/memory-user-memory/promptfooconfig.yaml +1 -0
- package/packages/memory-user-memory/scripts/generate-response-formats.ts +26 -2
- package/packages/memory-user-memory/src/extractors/activity.ts +44 -0
- package/packages/memory-user-memory/src/extractors/gatekeeper.test.ts +2 -1
- package/packages/memory-user-memory/src/extractors/gatekeeper.ts +2 -1
- package/packages/memory-user-memory/src/extractors/index.ts +1 -0
- package/packages/memory-user-memory/src/prompts/gatekeeper.ts +3 -3
- package/packages/memory-user-memory/src/prompts/index.ts +7 -1
- package/packages/memory-user-memory/src/prompts/layers/activity.ts +90 -0
- package/packages/memory-user-memory/src/prompts/layers/index.ts +1 -0
- package/packages/memory-user-memory/src/providers/existingUserMemory.test.ts +25 -1
- package/packages/memory-user-memory/src/providers/existingUserMemory.ts +113 -0
- package/packages/memory-user-memory/src/schemas/activity.ts +315 -0
- package/packages/memory-user-memory/src/schemas/experience.ts +5 -5
- package/packages/memory-user-memory/src/schemas/gatekeeper.ts +1 -0
- package/packages/memory-user-memory/src/schemas/index.ts +1 -0
- package/packages/memory-user-memory/src/services/extractExecutor.ts +29 -0
- package/packages/memory-user-memory/src/types.ts +7 -0
- package/packages/prompts/src/agents/index.ts +1 -0
- package/packages/prompts/src/agents/pageSelectionContext.ts +28 -0
- package/packages/types/src/aiChat.ts +4 -0
- package/packages/types/src/message/common/index.ts +1 -0
- package/packages/types/src/message/common/metadata.ts +8 -0
- package/packages/types/src/message/common/pageSelection.ts +36 -0
- package/packages/types/src/message/ui/params.ts +16 -0
- package/packages/types/src/serverConfig.ts +1 -1
- package/packages/types/src/userMemory/layers.ts +52 -0
- package/packages/types/src/userMemory/list.ts +20 -2
- package/packages/types/src/userMemory/shared.ts +22 -1
- package/packages/types/src/userMemory/trace.ts +1 -0
- package/packages/types/src/util.ts +9 -1
- package/scripts/prebuild.mts +1 -0
- package/src/features/ChatInput/Desktop/ContextContainer/ContextList.tsx +1 -1
- package/src/features/Conversation/ChatInput/index.tsx +9 -1
- package/src/features/Conversation/Messages/User/components/MessageContent.tsx +7 -1
- package/src/features/Conversation/Messages/User/components/PageSelections.tsx +62 -0
- package/src/features/PageEditor/EditorCanvas/useAskCopilotItem.tsx +5 -1
- package/src/libs/next/proxy/define-config.ts +1 -0
- package/src/locales/default/chat.ts +3 -2
- package/src/server/globalConfig/parseMemoryExtractionConfig.ts +7 -1
- package/src/server/routers/lambda/aiChat.ts +7 -0
- package/src/server/services/memory/userMemory/__tests__/extract.runtime.test.ts +2 -0
- package/src/server/services/memory/userMemory/extract.ts +108 -7
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +5 -19
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import type { Message, PipelineContext } from '../../types';
|
|
4
|
+
import { BaseEveryUserContentProvider } from '../BaseEveryUserContentProvider';
|
|
5
|
+
|
|
6
|
+
class TestEveryUserContentProvider extends BaseEveryUserContentProvider {
|
|
7
|
+
readonly name = 'TestEveryUserContentProvider';
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
private contentBuilder?: (
|
|
11
|
+
message: Message,
|
|
12
|
+
index: number,
|
|
13
|
+
isLastUser: boolean,
|
|
14
|
+
) => { content: string; contextType: string } | null,
|
|
15
|
+
) {
|
|
16
|
+
super();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
protected buildContentForMessage(
|
|
20
|
+
message: Message,
|
|
21
|
+
index: number,
|
|
22
|
+
isLastUser: boolean,
|
|
23
|
+
): { content: string; contextType: string } | null {
|
|
24
|
+
if (this.contentBuilder) {
|
|
25
|
+
return this.contentBuilder(message, index, isLastUser);
|
|
26
|
+
}
|
|
27
|
+
// Default: inject content for every user message
|
|
28
|
+
return {
|
|
29
|
+
content: `Content for message ${index}`,
|
|
30
|
+
contextType: 'test_context',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Expose protected methods for testing
|
|
35
|
+
testHasSystemContextWrapper(content: string | any[]) {
|
|
36
|
+
return this.hasSystemContextWrapper(content);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
testWrapWithSystemContext(content: string, contextType: string) {
|
|
40
|
+
return this.wrapWithSystemContext(content, contextType);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
testCreateContextBlock(content: string, contextType: string) {
|
|
44
|
+
return this.createContextBlock(content, contextType);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
testAppendToMessage(message: Message, content: string, contextType: string) {
|
|
48
|
+
return this.appendToMessage(message, content, contextType);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
testFindLastUserMessageIndex(messages: Message[]) {
|
|
52
|
+
return this.findLastUserMessageIndex(messages);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('BaseEveryUserContentProvider', () => {
|
|
57
|
+
const createContext = (messages: any[] = []): PipelineContext => ({
|
|
58
|
+
initialState: {
|
|
59
|
+
messages: [],
|
|
60
|
+
model: 'test-model',
|
|
61
|
+
provider: 'test-provider',
|
|
62
|
+
},
|
|
63
|
+
isAborted: false,
|
|
64
|
+
messages,
|
|
65
|
+
metadata: {
|
|
66
|
+
maxTokens: 4000,
|
|
67
|
+
model: 'test-model',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('findLastUserMessageIndex', () => {
|
|
72
|
+
it('should find the last user message', () => {
|
|
73
|
+
const provider = new TestEveryUserContentProvider();
|
|
74
|
+
const messages = [
|
|
75
|
+
{ content: 'Hello', role: 'user' },
|
|
76
|
+
{ content: 'Hi', role: 'assistant' },
|
|
77
|
+
{ content: 'Question', role: 'user' },
|
|
78
|
+
{ content: 'Answer', role: 'assistant' },
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
expect(provider.testFindLastUserMessageIndex(messages)).toBe(2);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should return -1 when no user messages exist', () => {
|
|
85
|
+
const provider = new TestEveryUserContentProvider();
|
|
86
|
+
const messages = [{ content: 'System', role: 'system' }];
|
|
87
|
+
|
|
88
|
+
expect(provider.testFindLastUserMessageIndex(messages)).toBe(-1);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('hasSystemContextWrapper', () => {
|
|
93
|
+
it('should detect existing system context wrapper in string content', () => {
|
|
94
|
+
const provider = new TestEveryUserContentProvider();
|
|
95
|
+
|
|
96
|
+
const withWrapper = `Question
|
|
97
|
+
<!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->
|
|
98
|
+
<test>content</test>
|
|
99
|
+
<!-- END SYSTEM CONTEXT -->`;
|
|
100
|
+
|
|
101
|
+
const withoutWrapper = 'Simple question';
|
|
102
|
+
|
|
103
|
+
expect(provider.testHasSystemContextWrapper(withWrapper)).toBe(true);
|
|
104
|
+
expect(provider.testHasSystemContextWrapper(withoutWrapper)).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should detect existing system context wrapper in array content', () => {
|
|
108
|
+
const provider = new TestEveryUserContentProvider();
|
|
109
|
+
|
|
110
|
+
const withWrapper = [
|
|
111
|
+
{
|
|
112
|
+
text: `Question
|
|
113
|
+
<!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->
|
|
114
|
+
<test>content</test>
|
|
115
|
+
<!-- END SYSTEM CONTEXT -->`,
|
|
116
|
+
type: 'text',
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
const withoutWrapper = [{ text: 'Simple question', type: 'text' }];
|
|
121
|
+
|
|
122
|
+
expect(provider.testHasSystemContextWrapper(withWrapper)).toBe(true);
|
|
123
|
+
expect(provider.testHasSystemContextWrapper(withoutWrapper)).toBe(false);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('wrapWithSystemContext', () => {
|
|
128
|
+
it('should wrap content with system context markers', () => {
|
|
129
|
+
const provider = new TestEveryUserContentProvider();
|
|
130
|
+
const result = provider.testWrapWithSystemContext('Test content', 'test_type');
|
|
131
|
+
|
|
132
|
+
expect(result).toContain('<!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->');
|
|
133
|
+
expect(result).toContain('<context.instruction>');
|
|
134
|
+
expect(result).toContain('<test_type>');
|
|
135
|
+
expect(result).toContain('Test content');
|
|
136
|
+
expect(result).toContain('</test_type>');
|
|
137
|
+
expect(result).toContain('<!-- END SYSTEM CONTEXT -->');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('createContextBlock', () => {
|
|
142
|
+
it('should create context block without wrapper', () => {
|
|
143
|
+
const provider = new TestEveryUserContentProvider();
|
|
144
|
+
const result = provider.testCreateContextBlock('Block content', 'block_type');
|
|
145
|
+
|
|
146
|
+
expect(result).toBe(`<block_type>
|
|
147
|
+
Block content
|
|
148
|
+
</block_type>`);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('appendToMessage', () => {
|
|
153
|
+
it('should append with new wrapper to string content without existing wrapper', () => {
|
|
154
|
+
const provider = new TestEveryUserContentProvider();
|
|
155
|
+
const message: Message = { content: 'Original question', role: 'user' };
|
|
156
|
+
|
|
157
|
+
const result = provider.testAppendToMessage(message, 'New content', 'new_type');
|
|
158
|
+
|
|
159
|
+
expect(result.content).toContain('Original question');
|
|
160
|
+
expect(result.content).toContain('<!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->');
|
|
161
|
+
expect(result.content).toContain('<new_type>');
|
|
162
|
+
expect(result.content).toContain('New content');
|
|
163
|
+
expect(result.content).toContain('<!-- END SYSTEM CONTEXT -->');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should insert into existing wrapper in string content', () => {
|
|
167
|
+
const provider = new TestEveryUserContentProvider();
|
|
168
|
+
const message: Message = {
|
|
169
|
+
content: `Original question
|
|
170
|
+
|
|
171
|
+
<!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->
|
|
172
|
+
<context.instruction>...</context.instruction>
|
|
173
|
+
<existing_type>
|
|
174
|
+
Existing content
|
|
175
|
+
</existing_type>
|
|
176
|
+
<!-- END SYSTEM CONTEXT -->`,
|
|
177
|
+
role: 'user',
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const result = provider.testAppendToMessage(message, 'New content', 'new_type');
|
|
181
|
+
|
|
182
|
+
// Should have only one SYSTEM CONTEXT wrapper
|
|
183
|
+
const content = result.content as string;
|
|
184
|
+
const startCount = (content.match(/<!-- SYSTEM CONTEXT/g) || []).length;
|
|
185
|
+
const endCount = (content.match(/<!-- END SYSTEM CONTEXT/g) || []).length;
|
|
186
|
+
|
|
187
|
+
expect(startCount).toBe(1);
|
|
188
|
+
expect(endCount).toBe(1);
|
|
189
|
+
expect(content).toContain('<existing_type>');
|
|
190
|
+
expect(content).toContain('<new_type>');
|
|
191
|
+
expect(content).toContain('New content');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should handle array content without existing wrapper', () => {
|
|
195
|
+
const provider = new TestEveryUserContentProvider();
|
|
196
|
+
const message: Message = {
|
|
197
|
+
content: [
|
|
198
|
+
{ text: 'Original question', type: 'text' },
|
|
199
|
+
{ image_url: { url: 'http://example.com/img.png' }, type: 'image_url' },
|
|
200
|
+
],
|
|
201
|
+
role: 'user',
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const result = provider.testAppendToMessage(message, 'New content', 'new_type');
|
|
205
|
+
|
|
206
|
+
expect(result.content[0].text).toContain('Original question');
|
|
207
|
+
expect(result.content[0].text).toContain('<!-- SYSTEM CONTEXT');
|
|
208
|
+
expect(result.content[0].text).toContain('<new_type>');
|
|
209
|
+
expect(result.content[1].type).toBe('image_url');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should add new text part when array content has no text part', () => {
|
|
213
|
+
const provider = new TestEveryUserContentProvider();
|
|
214
|
+
const message: Message = {
|
|
215
|
+
content: [{ image_url: { url: 'http://example.com/img.png' }, type: 'image_url' }],
|
|
216
|
+
role: 'user',
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const result = provider.testAppendToMessage(message, 'New content', 'new_type');
|
|
220
|
+
|
|
221
|
+
expect(result.content).toHaveLength(2);
|
|
222
|
+
expect(result.content[1].type).toBe('text');
|
|
223
|
+
expect(result.content[1].text).toContain('<!-- SYSTEM CONTEXT');
|
|
224
|
+
expect(result.content[1].text).toContain('New content');
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('process integration', () => {
|
|
229
|
+
it('should inject content to all user messages', async () => {
|
|
230
|
+
const provider = new TestEveryUserContentProvider();
|
|
231
|
+
const context = createContext([
|
|
232
|
+
{ content: 'First question', role: 'user' },
|
|
233
|
+
{ content: 'First answer', role: 'assistant' },
|
|
234
|
+
{ content: 'Second question', role: 'user' },
|
|
235
|
+
{ content: 'Second answer', role: 'assistant' },
|
|
236
|
+
{ content: 'Third question', role: 'user' },
|
|
237
|
+
]);
|
|
238
|
+
|
|
239
|
+
const result = await provider.process(context);
|
|
240
|
+
|
|
241
|
+
// All user messages should have content injected
|
|
242
|
+
expect(result.messages[0].content).toContain('First question');
|
|
243
|
+
expect(result.messages[0].content).toContain('<test_context>');
|
|
244
|
+
expect(result.messages[0].content).toContain('Content for message 0');
|
|
245
|
+
|
|
246
|
+
expect(result.messages[2].content).toContain('Second question');
|
|
247
|
+
expect(result.messages[2].content).toContain('<test_context>');
|
|
248
|
+
expect(result.messages[2].content).toContain('Content for message 2');
|
|
249
|
+
|
|
250
|
+
expect(result.messages[4].content).toContain('Third question');
|
|
251
|
+
expect(result.messages[4].content).toContain('<test_context>');
|
|
252
|
+
expect(result.messages[4].content).toContain('Content for message 4');
|
|
253
|
+
|
|
254
|
+
// Assistant messages should be unchanged
|
|
255
|
+
expect(result.messages[1].content).toBe('First answer');
|
|
256
|
+
expect(result.messages[3].content).toBe('Second answer');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should correctly identify isLastUser parameter', async () => {
|
|
260
|
+
const isLastUserCalls: boolean[] = [];
|
|
261
|
+
|
|
262
|
+
const provider = new TestEveryUserContentProvider((message, index, isLastUser) => {
|
|
263
|
+
isLastUserCalls.push(isLastUser);
|
|
264
|
+
return { content: `Content ${index}`, contextType: 'test' };
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const context = createContext([
|
|
268
|
+
{ content: 'First', role: 'user' },
|
|
269
|
+
{ content: 'Answer', role: 'assistant' },
|
|
270
|
+
{ content: 'Second', role: 'user' },
|
|
271
|
+
{ content: 'Answer', role: 'assistant' },
|
|
272
|
+
{ content: 'Third (last)', role: 'user' },
|
|
273
|
+
]);
|
|
274
|
+
|
|
275
|
+
await provider.process(context);
|
|
276
|
+
|
|
277
|
+
expect(isLastUserCalls).toEqual([false, false, true]);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should skip injection when buildContentForMessage returns null', async () => {
|
|
281
|
+
const provider = new TestEveryUserContentProvider((message, index) => {
|
|
282
|
+
// Only inject for first user message
|
|
283
|
+
if (index === 0) {
|
|
284
|
+
return { content: 'First only', contextType: 'test' };
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const context = createContext([
|
|
290
|
+
{ content: 'First question', role: 'user' },
|
|
291
|
+
{ content: 'Answer', role: 'assistant' },
|
|
292
|
+
{ content: 'Second question', role: 'user' },
|
|
293
|
+
]);
|
|
294
|
+
|
|
295
|
+
const result = await provider.process(context);
|
|
296
|
+
|
|
297
|
+
expect(result.messages[0].content).toContain('<test>');
|
|
298
|
+
expect(result.messages[0].content).toContain('First only');
|
|
299
|
+
expect(result.messages[2].content).toBe('Second question');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should update metadata with injection count', async () => {
|
|
303
|
+
const provider = new TestEveryUserContentProvider();
|
|
304
|
+
const context = createContext([
|
|
305
|
+
{ content: 'First', role: 'user' },
|
|
306
|
+
{ content: 'Second', role: 'user' },
|
|
307
|
+
{ content: 'Third', role: 'user' },
|
|
308
|
+
]);
|
|
309
|
+
|
|
310
|
+
const result = await provider.process(context);
|
|
311
|
+
|
|
312
|
+
expect(result.metadata.TestEveryUserContentProviderInjectedCount).toBe(3);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should not set metadata when no injections made', async () => {
|
|
316
|
+
const provider = new TestEveryUserContentProvider(() => null);
|
|
317
|
+
const context = createContext([{ content: 'Question', role: 'user' }]);
|
|
318
|
+
|
|
319
|
+
const result = await provider.process(context);
|
|
320
|
+
|
|
321
|
+
expect(result.metadata.TestEveryUserContentProviderInjectedCount).toBeUndefined();
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
describe('integration with BaseLastUserContentProvider', () => {
|
|
326
|
+
it('should allow BaseLastUserContentProvider to reuse wrapper created by BaseEveryUserContentProvider', async () => {
|
|
327
|
+
// First: BaseEveryUserContentProvider injects to last user message
|
|
328
|
+
const everyProvider = new TestEveryUserContentProvider((message, index, isLastUser) => {
|
|
329
|
+
if (isLastUser) {
|
|
330
|
+
return { content: 'Selection content', contextType: 'user_selections' };
|
|
331
|
+
}
|
|
332
|
+
return null;
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const context = createContext([
|
|
336
|
+
{ content: 'First question', role: 'user' },
|
|
337
|
+
{ content: 'Answer', role: 'assistant' },
|
|
338
|
+
{ content: 'Last question', role: 'user' },
|
|
339
|
+
]);
|
|
340
|
+
|
|
341
|
+
const result = await everyProvider.process(context);
|
|
342
|
+
|
|
343
|
+
// The last user message should have a SYSTEM CONTEXT wrapper
|
|
344
|
+
const lastUserContent = result.messages[2].content as string;
|
|
345
|
+
expect(lastUserContent).toContain('<!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->');
|
|
346
|
+
expect(lastUserContent).toContain('<user_selections>');
|
|
347
|
+
expect(lastUserContent).toContain('Selection content');
|
|
348
|
+
expect(lastUserContent).toContain('<!-- END SYSTEM CONTEXT -->');
|
|
349
|
+
|
|
350
|
+
// Now BaseLastUserContentProvider can detect and reuse this wrapper
|
|
351
|
+
expect(everyProvider.testHasSystemContextWrapper(lastUserContent)).toBe(true);
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for context injection
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* System context wrapper markers
|
|
7
|
+
* Used to wrap injected context content so models can distinguish it from user content
|
|
8
|
+
*/
|
|
9
|
+
export const SYSTEM_CONTEXT_START = '<!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->';
|
|
10
|
+
export const SYSTEM_CONTEXT_END = '<!-- END SYSTEM CONTEXT -->';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Context instruction text
|
|
14
|
+
* Provides guidance to the model on how to handle injected context
|
|
15
|
+
*/
|
|
16
|
+
export const CONTEXT_INSTRUCTION = `<context.instruction>following part contains context information injected by the system. Please follow these instructions:
|
|
17
|
+
|
|
18
|
+
1. Always prioritize handling user-visible content.
|
|
19
|
+
2. the context is only required when user's queries rely on it.
|
|
20
|
+
</context.instruction>`;
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
HistorySummaryProvider,
|
|
29
29
|
KnowledgeInjector,
|
|
30
30
|
PageEditorContextInjector,
|
|
31
|
+
PageSelectionsInjector,
|
|
31
32
|
SystemRoleInjector,
|
|
32
33
|
ToolSystemRoleProvider,
|
|
33
34
|
UserMemoryInjector,
|
|
@@ -143,19 +144,19 @@ export class MessagesEngine {
|
|
|
143
144
|
|
|
144
145
|
return [
|
|
145
146
|
// =============================================
|
|
146
|
-
// Phase
|
|
147
|
+
// Phase 1: System Role Injection
|
|
147
148
|
// =============================================
|
|
148
149
|
|
|
149
|
-
//
|
|
150
|
+
// 1. System role injection (agent's system role)
|
|
150
151
|
new SystemRoleInjector({ systemRole }),
|
|
151
152
|
|
|
152
153
|
// =============================================
|
|
153
|
-
// Phase 2
|
|
154
|
+
// Phase 2: First User Message Context Injection
|
|
154
155
|
// These providers inject content before the first user message
|
|
155
156
|
// Order matters: first executed = first in content
|
|
156
157
|
// =============================================
|
|
157
158
|
|
|
158
|
-
//
|
|
159
|
+
// 2. User memory injection (conditionally added, injected first)
|
|
159
160
|
...(isUserMemoryEnabled ? [new UserMemoryInjector(userMemory)] : []),
|
|
160
161
|
|
|
161
162
|
// 3. Group context injection (agent identity and group info for multi-agent chat)
|
|
@@ -169,7 +170,7 @@ export class MessagesEngine {
|
|
|
169
170
|
systemPrompt: agentGroup?.systemPrompt,
|
|
170
171
|
}),
|
|
171
172
|
|
|
172
|
-
// 4.
|
|
173
|
+
// 4. GTD Plan injection (conditionally added, after user memory, before knowledge)
|
|
173
174
|
...(isGTDPlanEnabled ? [new GTDPlanInjector({ enabled: true, plan: gtd.plan })] : []),
|
|
174
175
|
|
|
175
176
|
// 5. Knowledge injection (full content for agent files + metadata for knowledge bases)
|
|
@@ -179,7 +180,7 @@ export class MessagesEngine {
|
|
|
179
180
|
}),
|
|
180
181
|
|
|
181
182
|
// =============================================
|
|
182
|
-
// Phase
|
|
183
|
+
// Phase 3: Additional System Context
|
|
183
184
|
// =============================================
|
|
184
185
|
|
|
185
186
|
// 6. Agent Builder context injection (current agent config/meta for editing)
|
|
@@ -212,7 +213,10 @@ export class MessagesEngine {
|
|
|
212
213
|
historySummary,
|
|
213
214
|
}),
|
|
214
215
|
|
|
215
|
-
//
|
|
216
|
+
// 12. Page Selections injection (inject user-selected text into each user message that has them)
|
|
217
|
+
new PageSelectionsInjector({ enabled: isPageEditorEnabled }),
|
|
218
|
+
|
|
219
|
+
// 10. Page Editor context injection (inject current page content to last user message)
|
|
216
220
|
new PageEditorContextInjector({
|
|
217
221
|
enabled: isPageEditorEnabled,
|
|
218
222
|
// Use direct pageContentContext if provided (server-side), otherwise build from initialContext + stepContext (frontend)
|
|
@@ -232,37 +236,37 @@ export class MessagesEngine {
|
|
|
232
236
|
: undefined,
|
|
233
237
|
}),
|
|
234
238
|
|
|
235
|
-
//
|
|
239
|
+
// 11. GTD Todo injection (conditionally added, at end of last user message)
|
|
236
240
|
...(isGTDTodoEnabled ? [new GTDTodoInjector({ enabled: true, todos: gtd.todos })] : []),
|
|
237
241
|
|
|
238
242
|
// =============================================
|
|
239
|
-
// Phase
|
|
243
|
+
// Phase 4: Message Transformation
|
|
240
244
|
// =============================================
|
|
241
245
|
|
|
242
|
-
//
|
|
246
|
+
// 13. Input template processing
|
|
243
247
|
new InputTemplateProcessor({ inputTemplate }),
|
|
244
248
|
|
|
245
|
-
//
|
|
249
|
+
// 14. Placeholder variables processing
|
|
246
250
|
new PlaceholderVariablesProcessor({
|
|
247
251
|
variableGenerators: variableGenerators || {},
|
|
248
252
|
}),
|
|
249
253
|
|
|
250
|
-
//
|
|
254
|
+
// 15. AgentCouncil message flatten (convert role=agentCouncil to standard assistant + tool messages)
|
|
251
255
|
new AgentCouncilFlattenProcessor(),
|
|
252
256
|
|
|
253
|
-
//
|
|
257
|
+
// 16. Group message flatten (convert role=assistantGroup to standard assistant + tool messages)
|
|
254
258
|
new GroupMessageFlattenProcessor(),
|
|
255
259
|
|
|
256
|
-
//
|
|
260
|
+
// 17. Tasks message flatten (convert role=tasks to individual task messages)
|
|
257
261
|
new TasksFlattenProcessor(),
|
|
258
262
|
|
|
259
|
-
//
|
|
263
|
+
// 18. Task message processing (convert role=task to assistant with instruction + content)
|
|
260
264
|
new TaskMessageProcessor(),
|
|
261
265
|
|
|
262
|
-
//
|
|
266
|
+
// 19. Supervisor role restore (convert role=supervisor back to role=assistant for model)
|
|
263
267
|
new SupervisorRoleRestoreProcessor(),
|
|
264
268
|
|
|
265
|
-
//
|
|
269
|
+
// 20. Group orchestration filter (remove supervisor's orchestration messages like broadcast/speak)
|
|
266
270
|
// This must be BEFORE GroupRoleTransformProcessor so we filter based on original agentId/tools
|
|
267
271
|
...(isAgentGroupEnabled && agentGroup.agentMap && agentGroup.currentAgentId
|
|
268
272
|
? [
|
|
@@ -277,7 +281,7 @@ export class MessagesEngine {
|
|
|
277
281
|
]
|
|
278
282
|
: []),
|
|
279
283
|
|
|
280
|
-
//
|
|
284
|
+
// 21. Group role transform (convert other agents' messages to user role with speaker tags)
|
|
281
285
|
// This must be BEFORE ToolCallProcessor so other agents' tool messages are converted first
|
|
282
286
|
...(isAgentGroupEnabled && agentGroup.currentAgentId
|
|
283
287
|
? [
|
|
@@ -289,10 +293,10 @@ export class MessagesEngine {
|
|
|
289
293
|
: []),
|
|
290
294
|
|
|
291
295
|
// =============================================
|
|
292
|
-
// Phase
|
|
296
|
+
// Phase 5: Content Processing
|
|
293
297
|
// =============================================
|
|
294
298
|
|
|
295
|
-
//
|
|
299
|
+
// 22. Message content processing (image encoding, etc.)
|
|
296
300
|
new MessageContentProcessor({
|
|
297
301
|
fileContext: fileContext || { enabled: true, includeFileUrl: true },
|
|
298
302
|
isCanUseVideo: capabilities?.isCanUseVideo || (() => false),
|
|
@@ -301,7 +305,7 @@ export class MessagesEngine {
|
|
|
301
305
|
provider,
|
|
302
306
|
}),
|
|
303
307
|
|
|
304
|
-
//
|
|
308
|
+
// 23. Tool call processing
|
|
305
309
|
new ToolCallProcessor({
|
|
306
310
|
genToolCallingName: this.toolNameResolver.generate.bind(this.toolNameResolver),
|
|
307
311
|
isCanUseFC: capabilities?.isCanUseFC || (() => true),
|
|
@@ -309,10 +313,10 @@ export class MessagesEngine {
|
|
|
309
313
|
provider,
|
|
310
314
|
}),
|
|
311
315
|
|
|
312
|
-
//
|
|
316
|
+
// 24. Tool message reordering
|
|
313
317
|
new ToolMessageReorder(),
|
|
314
318
|
|
|
315
|
-
//
|
|
319
|
+
// 25. Message cleanup (final step, keep only necessary fields)
|
|
316
320
|
new MessageCleanupProcessor(),
|
|
317
321
|
];
|
|
318
322
|
}
|