@lobehub/lobehub 2.0.0-next.308 → 2.0.0-next.309
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 +25 -0
- package/changelog/v1.json +9 -0
- package/locales/en-US/plugin.json +1 -0
- package/locales/zh-CN/plugin.json +1 -0
- package/package.json +1 -1
- package/packages/agent-runtime/src/types/state.ts +2 -0
- package/packages/builtin-tool-group-agent-builder/src/client/Inspector/GetAgentInfo/index.tsx +68 -0
- package/packages/builtin-tool-group-agent-builder/src/client/Inspector/index.ts +3 -0
- package/packages/builtin-tool-memory/src/manifest.ts +581 -19
- package/packages/types/src/topic/thread.ts +2 -2
- package/packages/types/src/userMemory/layers.ts +19 -8
- package/packages/types/src/userMemory/shared.ts +7 -1
- package/src/locales/default/plugin.ts +1 -0
- package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +11 -3
- package/src/server/modules/Mecha/AgentToolsEngine/index.ts +14 -6
- package/src/server/modules/Mecha/AgentToolsEngine/types.ts +4 -3
- package/src/server/routers/lambda/aiAgent.ts +10 -0
- package/src/server/services/agent/index.test.ts +155 -0
- package/src/server/services/agent/index.ts +25 -0
- package/src/server/services/agentRuntime/AgentRuntimeService.ts +2 -0
- package/src/server/services/agentRuntime/types.ts +1 -0
- package/src/server/services/aiAgent/__tests__/execAgent.threadId.test.ts +29 -9
- package/src/server/services/aiAgent/index.ts +175 -6
- package/src/server/services/lobehubSkill/index.ts +109 -0
- package/src/server/services/toolExecution/builtin.ts +28 -2
- package/src/server/services/toolExecution/types.ts +3 -0
- package/src/store/chat/agents/createAgentExecutors.ts +4 -2
|
@@ -10,19 +10,33 @@ export enum UserMemoryContextObjectType {
|
|
|
10
10
|
Knowledge = 'knowledge',
|
|
11
11
|
Other = 'other',
|
|
12
12
|
Person = 'person',
|
|
13
|
-
Place = 'place'
|
|
13
|
+
Place = 'place',
|
|
14
14
|
}
|
|
15
|
+
export const CONTEXT_OBJECT_TYPES = Object.values(UserMemoryContextObjectType);
|
|
15
16
|
|
|
16
17
|
export enum UserMemoryContextSubjectType {
|
|
17
18
|
Item = 'item',
|
|
18
19
|
Other = 'other',
|
|
19
20
|
Person = 'person',
|
|
20
|
-
Pet = 'pet'
|
|
21
|
+
Pet = 'pet',
|
|
21
22
|
}
|
|
23
|
+
export const CONTEXT_SUBJECT_TYPES = Object.values(UserMemoryContextSubjectType);
|
|
22
24
|
|
|
23
25
|
export interface UserMemoryContext extends UserMemoryTimestamps {
|
|
24
|
-
associatedObjects:
|
|
25
|
-
|
|
26
|
+
associatedObjects:
|
|
27
|
+
| {
|
|
28
|
+
extra?: Record<string, unknown> | null;
|
|
29
|
+
name?: string;
|
|
30
|
+
type?: UserMemoryContextObjectType;
|
|
31
|
+
}[]
|
|
32
|
+
| null;
|
|
33
|
+
associatedSubjects:
|
|
34
|
+
| {
|
|
35
|
+
extra?: Record<string, unknown> | null;
|
|
36
|
+
name?: string;
|
|
37
|
+
type?: UserMemoryContextSubjectType;
|
|
38
|
+
}[]
|
|
39
|
+
| null;
|
|
26
40
|
currentStatus: string | null;
|
|
27
41
|
description: string | null;
|
|
28
42
|
descriptionVector: number[] | null;
|
|
@@ -97,7 +111,4 @@ export type UserMemoryPreferenceWithoutVectors = Omit<
|
|
|
97
111
|
'conclusionDirectivesVector'
|
|
98
112
|
>;
|
|
99
113
|
|
|
100
|
-
export type UserMemoryPreferencesListItem = Omit<
|
|
101
|
-
UserMemoryPreferenceWithoutVectors,
|
|
102
|
-
'suggestions'
|
|
103
|
-
>;
|
|
114
|
+
export type UserMemoryPreferencesListItem = Omit<UserMemoryPreferenceWithoutVectors, 'suggestions'>;
|
|
@@ -30,17 +30,20 @@ export enum RelationshipEnum {
|
|
|
30
30
|
Uncle = 'uncle',
|
|
31
31
|
Wife = 'wife',
|
|
32
32
|
}
|
|
33
|
+
export const RELATIONSHIPS = Object.values(RelationshipEnum);
|
|
33
34
|
|
|
34
35
|
export enum MergeStrategyEnum {
|
|
35
36
|
Merge = 'merge',
|
|
36
37
|
Replace = 'replace',
|
|
37
38
|
}
|
|
39
|
+
export const MERGE_STRATEGIES = Object.values(MergeStrategyEnum);
|
|
38
40
|
|
|
39
41
|
export enum IdentityTypeEnum {
|
|
40
42
|
Demographic = 'demographic',
|
|
41
43
|
Personal = 'personal',
|
|
42
44
|
Professional = 'professional',
|
|
43
45
|
}
|
|
46
|
+
export const IDENTITY_TYPES = Object.values(IdentityTypeEnum);
|
|
44
47
|
|
|
45
48
|
export enum LayersEnum {
|
|
46
49
|
Context = 'context',
|
|
@@ -48,6 +51,7 @@ export enum LayersEnum {
|
|
|
48
51
|
Identity = 'identity',
|
|
49
52
|
Preference = 'preference',
|
|
50
53
|
}
|
|
54
|
+
export const MEMORY_LAYERS = Object.values(LayersEnum);
|
|
51
55
|
|
|
52
56
|
export enum TypesEnum {
|
|
53
57
|
Activity = 'activity',
|
|
@@ -61,6 +65,7 @@ export enum TypesEnum {
|
|
|
61
65
|
Technology = 'technology',
|
|
62
66
|
Topic = 'topic',
|
|
63
67
|
}
|
|
68
|
+
export const MEMORY_TYPES = Object.values(TypesEnum);
|
|
64
69
|
|
|
65
70
|
export enum ContextStatusEnum {
|
|
66
71
|
Aborted = 'aborted',
|
|
@@ -68,5 +73,6 @@ export enum ContextStatusEnum {
|
|
|
68
73
|
Completed = 'completed',
|
|
69
74
|
OnHold = 'on_hold',
|
|
70
75
|
Ongoing = 'ongoing',
|
|
71
|
-
Planned = 'planned'
|
|
76
|
+
Planned = 'planned',
|
|
72
77
|
}
|
|
78
|
+
export const CONTEXT_STATUS = Object.values(ContextStatusEnum);
|
|
@@ -40,6 +40,7 @@ export default {
|
|
|
40
40
|
'builtins.lobe-cloud-sandbox.title': 'Cloud Sandbox',
|
|
41
41
|
'builtins.lobe-group-agent-builder.apiName.batchCreateAgents': 'Batch create agents',
|
|
42
42
|
'builtins.lobe-group-agent-builder.apiName.createAgent': 'Create agent',
|
|
43
|
+
'builtins.lobe-group-agent-builder.apiName.getAgentInfo': 'Get member info',
|
|
43
44
|
'builtins.lobe-group-agent-builder.apiName.getAvailableModels': 'Get available models',
|
|
44
45
|
'builtins.lobe-group-agent-builder.apiName.installPlugin': 'Install Skill',
|
|
45
46
|
'builtins.lobe-group-agent-builder.apiName.inviteAgent': 'Invite member',
|
|
@@ -55,6 +55,8 @@ export const createRuntimeExecutors = (
|
|
|
55
55
|
// Fallback to state's modelRuntimeConfig if not in payload
|
|
56
56
|
const model = llmPayload.model || state.modelRuntimeConfig?.model;
|
|
57
57
|
const provider = llmPayload.provider || state.modelRuntimeConfig?.provider;
|
|
58
|
+
// Fallback to state's tools if not in payload
|
|
59
|
+
const tools = llmPayload.tools || state.tools;
|
|
58
60
|
|
|
59
61
|
if (!model || !provider) {
|
|
60
62
|
throw new Error('Model and provider are required for call_llm instruction');
|
|
@@ -128,14 +130,14 @@ export const createRuntimeExecutors = (
|
|
|
128
130
|
const chatPayload = {
|
|
129
131
|
messages: llmPayload.messages,
|
|
130
132
|
model,
|
|
131
|
-
tools
|
|
133
|
+
tools,
|
|
132
134
|
};
|
|
133
135
|
|
|
134
136
|
log(
|
|
135
137
|
`${stagePrefix} calling model-runtime chat (model: %s, messages: %d, tools: %d)`,
|
|
136
138
|
model,
|
|
137
139
|
llmPayload.messages.length,
|
|
138
|
-
|
|
140
|
+
tools?.length ?? 0,
|
|
139
141
|
);
|
|
140
142
|
|
|
141
143
|
// Buffer: accumulate text and reasoning, send every 50ms
|
|
@@ -261,7 +263,12 @@ export const createRuntimeExecutors = (
|
|
|
261
263
|
}
|
|
262
264
|
},
|
|
263
265
|
onToolsCalling: async ({ toolsCalling: raw }) => {
|
|
264
|
-
const
|
|
266
|
+
const resolved = new ToolNameResolver().resolve(raw, state.toolManifestMap);
|
|
267
|
+
// Add source field from toolSourceMap for routing tool execution
|
|
268
|
+
const payload = resolved.map((p) => ({
|
|
269
|
+
...p,
|
|
270
|
+
source: state.toolSourceMap?.[p.identifier],
|
|
271
|
+
}));
|
|
265
272
|
// log(`[${operationLogId}][toolsCalling]`, payload);
|
|
266
273
|
toolsCalling = payload;
|
|
267
274
|
tool_calls = raw;
|
|
@@ -466,6 +473,7 @@ export const createRuntimeExecutors = (
|
|
|
466
473
|
// Execute tool using ToolExecutionService
|
|
467
474
|
log(`[${operationLogId}] Executing tool ${toolName} ...`);
|
|
468
475
|
const executionResult = await toolExecutionService.executeTool(chatToolPayload, {
|
|
476
|
+
serverDB: ctx.serverDB,
|
|
469
477
|
toolManifestMap: state.toolManifestMap,
|
|
470
478
|
userId: ctx.userId,
|
|
471
479
|
});
|
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
import { KnowledgeBaseManifest } from '@lobechat/builtin-tool-knowledge-base';
|
|
13
13
|
import { LocalSystemManifest } from '@lobechat/builtin-tool-local-system';
|
|
14
14
|
import { WebBrowsingManifest } from '@lobechat/builtin-tool-web-browsing';
|
|
15
|
-
import { ToolsEngine } from '@lobechat/context-engine';
|
|
16
|
-
import type { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
|
15
|
+
import { type LobeToolManifest, ToolsEngine } from '@lobechat/context-engine';
|
|
17
16
|
import debug from 'debug';
|
|
18
17
|
|
|
19
18
|
import { builtinTools } from '@/tools';
|
|
@@ -50,11 +49,11 @@ export const createServerToolsEngine = (
|
|
|
50
49
|
|
|
51
50
|
// Get plugin manifests from installed plugins (from database)
|
|
52
51
|
const pluginManifests = context.installedPlugins
|
|
53
|
-
.map((plugin) => plugin.manifest as
|
|
52
|
+
.map((plugin) => plugin.manifest as LobeToolManifest)
|
|
54
53
|
.filter(Boolean);
|
|
55
54
|
|
|
56
55
|
// Get all builtin tool manifests
|
|
57
|
-
const builtinManifests = builtinTools.map((tool) => tool.manifest as
|
|
56
|
+
const builtinManifests = builtinTools.map((tool) => tool.manifest as LobeToolManifest);
|
|
58
57
|
|
|
59
58
|
// Combine all manifests
|
|
60
59
|
const allManifests = [...pluginManifests, ...builtinManifests, ...additionalManifests];
|
|
@@ -87,18 +86,27 @@ export const createServerAgentToolsEngine = (
|
|
|
87
86
|
context: ServerAgentToolsContext,
|
|
88
87
|
params: ServerCreateAgentToolsEngineParams,
|
|
89
88
|
): ToolsEngine => {
|
|
90
|
-
const {
|
|
89
|
+
const {
|
|
90
|
+
additionalManifests,
|
|
91
|
+
agentConfig,
|
|
92
|
+
hasEnabledKnowledgeBases = false,
|
|
93
|
+
model,
|
|
94
|
+
provider,
|
|
95
|
+
} = params;
|
|
91
96
|
const searchMode = agentConfig.chatConfig?.searchMode ?? 'off';
|
|
92
97
|
const isSearchEnabled = searchMode !== 'off';
|
|
93
98
|
|
|
94
99
|
log(
|
|
95
|
-
'Creating agent tools engine for model=%s, provider=%s, searchMode=%s',
|
|
100
|
+
'Creating agent tools engine for model=%s, provider=%s, searchMode=%s, additionalManifests=%d',
|
|
96
101
|
model,
|
|
97
102
|
provider,
|
|
98
103
|
searchMode,
|
|
104
|
+
additionalManifests?.length ?? 0,
|
|
99
105
|
);
|
|
100
106
|
|
|
101
107
|
return createServerToolsEngine(context, {
|
|
108
|
+
// Pass additional manifests (e.g., LobeHub Skills)
|
|
109
|
+
additionalManifests,
|
|
102
110
|
// Add default tools based on configuration
|
|
103
111
|
defaultToolIds: [WebBrowsingManifest.identifier, KnowledgeBaseManifest.identifier],
|
|
104
112
|
// Create search-aware enableChecker for this request
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { PluginEnableChecker } from '@lobechat/context-engine';
|
|
1
|
+
import type { LobeToolManifest, PluginEnableChecker } from '@lobechat/context-engine';
|
|
2
2
|
import type { LobeTool } from '@lobechat/types';
|
|
3
|
-
import type { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Installed plugin with manifest
|
|
@@ -22,7 +21,7 @@ export interface ServerAgentToolsContext {
|
|
|
22
21
|
*/
|
|
23
22
|
export interface ServerAgentToolsEngineConfig {
|
|
24
23
|
/** Additional manifests to include (e.g., Klavis tools) */
|
|
25
|
-
additionalManifests?:
|
|
24
|
+
additionalManifests?: LobeToolManifest[];
|
|
26
25
|
/** Default tool IDs that will always be added */
|
|
27
26
|
defaultToolIds?: string[];
|
|
28
27
|
/** Custom enable checker for plugins */
|
|
@@ -33,6 +32,8 @@ export interface ServerAgentToolsEngineConfig {
|
|
|
33
32
|
* Parameters for createServerAgentToolsEngine
|
|
34
33
|
*/
|
|
35
34
|
export interface ServerCreateAgentToolsEngineParams {
|
|
35
|
+
/** Additional manifests to include (e.g., LobeHub Skills) */
|
|
36
|
+
additionalManifests?: LobeToolManifest[];
|
|
36
37
|
/** Agent configuration containing plugins array */
|
|
37
38
|
agentConfig: {
|
|
38
39
|
/** Optional agent chat config with searchMode */
|
|
@@ -642,6 +642,16 @@ export const aiAgentRouter = router({
|
|
|
642
642
|
const updatedStatus = updatedThread?.status ?? thread.status;
|
|
643
643
|
const updatedTaskStatus = threadStatusToTaskStatus[updatedStatus] || 'processing';
|
|
644
644
|
|
|
645
|
+
// DEBUG: Log metadata for failed tasks
|
|
646
|
+
if (updatedTaskStatus === 'failed') {
|
|
647
|
+
console.log('[DEBUG] getSubAgentTaskStatus - failed task metadata:', {
|
|
648
|
+
threadId,
|
|
649
|
+
updatedMetadata,
|
|
650
|
+
'updatedMetadata?.error': updatedMetadata?.error,
|
|
651
|
+
updatedStatus,
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
645
655
|
// 6. Query thread messages for result content or current activity
|
|
646
656
|
const threadMessages = await ctx.messageModel.query({ threadId });
|
|
647
657
|
const sortedMessages = threadMessages.sort(
|
|
@@ -216,6 +216,161 @@ describe('AgentService', () => {
|
|
|
216
216
|
});
|
|
217
217
|
});
|
|
218
218
|
|
|
219
|
+
describe('getAgentConfig', () => {
|
|
220
|
+
it('should return null if agent does not exist', async () => {
|
|
221
|
+
const mockAgentModel = {
|
|
222
|
+
getAgentConfig: vi.fn().mockResolvedValue(null),
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
(AgentModel as any).mockImplementation(() => mockAgentModel);
|
|
226
|
+
(parseAgentConfig as any).mockReturnValue({});
|
|
227
|
+
|
|
228
|
+
const newService = new AgentService(mockDb, mockUserId);
|
|
229
|
+
const result = await newService.getAgentConfig('non-existent');
|
|
230
|
+
|
|
231
|
+
expect(result).toBeNull();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should support lookup by agent id', async () => {
|
|
235
|
+
const mockAgent = {
|
|
236
|
+
id: 'agent-123',
|
|
237
|
+
model: 'gpt-4',
|
|
238
|
+
systemRole: 'Test role',
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const mockAgentModel = {
|
|
242
|
+
getAgentConfig: vi.fn().mockResolvedValue(mockAgent),
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
(AgentModel as any).mockImplementation(() => mockAgentModel);
|
|
246
|
+
(parseAgentConfig as any).mockReturnValue({});
|
|
247
|
+
|
|
248
|
+
const newService = new AgentService(mockDb, mockUserId);
|
|
249
|
+
const result = await newService.getAgentConfig('agent-123');
|
|
250
|
+
|
|
251
|
+
expect(mockAgentModel.getAgentConfig).toHaveBeenCalledWith('agent-123');
|
|
252
|
+
expect(result?.id).toBe('agent-123');
|
|
253
|
+
expect(result?.model).toBe('gpt-4');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should support lookup by slug', async () => {
|
|
257
|
+
const mockAgent = {
|
|
258
|
+
id: 'agent-123',
|
|
259
|
+
model: 'claude-3',
|
|
260
|
+
slug: 'my-agent',
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const mockAgentModel = {
|
|
264
|
+
getAgentConfig: vi.fn().mockResolvedValue(mockAgent),
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
(AgentModel as any).mockImplementation(() => mockAgentModel);
|
|
268
|
+
(parseAgentConfig as any).mockReturnValue({});
|
|
269
|
+
|
|
270
|
+
const newService = new AgentService(mockDb, mockUserId);
|
|
271
|
+
const result = await newService.getAgentConfig('my-agent');
|
|
272
|
+
|
|
273
|
+
expect(mockAgentModel.getAgentConfig).toHaveBeenCalledWith('my-agent');
|
|
274
|
+
expect(result?.id).toBe('agent-123');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('should merge DEFAULT_AGENT_CONFIG and serverDefaultAgentConfig with agent config', async () => {
|
|
278
|
+
const mockAgent = {
|
|
279
|
+
id: 'agent-1',
|
|
280
|
+
systemRole: 'Custom system role',
|
|
281
|
+
};
|
|
282
|
+
const serverDefaultConfig = { model: 'gpt-4', params: { temperature: 0.7 } };
|
|
283
|
+
|
|
284
|
+
const mockAgentModel = {
|
|
285
|
+
getAgentConfig: vi.fn().mockResolvedValue(mockAgent),
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
(AgentModel as any).mockImplementation(() => mockAgentModel);
|
|
289
|
+
(parseAgentConfig as any).mockReturnValue(serverDefaultConfig);
|
|
290
|
+
|
|
291
|
+
const newService = new AgentService(mockDb, mockUserId);
|
|
292
|
+
const result = await newService.getAgentConfig('agent-1');
|
|
293
|
+
|
|
294
|
+
expect(result).toMatchObject({
|
|
295
|
+
chatConfig: DEFAULT_AGENT_CONFIG.chatConfig,
|
|
296
|
+
plugins: DEFAULT_AGENT_CONFIG.plugins,
|
|
297
|
+
tts: DEFAULT_AGENT_CONFIG.tts,
|
|
298
|
+
model: 'gpt-4',
|
|
299
|
+
params: { temperature: 0.7 },
|
|
300
|
+
id: 'agent-1',
|
|
301
|
+
systemRole: 'Custom system role',
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should use default model/provider when agent has none', async () => {
|
|
306
|
+
const mockAgent = {
|
|
307
|
+
id: 'agent-1',
|
|
308
|
+
systemRole: 'Test',
|
|
309
|
+
// No model or provider set
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const mockAgentModel = {
|
|
313
|
+
getAgentConfig: vi.fn().mockResolvedValue(mockAgent),
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
(AgentModel as any).mockImplementation(() => mockAgentModel);
|
|
317
|
+
(parseAgentConfig as any).mockReturnValue({});
|
|
318
|
+
|
|
319
|
+
const newService = new AgentService(mockDb, mockUserId);
|
|
320
|
+
const result = await newService.getAgentConfig('agent-1');
|
|
321
|
+
|
|
322
|
+
// Should have default model/provider from DEFAULT_AGENT_CONFIG
|
|
323
|
+
expect(result?.model).toBe(DEFAULT_AGENT_CONFIG.model);
|
|
324
|
+
expect(result?.provider).toBe(DEFAULT_AGENT_CONFIG.provider);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('should prioritize agent model/provider over defaults', async () => {
|
|
328
|
+
const mockAgent = {
|
|
329
|
+
id: 'agent-1',
|
|
330
|
+
model: 'claude-3-opus',
|
|
331
|
+
provider: 'anthropic',
|
|
332
|
+
};
|
|
333
|
+
const serverDefaultConfig = { model: 'gpt-4', provider: 'openai' };
|
|
334
|
+
|
|
335
|
+
const mockAgentModel = {
|
|
336
|
+
getAgentConfig: vi.fn().mockResolvedValue(mockAgent),
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
(AgentModel as any).mockImplementation(() => mockAgentModel);
|
|
340
|
+
(parseAgentConfig as any).mockReturnValue(serverDefaultConfig);
|
|
341
|
+
|
|
342
|
+
const newService = new AgentService(mockDb, mockUserId);
|
|
343
|
+
const result = await newService.getAgentConfig('agent-1');
|
|
344
|
+
|
|
345
|
+
// Agent config should override server default
|
|
346
|
+
expect(result?.model).toBe('claude-3-opus');
|
|
347
|
+
expect(result?.provider).toBe('anthropic');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should merge user default agent config', async () => {
|
|
351
|
+
const mockAgent = {
|
|
352
|
+
id: 'agent-1',
|
|
353
|
+
};
|
|
354
|
+
const userDefaultConfig = { model: 'user-preferred-model', provider: 'user-provider' };
|
|
355
|
+
|
|
356
|
+
const mockAgentModel = {
|
|
357
|
+
getAgentConfig: vi.fn().mockResolvedValue(mockAgent),
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
(AgentModel as any).mockImplementation(() => mockAgentModel);
|
|
361
|
+
(parseAgentConfig as any).mockReturnValue({});
|
|
362
|
+
// Use mockResolvedValueOnce to avoid affecting subsequent tests
|
|
363
|
+
mockUserModel.getUserSettingsDefaultAgentConfig.mockResolvedValueOnce({ config: userDefaultConfig });
|
|
364
|
+
|
|
365
|
+
const newService = new AgentService(mockDb, mockUserId);
|
|
366
|
+
const result = await newService.getAgentConfig('agent-1');
|
|
367
|
+
|
|
368
|
+
// User default config should be applied
|
|
369
|
+
expect(result?.model).toBe('user-preferred-model');
|
|
370
|
+
expect(result?.provider).toBe('user-provider');
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
219
374
|
describe('getAgentConfigById', () => {
|
|
220
375
|
it('should return null if agent does not exist', async () => {
|
|
221
376
|
const mockAgentModel = {
|
|
@@ -17,6 +17,12 @@ import { type UpdateAgentResult } from './type';
|
|
|
17
17
|
|
|
18
18
|
const log = debug('lobe-agent:service');
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Agent config with required id field.
|
|
22
|
+
* Used when returning agent config from database (id is always present).
|
|
23
|
+
*/
|
|
24
|
+
export type AgentConfigWithId = LobeAgentConfig & { id: string };
|
|
25
|
+
|
|
20
26
|
interface AgentWelcomeData {
|
|
21
27
|
openQuestions: string[];
|
|
22
28
|
welcomeMessage: string;
|
|
@@ -78,6 +84,25 @@ export class AgentService {
|
|
|
78
84
|
return mergedConfig;
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Get agent config by ID or slug with default config merged.
|
|
89
|
+
* Supports both agentId and slug lookup.
|
|
90
|
+
*
|
|
91
|
+
* The returned agent config is merged with:
|
|
92
|
+
* 1. DEFAULT_AGENT_CONFIG (hardcoded defaults)
|
|
93
|
+
* 2. Server's globalDefaultAgentConfig (from environment variable DEFAULT_AGENT_CONFIG)
|
|
94
|
+
* 3. User's defaultAgentConfig (from user settings)
|
|
95
|
+
* 4. The actual agent config from database
|
|
96
|
+
*/
|
|
97
|
+
async getAgentConfig(idOrSlug: string): Promise<AgentConfigWithId | null> {
|
|
98
|
+
const [agent, defaultAgentConfig] = await Promise.all([
|
|
99
|
+
this.agentModel.getAgentConfig(idOrSlug),
|
|
100
|
+
this.userModel.getUserSettingsDefaultAgentConfig(),
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
return this.mergeDefaultConfig(agent, defaultAgentConfig) as AgentConfigWithId | null;
|
|
104
|
+
}
|
|
105
|
+
|
|
81
106
|
/**
|
|
82
107
|
* Get agent config by ID with default config merged.
|
|
83
108
|
*
|
|
@@ -231,6 +231,7 @@ export class AgentRuntimeService {
|
|
|
231
231
|
initialMessages = [],
|
|
232
232
|
appContext,
|
|
233
233
|
toolManifestMap,
|
|
234
|
+
toolSourceMap,
|
|
234
235
|
stepCallbacks,
|
|
235
236
|
} = params;
|
|
236
237
|
|
|
@@ -258,6 +259,7 @@ export class AgentRuntimeService {
|
|
|
258
259
|
status: 'idle',
|
|
259
260
|
stepCount: 0,
|
|
260
261
|
toolManifestMap,
|
|
262
|
+
toolSourceMap,
|
|
261
263
|
tools,
|
|
262
264
|
} as Partial<AgentState>;
|
|
263
265
|
|
|
@@ -87,6 +87,7 @@ export interface OperationCreationParams {
|
|
|
87
87
|
*/
|
|
88
88
|
stepCallbacks?: StepLifecycleCallbacks;
|
|
89
89
|
toolManifestMap: Record<string, LobeToolManifest>;
|
|
90
|
+
toolSourceMap?: Record<string, 'builtin' | 'plugin' | 'mcp' | 'klavis' | 'lobehubSkill'>;
|
|
90
91
|
tools?: any[];
|
|
91
92
|
userId?: string;
|
|
92
93
|
}
|
|
@@ -29,6 +29,22 @@ vi.mock('@/database/models/agent', () => ({
|
|
|
29
29
|
})),
|
|
30
30
|
}));
|
|
31
31
|
|
|
32
|
+
// Mock AgentService
|
|
33
|
+
vi.mock('@/server/services/agent', () => ({
|
|
34
|
+
AgentService: vi.fn().mockImplementation(() => ({
|
|
35
|
+
getAgentConfig: vi.fn().mockResolvedValue({
|
|
36
|
+
chatConfig: {},
|
|
37
|
+
files: [],
|
|
38
|
+
id: 'agent-1',
|
|
39
|
+
knowledgeBases: [],
|
|
40
|
+
model: 'gpt-4',
|
|
41
|
+
plugins: [],
|
|
42
|
+
provider: 'openai',
|
|
43
|
+
systemRole: 'You are a helpful assistant',
|
|
44
|
+
}),
|
|
45
|
+
})),
|
|
46
|
+
}));
|
|
47
|
+
|
|
32
48
|
// Mock PluginModel
|
|
33
49
|
vi.mock('@/database/models/plugin', () => ({
|
|
34
50
|
PluginModel: vi.fn().mockImplementation(() => ({
|
|
@@ -74,15 +90,19 @@ vi.mock('@/server/modules/Mecha', () => ({
|
|
|
74
90
|
}));
|
|
75
91
|
|
|
76
92
|
// Mock model-bank
|
|
77
|
-
vi.mock('model-bank', () =>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
vi.mock('model-bank', async (importOriginal) => {
|
|
94
|
+
const actual = await importOriginal<typeof import('model-bank')>();
|
|
95
|
+
return {
|
|
96
|
+
...actual,
|
|
97
|
+
LOBE_DEFAULT_MODEL_LIST: [
|
|
98
|
+
{
|
|
99
|
+
abilities: { functionCall: true, video: false, vision: true },
|
|
100
|
+
id: 'gpt-4',
|
|
101
|
+
providerId: 'openai',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
});
|
|
86
106
|
|
|
87
107
|
describe('AiAgentService.execAgent - threadId handling', () => {
|
|
88
108
|
let service: AiAgentService;
|