mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 17:47:27 +00:00
💄 style: suppot agent management (#12061)
* feat: improve the inject model context plugins decriptions fix: change the conversation-flow to change the subAgent message show place fix: eslint fixed fix: slove the inject not work problem feat: add the lost agent management inject open feat: add the AgentManagementInjector fix: add the exec task mode & improve the Pre-load agents fix: improve the executor import way & update the getEffectiveAgentId function fix: slove the test problem 🐛 fix: support agnet manager ments (#12171) feat: add the sub agents in context scope to support call subagent refactor agent management implement update add builtin agent management * fix types * fix import * fix test * fix tests * fix tests
This commit is contained in:
parent
4f3055e0c5
commit
eef04c499f
62 changed files with 4407 additions and 1584 deletions
|
|
@ -324,11 +324,6 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/server/routers/lambda/aiAgent.ts": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/server/routers/lambda/aiChat.ts": {
|
||||
"prefer-const": {
|
||||
"count": 1
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@
|
|||
"@lobechat/builtin-agents": "workspace:*",
|
||||
"@lobechat/builtin-skills": "workspace:*",
|
||||
"@lobechat/builtin-tool-agent-builder": "workspace:*",
|
||||
"@lobechat/builtin-tool-agent-management": "workspace:*",
|
||||
"@lobechat/builtin-tool-calculator": "workspace:*",
|
||||
"@lobechat/builtin-tool-cloud-sandbox": "workspace:*",
|
||||
"@lobechat/builtin-tool-group-agent-builder": "workspace:*",
|
||||
|
|
|
|||
14
packages/agent-manager-runtime/package.json
Normal file
14
packages/agent-manager-runtime/package.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "@lobechat/agent-manager-runtime",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"dependencies": {
|
||||
"@lobechat/const": "workspace:*",
|
||||
"@lobechat/prompts": "workspace:*",
|
||||
"@lobechat/types": "workspace:*"
|
||||
}
|
||||
}
|
||||
1059
packages/agent-manager-runtime/src/AgentManagerRuntime.ts
Normal file
1059
packages/agent-manager-runtime/src/AgentManagerRuntime.ts
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,440 @@
|
|||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { AgentManagerRuntime } from '../AgentManagerRuntime';
|
||||
import type { IAgentService, IDiscoverService } from '../types';
|
||||
|
||||
// Create mock services
|
||||
const mockAgentService: IAgentService = {
|
||||
createAgent: vi.fn(),
|
||||
queryAgents: vi.fn(),
|
||||
removeAgent: vi.fn(),
|
||||
};
|
||||
|
||||
const mockDiscoverService: IDiscoverService = {
|
||||
getAssistantList: vi.fn(),
|
||||
getMcpList: vi.fn(),
|
||||
};
|
||||
|
||||
// Mock stores
|
||||
const mockAgentConfig = {
|
||||
plugins: ['plugin-1'],
|
||||
systemRole: 'Previous prompt',
|
||||
};
|
||||
|
||||
const mockAgentMeta = {
|
||||
avatar: '🤖',
|
||||
title: 'Test Agent',
|
||||
};
|
||||
|
||||
vi.mock('@/store/agent', () => ({
|
||||
getAgentStoreState: vi.fn(() => ({
|
||||
appendStreamingSystemRole: vi.fn(),
|
||||
finishStreamingSystemRole: vi.fn(),
|
||||
optimisticUpdateAgentConfig: vi.fn(),
|
||||
optimisticUpdateAgentMeta: vi.fn(),
|
||||
startStreamingSystemRole: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('@/store/agent/selectors/selectors', () => ({
|
||||
agentSelectors: {
|
||||
getAgentConfigById: vi.fn(() => () => mockAgentConfig),
|
||||
getAgentMetaById: vi.fn(() => () => mockAgentMeta),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@/store/aiInfra', () => ({
|
||||
getAiInfraStoreState: vi.fn(() => ({
|
||||
enabledChatModelList: [
|
||||
{
|
||||
id: 'openai',
|
||||
name: 'OpenAI',
|
||||
children: [
|
||||
{ id: 'gpt-4o', displayName: 'GPT-4o', abilities: { functionCall: true, vision: true } },
|
||||
{ id: 'gpt-3.5-turbo', displayName: 'GPT-3.5 Turbo' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'anthropic',
|
||||
name: 'Anthropic',
|
||||
children: [
|
||||
{
|
||||
id: 'claude-3-5-sonnet',
|
||||
displayName: 'Claude 3.5 Sonnet',
|
||||
abilities: { reasoning: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('@/store/tool', () => ({
|
||||
getToolStoreState: vi.fn(() => ({
|
||||
installMCPPlugin: vi.fn().mockResolvedValue(true),
|
||||
refreshPlugins: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('@/store/tool/selectors', () => ({
|
||||
builtinToolSelectors: {
|
||||
metaList: vi.fn(() => [{ identifier: 'lobe-web-browsing', meta: { title: 'Web Browsing' } }]),
|
||||
},
|
||||
klavisStoreSelectors: {
|
||||
getServers: vi.fn(() => []),
|
||||
},
|
||||
lobehubSkillStoreSelectors: {
|
||||
getServers: vi.fn(() => []),
|
||||
},
|
||||
pluginSelectors: {
|
||||
getInstalledPluginById: vi.fn(() => () => null),
|
||||
isPluginInstalled: vi.fn(() => () => false),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@/store/user', () => ({
|
||||
getUserStoreState: vi.fn(() => ({})),
|
||||
}));
|
||||
|
||||
vi.mock('@/store/user/selectors', () => ({
|
||||
userProfileSelectors: {
|
||||
userId: vi.fn(() => 'test-user-id'),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('AgentManagerRuntime', () => {
|
||||
let runtime: AgentManagerRuntime;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
runtime = new AgentManagerRuntime({
|
||||
agentService: mockAgentService,
|
||||
discoverService: mockDiscoverService,
|
||||
});
|
||||
});
|
||||
|
||||
describe('createAgent', () => {
|
||||
it('should create an agent successfully', async () => {
|
||||
vi.mocked(mockAgentService.createAgent).mockResolvedValue({
|
||||
agentId: 'new-agent-id',
|
||||
sessionId: 'new-session-id',
|
||||
});
|
||||
|
||||
const result = await runtime.createAgent({
|
||||
title: 'My New Agent',
|
||||
description: 'A test agent',
|
||||
systemRole: 'You are a helpful assistant',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('Successfully created agent');
|
||||
expect(result.content).toContain('My New Agent');
|
||||
expect(result.state).toMatchObject({
|
||||
agentId: 'new-agent-id',
|
||||
sessionId: 'new-session-id',
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle creation failure', async () => {
|
||||
vi.mocked(mockAgentService.createAgent).mockRejectedValue(new Error('Creation failed'));
|
||||
|
||||
const result = await runtime.createAgent({
|
||||
title: 'My Agent',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.content).toContain('Failed to create agent');
|
||||
expect(result.error).toMatchObject({
|
||||
message: 'Creation failed',
|
||||
type: 'RuntimeError',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateAgentConfig', () => {
|
||||
it('should update agent config successfully', async () => {
|
||||
const result = await runtime.updateAgentConfig('agent-id', {
|
||||
config: { model: 'gpt-4o' },
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('Successfully updated agent');
|
||||
expect(result.state).toMatchObject({
|
||||
success: true,
|
||||
config: {
|
||||
updatedFields: ['model'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should update agent meta successfully', async () => {
|
||||
const result = await runtime.updateAgentConfig('agent-id', {
|
||||
meta: { title: 'New Title', avatar: '🎉' },
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('meta fields: title, avatar');
|
||||
});
|
||||
|
||||
it('should handle togglePlugin', async () => {
|
||||
const result = await runtime.updateAgentConfig('agent-id', {
|
||||
togglePlugin: { pluginId: 'new-plugin', enabled: true },
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('plugin new-plugin enabled');
|
||||
expect(result.state).toMatchObject({
|
||||
success: true,
|
||||
togglePlugin: {
|
||||
enabled: true,
|
||||
pluginId: 'new-plugin',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return no fields message when nothing to update', async () => {
|
||||
const result = await runtime.updateAgentConfig('agent-id', {});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toBe('No fields to update.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteAgent', () => {
|
||||
it('should delete agent successfully', async () => {
|
||||
vi.mocked(mockAgentService.removeAgent).mockResolvedValue({} as any);
|
||||
|
||||
const result = await runtime.deleteAgent('agent-to-delete');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('Successfully deleted agent');
|
||||
expect(result.state).toMatchObject({
|
||||
agentId: 'agent-to-delete',
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle deletion failure', async () => {
|
||||
vi.mocked(mockAgentService.removeAgent).mockRejectedValue(new Error('Deletion failed'));
|
||||
|
||||
const result = await runtime.deleteAgent('agent-id');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.content).toContain('Failed to delete agent');
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchAgents', () => {
|
||||
it('should search user agents', async () => {
|
||||
vi.mocked(mockAgentService.queryAgents).mockResolvedValue([
|
||||
{
|
||||
id: 'agent-1',
|
||||
title: 'Agent One',
|
||||
description: 'First agent',
|
||||
avatar: null,
|
||||
backgroundColor: null,
|
||||
},
|
||||
{
|
||||
id: 'agent-2',
|
||||
title: 'Agent Two',
|
||||
description: 'Second agent',
|
||||
avatar: null,
|
||||
backgroundColor: null,
|
||||
},
|
||||
] as any);
|
||||
|
||||
const result = await runtime.searchAgents({
|
||||
keyword: 'test',
|
||||
source: 'user',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('Found 2 agents');
|
||||
expect(result.state).toMatchObject({
|
||||
agents: expect.arrayContaining([
|
||||
expect.objectContaining({ id: 'agent-1', isMarket: false }),
|
||||
expect.objectContaining({ id: 'agent-2', isMarket: false }),
|
||||
]),
|
||||
source: 'user',
|
||||
totalCount: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('should search marketplace agents', async () => {
|
||||
vi.mocked(mockDiscoverService.getAssistantList).mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
identifier: 'market-agent-1',
|
||||
title: 'Market Agent',
|
||||
description: 'From market',
|
||||
} as any,
|
||||
],
|
||||
totalCount: 1,
|
||||
} as any);
|
||||
|
||||
const result = await runtime.searchAgents({
|
||||
keyword: 'market',
|
||||
source: 'market',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.state).toMatchObject({
|
||||
agents: expect.arrayContaining([
|
||||
expect.objectContaining({ id: 'market-agent-1', isMarket: true }),
|
||||
]),
|
||||
source: 'market',
|
||||
});
|
||||
});
|
||||
|
||||
it('should search all sources by default', async () => {
|
||||
vi.mocked(mockAgentService.queryAgents).mockResolvedValue([
|
||||
{
|
||||
id: 'user-agent',
|
||||
title: 'User Agent',
|
||||
avatar: null,
|
||||
backgroundColor: null,
|
||||
description: null,
|
||||
},
|
||||
] as any);
|
||||
vi.mocked(mockDiscoverService.getAssistantList).mockResolvedValue({
|
||||
items: [{ identifier: 'market-agent', title: 'Market Agent' } as any],
|
||||
totalCount: 1,
|
||||
} as any);
|
||||
|
||||
const result = await runtime.searchAgents({ keyword: 'test' });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.state?.source).toBe('all');
|
||||
expect(result.state?.agents).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should return no agents found message', async () => {
|
||||
vi.mocked(mockAgentService.queryAgents).mockResolvedValue([]);
|
||||
|
||||
const result = await runtime.searchAgents({ keyword: 'nonexistent', source: 'user' });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('No agents found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAvailableModels', () => {
|
||||
it('should return all available models', async () => {
|
||||
const result = await runtime.getAvailableModels({});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('Found 2 provider(s)');
|
||||
expect(result.content).toContain('3 model(s)');
|
||||
expect(result.state).toMatchObject({
|
||||
providers: expect.arrayContaining([
|
||||
expect.objectContaining({ id: 'openai' }),
|
||||
expect.objectContaining({ id: 'anthropic' }),
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter by providerId', async () => {
|
||||
const result = await runtime.getAvailableModels({ providerId: 'openai' });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.state?.providers).toHaveLength(1);
|
||||
expect(result.state?.providers[0].id).toBe('openai');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updatePrompt', () => {
|
||||
it('should update prompt without streaming', async () => {
|
||||
const result = await runtime.updatePrompt('agent-id', {
|
||||
prompt: 'New system prompt',
|
||||
streaming: false,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('Successfully updated system prompt');
|
||||
expect(result.content).toContain('17 characters');
|
||||
expect(result.state).toMatchObject({
|
||||
newPrompt: 'New system prompt',
|
||||
previousPrompt: 'Previous prompt',
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should clear prompt when empty', async () => {
|
||||
const result = await runtime.updatePrompt('agent-id', {
|
||||
prompt: '',
|
||||
streaming: false,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('Successfully cleared system prompt');
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchMarketTools', () => {
|
||||
it('should search market tools', async () => {
|
||||
vi.mocked(mockDiscoverService.getMcpList).mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
identifier: 'tool-1',
|
||||
name: 'Tool One',
|
||||
description: 'First tool',
|
||||
author: 'Author',
|
||||
} as any,
|
||||
{ identifier: 'tool-2', name: 'Tool Two', description: 'Second tool' } as any,
|
||||
],
|
||||
totalCount: 2,
|
||||
} as any);
|
||||
|
||||
const result = await runtime.searchMarketTools({ query: 'test' });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('Found 2 tool(s)');
|
||||
expect(result.state).toMatchObject({
|
||||
query: 'test',
|
||||
tools: expect.arrayContaining([
|
||||
expect.objectContaining({ identifier: 'tool-1' }),
|
||||
expect.objectContaining({ identifier: 'tool-2' }),
|
||||
]),
|
||||
totalCount: 2,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('installPlugin', () => {
|
||||
it('should install builtin tool', async () => {
|
||||
const result = await runtime.installPlugin('agent-id', {
|
||||
identifier: 'lobe-web-browsing',
|
||||
source: 'official',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('Successfully enabled builtin tool');
|
||||
expect(result.state).toMatchObject({
|
||||
installed: true,
|
||||
pluginId: 'lobe-web-browsing',
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error for unknown official tool', async () => {
|
||||
const result = await runtime.installPlugin('agent-id', {
|
||||
identifier: 'unknown-tool',
|
||||
source: 'official',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.content).toContain('not found');
|
||||
});
|
||||
|
||||
it('should install market plugin', async () => {
|
||||
const result = await runtime.installPlugin('agent-id', {
|
||||
identifier: 'market-plugin',
|
||||
source: 'market',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('Successfully installed and enabled MCP plugin');
|
||||
});
|
||||
});
|
||||
});
|
||||
2
packages/agent-manager-runtime/src/index.ts
Normal file
2
packages/agent-manager-runtime/src/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { AgentManagerRuntime } from './AgentManagerRuntime';
|
||||
export * from './types';
|
||||
237
packages/agent-manager-runtime/src/types.ts
Normal file
237
packages/agent-manager-runtime/src/types.ts
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
import type { LobeAgentConfig, MetaData } from '@lobechat/types';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
|
||||
// ==================== Service Interfaces ====================
|
||||
|
||||
/**
|
||||
* Interface for agent service operations
|
||||
* Can be implemented by client-side or server-side services
|
||||
*/
|
||||
export interface IAgentService {
|
||||
createAgent: (params: { config: Record<string, unknown> }) => Promise<{
|
||||
agentId?: string;
|
||||
sessionId?: string;
|
||||
}>;
|
||||
queryAgents: (params: { keyword?: string; limit?: number }) => Promise<
|
||||
Array<{
|
||||
avatar?: string | null;
|
||||
backgroundColor?: string | null;
|
||||
description?: string | null;
|
||||
id: string;
|
||||
title?: string | null;
|
||||
}>
|
||||
>;
|
||||
removeAgent: (agentId: string) => Promise<unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for discover/marketplace service operations
|
||||
*/
|
||||
export interface IDiscoverService {
|
||||
getAssistantList: (params: { category?: string; pageSize?: number; q?: string }) => Promise<{
|
||||
items: Array<{
|
||||
avatar?: string;
|
||||
backgroundColor?: string;
|
||||
description?: string;
|
||||
identifier: string;
|
||||
title?: string;
|
||||
}>;
|
||||
totalCount: number;
|
||||
}>;
|
||||
getMcpList: (params: { category?: string; pageSize?: number; q?: string }) => Promise<{
|
||||
items: Array<{
|
||||
author?: string;
|
||||
cloudEndPoint?: string;
|
||||
description?: string;
|
||||
haveCloudEndpoint?: boolean;
|
||||
icon?: string;
|
||||
identifier: string;
|
||||
name: string;
|
||||
tags?: string[];
|
||||
}>;
|
||||
totalCount: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Required services for AgentManagerRuntime
|
||||
* Services must be injected for runtime-agnostic usage
|
||||
*/
|
||||
export interface AgentManagerRuntimeServices {
|
||||
/**
|
||||
* Agent service for CRUD operations
|
||||
*/
|
||||
agentService: IAgentService;
|
||||
/**
|
||||
* Discover service for marketplace operations
|
||||
*/
|
||||
discoverService: IDiscoverService;
|
||||
}
|
||||
|
||||
// ==================== Agent CRUD Types ====================
|
||||
|
||||
export interface CreateAgentParams {
|
||||
avatar?: string;
|
||||
backgroundColor?: string;
|
||||
description?: string;
|
||||
model?: string;
|
||||
openingMessage?: string;
|
||||
openingQuestions?: string[];
|
||||
plugins?: string[];
|
||||
provider?: string;
|
||||
systemRole?: string;
|
||||
tags?: string[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface CreateAgentState {
|
||||
agentId?: string;
|
||||
error?: string;
|
||||
sessionId?: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateAgentConfigParams {
|
||||
config?: PartialDeep<LobeAgentConfig>;
|
||||
meta?: Partial<MetaData>;
|
||||
togglePlugin?: {
|
||||
enabled?: boolean;
|
||||
pluginId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateAgentConfigState {
|
||||
config?: {
|
||||
newValues: Record<string, unknown>;
|
||||
previousValues: Record<string, unknown>;
|
||||
updatedFields: string[];
|
||||
};
|
||||
meta?: {
|
||||
newValues: Partial<MetaData>;
|
||||
previousValues: Partial<MetaData>;
|
||||
updatedFields: string[];
|
||||
};
|
||||
success: boolean;
|
||||
togglePlugin?: {
|
||||
enabled: boolean;
|
||||
pluginId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DeleteAgentState {
|
||||
agentId: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
// ==================== Search Types ====================
|
||||
|
||||
export type SearchAgentSource = 'user' | 'market' | 'all';
|
||||
|
||||
export interface SearchAgentParams {
|
||||
category?: string;
|
||||
keyword?: string;
|
||||
limit?: number;
|
||||
source?: SearchAgentSource;
|
||||
}
|
||||
|
||||
export interface AgentSearchItem {
|
||||
avatar?: string;
|
||||
backgroundColor?: string;
|
||||
description?: string;
|
||||
id: string;
|
||||
isMarket?: boolean;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface SearchAgentState {
|
||||
agents: AgentSearchItem[];
|
||||
keyword?: string;
|
||||
source: SearchAgentSource;
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
// ==================== Models Types ====================
|
||||
|
||||
export interface GetAvailableModelsParams {
|
||||
providerId?: string;
|
||||
}
|
||||
|
||||
export interface AvailableModel {
|
||||
abilities?: {
|
||||
files?: boolean;
|
||||
functionCall?: boolean;
|
||||
reasoning?: boolean;
|
||||
vision?: boolean;
|
||||
};
|
||||
description?: string;
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface AvailableProvider {
|
||||
id: string;
|
||||
models: AvailableModel[];
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface GetAvailableModelsState {
|
||||
providers: AvailableProvider[];
|
||||
}
|
||||
|
||||
// ==================== Prompt Types ====================
|
||||
|
||||
export interface UpdatePromptParams {
|
||||
prompt: string;
|
||||
streaming?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdatePromptState {
|
||||
newPrompt: string;
|
||||
previousPrompt?: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
// ==================== Plugin/Tools Types ====================
|
||||
|
||||
export interface SearchMarketToolsParams {
|
||||
category?: string;
|
||||
pageSize?: number;
|
||||
query?: string;
|
||||
}
|
||||
|
||||
export interface MarketToolItem {
|
||||
author?: string;
|
||||
cloudEndPoint?: string;
|
||||
description?: string;
|
||||
haveCloudEndpoint?: boolean;
|
||||
icon?: string;
|
||||
identifier: string;
|
||||
installed?: boolean;
|
||||
name: string;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export interface SearchMarketToolsState {
|
||||
query?: string;
|
||||
tools: MarketToolItem[];
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
export interface InstallPluginParams {
|
||||
identifier: string;
|
||||
source: 'market' | 'official';
|
||||
}
|
||||
|
||||
export interface InstallPluginState {
|
||||
awaitingApproval?: boolean;
|
||||
error?: string;
|
||||
installed: boolean;
|
||||
isKlavis?: boolean;
|
||||
isLobehubSkill?: boolean;
|
||||
oauthUrl?: string;
|
||||
pluginId: string;
|
||||
pluginName?: string;
|
||||
serverName?: string;
|
||||
serverStatus?: 'connected' | 'pending_auth' | 'error' | 'not_connected';
|
||||
success: boolean;
|
||||
}
|
||||
|
|
@ -5,11 +5,11 @@
|
|||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./client": "./src/client/index.ts",
|
||||
"./executor": "./src/executor.ts",
|
||||
"./executionRuntime": "./src/ExecutionRuntime/index.ts"
|
||||
"./executor": "./src/executor.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"dependencies": {
|
||||
"@lobechat/agent-manager-runtime": "workspace:*",
|
||||
"@lobechat/const": "workspace:*",
|
||||
"@lobechat/prompts": "workspace:*",
|
||||
"type-fest": "^4.18.3"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2,11 +2,15 @@
|
|||
* Agent Builder Executor
|
||||
*
|
||||
* Handles all agent builder tool calls for configuring and customizing agents.
|
||||
* Delegates to AgentManagerRuntime for actual implementation.
|
||||
*/
|
||||
import { AgentManagerRuntime } from '@lobechat/agent-manager-runtime';
|
||||
import type { BuiltinToolContext, BuiltinToolResult } from '@lobechat/types';
|
||||
import { BaseExecutor } from '@lobechat/types';
|
||||
|
||||
import { AgentBuilderExecutionRuntime } from './ExecutionRuntime';
|
||||
import { agentService } from '@/services/agent';
|
||||
import { discoverService } from '@/services/discover';
|
||||
|
||||
import type {
|
||||
GetAvailableModelsParams,
|
||||
InstallPluginParams,
|
||||
|
|
@ -16,7 +20,10 @@ import type {
|
|||
} from './types';
|
||||
import { AgentBuilderApiName, AgentBuilderIdentifier } from './types';
|
||||
|
||||
const runtime = new AgentBuilderExecutionRuntime();
|
||||
const runtime = new AgentManagerRuntime({
|
||||
agentService,
|
||||
discoverService,
|
||||
});
|
||||
|
||||
class AgentBuilderExecutor extends BaseExecutor<typeof AgentBuilderApiName> {
|
||||
readonly identifier = AgentBuilderIdentifier;
|
||||
|
|
@ -25,27 +32,11 @@ class AgentBuilderExecutor extends BaseExecutor<typeof AgentBuilderApiName> {
|
|||
// ==================== Read Operations ====================
|
||||
|
||||
getAvailableModels = async (params: GetAvailableModelsParams): Promise<BuiltinToolResult> => {
|
||||
const result = await runtime.getAvailableModels(params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return runtime.getAvailableModels(params);
|
||||
};
|
||||
|
||||
searchMarketTools = async (params: SearchMarketToolsParams): Promise<BuiltinToolResult> => {
|
||||
const result = await runtime.searchMarketTools(params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return runtime.searchMarketTools(params);
|
||||
};
|
||||
|
||||
// ==================== Write Operations ====================
|
||||
|
|
@ -64,15 +55,7 @@ class AgentBuilderExecutor extends BaseExecutor<typeof AgentBuilderApiName> {
|
|||
};
|
||||
}
|
||||
|
||||
const result = await runtime.updateAgentConfig(agentId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return runtime.updateAgentConfig(agentId, params);
|
||||
};
|
||||
|
||||
updatePrompt = async (
|
||||
|
|
@ -89,18 +72,10 @@ class AgentBuilderExecutor extends BaseExecutor<typeof AgentBuilderApiName> {
|
|||
};
|
||||
}
|
||||
|
||||
const result = await runtime.updatePrompt(agentId, {
|
||||
return runtime.updatePrompt(agentId, {
|
||||
streaming: true,
|
||||
...params,
|
||||
});
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
installPlugin = async (
|
||||
|
|
@ -117,15 +92,7 @@ class AgentBuilderExecutor extends BaseExecutor<typeof AgentBuilderApiName> {
|
|||
};
|
||||
}
|
||||
|
||||
const result = await runtime.installPlugin(agentId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return runtime.installPlugin(agentId, params);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
17
packages/builtin-tool-agent-management/package.json
Normal file
17
packages/builtin-tool-agent-management/package.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "@lobechat/builtin-tool-agent-management",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./client": "./src/client/index.ts",
|
||||
"./executor": "./src/executor.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"dependencies": {
|
||||
"@lobechat/agent-manager-runtime": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lobechat/types": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
'use client';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@lobechat/const';
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Avatar, Flexbox } from '@lobehub/ui';
|
||||
import { createStaticStyles, cx, useTheme } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useAgentStore } from '@/store/agent';
|
||||
import { agentSelectors } from '@/store/agent/selectors';
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { CallAgentParams } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
`,
|
||||
title: css`
|
||||
flex-shrink: 0;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
white-space: nowrap;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const CallAgentInspector = memo<BuiltinInspectorProps<CallAgentParams>>(
|
||||
({ args, partialArgs, isArgumentsStreaming }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const theme = useTheme();
|
||||
|
||||
const agentId = args?.agentId || partialArgs?.agentId;
|
||||
const runAsTask = args?.runAsTask || partialArgs?.runAsTask;
|
||||
|
||||
// Get agent meta from store
|
||||
const agentMeta = useAgentStore((s) =>
|
||||
agentId ? agentSelectors.getAgentMetaById(agentId)(s) : undefined,
|
||||
);
|
||||
|
||||
if (isArgumentsStreaming && !agentId) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-agent-management.apiName.callAgent')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const titleKey = runAsTask
|
||||
? 'builtins.lobe-agent-management.inspector.callAgent.task'
|
||||
: 'builtins.lobe-agent-management.inspector.callAgent.sync';
|
||||
|
||||
const agentName = agentMeta?.title || agentId;
|
||||
|
||||
return (
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}
|
||||
gap={8}
|
||||
horizontal
|
||||
>
|
||||
<span className={styles.title}>{t(titleKey)}</span>
|
||||
{agentMeta && (
|
||||
<Avatar
|
||||
avatar={agentMeta.avatar || DEFAULT_AVATAR}
|
||||
background={agentMeta.backgroundColor || theme.colorBgContainer}
|
||||
shape={'square'}
|
||||
size={24}
|
||||
title={agentMeta.title || undefined}
|
||||
/>
|
||||
)}
|
||||
{agentName && <span className={highlightTextStyles.primary}>{agentName}</span>}
|
||||
</Flexbox>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
CallAgentInspector.displayName = 'CallAgentInspector';
|
||||
|
||||
export default CallAgentInspector;
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
'use client';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@lobechat/const';
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Avatar, Flexbox } from '@lobehub/ui';
|
||||
import { createStaticStyles, cx, useTheme } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { CreateAgentParams } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
`,
|
||||
title: css`
|
||||
flex-shrink: 0;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
white-space: nowrap;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const CreateAgentInspector = memo<BuiltinInspectorProps<CreateAgentParams>>(
|
||||
({ args, partialArgs, isArgumentsStreaming }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const theme = useTheme();
|
||||
|
||||
const title = args?.title || partialArgs?.title;
|
||||
const avatar = args?.avatar || partialArgs?.avatar;
|
||||
const backgroundColor = args?.backgroundColor || partialArgs?.backgroundColor;
|
||||
|
||||
if (isArgumentsStreaming && !title) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-agent-management.apiName.createAgent')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}
|
||||
gap={8}
|
||||
horizontal
|
||||
>
|
||||
<span className={styles.title}>
|
||||
{t('builtins.lobe-agent-management.inspector.createAgent.title')}
|
||||
</span>
|
||||
<Avatar
|
||||
avatar={avatar || DEFAULT_AVATAR}
|
||||
background={backgroundColor || theme.colorBgContainer}
|
||||
shape={'square'}
|
||||
size={24}
|
||||
title={title || undefined}
|
||||
/>
|
||||
{title && <span className={highlightTextStyles.primary}>{title}</span>}
|
||||
</Flexbox>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
CreateAgentInspector.displayName = 'CreateAgentInspector';
|
||||
|
||||
export default CreateAgentInspector;
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Flexbox } from '@lobehub/ui';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { SearchAgentParams, SearchAgentSource } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
`,
|
||||
title: css`
|
||||
flex-shrink: 0;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
white-space: nowrap;
|
||||
`,
|
||||
}));
|
||||
|
||||
const getSourceTitleKey = (source: SearchAgentSource = 'all') => {
|
||||
switch (source) {
|
||||
case 'user': {
|
||||
return 'builtins.lobe-agent-management.inspector.searchAgent.user';
|
||||
}
|
||||
case 'market': {
|
||||
return 'builtins.lobe-agent-management.inspector.searchAgent.market';
|
||||
}
|
||||
default: {
|
||||
return 'builtins.lobe-agent-management.inspector.searchAgent.all';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const SearchAgentInspector = memo<BuiltinInspectorProps<SearchAgentParams>>(
|
||||
({ args, partialArgs, isArgumentsStreaming }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const keyword = args?.keyword || partialArgs?.keyword;
|
||||
const source = args?.source || partialArgs?.source || 'all';
|
||||
|
||||
const titleKey = useMemo(() => getSourceTitleKey(source), [source]);
|
||||
|
||||
if (isArgumentsStreaming && !keyword) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-agent-management.apiName.searchAgent')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}
|
||||
gap={8}
|
||||
horizontal
|
||||
>
|
||||
<span className={styles.title}>{t(titleKey)}</span>
|
||||
{keyword && <span className={highlightTextStyles.primary}>{keyword}</span>}
|
||||
</Flexbox>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
SearchAgentInspector.displayName = 'SearchAgentInspector';
|
||||
|
||||
export default SearchAgentInspector;
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Flexbox } from '@lobehub/ui';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { UpdateAgentParams } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
`,
|
||||
title: css`
|
||||
flex-shrink: 0;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
white-space: nowrap;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const UpdateAgentInspector = memo<BuiltinInspectorProps<UpdateAgentParams>>(
|
||||
({ args, partialArgs, isArgumentsStreaming }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const agentId = args?.agentId || partialArgs?.agentId;
|
||||
|
||||
if (isArgumentsStreaming && !agentId) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-agent-management.apiName.updateAgent')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}
|
||||
gap={8}
|
||||
horizontal
|
||||
>
|
||||
<span className={styles.title}>
|
||||
{t('builtins.lobe-agent-management.inspector.updateAgent.title')}
|
||||
</span>
|
||||
{agentId && <span className={highlightTextStyles.primary}>{agentId}</span>}
|
||||
</Flexbox>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
UpdateAgentInspector.displayName = 'UpdateAgentInspector';
|
||||
|
||||
export default UpdateAgentInspector;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { type BuiltinInspector } from '@lobechat/types';
|
||||
|
||||
import { AgentManagementApiName } from '../../types';
|
||||
import { CallAgentInspector } from './CallAgent';
|
||||
import { CreateAgentInspector } from './CreateAgent';
|
||||
import { SearchAgentInspector } from './SearchAgent';
|
||||
import { UpdateAgentInspector } from './UpdateAgent';
|
||||
|
||||
/**
|
||||
* Agent Management Inspector Components Registry
|
||||
*
|
||||
* Inspector components customize the title/header area
|
||||
* of tool calls in the conversation UI.
|
||||
*/
|
||||
export const AgentManagementInspectors: Record<string, BuiltinInspector> = {
|
||||
[AgentManagementApiName.callAgent]: CallAgentInspector as BuiltinInspector,
|
||||
[AgentManagementApiName.createAgent]: CreateAgentInspector as BuiltinInspector,
|
||||
[AgentManagementApiName.searchAgent]: SearchAgentInspector as BuiltinInspector,
|
||||
[AgentManagementApiName.updateAgent]: UpdateAgentInspector as BuiltinInspector,
|
||||
};
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
'use client';
|
||||
|
||||
import type { BuiltinRenderProps } from '@lobechat/types';
|
||||
import { Markdown } from '@lobehub/ui';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
|
||||
import type { CallAgentParams } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
container: css`
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: ${cssVar.colorFillQuaternary};
|
||||
`,
|
||||
instruction: css`
|
||||
font-size: 13px;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const CallAgentRender = memo<BuiltinRenderProps<CallAgentParams>>(({ args }) => {
|
||||
const { instruction } = args || {};
|
||||
|
||||
if (!instruction) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.instruction}>
|
||||
<Markdown variant={'chat'}>{instruction}</Markdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
CallAgentRender.displayName = 'CallAgentRender';
|
||||
|
||||
export default CallAgentRender;
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
'use client';
|
||||
|
||||
import type { BuiltinRenderProps } from '@lobechat/types';
|
||||
import { Block, Markdown, Tag , Flexbox } from '@lobehub/ui';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
|
||||
import type { CreateAgentParams } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
container: css`
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: ${cssVar.colorFillQuaternary};
|
||||
`,
|
||||
field: css`
|
||||
margin-block-end: 8px;
|
||||
|
||||
&:last-child {
|
||||
margin-block-end: 0;
|
||||
}
|
||||
`,
|
||||
label: css`
|
||||
margin-block-end: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
value: css`
|
||||
font-size: 13px;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const CreateAgentRender = memo<BuiltinRenderProps<CreateAgentParams>>(({ args }) => {
|
||||
const { title, description, systemRole, plugins, model, provider } = args || {};
|
||||
|
||||
if (!title && !description && !systemRole && !plugins?.length) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{title && (
|
||||
<div className={styles.field}>
|
||||
<div className={styles.label}>Title</div>
|
||||
<div className={styles.value}>{title}</div>
|
||||
</div>
|
||||
)}
|
||||
{description && (
|
||||
<div className={styles.field}>
|
||||
<div className={styles.label}>Description</div>
|
||||
<div className={styles.value}>{description}</div>
|
||||
</div>
|
||||
)}
|
||||
{(model || provider) && (
|
||||
<div className={styles.field}>
|
||||
<div className={styles.label}>Model</div>
|
||||
<div className={styles.value}>
|
||||
{provider && `${provider}/`}
|
||||
{model}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{plugins && plugins.length > 0 && (
|
||||
<div className={styles.field}>
|
||||
<div className={styles.label}>Plugins</div>
|
||||
<Flexbox gap={4} horizontal wrap={'wrap'}>
|
||||
{plugins.map((plugin) => (
|
||||
<Tag key={plugin}>{plugin}</Tag>
|
||||
))}
|
||||
</Flexbox>
|
||||
</div>
|
||||
)}
|
||||
{systemRole && (
|
||||
<div className={styles.field}>
|
||||
<div className={styles.label}>System Prompt</div>
|
||||
<Block paddingBlock={8} paddingInline={12} variant={'outlined'} width="100%">
|
||||
<Markdown fontSize={13} variant={'chat'}>
|
||||
{systemRole}
|
||||
</Markdown>
|
||||
</Block>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
CreateAgentRender.displayName = 'CreateAgentRender';
|
||||
|
||||
export default CreateAgentRender;
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
'use client';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@lobechat/const';
|
||||
import type { BuiltinRenderProps } from '@lobechat/types';
|
||||
import { Avatar, Flexbox } from '@lobehub/ui';
|
||||
import { createStaticStyles, useTheme } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { AgentSearchItem, SearchAgentParams, SearchAgentState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
agentItem: css`
|
||||
padding-block: 8px;
|
||||
padding-inline: 12px;
|
||||
border-radius: 6px;
|
||||
background: ${cssVar.colorFillQuaternary};
|
||||
`,
|
||||
agentTitle: css`
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
`,
|
||||
container: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
|
||||
background: ${cssVar.colorFillQuaternary};
|
||||
`,
|
||||
description: css`
|
||||
overflow: hidden;
|
||||
|
||||
font-size: 12px;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`,
|
||||
marketBadge: css`
|
||||
padding-block: 2px;
|
||||
padding-inline: 6px;
|
||||
border-radius: 4px;
|
||||
|
||||
font-size: 10px;
|
||||
color: ${cssVar.colorPrimary};
|
||||
|
||||
background: ${cssVar.colorPrimaryBg};
|
||||
`,
|
||||
noResults: css`
|
||||
padding: 12px;
|
||||
font-size: 13px;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
text-align: center;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const SearchAgentRender = memo<BuiltinRenderProps<SearchAgentParams, SearchAgentState>>(
|
||||
({ pluginState }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const theme = useTheme();
|
||||
const agents = pluginState?.agents || [];
|
||||
|
||||
if (agents.length === 0) {
|
||||
return (
|
||||
<div className={styles.noResults}>
|
||||
{t('builtins.lobe-agent-builder.inspector.noResults')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{agents.map((agent: AgentSearchItem) => (
|
||||
<Flexbox align={'center'} className={styles.agentItem} gap={12} horizontal key={agent.id}>
|
||||
<Avatar
|
||||
avatar={agent.avatar || DEFAULT_AVATAR}
|
||||
background={agent.backgroundColor || theme.colorBgContainer}
|
||||
shape={'square'}
|
||||
size={32}
|
||||
title={agent.title || undefined}
|
||||
/>
|
||||
<Flexbox flex={1} gap={2}>
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<span className={styles.agentTitle}>{agent.title || agent.id}</span>
|
||||
{agent.isMarket && <span className={styles.marketBadge}>Market</span>}
|
||||
</Flexbox>
|
||||
{agent.description && <span className={styles.description}>{agent.description}</span>}
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
SearchAgentRender.displayName = 'SearchAgentRender';
|
||||
|
||||
export default SearchAgentRender;
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
'use client';
|
||||
|
||||
import type { BuiltinRenderProps } from '@lobechat/types';
|
||||
import { Block, Markdown } from '@lobehub/ui';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
|
||||
import type { UpdateAgentParams } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
container: css`
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: ${cssVar.colorFillQuaternary};
|
||||
`,
|
||||
field: css`
|
||||
margin-block-end: 8px;
|
||||
|
||||
&:last-child {
|
||||
margin-block-end: 0;
|
||||
}
|
||||
`,
|
||||
label: css`
|
||||
margin-block-end: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
value: css`
|
||||
font-size: 13px;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const UpdateAgentRender = memo<BuiltinRenderProps<UpdateAgentParams>>(({ args }) => {
|
||||
const { config, meta } = args || {};
|
||||
|
||||
const hasConfig = config && Object.keys(config).length > 0;
|
||||
const hasMeta = meta && Object.keys(meta).length > 0;
|
||||
|
||||
if (!hasConfig && !hasMeta) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{meta?.title && (
|
||||
<div className={styles.field}>
|
||||
<div className={styles.label}>Title</div>
|
||||
<div className={styles.value}>{meta.title}</div>
|
||||
</div>
|
||||
)}
|
||||
{meta?.description && (
|
||||
<div className={styles.field}>
|
||||
<div className={styles.label}>Description</div>
|
||||
<div className={styles.value}>{meta.description}</div>
|
||||
</div>
|
||||
)}
|
||||
{config?.systemRole && (
|
||||
<div className={styles.field}>
|
||||
<div className={styles.label}>System Prompt</div>
|
||||
<Block paddingBlock={8} paddingInline={12} variant={'outlined'} width="100%">
|
||||
<Markdown fontSize={13} variant={'chat'}>
|
||||
{config.systemRole as string}
|
||||
</Markdown>
|
||||
</Block>
|
||||
</div>
|
||||
)}
|
||||
{config?.model && (
|
||||
<div className={styles.field}>
|
||||
<div className={styles.label}>Model</div>
|
||||
<div className={styles.value}>{config.model as string}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
UpdateAgentRender.displayName = 'UpdateAgentRender';
|
||||
|
||||
export default UpdateAgentRender;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { AgentManagementApiName } from '../../types';
|
||||
import CallAgentRender from './CallAgent';
|
||||
import CreateAgentRender from './CreateAgent';
|
||||
import SearchAgentRender from './SearchAgent';
|
||||
import UpdateAgentRender from './UpdateAgent';
|
||||
|
||||
/**
|
||||
* Agent Management Tool Render Components Registry
|
||||
*/
|
||||
export const AgentManagementRenders = {
|
||||
[AgentManagementApiName.callAgent]: CallAgentRender,
|
||||
[AgentManagementApiName.createAgent]: CreateAgentRender,
|
||||
[AgentManagementApiName.searchAgent]: SearchAgentRender,
|
||||
[AgentManagementApiName.updateAgent]: UpdateAgentRender,
|
||||
};
|
||||
|
||||
export { default as CallAgentRender } from './CallAgent';
|
||||
export { default as CreateAgentRender } from './CreateAgent';
|
||||
export { default as SearchAgentRender } from './SearchAgent';
|
||||
export { default as UpdateAgentRender } from './UpdateAgent';
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
'use client';
|
||||
|
||||
import type { BuiltinStreamingProps } from '@lobechat/types';
|
||||
import { Block, Flexbox, Markdown, Tag } from '@lobehub/ui';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
|
||||
import type { CreateAgentParams } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
container: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
`,
|
||||
field: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
`,
|
||||
label: css`
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
value: css`
|
||||
font-size: 13px;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const CreateAgentStreaming = memo<BuiltinStreamingProps<CreateAgentParams>>(({ args }) => {
|
||||
const { title, description, systemRole, plugins, model, provider } = args || {};
|
||||
|
||||
if (!title && !description && !systemRole && !plugins?.length) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{title && (
|
||||
<div className={styles.field}>
|
||||
<div className={styles.label}>Title</div>
|
||||
<div className={styles.value}>{title}</div>
|
||||
</div>
|
||||
)}
|
||||
{description && (
|
||||
<div className={styles.field}>
|
||||
<div className={styles.label}>Description</div>
|
||||
<div className={styles.value}>{description}</div>
|
||||
</div>
|
||||
)}
|
||||
{(model || provider) && (
|
||||
<div className={styles.field}>
|
||||
<div className={styles.label}>Model</div>
|
||||
<div className={styles.value}>
|
||||
{provider && `${provider}/`}
|
||||
{model}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{plugins && plugins.length > 0 && (
|
||||
<div className={styles.field}>
|
||||
<div className={styles.label}>Plugins</div>
|
||||
<Flexbox gap={4} horizontal wrap={'wrap'}>
|
||||
{plugins.map((plugin) => (
|
||||
<Tag key={plugin}>{plugin}</Tag>
|
||||
))}
|
||||
</Flexbox>
|
||||
</div>
|
||||
)}
|
||||
{systemRole && (
|
||||
<div className={styles.field}>
|
||||
<div className={styles.label}>System Prompt</div>
|
||||
<Block paddingBlock={8} paddingInline={12} variant={'outlined'} width="100%">
|
||||
<Markdown animated variant={'chat'}>
|
||||
{systemRole}
|
||||
</Markdown>
|
||||
</Block>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
CreateAgentStreaming.displayName = 'CreateAgentStreaming';
|
||||
|
||||
export default CreateAgentStreaming;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import type { BuiltinStreaming } from '@lobechat/types';
|
||||
|
||||
import { AgentManagementApiName } from '../../types';
|
||||
import { CreateAgentStreaming } from './CreateAgent';
|
||||
|
||||
/**
|
||||
* Agent Management Streaming Components Registry
|
||||
*
|
||||
* Streaming components render tool calls while they are
|
||||
* still executing, allowing real-time feedback to users.
|
||||
*/
|
||||
export const AgentManagementStreamings: Record<string, BuiltinStreaming> = {
|
||||
[AgentManagementApiName.createAgent]: CreateAgentStreaming as BuiltinStreaming,
|
||||
};
|
||||
|
||||
export { CreateAgentStreaming } from './CreateAgent';
|
||||
12
packages/builtin-tool-agent-management/src/client/index.ts
Normal file
12
packages/builtin-tool-agent-management/src/client/index.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Inspector components (title/header area)
|
||||
export { AgentManagementInspectors } from './Inspector';
|
||||
|
||||
// Streaming components (real-time feedback)
|
||||
export { AgentManagementStreamings } from './Streaming';
|
||||
|
||||
// Render components (read-only snapshots)
|
||||
export { AgentManagementRenders } from './Render';
|
||||
|
||||
// Re-export types and manifest for convenience
|
||||
export { AgentManagementManifest } from '../manifest';
|
||||
export * from '../types';
|
||||
236
packages/builtin-tool-agent-management/src/executor.ts
Normal file
236
packages/builtin-tool-agent-management/src/executor.ts
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
/**
|
||||
* Agent Management Executor
|
||||
*
|
||||
* Handles all agent management tool calls for creating, updating,
|
||||
* deleting, searching, and calling AI agents.
|
||||
* Delegates to AgentManagerRuntime for actual implementation.
|
||||
*/
|
||||
import { AgentManagerRuntime } from '@lobechat/agent-manager-runtime';
|
||||
import {
|
||||
BaseExecutor,
|
||||
type BuiltinToolContext,
|
||||
type BuiltinToolResult,
|
||||
type ConversationContext,
|
||||
} from '@lobechat/types';
|
||||
|
||||
import { agentService } from '@/services/agent';
|
||||
import { discoverService } from '@/services/discover';
|
||||
import { useAgentStore } from '@/store/agent';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
import { dbMessageSelectors } from '@/store/chat/slices/message/selectors';
|
||||
import { messageMapKey } from '@/store/chat/utils/messageMapKey';
|
||||
|
||||
import {
|
||||
AgentManagementApiName,
|
||||
AgentManagementIdentifier,
|
||||
type CallAgentParams,
|
||||
type CallAgentState,
|
||||
type CreateAgentParams,
|
||||
type DeleteAgentParams,
|
||||
type SearchAgentParams,
|
||||
type UpdateAgentParams,
|
||||
} from './types';
|
||||
|
||||
const runtime = new AgentManagerRuntime({
|
||||
agentService,
|
||||
discoverService,
|
||||
});
|
||||
|
||||
class AgentManagementExecutor extends BaseExecutor<typeof AgentManagementApiName> {
|
||||
readonly identifier = AgentManagementIdentifier;
|
||||
protected readonly apiEnum = AgentManagementApiName;
|
||||
|
||||
// ==================== Agent CRUD ====================
|
||||
|
||||
createAgent = async (params: CreateAgentParams): Promise<BuiltinToolResult> => {
|
||||
return runtime.createAgent(params);
|
||||
};
|
||||
|
||||
updateAgent = async (params: UpdateAgentParams): Promise<BuiltinToolResult> => {
|
||||
const { agentId, config, meta } = params;
|
||||
return runtime.updateAgentConfig(agentId, { config, meta });
|
||||
};
|
||||
|
||||
deleteAgent = async (params: DeleteAgentParams): Promise<BuiltinToolResult> => {
|
||||
return runtime.deleteAgent(params.agentId);
|
||||
};
|
||||
|
||||
// ==================== Search ====================
|
||||
|
||||
searchAgent = async (params: SearchAgentParams): Promise<BuiltinToolResult> => {
|
||||
return runtime.searchAgents(params);
|
||||
};
|
||||
|
||||
// ==================== Execution ====================
|
||||
|
||||
callAgent = async (
|
||||
params: CallAgentParams,
|
||||
ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
const { agentId, instruction, runAsTask, taskTitle, timeout, skipCallSupervisor = false } =
|
||||
params;
|
||||
|
||||
if (runAsTask) {
|
||||
// Execute as async task using GTD exec_task pattern
|
||||
// Pre-load target agent config to ensure it exists
|
||||
const targetAgentExists = useAgentStore.getState().agentMap[agentId];
|
||||
if (!targetAgentExists) {
|
||||
try {
|
||||
const config = await agentService.getAgentConfigById(agentId);
|
||||
if (!config) {
|
||||
return {
|
||||
content: `Agent "${agentId}" not found in your workspace. Please check the agent ID and try again.`,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
useAgentStore.getState().internal_dispatchAgentMap(agentId, config);
|
||||
} catch (error) {
|
||||
console.error('[callAgent] Failed to load agent config:', error);
|
||||
return {
|
||||
content: `Failed to load agent "${agentId}": ${(error as Error).message}`,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Return special state that will be recognized by AgentRuntime's exec_task executor
|
||||
// Following GTD execTask pattern: stop: true + state.type = 'execTask'
|
||||
return {
|
||||
content: `🚀 Triggered async task to call agent "${agentId}"${taskTitle ? `: ${taskTitle}` : ''}`,
|
||||
state: {
|
||||
parentMessageId: ctx.messageId,
|
||||
task: {
|
||||
description: taskTitle || `Call agent ${agentId}`,
|
||||
instruction,
|
||||
targetAgentId: agentId, // Special field for callAgent - indicates target agent
|
||||
timeout: timeout || 1_800_000,
|
||||
},
|
||||
type: 'execTask', // Use same type as GTD to reuse existing executor
|
||||
},
|
||||
stop: true,
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Execute as synchronous speak
|
||||
// Two modes: Group vs Agents
|
||||
|
||||
// Mode 1: Group environment - use group orchestration
|
||||
if (ctx.groupId && ctx.groupOrchestration && ctx.agentId && ctx.registerAfterCompletion) {
|
||||
// Register afterCompletion callback to trigger group orchestration
|
||||
ctx.registerAfterCompletion(() =>
|
||||
ctx.groupOrchestration!.triggerSpeak({
|
||||
agentId,
|
||||
instruction,
|
||||
skipCallSupervisor,
|
||||
supervisorAgentId: ctx.agentId!,
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
content: `Triggered agent "${agentId}" to respond.`,
|
||||
state: {
|
||||
agentId,
|
||||
instruction,
|
||||
mode: 'speak',
|
||||
skipCallSupervisor,
|
||||
} as CallAgentState,
|
||||
stop: true,
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Mode 2: Agents mode (non-group) - execute directly with subAgentId
|
||||
if (ctx.registerAfterCompletion) {
|
||||
// Pre-load target agent config if not already loaded (before registerAfterCompletion)
|
||||
// This ensures we fail fast with a clear error message if agent doesn't exist
|
||||
const targetAgentExists = useAgentStore.getState().agentMap[agentId];
|
||||
if (!targetAgentExists) {
|
||||
try {
|
||||
const config = await agentService.getAgentConfigById(agentId);
|
||||
if (!config) {
|
||||
return {
|
||||
content: `Agent "${agentId}" not found in your workspace. Please check the agent ID and try again.`,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
useAgentStore.getState().internal_dispatchAgentMap(agentId, config);
|
||||
} catch (error) {
|
||||
console.error('[callAgent] Failed to load agent config:', error);
|
||||
return {
|
||||
content: `Failed to load agent "${agentId}": ${(error as Error).message}`,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Register afterCompletion to execute the agent
|
||||
ctx.registerAfterCompletion(async () => {
|
||||
const get = useChatStore.getState;
|
||||
|
||||
// Build conversation context - use current agent's context
|
||||
const conversationContext: ConversationContext = {
|
||||
agentId: ctx.agentId || '',
|
||||
topicId: ctx.topicId || null,
|
||||
// subAgentId will be set when calling internal_execAgentRuntime
|
||||
};
|
||||
|
||||
// Get current messages
|
||||
const chatKey = messageMapKey(conversationContext);
|
||||
const messages = dbMessageSelectors.getDbMessagesByKey(chatKey)(get());
|
||||
|
||||
if (messages.length === 0) {
|
||||
console.error('[callAgent] No messages found in current conversation');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Execute with subAgentId + scope: 'sub_agent'
|
||||
// - context.agentId = current agent (for message storage and message.agentId)
|
||||
// - context.topicId = current topic
|
||||
// - context.subAgentId = target agent (for agent config - model, prompt, etc.)
|
||||
// - context.scope = 'sub_agent' (indicates this is agent-to-agent call, not group)
|
||||
// This will create messages in current agent's conversation but use target agent's config
|
||||
// The message.agentId will still be current agent, but metadata stores subAgentId + scope
|
||||
await get().internal_execAgentRuntime({
|
||||
context: { ...conversationContext, subAgentId: agentId, scope: 'sub_agent' },
|
||||
messages: messages,
|
||||
parentMessageId: ctx.messageId,
|
||||
parentMessageType: 'tool',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[callAgent] internal_execAgentRuntime failed:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
content: `Called agent "${agentId}" to respond.`,
|
||||
state: {
|
||||
agentId,
|
||||
instruction,
|
||||
mode: 'speak',
|
||||
skipCallSupervisor,
|
||||
} as CallAgentState,
|
||||
stop: true,
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Fallback if registerAfterCompletion not available
|
||||
console.warn('[callAgent] registerAfterCompletion not available in context');
|
||||
return {
|
||||
content: `Called agent "${agentId}" but execution may not complete properly.`,
|
||||
state: {
|
||||
agentId,
|
||||
instruction,
|
||||
mode: 'speak',
|
||||
skipCallSupervisor,
|
||||
} as CallAgentState,
|
||||
stop: true,
|
||||
success: false,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const agentManagementExecutor = new AgentManagementExecutor();
|
||||
3
packages/builtin-tool-agent-management/src/index.ts
Normal file
3
packages/builtin-tool-agent-management/src/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from './manifest';
|
||||
export * from './systemRole';
|
||||
export * from './types';
|
||||
248
packages/builtin-tool-agent-management/src/manifest.ts
Normal file
248
packages/builtin-tool-agent-management/src/manifest.ts
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
import type { BuiltinToolManifest } from '@lobechat/types';
|
||||
|
||||
import { systemPrompt } from './systemRole';
|
||||
import { AgentManagementApiName, AgentManagementIdentifier } from './types';
|
||||
|
||||
export const AgentManagementManifest: BuiltinToolManifest = {
|
||||
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
||||
api: [
|
||||
// ==================== Agent CRUD ====================
|
||||
{
|
||||
description:
|
||||
'Create a new AI agent with custom configuration. The agent will be added to your workspace and can be used for conversations or tasks.',
|
||||
name: AgentManagementApiName.createAgent,
|
||||
parameters: {
|
||||
properties: {
|
||||
title: {
|
||||
description: 'The display name for the agent (required)',
|
||||
type: 'string',
|
||||
},
|
||||
description: {
|
||||
description: 'A brief description of what the agent does',
|
||||
type: 'string',
|
||||
},
|
||||
systemRole: {
|
||||
description:
|
||||
"The system prompt that defines the agent's personality, expertise, and behavior. This is the core instruction for the agent.",
|
||||
type: 'string',
|
||||
},
|
||||
avatar: {
|
||||
description: 'Agent avatar (emoji like "🤖" or image URL)',
|
||||
type: 'string',
|
||||
},
|
||||
backgroundColor: {
|
||||
description: 'Background color for the agent card (hex color code)',
|
||||
type: 'string',
|
||||
},
|
||||
model: {
|
||||
description:
|
||||
'The AI model to use (e.g., "gpt-4o", "gpt-4o-mini", "claude-3-5-sonnet-20241022")',
|
||||
type: 'string',
|
||||
},
|
||||
provider: {
|
||||
description: 'The AI provider (e.g., "openai", "anthropic", "google")',
|
||||
type: 'string',
|
||||
},
|
||||
plugins: {
|
||||
description: 'Array of plugin identifiers to enable for this agent',
|
||||
items: { type: 'string' },
|
||||
type: 'array',
|
||||
},
|
||||
openingMessage: {
|
||||
description: 'Welcome message displayed when starting a new conversation',
|
||||
type: 'string',
|
||||
},
|
||||
openingQuestions: {
|
||||
description: 'Suggested questions to help users start the conversation',
|
||||
items: { type: 'string' },
|
||||
type: 'array',
|
||||
},
|
||||
tags: {
|
||||
description: 'Tags for categorizing the agent',
|
||||
items: { type: 'string' },
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
required: ['title'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Update an existing agent configuration. Only include fields you want to change.',
|
||||
name: AgentManagementApiName.updateAgent,
|
||||
parameters: {
|
||||
properties: {
|
||||
agentId: {
|
||||
description: 'The ID of the agent to update',
|
||||
type: 'string',
|
||||
},
|
||||
config: {
|
||||
description: 'Partial agent configuration to update',
|
||||
properties: {
|
||||
model: {
|
||||
description: 'The AI model to use',
|
||||
type: 'string',
|
||||
},
|
||||
provider: {
|
||||
description: 'The AI provider',
|
||||
type: 'string',
|
||||
},
|
||||
systemRole: {
|
||||
description: 'The system prompt',
|
||||
type: 'string',
|
||||
},
|
||||
plugins: {
|
||||
description: 'Array of enabled plugin identifiers',
|
||||
items: { type: 'string' },
|
||||
type: 'array',
|
||||
},
|
||||
openingMessage: {
|
||||
description: 'Opening message for new conversations',
|
||||
type: 'string',
|
||||
},
|
||||
openingQuestions: {
|
||||
description: 'Suggested opening questions',
|
||||
items: { type: 'string' },
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
meta: {
|
||||
description: 'Partial metadata to update',
|
||||
properties: {
|
||||
title: {
|
||||
description: 'Agent display name',
|
||||
type: 'string',
|
||||
},
|
||||
description: {
|
||||
description: 'Agent description',
|
||||
type: 'string',
|
||||
},
|
||||
avatar: {
|
||||
description: 'Agent avatar',
|
||||
type: 'string',
|
||||
},
|
||||
backgroundColor: {
|
||||
description: 'Background color',
|
||||
type: 'string',
|
||||
},
|
||||
tags: {
|
||||
description: 'Tags for categorization',
|
||||
items: { type: 'string' },
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
required: ['agentId'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Delete an agent from your workspace. This action cannot be undone. The agent and its associated session will be removed.',
|
||||
humanIntervention: 'required',
|
||||
name: AgentManagementApiName.deleteAgent,
|
||||
parameters: {
|
||||
properties: {
|
||||
agentId: {
|
||||
description: 'The ID of the agent to delete',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['agentId'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
|
||||
// ==================== Search ====================
|
||||
{
|
||||
description:
|
||||
"Search for agents in your workspace or the marketplace. Use 'user' source to find your own agents, 'market' for marketplace agents, or 'all' for both.",
|
||||
name: AgentManagementApiName.searchAgent,
|
||||
parameters: {
|
||||
properties: {
|
||||
keyword: {
|
||||
description: 'Search keywords to find agents by name or description',
|
||||
type: 'string',
|
||||
},
|
||||
source: {
|
||||
description:
|
||||
"Where to search: 'user' (your agents), 'market' (marketplace), 'all' (both). Default: 'all'",
|
||||
enum: ['user', 'market', 'all'],
|
||||
type: 'string',
|
||||
},
|
||||
category: {
|
||||
description:
|
||||
'Category filter for marketplace search (e.g., "programming", "writing", "translation")',
|
||||
type: 'string',
|
||||
},
|
||||
limit: {
|
||||
default: 10,
|
||||
description: 'Maximum number of results to return (default: 10, max: 20)',
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
required: [],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
|
||||
// ==================== Execution ====================
|
||||
{
|
||||
description:
|
||||
'Call an agent to handle a specific task or respond to an instruction. Can run synchronously (immediate response) or as a background task for longer operations.',
|
||||
name: AgentManagementApiName.callAgent,
|
||||
parameters: {
|
||||
properties: {
|
||||
agentId: {
|
||||
description: 'The ID of the agent to call',
|
||||
type: 'string',
|
||||
},
|
||||
instruction: {
|
||||
description:
|
||||
'The instruction or task for the agent to execute. Be specific about expected deliverables.',
|
||||
type: 'string',
|
||||
},
|
||||
runAsTask: {
|
||||
default: false,
|
||||
description:
|
||||
'If true, run as a background task for longer operations. The agent will work asynchronously and return results upon completion.',
|
||||
type: 'boolean',
|
||||
},
|
||||
taskTitle: {
|
||||
description: 'Brief title for the task (shown in UI). Required when runAsTask is true.',
|
||||
type: 'string',
|
||||
},
|
||||
timeout: {
|
||||
default: 1_800_000,
|
||||
description:
|
||||
'Maximum time in milliseconds to wait for task completion (default: 1800000 = 30 minutes). Only applies when runAsTask is true.',
|
||||
type: 'number',
|
||||
},
|
||||
skipCallSupervisor: {
|
||||
default: false,
|
||||
description:
|
||||
'If true (and in a group context), the orchestration will end after this agent responds, without calling the supervisor again. Only relevant when used within agent groups.',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
required: ['agentId', 'instruction'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
],
|
||||
identifier: AgentManagementIdentifier,
|
||||
meta: {
|
||||
avatar: '🤖',
|
||||
description: 'Create, manage, and orchestrate AI agents',
|
||||
title: 'Agent Management',
|
||||
},
|
||||
systemRole: systemPrompt,
|
||||
type: 'builtin',
|
||||
};
|
||||
|
||||
export { AgentManagementApiName, AgentManagementIdentifier } from './types';
|
||||
193
packages/builtin-tool-agent-management/src/systemRole.ts
Normal file
193
packages/builtin-tool-agent-management/src/systemRole.ts
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
/**
|
||||
* System role for Agent Management tool
|
||||
*
|
||||
* This provides guidance on how to effectively use the agent management tools
|
||||
* to create, configure, search, and orchestrate AI agents.
|
||||
*/
|
||||
export const systemPrompt = `You have Agent Management tools to create, configure, and orchestrate AI agents. Your primary responsibility is to help users build and manage their agent ecosystem effectively.
|
||||
|
||||
<core_capabilities>
|
||||
## Tool Overview
|
||||
|
||||
**Agent CRUD:**
|
||||
- **createAgent**: Create a new agent with custom configuration (title, description, systemRole, model, provider, plugins, avatar, etc.)
|
||||
- **updateAgent**: Modify an existing agent's settings
|
||||
- **deleteAgent**: Remove an agent from the workspace
|
||||
|
||||
**Discovery:**
|
||||
- **searchAgent**: Find agents in user's workspace or marketplace
|
||||
|
||||
**Execution:**
|
||||
- **callAgent**: Invoke an agent to handle a task (synchronously or as async background task)
|
||||
</core_capabilities>
|
||||
|
||||
<context_injection>
|
||||
## Available Resources
|
||||
|
||||
When this tool is enabled, you will receive contextual information about:
|
||||
- **Available Models**: List of AI models and providers you can use when creating/updating agents
|
||||
- **Available Plugins**: List of plugins (builtin tools, Klavis integrations, LobehubSkill providers) you can enable for agents
|
||||
|
||||
This information is automatically injected into the conversation context. Use the exact IDs from the context when specifying model/provider/plugins parameters.
|
||||
</context_injection>
|
||||
|
||||
<agent_creation_guide>
|
||||
## Creating Effective Agents
|
||||
|
||||
When creating an agent using createAgent, you can specify:
|
||||
|
||||
### 1. Basic Information (Required)
|
||||
- **title** (required): Clear, concise name that reflects the agent's purpose
|
||||
- **description** (optional): Brief summary of capabilities and use cases
|
||||
|
||||
### 2. System Prompt (systemRole)
|
||||
The system prompt is the most important element. A good system prompt should:
|
||||
- Define the agent's role and expertise
|
||||
- Specify the communication style and tone
|
||||
- Include constraints and guidelines
|
||||
- Provide examples when helpful
|
||||
|
||||
**Example structure:**
|
||||
\`\`\`
|
||||
You are a [role] specialized in [domain].
|
||||
|
||||
## Core Responsibilities
|
||||
- [Responsibility 1]
|
||||
- [Responsibility 2]
|
||||
|
||||
## Guidelines
|
||||
- [Guideline 1]
|
||||
- [Guideline 2]
|
||||
|
||||
## Response Format
|
||||
[How to structure responses]
|
||||
\`\`\`
|
||||
|
||||
### 3. Model & Provider Selection
|
||||
|
||||
**CRITICAL: You MUST select from the available models and providers listed in the injected context above. Do NOT use models that are not explicitly listed.**
|
||||
|
||||
When selecting a model, follow this priority order:
|
||||
|
||||
1. **First Priority - LobeHub Provider Models**:
|
||||
- If available, prioritize models from the "lobehub" provider
|
||||
- These are optimized for the LobeHub ecosystem
|
||||
|
||||
2. **Second Priority - Premium Frontier Models**:
|
||||
- **Anthropic**: Claude Sonnet 4.5, Claude Opus 4.5, or newer Opus/Sonnet series
|
||||
- **OpenAI**: GPT-5 or higher (exclude mini variants)
|
||||
- **Google**: Gemini 2.5 Pro or newer versions
|
||||
|
||||
3. **Third Priority - Standard Models**:
|
||||
- If none of the above are available, choose from other enabled models based on task requirements
|
||||
- Consider model capabilities (reasoning, vision, function calling) from the injected context
|
||||
|
||||
**Task-Based Recommendations**:
|
||||
- **Complex reasoning, analysis**: Choose models with strong reasoning capabilities
|
||||
- **Fast, simple tasks**: Choose lighter models for cost-effectiveness
|
||||
- **Multimodal tasks**: Ensure the model supports vision/video if needed
|
||||
- **Tool use**: Verify function calling support for agents using plugins
|
||||
|
||||
**IMPORTANT:** Always specify both \`model\` and \`provider\` parameters together using the exact IDs from the injected context.
|
||||
|
||||
### 4. Plugins (Optional)
|
||||
You can specify plugins during agent creation using the \`plugins\` parameter:
|
||||
- **plugins**: Array of plugin identifiers (e.g., ["lobe-image-designer", "search-engine"])
|
||||
|
||||
**Plugin types available:**
|
||||
- **Builtin tools**: Core system tools (e.g., web search, image generation)
|
||||
- **Klavis integrations**: Third-party service integrations requiring OAuth
|
||||
- **LobehubSkill providers**: Advanced skill providers
|
||||
|
||||
Refer to the injected context for available plugin IDs and descriptions.
|
||||
|
||||
### 5. Visual Customization (Optional)
|
||||
- **avatar**: Emoji or image URL (e.g., "🤖")
|
||||
- **backgroundColor**: Hex color code (e.g., "#3B82F6")
|
||||
- **tags**: Array of tags for categorization (e.g., ["coding", "assistant"])
|
||||
|
||||
### 6. User Experience (Optional)
|
||||
- **openingMessage**: Welcome message displayed when starting a new conversation
|
||||
- **openingQuestions**: Array of suggested questions to help users start (e.g., ["What can you help me with?"])
|
||||
</agent_creation_guide>
|
||||
|
||||
<search_guide>
|
||||
## Finding the Right Agent
|
||||
|
||||
Use searchAgent to discover agents:
|
||||
|
||||
**User Agents** (source: 'user'):
|
||||
- Your personally created agents
|
||||
- Previously used marketplace agents
|
||||
|
||||
**Marketplace Agents** (source: 'market'):
|
||||
- Community-created agents
|
||||
- Professional templates
|
||||
- Specialized tools
|
||||
|
||||
**Search Tips:**
|
||||
- Use specific keywords related to the task
|
||||
- Filter by category when browsing marketplace
|
||||
- Check agent descriptions for capability details
|
||||
</search_guide>
|
||||
|
||||
<execution_guide>
|
||||
## Calling Agents
|
||||
|
||||
### Synchronous Call (default)
|
||||
For quick responses in the conversation context:
|
||||
\`\`\`
|
||||
callAgent(agentId, instruction)
|
||||
\`\`\`
|
||||
The agent will respond directly in the current conversation.
|
||||
|
||||
### Asynchronous Task
|
||||
For longer operations that benefit from focused execution:
|
||||
\`\`\`
|
||||
callAgent(agentId, instruction, runAsTask: true, taskTitle: "Brief description")
|
||||
\`\`\`
|
||||
The agent will work in the background and return results upon completion.
|
||||
|
||||
**When to use runAsTask:**
|
||||
- Complex multi-step operations
|
||||
- Tasks requiring extended processing time
|
||||
- Work that shouldn't block the conversation flow
|
||||
- Operations that benefit from isolated execution context
|
||||
</execution_guide>
|
||||
|
||||
<workflow_patterns>
|
||||
## Common Workflows
|
||||
|
||||
### Pattern 1: Create with Full Configuration
|
||||
1. Review available models and plugins from injected context
|
||||
2. Create agent with complete configuration (title, systemRole, model, provider, plugins)
|
||||
3. Test the agent with sample tasks
|
||||
|
||||
### Pattern 2: Create and Refine
|
||||
1. Create agent with basic configuration (title, systemRole, model, provider)
|
||||
2. Test with sample tasks
|
||||
3. Update configuration based on results (add plugins, adjust settings)
|
||||
|
||||
### Pattern 3: Find and Use
|
||||
1. Search for existing agents (workspace or marketplace)
|
||||
2. Select the best match for the task
|
||||
3. Call agent with specific instruction
|
||||
|
||||
### Pattern 4: Create, Call, and Iterate
|
||||
1. Create a specialized agent for a specific task
|
||||
2. Immediately call the agent to execute the task
|
||||
3. Refine agent configuration based on results
|
||||
</workflow_patterns>
|
||||
|
||||
<best_practices>
|
||||
## Best Practices
|
||||
|
||||
1. **Use Context Information**: Always refer to the injected context for accurate model IDs, provider IDs, and plugin IDs
|
||||
2. **Specify Model AND Provider**: When setting a model, always specify both \`model\` and \`provider\` together
|
||||
3. **Start with Essential Config**: Begin with title, systemRole, model, and provider. Add plugins and other settings as needed
|
||||
4. **Clear Instructions**: When calling agents, be specific about expected outcomes and deliverables
|
||||
5. **Right Tool for the Job**: Match agent capabilities (model, plugins) to task requirements
|
||||
6. **Meaningful Metadata**: Use descriptive titles, tags, and descriptions for easy discovery
|
||||
7. **Test and Iterate**: Test agents with sample tasks and refine configuration based on actual usage
|
||||
8. **Plugin Selection**: Only enable plugins that are relevant to the agent's purpose to avoid unnecessary overhead
|
||||
</best_practices>`;
|
||||
304
packages/builtin-tool-agent-management/src/types.ts
Normal file
304
packages/builtin-tool-agent-management/src/types.ts
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
import type { LobeAgentConfig, MetaData } from '@lobechat/types';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
|
||||
/**
|
||||
* Agent Management Tool Identifier
|
||||
*/
|
||||
export const AgentManagementIdentifier = 'lobe-agent-management';
|
||||
|
||||
/**
|
||||
* Agent Management API Names
|
||||
*/
|
||||
export const AgentManagementApiName = {
|
||||
|
||||
|
||||
// ==================== Execution ====================
|
||||
/** Call an agent to handle a task */
|
||||
callAgent: 'callAgent',
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================== Agent CRUD ====================
|
||||
/** Create a new agent */
|
||||
createAgent: 'createAgent',
|
||||
|
||||
|
||||
|
||||
|
||||
/** Delete an agent */
|
||||
deleteAgent: 'deleteAgent',
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================== Search ====================
|
||||
/** Search agents (user's own and marketplace) */
|
||||
searchAgent: 'searchAgent',
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** Update an existing agent */
|
||||
updateAgent: 'updateAgent',
|
||||
} as const;
|
||||
|
||||
export type AgentManagementApiNameType =
|
||||
(typeof AgentManagementApiName)[keyof typeof AgentManagementApiName];
|
||||
|
||||
// ==================== Create Agent ====================
|
||||
|
||||
export interface CreateAgentParams {
|
||||
/**
|
||||
* Agent avatar (emoji or image URL)
|
||||
*/
|
||||
avatar?: string;
|
||||
/**
|
||||
* Background color for the agent card
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
/**
|
||||
* Agent description
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* AI model to use (e.g., "gpt-4o", "claude-3-5-sonnet")
|
||||
*/
|
||||
model?: string;
|
||||
/**
|
||||
* Opening message for new conversations
|
||||
*/
|
||||
openingMessage?: string;
|
||||
/**
|
||||
* Suggested opening questions
|
||||
*/
|
||||
openingQuestions?: string[];
|
||||
/**
|
||||
* Enabled plugins
|
||||
*/
|
||||
plugins?: string[];
|
||||
/**
|
||||
* AI provider (e.g., "openai", "anthropic")
|
||||
*/
|
||||
provider?: string;
|
||||
/**
|
||||
* System prompt that defines the agent's behavior
|
||||
*/
|
||||
systemRole?: string;
|
||||
/**
|
||||
* Tags for categorization
|
||||
*/
|
||||
tags?: string[];
|
||||
/**
|
||||
* Agent display name/title
|
||||
*/
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface CreateAgentState {
|
||||
/**
|
||||
* The created agent's ID
|
||||
*/
|
||||
agentId?: string;
|
||||
/**
|
||||
* Error message if creation failed
|
||||
*/
|
||||
error?: string;
|
||||
/**
|
||||
* The associated session ID
|
||||
*/
|
||||
sessionId?: string;
|
||||
/**
|
||||
* Whether the creation was successful
|
||||
*/
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
// ==================== Update Agent ====================
|
||||
|
||||
export interface UpdateAgentParams {
|
||||
/**
|
||||
* The agent ID to update
|
||||
*/
|
||||
agentId: string;
|
||||
/**
|
||||
* Partial agent configuration to update
|
||||
*/
|
||||
config?: PartialDeep<LobeAgentConfig>;
|
||||
/**
|
||||
* Partial metadata to update
|
||||
*/
|
||||
meta?: Partial<MetaData>;
|
||||
}
|
||||
|
||||
export interface UpdateAgentState {
|
||||
/**
|
||||
* The agent ID that was updated
|
||||
*/
|
||||
agentId: string;
|
||||
/**
|
||||
* Updated configuration fields
|
||||
*/
|
||||
config?: {
|
||||
newValues: Record<string, unknown>;
|
||||
previousValues: Record<string, unknown>;
|
||||
updatedFields: string[];
|
||||
};
|
||||
/**
|
||||
* Updated metadata fields
|
||||
*/
|
||||
meta?: {
|
||||
newValues: Partial<MetaData>;
|
||||
previousValues: Partial<MetaData>;
|
||||
updatedFields: string[];
|
||||
};
|
||||
/**
|
||||
* Whether the update was successful
|
||||
*/
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
// ==================== Delete Agent ====================
|
||||
|
||||
export interface DeleteAgentParams {
|
||||
/**
|
||||
* The agent ID to delete
|
||||
*/
|
||||
agentId: string;
|
||||
}
|
||||
|
||||
export interface DeleteAgentState {
|
||||
/**
|
||||
* The deleted agent ID
|
||||
*/
|
||||
agentId: string;
|
||||
/**
|
||||
* Whether the deletion was successful
|
||||
*/
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
// ==================== Search Agent ====================
|
||||
|
||||
export type SearchAgentSource = 'user' | 'market' | 'all';
|
||||
|
||||
export interface SearchAgentParams {
|
||||
/**
|
||||
* Category filter for marketplace search
|
||||
*/
|
||||
category?: string;
|
||||
/**
|
||||
* Search keywords
|
||||
*/
|
||||
keyword?: string;
|
||||
/**
|
||||
* Maximum number of results (default: 10)
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* Search source: 'user' (own agents), 'market' (marketplace), 'all' (both)
|
||||
*/
|
||||
source?: SearchAgentSource;
|
||||
}
|
||||
|
||||
export interface AgentSearchItem {
|
||||
/**
|
||||
* Agent avatar
|
||||
*/
|
||||
avatar?: string;
|
||||
/**
|
||||
* Background color
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
/**
|
||||
* Agent description
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* Agent ID (for user agents) or identifier (for market agents)
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Whether this is a marketplace agent
|
||||
*/
|
||||
isMarket?: boolean;
|
||||
/**
|
||||
* Agent title
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface SearchAgentState {
|
||||
/**
|
||||
* List of matching agents
|
||||
*/
|
||||
agents: AgentSearchItem[];
|
||||
/**
|
||||
* The search keyword used
|
||||
*/
|
||||
keyword?: string;
|
||||
/**
|
||||
* The search source used
|
||||
*/
|
||||
source: SearchAgentSource;
|
||||
/**
|
||||
* Total count of matching agents
|
||||
*/
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
// ==================== Call Agent ====================
|
||||
|
||||
export interface CallAgentParams {
|
||||
/**
|
||||
* The agent ID to call
|
||||
*/
|
||||
agentId: string;
|
||||
/**
|
||||
* Instruction or task for the agent to execute
|
||||
*/
|
||||
instruction: string;
|
||||
/**
|
||||
* If true, execute as an async background task
|
||||
*/
|
||||
runAsTask?: boolean;
|
||||
/**
|
||||
* Task title (required when runAsTask is true)
|
||||
*/
|
||||
taskTitle?: string;
|
||||
/**
|
||||
* Timeout in milliseconds for task execution (default: 1800000 = 30 minutes)
|
||||
*/
|
||||
timeout?: number;
|
||||
/**
|
||||
* If true (and in a group context), skip calling supervisor after agent responds.
|
||||
* Only relevant when used within agent groups. Default: false
|
||||
*/
|
||||
skipCallSupervisor?: boolean;
|
||||
}
|
||||
|
||||
export interface CallAgentState {
|
||||
/**
|
||||
* The agent ID being called
|
||||
*/
|
||||
agentId: string;
|
||||
/**
|
||||
* The instruction given
|
||||
*/
|
||||
instruction: string;
|
||||
/**
|
||||
* Execution mode
|
||||
*/
|
||||
mode: 'speak' | 'task';
|
||||
/**
|
||||
* Task ID if running as background task
|
||||
*/
|
||||
taskId?: string;
|
||||
/**
|
||||
* Whether to skip calling supervisor after agent responds (only relevant in group context)
|
||||
*/
|
||||
skipCallSupervisor?: boolean;
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
},
|
||||
"main": "./src/index.ts",
|
||||
"dependencies": {
|
||||
"@lobechat/agent-manager-runtime": "workspace:*",
|
||||
"@lobechat/builtin-tool-agent-builder": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { formatAgentProfile } from '@lobechat/prompts';
|
||||
import type { BuiltinServerRuntimeOutput } from '@lobechat/types';
|
||||
import type { BuiltinToolResult } from '@lobechat/types';
|
||||
|
||||
import { agentService } from '@/services/agent';
|
||||
import type { GroupMemberConfig } from '@/services/chatGroup';
|
||||
|
|
@ -42,10 +42,11 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
async getAgentInfo(
|
||||
groupId: string | undefined,
|
||||
args: GetAgentInfoParams,
|
||||
): Promise<BuiltinServerRuntimeOutput> {
|
||||
): Promise<BuiltinToolResult> {
|
||||
if (!groupId) {
|
||||
return {
|
||||
content: 'No group context available',
|
||||
error: { message: 'No group context available', type: 'NoGroupContext' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
|
@ -54,7 +55,11 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
const agent = agentGroupSelectors.getAgentByIdFromGroup(groupId, args.agentId)(state);
|
||||
|
||||
if (!agent) {
|
||||
return { content: `Agent "${args.agentId}" not found in this group`, success: false };
|
||||
return {
|
||||
content: `Agent "${args.agentId}" not found in this group`,
|
||||
error: { message: `Agent "${args.agentId}" not found`, type: 'AgentNotFound' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Return formatted agent profile for the supervisor
|
||||
|
|
@ -66,7 +71,7 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
/**
|
||||
* Search for agents that can be invited to the group
|
||||
*/
|
||||
async searchAgent(args: SearchAgentParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
async searchAgent(args: SearchAgentParams): Promise<BuiltinToolResult> {
|
||||
const { query, limit = 10 } = args;
|
||||
|
||||
try {
|
||||
|
|
@ -107,19 +112,14 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `Failed to search agents: ${err.message}`,
|
||||
error,
|
||||
success: false,
|
||||
};
|
||||
return this.handleError(error, 'Failed to search agents');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new agent and add it to the group
|
||||
*/
|
||||
async createAgent(groupId: string, args: CreateAgentParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
async createAgent(groupId: string, args: CreateAgentParams): Promise<BuiltinToolResult> {
|
||||
try {
|
||||
const state = getChatGroupStoreState();
|
||||
const group = agentGroupSelectors.getGroupById(groupId)(state);
|
||||
|
|
@ -127,7 +127,7 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
if (!group) {
|
||||
return {
|
||||
content: 'Group not found',
|
||||
error: 'Group not found',
|
||||
error: { message: 'Group not found', type: 'GroupNotFound' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
|
@ -149,6 +149,7 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
if (!result.agentId) {
|
||||
return {
|
||||
content: 'Failed to create agent: No agent ID returned',
|
||||
error: { message: 'No agent ID returned', type: 'CreateError' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
|
@ -166,12 +167,7 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `Failed to create agent: ${err.message}`,
|
||||
error,
|
||||
success: false,
|
||||
};
|
||||
return this.handleError(error, 'Failed to create agent');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -182,7 +178,7 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
async batchCreateAgents(
|
||||
groupId: string,
|
||||
args: BatchCreateAgentsParams,
|
||||
): Promise<BuiltinServerRuntimeOutput> {
|
||||
): Promise<BuiltinToolResult> {
|
||||
try {
|
||||
const state = getChatGroupStoreState();
|
||||
const group = agentGroupSelectors.getGroupById(groupId)(state);
|
||||
|
|
@ -190,7 +186,7 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
if (!group) {
|
||||
return {
|
||||
content: 'Group not found',
|
||||
error: 'Group not found',
|
||||
error: { message: 'Group not found', type: 'GroupNotFound' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
|
@ -231,19 +227,14 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `Failed to create agents: ${err.message}`,
|
||||
error,
|
||||
success: false,
|
||||
};
|
||||
return this.handleError(error, 'Failed to create agents');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invite an agent to the group
|
||||
*/
|
||||
async inviteAgent(groupId: string, args: InviteAgentParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
async inviteAgent(groupId: string, args: InviteAgentParams): Promise<BuiltinToolResult> {
|
||||
try {
|
||||
const state = getChatGroupStoreState();
|
||||
const group = agentGroupSelectors.getGroupById(groupId)(state);
|
||||
|
|
@ -251,7 +242,7 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
if (!group) {
|
||||
return {
|
||||
content: 'Group not found',
|
||||
error: 'Group not found',
|
||||
error: { message: 'Group not found', type: 'GroupNotFound' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
|
@ -302,19 +293,14 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
success: wasAdded,
|
||||
};
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `Failed to invite agent: ${err.message}`,
|
||||
error,
|
||||
success: false,
|
||||
};
|
||||
return this.handleError(error, 'Failed to invite agent');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an agent from the group
|
||||
*/
|
||||
async removeAgent(groupId: string, args: RemoveAgentParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
async removeAgent(groupId: string, args: RemoveAgentParams): Promise<BuiltinToolResult> {
|
||||
try {
|
||||
const state = getChatGroupStoreState();
|
||||
const group = agentGroupSelectors.getGroupById(groupId)(state);
|
||||
|
|
@ -322,7 +308,7 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
if (!group) {
|
||||
return {
|
||||
content: 'Group not found',
|
||||
error: 'Group not found',
|
||||
error: { message: 'Group not found', type: 'GroupNotFound' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
|
@ -379,12 +365,7 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `Failed to remove agent: ${err.message}`,
|
||||
error,
|
||||
success: false,
|
||||
};
|
||||
return this.handleError(error, 'Failed to remove agent');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -396,7 +377,7 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
async updateAgentPrompt(
|
||||
groupId: string,
|
||||
args: UpdateAgentPromptParams,
|
||||
): Promise<BuiltinServerRuntimeOutput> {
|
||||
): Promise<BuiltinToolResult> {
|
||||
try {
|
||||
const { agentId, prompt } = args;
|
||||
|
||||
|
|
@ -432,19 +413,14 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `Failed to update agent prompt: ${err.message}`,
|
||||
error,
|
||||
success: false,
|
||||
};
|
||||
return this.handleError(error, 'Failed to update agent prompt');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update group configuration and metadata (unified method)
|
||||
*/
|
||||
async updateGroup(args: UpdateGroupParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
async updateGroup(args: UpdateGroupParams): Promise<BuiltinToolResult> {
|
||||
try {
|
||||
const state = getChatGroupStoreState();
|
||||
const group = agentGroupSelectors.currentGroup(state);
|
||||
|
|
@ -452,7 +428,7 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
if (!group) {
|
||||
return {
|
||||
content: 'No active group found',
|
||||
error: 'No active group found',
|
||||
error: { message: 'No active group found', type: 'NoGroupContext' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
|
@ -462,7 +438,7 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
if (!config && !meta) {
|
||||
return {
|
||||
content: 'No configuration or metadata provided',
|
||||
error: 'No configuration or metadata provided',
|
||||
error: { message: 'No configuration or metadata provided', type: 'NoDataProvided' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
|
@ -532,19 +508,14 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `Failed to update group: ${err.message}`,
|
||||
error,
|
||||
success: false,
|
||||
};
|
||||
return this.handleError(error, 'Failed to update group');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update group shared prompt/content
|
||||
*/
|
||||
async updateGroupPrompt(args: UpdateGroupPromptParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
async updateGroupPrompt(args: UpdateGroupPromptParams): Promise<BuiltinToolResult> {
|
||||
try {
|
||||
const state = getChatGroupStoreState();
|
||||
const group = agentGroupSelectors.currentGroup(state);
|
||||
|
|
@ -552,7 +523,7 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
if (!group) {
|
||||
return {
|
||||
content: 'No active group found',
|
||||
error: 'No active group found',
|
||||
error: { message: 'No active group found', type: 'NoGroupContext' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
|
@ -589,16 +560,10 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `Failed to update group prompt: ${err.message}`,
|
||||
error,
|
||||
state: {
|
||||
newPrompt: args.prompt,
|
||||
success: false,
|
||||
} as UpdateGroupPromptState,
|
||||
return this.handleErrorWithState(error, 'Failed to update group prompt', {
|
||||
newPrompt: args.prompt,
|
||||
success: false,
|
||||
};
|
||||
} as UpdateGroupPromptState);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -613,4 +578,37 @@ export class GroupAgentBuilderExecutionRuntime {
|
|||
|
||||
await state.updateGroup(group.id, { content: prompt });
|
||||
}
|
||||
|
||||
// ==================== Error Handling ====================
|
||||
|
||||
private handleError(error: unknown, context: string): BuiltinToolResult {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `${context}: ${err.message}`,
|
||||
error: {
|
||||
body: error,
|
||||
message: err.message,
|
||||
type: 'RuntimeError',
|
||||
},
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
private handleErrorWithState<T extends object>(
|
||||
error: unknown,
|
||||
context: string,
|
||||
state: T,
|
||||
): BuiltinToolResult {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `${context}: ${err.message}`,
|
||||
error: {
|
||||
body: error,
|
||||
message: err.message,
|
||||
type: 'RuntimeError',
|
||||
},
|
||||
state,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,18 @@
|
|||
* Handles all group agent builder tool calls for configuring groups and their agents.
|
||||
* Extends AgentBuilder functionality with group-specific operations.
|
||||
*/
|
||||
import { AgentManagerRuntime } from '@lobechat/agent-manager-runtime';
|
||||
import type {
|
||||
GetAvailableModelsParams,
|
||||
InstallPluginParams,
|
||||
SearchMarketToolsParams,
|
||||
} from '@lobechat/builtin-tool-agent-builder';
|
||||
import { AgentBuilderExecutionRuntime } from '@lobechat/builtin-tool-agent-builder/executionRuntime';
|
||||
import type { BuiltinToolContext, BuiltinToolResult } from '@lobechat/types';
|
||||
import { BaseExecutor } from '@lobechat/types';
|
||||
|
||||
import { agentService } from '@/services/agent';
|
||||
import { discoverService } from '@/services/discover';
|
||||
|
||||
import { GroupAgentBuilderExecutionRuntime } from './ExecutionRuntime';
|
||||
import type {
|
||||
BatchCreateAgentsParams,
|
||||
|
|
@ -28,7 +31,10 @@ import type {
|
|||
} from './types';
|
||||
import { GroupAgentBuilderApiName, GroupAgentBuilderIdentifier } from './types';
|
||||
|
||||
const agentBuilderRuntime = new AgentBuilderExecutionRuntime();
|
||||
const agentManagerRuntime = new AgentManagerRuntime({
|
||||
agentService,
|
||||
discoverService,
|
||||
});
|
||||
const groupAgentBuilderRuntime = new GroupAgentBuilderExecutionRuntime();
|
||||
|
||||
class GroupAgentBuilderExecutor extends BaseExecutor<typeof GroupAgentBuilderApiName> {
|
||||
|
|
@ -41,29 +47,13 @@ class GroupAgentBuilderExecutor extends BaseExecutor<typeof GroupAgentBuilderApi
|
|||
params: GetAgentInfoParams,
|
||||
ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
const result = await groupAgentBuilderRuntime.getAgentInfo(ctx.groupId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return groupAgentBuilderRuntime.getAgentInfo(ctx.groupId, params);
|
||||
};
|
||||
|
||||
// ==================== Group Member Management ====================
|
||||
|
||||
searchAgent = async (params: SearchAgentParams): Promise<BuiltinToolResult> => {
|
||||
const result = await groupAgentBuilderRuntime.searchAgent(params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return groupAgentBuilderRuntime.searchAgent(params);
|
||||
};
|
||||
|
||||
createAgent = async (
|
||||
|
|
@ -80,15 +70,7 @@ class GroupAgentBuilderExecutor extends BaseExecutor<typeof GroupAgentBuilderApi
|
|||
};
|
||||
}
|
||||
|
||||
const result = await groupAgentBuilderRuntime.createAgent(groupId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return groupAgentBuilderRuntime.createAgent(groupId, params);
|
||||
};
|
||||
|
||||
batchCreateAgents = async (
|
||||
|
|
@ -105,15 +87,7 @@ class GroupAgentBuilderExecutor extends BaseExecutor<typeof GroupAgentBuilderApi
|
|||
};
|
||||
}
|
||||
|
||||
const result = await groupAgentBuilderRuntime.batchCreateAgents(groupId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return groupAgentBuilderRuntime.batchCreateAgents(groupId, params);
|
||||
};
|
||||
|
||||
inviteAgent = async (
|
||||
|
|
@ -130,15 +104,7 @@ class GroupAgentBuilderExecutor extends BaseExecutor<typeof GroupAgentBuilderApi
|
|||
};
|
||||
}
|
||||
|
||||
const result = await groupAgentBuilderRuntime.inviteAgent(groupId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return groupAgentBuilderRuntime.inviteAgent(groupId, params);
|
||||
};
|
||||
|
||||
removeAgent = async (
|
||||
|
|
@ -155,15 +121,7 @@ class GroupAgentBuilderExecutor extends BaseExecutor<typeof GroupAgentBuilderApi
|
|||
};
|
||||
}
|
||||
|
||||
const result = await groupAgentBuilderRuntime.removeAgent(groupId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return groupAgentBuilderRuntime.removeAgent(groupId, params);
|
||||
};
|
||||
|
||||
// ==================== Group Configuration ====================
|
||||
|
|
@ -182,68 +140,28 @@ class GroupAgentBuilderExecutor extends BaseExecutor<typeof GroupAgentBuilderApi
|
|||
};
|
||||
}
|
||||
|
||||
const result = await groupAgentBuilderRuntime.updateAgentPrompt(groupId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return groupAgentBuilderRuntime.updateAgentPrompt(groupId, params);
|
||||
};
|
||||
|
||||
updateGroup = async (params: UpdateGroupParams): Promise<BuiltinToolResult> => {
|
||||
const result = await groupAgentBuilderRuntime.updateGroup(params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return groupAgentBuilderRuntime.updateGroup(params);
|
||||
};
|
||||
|
||||
updateGroupPrompt = async (params: UpdateGroupPromptParams): Promise<BuiltinToolResult> => {
|
||||
const result = await groupAgentBuilderRuntime.updateGroupPrompt({
|
||||
return groupAgentBuilderRuntime.updateGroupPrompt({
|
||||
streaming: true,
|
||||
...params,
|
||||
});
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
// ==================== Inherited Operations (for supervisor agent) ====================
|
||||
|
||||
getAvailableModels = async (params: GetAvailableModelsParams): Promise<BuiltinToolResult> => {
|
||||
const result = await agentBuilderRuntime.getAvailableModels(params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return agentManagerRuntime.getAvailableModels(params);
|
||||
};
|
||||
|
||||
searchMarketTools = async (params: SearchMarketToolsParams): Promise<BuiltinToolResult> => {
|
||||
const result = await agentBuilderRuntime.searchMarketTools(params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return agentManagerRuntime.searchMarketTools(params);
|
||||
};
|
||||
|
||||
updateConfig = async (
|
||||
|
|
@ -263,15 +181,7 @@ class GroupAgentBuilderExecutor extends BaseExecutor<typeof GroupAgentBuilderApi
|
|||
};
|
||||
}
|
||||
|
||||
const result = await agentBuilderRuntime.updateAgentConfig(agentId, restParams);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return agentManagerRuntime.updateAgentConfig(agentId, restParams);
|
||||
};
|
||||
|
||||
installPlugin = async (
|
||||
|
|
@ -288,15 +198,7 @@ class GroupAgentBuilderExecutor extends BaseExecutor<typeof GroupAgentBuilderApi
|
|||
};
|
||||
}
|
||||
|
||||
const result = await agentBuilderRuntime.installPlugin(agentId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
return agentManagerRuntime.installPlugin(agentId, params);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { AgentBuilderManifest } from '@lobechat/builtin-tool-agent-builder';
|
||||
import { AgentManagementManifest } from '@lobechat/builtin-tool-agent-management';
|
||||
import { CalculatorManifest } from '@lobechat/builtin-tool-calculator';
|
||||
import { CloudSandboxManifest } from '@lobechat/builtin-tool-cloud-sandbox';
|
||||
import { GroupAgentBuilderManifest } from '@lobechat/builtin-tool-group-agent-builder';
|
||||
|
|
@ -15,6 +16,7 @@ import { WebBrowsingManifest } from '@lobechat/builtin-tool-web-browsing';
|
|||
|
||||
export const builtinToolIdentifiers: string[] = [
|
||||
AgentBuilderManifest.identifier,
|
||||
AgentManagementManifest.identifier,
|
||||
CalculatorManifest.identifier,
|
||||
LocalSystemManifest.identifier,
|
||||
WebBrowsingManifest.identifier,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { AgentBuilderManifest } from '@lobechat/builtin-tool-agent-builder';
|
||||
import { CalculatorManifest } from '@lobechat/builtin-tool-calculator';
|
||||
import { AgentManagementManifest } from '@lobechat/builtin-tool-agent-management';
|
||||
import { CloudSandboxManifest } from '@lobechat/builtin-tool-cloud-sandbox';
|
||||
import { GroupAgentBuilderManifest } from '@lobechat/builtin-tool-group-agent-builder';
|
||||
import { GroupManagementManifest } from '@lobechat/builtin-tool-group-management';
|
||||
|
|
@ -107,6 +108,12 @@ export const builtinTools: LobeBuiltinTool[] = [
|
|||
manifest: GroupManagementManifest,
|
||||
type: 'builtin',
|
||||
},
|
||||
{
|
||||
hidden: true,
|
||||
identifier: AgentManagementManifest.identifier,
|
||||
manifest: AgentManagementManifest,
|
||||
type: 'builtin',
|
||||
},
|
||||
{
|
||||
identifier: GTDManifest.identifier,
|
||||
manifest: GTDManifest,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ import {
|
|||
AgentBuilderInspectors,
|
||||
AgentBuilderManifest,
|
||||
} from '@lobechat/builtin-tool-agent-builder/client';
|
||||
import {
|
||||
AgentManagementInspectors,
|
||||
AgentManagementManifest,
|
||||
} from '@lobechat/builtin-tool-agent-management/client';
|
||||
import {
|
||||
CloudSandboxIdentifier,
|
||||
CloudSandboxInspectors,
|
||||
|
|
@ -47,6 +51,10 @@ import { type BuiltinInspector } from '@lobechat/types';
|
|||
*/
|
||||
const BuiltinToolInspectors: Record<string, Record<string, BuiltinInspector>> = {
|
||||
[AgentBuilderManifest.identifier]: AgentBuilderInspectors as Record<string, BuiltinInspector>,
|
||||
[AgentManagementManifest.identifier]: AgentManagementInspectors as Record<
|
||||
string,
|
||||
BuiltinInspector
|
||||
>,
|
||||
[CloudSandboxIdentifier]: CloudSandboxInspectors as Record<string, BuiltinInspector>,
|
||||
[GroupAgentBuilderManifest.identifier]: GroupAgentBuilderInspectors as Record<
|
||||
string,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { AgentBuilderManifest } from '@lobechat/builtin-tool-agent-builder';
|
||||
import { AgentBuilderRenders } from '@lobechat/builtin-tool-agent-builder/client';
|
||||
import { AgentManagementManifest } from '@lobechat/builtin-tool-agent-management';
|
||||
import { AgentManagementRenders } from '@lobechat/builtin-tool-agent-management/client';
|
||||
import { CloudSandboxManifest } from '@lobechat/builtin-tool-cloud-sandbox';
|
||||
import { CloudSandboxRenders } from '@lobechat/builtin-tool-cloud-sandbox/client';
|
||||
import { GroupAgentBuilderManifest } from '@lobechat/builtin-tool-group-agent-builder';
|
||||
|
|
@ -31,6 +33,7 @@ import { type BuiltinRender } from '@lobechat/types';
|
|||
*/
|
||||
const BuiltinToolsRenders: Record<string, Record<string, BuiltinRender>> = {
|
||||
[AgentBuilderManifest.identifier]: AgentBuilderRenders as Record<string, BuiltinRender>,
|
||||
[AgentManagementManifest.identifier]: AgentManagementRenders as Record<string, BuiltinRender>,
|
||||
[CloudSandboxManifest.identifier]: CloudSandboxRenders as Record<string, BuiltinRender>,
|
||||
[GroupAgentBuilderManifest.identifier]: GroupAgentBuilderRenders as Record<string, BuiltinRender>,
|
||||
[GroupManagementManifest.identifier]: GroupManagementRenders as Record<string, BuiltinRender>,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ import {
|
|||
AgentBuilderManifest,
|
||||
AgentBuilderStreamings,
|
||||
} from '@lobechat/builtin-tool-agent-builder/client';
|
||||
import {
|
||||
AgentManagementManifest,
|
||||
AgentManagementStreamings,
|
||||
} from '@lobechat/builtin-tool-agent-management/client';
|
||||
import {
|
||||
CloudSandboxManifest,
|
||||
CloudSandboxStreamings,
|
||||
|
|
@ -33,6 +37,10 @@ import { type BuiltinStreaming } from '@lobechat/types';
|
|||
*/
|
||||
const BuiltinToolStreamings: Record<string, Record<string, BuiltinStreaming>> = {
|
||||
[AgentBuilderManifest.identifier]: AgentBuilderStreamings as Record<string, BuiltinStreaming>,
|
||||
[AgentManagementManifest.identifier]: AgentManagementStreamings as Record<
|
||||
string,
|
||||
BuiltinStreaming
|
||||
>,
|
||||
[CloudSandboxManifest.identifier]: CloudSandboxStreamings as Record<string, BuiltinStreaming>,
|
||||
[GroupAgentBuilderManifest.identifier]: GroupAgentBuilderStreamings as Record<
|
||||
string,
|
||||
|
|
|
|||
|
|
@ -24,10 +24,11 @@ import {
|
|||
AgentBuilderContextInjector,
|
||||
EvalContextSystemInjector,
|
||||
ForceFinishSummaryInjector,
|
||||
GroupAgentBuilderContextInjector,
|
||||
GroupContextInjector,
|
||||
AgentManagementContextInjector,
|
||||
GTDPlanInjector,
|
||||
GTDTodoInjector,
|
||||
GroupAgentBuilderContextInjector,
|
||||
GroupContextInjector,
|
||||
HistorySummaryProvider,
|
||||
KnowledgeInjector,
|
||||
PageEditorContextInjector,
|
||||
|
|
@ -131,6 +132,7 @@ export class MessagesEngine {
|
|||
fileContext,
|
||||
agentBuilderContext,
|
||||
evalContext,
|
||||
agentManagementContext,
|
||||
groupAgentBuilderContext,
|
||||
agentGroup,
|
||||
gtd,
|
||||
|
|
@ -142,6 +144,8 @@ export class MessagesEngine {
|
|||
} = this.params;
|
||||
|
||||
const isAgentBuilderEnabled = !!agentBuilderContext;
|
||||
const isAgentManagementEnabled = !!agentManagementContext;
|
||||
|
||||
const isGroupAgentBuilderEnabled = !!groupAgentBuilderContext;
|
||||
const isAgentGroupEnabled = agentGroup?.agentMap && Object.keys(agentGroup.agentMap).length > 0;
|
||||
const isGroupContextEnabled =
|
||||
|
|
@ -218,7 +222,13 @@ export class MessagesEngine {
|
|||
agentContext: agentBuilderContext,
|
||||
}),
|
||||
|
||||
// 10. Group Agent Builder context injection (current group config/members for editing)
|
||||
// 7. Agent Management context injection (available models and plugins for agent creation)
|
||||
new AgentManagementContextInjector({
|
||||
enabled: isAgentManagementEnabled,
|
||||
context: agentManagementContext,
|
||||
}),
|
||||
|
||||
// 8. Group Agent Builder context injection (current group config/members for editing)
|
||||
new GroupAgentBuilderContextInjector({
|
||||
enabled: isGroupAgentBuilderEnabled,
|
||||
groupContext: groupAgentBuilderContext,
|
||||
|
|
@ -236,13 +246,13 @@ export class MessagesEngine {
|
|||
// 12. Tool system role injection (conditionally added)
|
||||
...(toolsConfig?.manifests && toolsConfig.manifests.length > 0
|
||||
? [
|
||||
new ToolSystemRoleProvider({
|
||||
isCanUseFC: capabilities?.isCanUseFC || (() => true),
|
||||
manifests: toolsConfig.manifests,
|
||||
model,
|
||||
provider,
|
||||
}),
|
||||
]
|
||||
new ToolSystemRoleProvider({
|
||||
isCanUseFC: capabilities?.isCanUseFC || (() => true),
|
||||
manifests: toolsConfig.manifests,
|
||||
model,
|
||||
provider,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
|
||||
// 13. History summary injection
|
||||
|
|
@ -262,15 +272,15 @@ export class MessagesEngine {
|
|||
? pageContentContext
|
||||
: initialContext?.pageEditor
|
||||
? {
|
||||
markdown: initialContext.pageEditor.markdown,
|
||||
metadata: {
|
||||
charCount: initialContext.pageEditor.metadata.charCount,
|
||||
lineCount: initialContext.pageEditor.metadata.lineCount,
|
||||
title: initialContext.pageEditor.metadata.title,
|
||||
},
|
||||
// Use latest XML from stepContext if available, otherwise fallback to initial XML
|
||||
xml: stepContext?.stepPageEditor?.xml || initialContext.pageEditor.xml,
|
||||
}
|
||||
markdown: initialContext.pageEditor.markdown,
|
||||
metadata: {
|
||||
charCount: initialContext.pageEditor.metadata.charCount,
|
||||
lineCount: initialContext.pageEditor.metadata.lineCount,
|
||||
title: initialContext.pageEditor.metadata.title,
|
||||
},
|
||||
// Use latest XML from stepContext if available, otherwise fallback to initial XML
|
||||
xml: stepContext?.stepPageEditor?.xml || initialContext.pageEditor.xml,
|
||||
}
|
||||
: undefined,
|
||||
}),
|
||||
|
||||
|
|
@ -311,26 +321,26 @@ export class MessagesEngine {
|
|||
// This must be BEFORE GroupRoleTransformProcessor so we filter based on original agentId/tools
|
||||
...(isAgentGroupEnabled && agentGroup.agentMap && agentGroup.currentAgentId
|
||||
? [
|
||||
new GroupOrchestrationFilterProcessor({
|
||||
agentMap: Object.fromEntries(
|
||||
Object.entries(agentGroup.agentMap).map(([id, info]) => [id, { role: info.role }]),
|
||||
),
|
||||
currentAgentId: agentGroup.currentAgentId,
|
||||
// Only enabled when current agent is NOT supervisor (supervisor needs to see orchestration history)
|
||||
enabled: agentGroup.currentAgentRole !== 'supervisor',
|
||||
}),
|
||||
]
|
||||
new GroupOrchestrationFilterProcessor({
|
||||
agentMap: Object.fromEntries(
|
||||
Object.entries(agentGroup.agentMap).map(([id, info]) => [id, { role: info.role }]),
|
||||
),
|
||||
currentAgentId: agentGroup.currentAgentId,
|
||||
// Only enabled when current agent is NOT supervisor (supervisor needs to see orchestration history)
|
||||
enabled: agentGroup.currentAgentRole !== 'supervisor',
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
|
||||
// 26. Group role transform (convert other agents' messages to user role with speaker tags)
|
||||
// This must be BEFORE ToolCallProcessor so other agents' tool messages are converted first
|
||||
...(isAgentGroupEnabled && agentGroup.currentAgentId
|
||||
? [
|
||||
new GroupRoleTransformProcessor({
|
||||
agentMap: agentGroup.agentMap!,
|
||||
currentAgentId: agentGroup.currentAgentId,
|
||||
}),
|
||||
]
|
||||
new GroupRoleTransformProcessor({
|
||||
agentMap: agentGroup.agentMap!,
|
||||
currentAgentId: agentGroup.currentAgentId,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
|
||||
// =============================================
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import type { AgentBuilderContext } from '../../providers/AgentBuilderContextInj
|
|||
import type { EvalContext } from '../../providers/EvalContextSystemInjector';
|
||||
import type { GroupAgentBuilderContext } from '../../providers/GroupAgentBuilderContextInjector';
|
||||
import type { GroupMemberInfo } from '../../providers/GroupContextInjector';
|
||||
import type { AgentManagementContext } from '../../providers/AgentManagementContextInjector';
|
||||
import type { GTDPlan } from '../../providers/GTDPlanInjector';
|
||||
import type { GTDTodoList } from '../../providers/GTDTodoInjector';
|
||||
import type { SkillMeta } from '../../providers/SkillContextProvider';
|
||||
|
|
@ -245,6 +246,8 @@ export interface MessagesEngineParams {
|
|||
agentBuilderContext?: AgentBuilderContext;
|
||||
/** Eval context for injecting environment prompts into system message */
|
||||
evalContext?: EvalContext;
|
||||
/** Agent Management context */
|
||||
agentManagementContext?: AgentManagementContext;
|
||||
/** Agent group configuration for multi-agent scenarios */
|
||||
agentGroup?: AgentGroupConfig;
|
||||
/** Group Agent Builder context */
|
||||
|
|
@ -300,6 +303,7 @@ export interface MessagesEngineResult {
|
|||
export { type AgentInfo } from '../../processors/GroupRoleTransform';
|
||||
export { type AgentBuilderContext } from '../../providers/AgentBuilderContextInjector';
|
||||
export { type EvalContext } from '../../providers/EvalContextSystemInjector';
|
||||
export { type AgentManagementContext } from '../../providers/AgentManagementContextInjector';
|
||||
export { type GroupAgentBuilderContext } from '../../providers/GroupAgentBuilderContextInjector';
|
||||
export { type GTDPlan } from '../../providers/GTDPlanInjector';
|
||||
export { type GTDTodoItem, type GTDTodoList } from '../../providers/GTDTodoInjector';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,235 @@
|
|||
import debug from 'debug';
|
||||
|
||||
import { BaseProvider } from '../base/BaseProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
const log = debug('context-engine:provider:AgentManagementContextInjector');
|
||||
|
||||
/**
|
||||
* Escape XML special characters
|
||||
*/
|
||||
const escapeXml = (str: string): string => {
|
||||
return str
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
};
|
||||
|
||||
/**
|
||||
* Available model info for Agent Management context
|
||||
*/
|
||||
export interface AvailableModelInfo {
|
||||
/** Model abilities */
|
||||
abilities?: {
|
||||
files?: boolean;
|
||||
functionCall?: boolean;
|
||||
reasoning?: boolean;
|
||||
vision?: boolean;
|
||||
};
|
||||
/** Model description */
|
||||
description?: string;
|
||||
/** Model ID */
|
||||
id: string;
|
||||
/** Model display name */
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Available provider info for Agent Management context
|
||||
*/
|
||||
export interface AvailableProviderInfo {
|
||||
/** Provider ID */
|
||||
id: string;
|
||||
/** Available models under this provider */
|
||||
models: AvailableModelInfo[];
|
||||
/** Provider display name */
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Available plugin info for Agent Management context
|
||||
*/
|
||||
export interface AvailablePluginInfo {
|
||||
/** Plugin description */
|
||||
description?: string;
|
||||
/** Plugin identifier */
|
||||
identifier: string;
|
||||
/** Plugin display name */
|
||||
name: string;
|
||||
/** Plugin type: 'builtin' for built-in tools, 'klavis' for Klavis servers, 'lobehub-skill' for LobehubSkill providers */
|
||||
type: 'builtin' | 'klavis' | 'lobehub-skill';
|
||||
}
|
||||
|
||||
/**
|
||||
* Agent Management context
|
||||
*/
|
||||
export interface AgentManagementContext {
|
||||
/** Available plugins (all types) */
|
||||
availablePlugins?: AvailablePluginInfo[];
|
||||
/** Available providers and models */
|
||||
availableProviders?: AvailableProviderInfo[];
|
||||
}
|
||||
|
||||
export interface AgentManagementContextInjectorConfig {
|
||||
/** Agent Management context to inject */
|
||||
context?: AgentManagementContext;
|
||||
/** Whether Agent Management tool is enabled */
|
||||
enabled?: boolean;
|
||||
/** Function to format Agent Management context */
|
||||
formatContext?: (context: AgentManagementContext) => string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format Agent Management context as XML for injection
|
||||
*/
|
||||
const defaultFormatContext = (context: AgentManagementContext): string => {
|
||||
const parts: string[] = [];
|
||||
|
||||
// Add available models section
|
||||
if (context.availableProviders && context.availableProviders.length > 0) {
|
||||
const providersXml = context.availableProviders
|
||||
.map((provider) => {
|
||||
const modelsXml = provider.models
|
||||
.map((model) => {
|
||||
const attrs: string[] = [`id="${model.id}"`];
|
||||
if (model.abilities) {
|
||||
if (model.abilities.functionCall) attrs.push('functionCall="true"');
|
||||
if (model.abilities.vision) attrs.push('vision="true"');
|
||||
if (model.abilities.files) attrs.push('files="true"');
|
||||
if (model.abilities.reasoning) attrs.push('reasoning="true"');
|
||||
}
|
||||
const desc = model.description ? ` - ${escapeXml(model.description)}` : '';
|
||||
return ` <model ${attrs.join(' ')}>${escapeXml(model.name)}${desc}</model>`;
|
||||
})
|
||||
.join('\n');
|
||||
return ` <provider id="${provider.id}" name="${escapeXml(provider.name)}">\n${modelsXml}\n </provider>`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
parts.push(`<available_models>\n${providersXml}\n</available_models>`);
|
||||
}
|
||||
|
||||
// Add available plugins section
|
||||
if (context.availablePlugins && context.availablePlugins.length > 0) {
|
||||
const builtinPlugins = context.availablePlugins.filter((p) => p.type === 'builtin');
|
||||
const klavisPlugins = context.availablePlugins.filter((p) => p.type === 'klavis');
|
||||
const lobehubSkillPlugins = context.availablePlugins.filter((p) => p.type === 'lobehub-skill');
|
||||
|
||||
const pluginsSections: string[] = [];
|
||||
|
||||
if (builtinPlugins.length > 0) {
|
||||
const builtinItems = builtinPlugins
|
||||
.map((p) => {
|
||||
const desc = p.description ? ` - ${escapeXml(p.description)}` : '';
|
||||
return ` <plugin id="${p.identifier}">${escapeXml(p.name)}${desc}</plugin>`;
|
||||
})
|
||||
.join('\n');
|
||||
pluginsSections.push(` <builtin_plugins>\n${builtinItems}\n </builtin_plugins>`);
|
||||
}
|
||||
|
||||
if (klavisPlugins.length > 0) {
|
||||
const klavisItems = klavisPlugins
|
||||
.map((p) => {
|
||||
const desc = p.description ? ` - ${escapeXml(p.description)}` : '';
|
||||
return ` <plugin id="${p.identifier}">${escapeXml(p.name)}${desc}</plugin>`;
|
||||
})
|
||||
.join('\n');
|
||||
pluginsSections.push(` <klavis_plugins>\n${klavisItems}\n </klavis_plugins>`);
|
||||
}
|
||||
|
||||
if (lobehubSkillPlugins.length > 0) {
|
||||
const lobehubSkillItems = lobehubSkillPlugins
|
||||
.map((p) => {
|
||||
const desc = p.description ? ` - ${escapeXml(p.description)}` : '';
|
||||
return ` <plugin id="${p.identifier}">${escapeXml(p.name)}${desc}</plugin>`;
|
||||
})
|
||||
.join('\n');
|
||||
pluginsSections.push(
|
||||
` <lobehub_skill_plugins>\n${lobehubSkillItems}\n </lobehub_skill_plugins>`,
|
||||
);
|
||||
}
|
||||
|
||||
if (pluginsSections.length > 0) {
|
||||
parts.push(`<available_plugins>\n${pluginsSections.join('\n')}\n</available_plugins>`);
|
||||
}
|
||||
}
|
||||
|
||||
if (parts.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `<agent_management_context>
|
||||
<instruction>When creating or updating agents using the Agent Management tools, you can select from these available models and plugins. Use the exact IDs from this context when specifying model/provider/plugins parameters.</instruction>
|
||||
${parts.join('\n')}
|
||||
</agent_management_context>`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Agent Management Context Injector
|
||||
* Responsible for injecting available models and plugins when Agent Management tool is enabled
|
||||
*/
|
||||
export class AgentManagementContextInjector extends BaseProvider {
|
||||
readonly name = 'AgentManagementContextInjector';
|
||||
|
||||
constructor(
|
||||
private config: AgentManagementContextInjectorConfig,
|
||||
options: ProcessorOptions = {},
|
||||
) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
protected async doProcess(context: PipelineContext): Promise<PipelineContext> {
|
||||
const clonedContext = this.cloneContext(context);
|
||||
|
||||
// Skip if Agent Management is not enabled
|
||||
if (!this.config.enabled) {
|
||||
log('Agent Management not enabled, skipping injection');
|
||||
return this.markAsExecuted(clonedContext);
|
||||
}
|
||||
|
||||
// Skip if no context data
|
||||
if (!this.config.context) {
|
||||
log('No Agent Management context provided, skipping injection');
|
||||
return this.markAsExecuted(clonedContext);
|
||||
}
|
||||
|
||||
// Format context
|
||||
const formatFn = this.config.formatContext || defaultFormatContext;
|
||||
const formattedContent = formatFn(this.config.context);
|
||||
|
||||
// Skip if no content to inject
|
||||
if (!formattedContent) {
|
||||
log('No content to inject after formatting');
|
||||
return this.markAsExecuted(clonedContext);
|
||||
}
|
||||
|
||||
// Find the first user message index
|
||||
const firstUserIndex = clonedContext.messages.findIndex((msg) => msg.role === 'user');
|
||||
|
||||
if (firstUserIndex === -1) {
|
||||
log('No user messages found, skipping injection');
|
||||
return this.markAsExecuted(clonedContext);
|
||||
}
|
||||
|
||||
// Insert a new user message with context before the first user message
|
||||
const contextMessage = {
|
||||
content: formattedContent,
|
||||
createdAt: Date.now(),
|
||||
id: `agent-management-context-${Date.now()}`,
|
||||
meta: { injectType: 'agent-management-context', systemInjection: true },
|
||||
role: 'user' as const,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
clonedContext.messages.splice(firstUserIndex, 0, contextMessage);
|
||||
|
||||
// Update metadata
|
||||
clonedContext.metadata.agentManagementContextInjected = true;
|
||||
|
||||
log('Agent Management context injected as new user message');
|
||||
|
||||
return this.markAsExecuted(clonedContext);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
export { AgentBuilderContextInjector } from './AgentBuilderContextInjector';
|
||||
export { EvalContextSystemInjector } from './EvalContextSystemInjector';
|
||||
export { ForceFinishSummaryInjector } from './ForceFinishSummaryInjector';
|
||||
export { AgentManagementContextInjector } from './AgentManagementContextInjector';
|
||||
export { GroupAgentBuilderContextInjector } from './GroupAgentBuilderContextInjector';
|
||||
export { GroupContextInjector } from './GroupContextInjector';
|
||||
export { GTDPlanInjector } from './GTDPlanInjector';
|
||||
|
|
@ -25,6 +26,13 @@ export type {
|
|||
} from './AgentBuilderContextInjector';
|
||||
export type { EvalContext, EvalContextSystemInjectorConfig } from './EvalContextSystemInjector';
|
||||
export type { ForceFinishSummaryInjectorConfig } from './ForceFinishSummaryInjector';
|
||||
export type {
|
||||
AgentManagementContext,
|
||||
AgentManagementContextInjectorConfig,
|
||||
AvailableModelInfo,
|
||||
AvailablePluginInfo,
|
||||
AvailableProviderInfo,
|
||||
} from './AgentManagementContextInjector';
|
||||
export type {
|
||||
GroupAgentBuilderContext,
|
||||
GroupAgentBuilderContextInjectorConfig,
|
||||
|
|
|
|||
|
|
@ -21,9 +21,20 @@ import type { Message, MessageGroupMetadata, ParseResult } from './types';
|
|||
* @returns ParseResult containing messageMap, displayTree, and flatList
|
||||
*/
|
||||
export function parse(messages: Message[], messageGroups?: MessageGroupMetadata[]): ParseResult {
|
||||
// Pre-processing: Transform sub_agent messages before building helper maps
|
||||
// This ensures FlatListBuilder and MessageCollector see the correct agentId
|
||||
// and won't merge messages from different agents into the same group
|
||||
// Only applies to scope: 'sub_agent' (agent-to-agent calls, not group orchestration)
|
||||
const processedMessages = messages.map((msg) => {
|
||||
if (msg.metadata?.scope === 'sub_agent' && msg.metadata?.subAgentId) {
|
||||
return { ...msg, agentId: msg.metadata.subAgentId };
|
||||
}
|
||||
return msg;
|
||||
});
|
||||
|
||||
// Phase 1: Indexing
|
||||
// Build helper maps for O(1) access patterns
|
||||
const helperMaps = buildHelperMaps(messages, messageGroups);
|
||||
const helperMaps = buildHelperMaps(processedMessages, messageGroups);
|
||||
|
||||
// Phase 2: Structuring
|
||||
// Convert flat parent-child relationships to tree structure
|
||||
|
|
@ -37,7 +48,7 @@ export function parse(messages: Message[], messageGroups?: MessageGroupMetadata[
|
|||
|
||||
// Phase 3b: Generate flatList for virtual list rendering
|
||||
// Implements RFC priority-based pattern matching
|
||||
const flatList = transformer.flatten(messages);
|
||||
const flatList = transformer.flatten(processedMessages);
|
||||
|
||||
// Convert messageMap from Map to plain object for serialization
|
||||
// Clean up metadata for assistant messages with tools
|
||||
|
|
@ -76,6 +87,9 @@ export function parse(messages: Message[], messageGroups?: MessageGroupMetadata[
|
|||
processedMessage = { ...message, role: 'supervisor' as const };
|
||||
}
|
||||
|
||||
// Note: sub_agent scope transformation is done in pre-processing phase (before buildHelperMaps)
|
||||
// No need to transform agentId here since it's already been transformed
|
||||
|
||||
// For assistant messages with tools, clean metadata to keep only usage/performance fields
|
||||
if (
|
||||
processedMessage.role === 'assistant' &&
|
||||
|
|
@ -100,7 +114,9 @@ export function parse(messages: Message[], messageGroups?: MessageGroupMetadata[
|
|||
|
||||
// Transform supervisor messages in flatList
|
||||
// For non-grouped supervisor messages (e.g., supervisor summary without tools)
|
||||
// Note: sub_agent scope transformation is done in pre-processing phase (before buildHelperMaps)
|
||||
const processedFlatList = flatList.map((msg) => {
|
||||
// Transform supervisor messages
|
||||
if (msg.role === 'assistant' && msg.metadata?.isSupervisor) {
|
||||
return { ...msg, role: 'supervisor' as const };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ export class MessageCollector {
|
|||
const nextMessages = allMessages.filter((m) => m.parentId === toolMsg.id);
|
||||
|
||||
// Stop if there are task children - they should be handled separately, not part of AssistantGroup
|
||||
// This ensures that messages after a task are not merged into the AssistantGroup before the task
|
||||
const taskChildren = nextMessages.filter((m) => m.role === 'task');
|
||||
if (taskChildren.length > 0) {
|
||||
continue;
|
||||
|
|
@ -142,7 +143,8 @@ export class MessageCollector {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Stop if there are task children - they should be handled separately, not part of AssistantGroup
|
||||
// Stop if there are ANY task children - they should be processed separately, not part of AssistantGroup
|
||||
// This ensures that messages after a task are not merged into the AssistantGroup before the task
|
||||
const taskChildren = toolNode.children.filter((child) => {
|
||||
const childMsg = this.messageMap.get(child.id);
|
||||
return childMsg?.role === 'task';
|
||||
|
|
@ -181,7 +183,7 @@ export class MessageCollector {
|
|||
return lastNode;
|
||||
}
|
||||
|
||||
// Check if lastNode is a tool with task children
|
||||
// Check if lastNode is a tool with ANY task children
|
||||
// In this case, return the tool node itself so ContextTreeBuilder can process tasks
|
||||
if (lastMsg?.role === 'tool') {
|
||||
const taskChildren = lastNode.children.filter((child) => {
|
||||
|
|
@ -224,7 +226,8 @@ export class MessageCollector {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Stop if there are task children - they should be handled separately, not part of AssistantGroup
|
||||
// Stop if there are ANY task children - they should be processed separately, not part of AssistantGroup
|
||||
// This ensures that messages after a task are not merged into the AssistantGroup before the task
|
||||
const taskNodes = toolNode.children.filter((child) => {
|
||||
const childMsg = this.messageMap.get(child.id);
|
||||
return childMsg?.role === 'task';
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import type { IThreadType } from './topic/thread';
|
|||
* - thread: Agent thread conversation
|
||||
* - group: Group main conversation
|
||||
* - group_agent: Agent conversation within a group
|
||||
* - sub_agent: Agent-to-agent communication (non-group, uses subAgentId for config/display only)
|
||||
*/
|
||||
export type MessageMapScope =
|
||||
| 'main'
|
||||
|
|
@ -14,7 +15,8 @@ export type MessageMapScope =
|
|||
| 'group_agent'
|
||||
| 'group_agent_builder'
|
||||
| 'page'
|
||||
| 'agent_builder';
|
||||
| 'agent_builder'
|
||||
| 'sub_agent';
|
||||
|
||||
/**
|
||||
* Context for generating message map key with scope-driven architecture
|
||||
|
|
|
|||
|
|
@ -98,6 +98,8 @@ export const MessageMetadataSchema = ModelUsageSchema.merge(ModelPerformanceSche
|
|||
isSupervisor: z.boolean().optional(),
|
||||
pageSelections: z.array(PageSelectionSchema).optional(),
|
||||
reactions: z.array(EmojiReactionSchema).optional(),
|
||||
scope: z.string().optional(),
|
||||
subAgentId: z.string().optional(),
|
||||
});
|
||||
|
||||
export interface ModelUsage extends ModelTokensUsage {
|
||||
|
|
@ -154,6 +156,19 @@ export interface MessageMetadata extends ModelUsage, ModelPerformance {
|
|||
* Used by conversation-flow to transform role to 'supervisor' for UI rendering
|
||||
*/
|
||||
isSupervisor?: boolean;
|
||||
/**
|
||||
* Message scope - indicates the context in which this message was created
|
||||
* Used by conversation-flow to determine how to handle message grouping and display
|
||||
* See MessageMapScope for available values
|
||||
*/
|
||||
scope?: string;
|
||||
/**
|
||||
* Sub Agent ID - behavior depends on scope
|
||||
* - scope: 'sub_agent': conversation-flow will transform message.agentId to this value for display
|
||||
* - scope: 'group' | 'group_agent': indicates the agent that generated this message in group mode
|
||||
* Used by callAgent tool (sub_agent) and group orchestration (group modes)
|
||||
*/
|
||||
subAgentId?: string;
|
||||
/**
|
||||
* Flag indicating if message is pinned (excluded from compression)
|
||||
*/
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 111 KiB |
|
|
@ -24,6 +24,20 @@ export default {
|
|||
'builtins.lobe-agent-builder.inspector.noResults': 'No results',
|
||||
'builtins.lobe-agent-builder.inspector.togglePlugin': 'Toggle',
|
||||
'builtins.lobe-agent-builder.title': 'Agent Builder Expert',
|
||||
'builtins.lobe-agent-management.apiName.callAgent': 'Call agent',
|
||||
'builtins.lobe-agent-management.apiName.createAgent': 'Create agent',
|
||||
'builtins.lobe-agent-management.apiName.deleteAgent': 'Delete agent',
|
||||
'builtins.lobe-agent-management.apiName.searchAgent': 'Search agents',
|
||||
'builtins.lobe-agent-management.apiName.updateAgent': 'Update agent',
|
||||
'builtins.lobe-agent-management.inspector.callAgent.sync': 'Calling:',
|
||||
'builtins.lobe-agent-management.inspector.callAgent.task': 'Assigning task to:',
|
||||
'builtins.lobe-agent-management.inspector.createAgent.title': 'Creating agent:',
|
||||
'builtins.lobe-agent-management.inspector.searchAgent.all': 'Search agents:',
|
||||
'builtins.lobe-agent-management.inspector.searchAgent.market': 'Search market:',
|
||||
'builtins.lobe-agent-management.inspector.searchAgent.results': '{{count}} results',
|
||||
'builtins.lobe-agent-management.inspector.searchAgent.user': 'Search my agents:',
|
||||
'builtins.lobe-agent-management.inspector.updateAgent.title': 'Updating agent:',
|
||||
'builtins.lobe-agent-management.title': 'Agent Manager',
|
||||
'builtins.lobe-cloud-sandbox.apiName.editLocalFile': 'Edit file',
|
||||
'builtins.lobe-cloud-sandbox.apiName.executeCode': 'Execute code',
|
||||
'builtins.lobe-cloud-sandbox.apiName.exportFile': 'Export file',
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ export const serverMessagesEngine = async ({
|
|||
userMemory,
|
||||
agentBuilderContext,
|
||||
evalContext,
|
||||
agentManagementContext,
|
||||
pageContentContext,
|
||||
}: ServerMessagesEngineParams): Promise<OpenAIChatMessage[]> => {
|
||||
const engine = new MessagesEngine({
|
||||
|
|
@ -118,6 +119,7 @@ export const serverMessagesEngine = async ({
|
|||
// Extended contexts
|
||||
...(agentBuilderContext && { agentBuilderContext }),
|
||||
...(evalContext && { evalContext }),
|
||||
...(agentManagementContext && { agentManagementContext }),
|
||||
...(pageContentContext && { pageContentContext }),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/* eslint-disable perfectionist/sort-interfaces */
|
||||
import type {
|
||||
AgentBuilderContext,
|
||||
AgentManagementContext,
|
||||
EvalContext,
|
||||
FileContent,
|
||||
KnowledgeBaseInfo,
|
||||
|
|
@ -62,6 +63,8 @@ export interface ServerMessagesEngineParams {
|
|||
// ========== Extended contexts ==========
|
||||
/** Agent Builder context (optional, for editing agents) */
|
||||
agentBuilderContext?: AgentBuilderContext;
|
||||
/** Agent Management context (optional, available models and plugins) */
|
||||
agentManagementContext?: AgentManagementContext;
|
||||
// ========== Capability injection ==========
|
||||
/** Model capability checkers */
|
||||
capabilities?: ServerModelCapabilities;
|
||||
|
|
@ -116,6 +119,7 @@ export interface ServerMessagesEngineParams {
|
|||
export {
|
||||
type AgentBuilderContext,
|
||||
type EvalContext,
|
||||
type AgentManagementContext,
|
||||
type FileContent,
|
||||
type KnowledgeBaseInfo,
|
||||
type UserMemoryData,
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ export const agentRouter = router({
|
|||
chatConfig: true,
|
||||
openingMessage: true,
|
||||
openingQuestions: true,
|
||||
plugins: true,
|
||||
tags: true,
|
||||
tts: true,
|
||||
})
|
||||
|
|
@ -67,7 +66,7 @@ export const agentRouter = router({
|
|||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const session = await ctx.sessionModel.create({
|
||||
config: input.config,
|
||||
config: input.config as any,
|
||||
session: { groupId: input.groupId },
|
||||
type: 'agent',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -855,12 +855,25 @@ export const aiAgentRouter = router({
|
|||
|
||||
log('getSubAgentTaskStatus: marked thread %s as completed', threadId);
|
||||
} else if (realtimeStatus.hasError || redisState.status === 'error') {
|
||||
updatedMetadata.error = redisState.error;
|
||||
// Format error properly to avoid [object Object] in serialization
|
||||
const errorObj = redisState.error as any;
|
||||
const formattedError = errorObj
|
||||
? typeof errorObj === 'object' && 'message' in errorObj
|
||||
? { message: errorObj.message, ...errorObj }
|
||||
: { message: String(errorObj) }
|
||||
: undefined;
|
||||
|
||||
updatedMetadata.error = formattedError;
|
||||
updatedMetadata.completedAt = new Date().toISOString();
|
||||
if (metadata?.startedAt) {
|
||||
updatedMetadata.duration = Date.now() - new Date(metadata.startedAt).getTime();
|
||||
}
|
||||
|
||||
log('getSubAgentTaskStatus: error formatting for thread %s: %O', threadId, {
|
||||
originalError: redisState.error,
|
||||
formattedError,
|
||||
});
|
||||
|
||||
await ctx.threadModel.update(threadId, {
|
||||
metadata: updatedMetadata,
|
||||
status: ThreadStatus.Failed,
|
||||
|
|
@ -888,12 +901,10 @@ export const aiAgentRouter = router({
|
|||
const updatedStatus = updatedThread?.status ?? thread.status;
|
||||
const updatedTaskStatus = threadStatusToTaskStatus[updatedStatus] || 'processing';
|
||||
|
||||
// DEBUG: Log metadata for failed tasks
|
||||
if (updatedTaskStatus === 'failed') {
|
||||
console.log('[DEBUG] getSubAgentTaskStatus - failed task metadata:', {
|
||||
threadId,
|
||||
console.error('getSubAgentTaskStatus: failed task metadata for thread %s: %O', threadId, {
|
||||
updatedMetadata,
|
||||
'updatedMetadata?.error': updatedMetadata?.error,
|
||||
error: updatedMetadata?.error,
|
||||
updatedStatus,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,19 @@ vi.mock('@/database/models/topic', () => ({
|
|||
})),
|
||||
}));
|
||||
|
||||
// Mock AgentService
|
||||
vi.mock('@/server/services/agent', () => ({
|
||||
AgentService: vi.fn().mockImplementation(() => ({
|
||||
getAgentConfig: vi.fn().mockResolvedValue({
|
||||
chatConfig: {},
|
||||
id: 'agent-1',
|
||||
model: 'gpt-4',
|
||||
plugins: [],
|
||||
provider: 'openai',
|
||||
}),
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock AgentRuntimeService
|
||||
vi.mock('@/server/services/agentRuntime', () => ({
|
||||
AgentRuntimeService: vi.fn().mockImplementation(() => ({
|
||||
|
|
@ -60,6 +73,20 @@ vi.mock('@/server/services/agentRuntime', () => ({
|
|||
})),
|
||||
}));
|
||||
|
||||
// Mock MarketService
|
||||
vi.mock('@/server/services/market', () => ({
|
||||
MarketService: vi.fn().mockImplementation(() => ({
|
||||
getLobehubSkillManifests: vi.fn().mockResolvedValue([]),
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock KlavisService
|
||||
vi.mock('@/server/services/klavis', () => ({
|
||||
KlavisService: vi.fn().mockImplementation(() => ({
|
||||
getKlavisManifests: vi.fn().mockResolvedValue([]),
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('AiAgentService.execSubAgentTask', () => {
|
||||
let service: AiAgentService;
|
||||
const mockDb = {} as any;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { type AgentRuntimeContext, type AgentState } from '@lobechat/agent-runtime';
|
||||
import { builtinTools } from '@lobechat/builtin-tools';
|
||||
import { LOADING_FLAT } from '@lobechat/const';
|
||||
import { type LobeToolManifest } from '@lobechat/context-engine';
|
||||
import { type LobeChatDatabase } from '@lobechat/database';
|
||||
import {
|
||||
|
|
@ -14,14 +16,17 @@ import { ThreadStatus, ThreadType } from '@lobechat/types';
|
|||
import { nanoid } from '@lobechat/utils';
|
||||
import debug from 'debug';
|
||||
|
||||
import { LOADING_FLAT } from '@/const/message';
|
||||
import { AgentModel } from '@/database/models/agent';
|
||||
import { AiModelModel } from '@/database/models/aiModel';
|
||||
import { MessageModel } from '@/database/models/message';
|
||||
import { PluginModel } from '@/database/models/plugin';
|
||||
import { ThreadModel } from '@/database/models/thread';
|
||||
import { TopicModel } from '@/database/models/topic';
|
||||
import { type EvalContext, type ServerAgentToolsContext } from '@/server/modules/Mecha';
|
||||
import { createServerAgentToolsEngine } from '@/server/modules/Mecha';
|
||||
import {
|
||||
createServerAgentToolsEngine,
|
||||
type EvalContext,
|
||||
type ServerAgentToolsContext,
|
||||
} from '@/server/modules/Mecha';
|
||||
import { AgentService } from '@/server/services/agent';
|
||||
import { AgentRuntimeService } from '@/server/services/agentRuntime';
|
||||
import { type StepLifecycleCallbacks } from '@/server/services/agentRuntime/types';
|
||||
|
|
@ -168,7 +173,13 @@ export class AiAgentService {
|
|||
// Use actual agent ID from config for subsequent operations
|
||||
const resolvedAgentId = agentConfig.id;
|
||||
|
||||
log('execAgent: got agent config for %s (id: %s)', identifier, resolvedAgentId);
|
||||
log(
|
||||
'execAgent: got agent config for %s (id: %s), model: %s, provider: %s',
|
||||
identifier,
|
||||
resolvedAgentId,
|
||||
agentConfig.model,
|
||||
agentConfig.provider,
|
||||
);
|
||||
|
||||
// 2. Handle topic creation: if no topicId provided, create a new topic; otherwise reuse existing
|
||||
let topicId = appContext?.topicId;
|
||||
|
|
@ -203,9 +214,6 @@ export class AiAgentService {
|
|||
|
||||
// 4. Get model abilities from model-bank for function calling support check
|
||||
const { LOBE_DEFAULT_MODEL_LIST } = await import('model-bank');
|
||||
const modelInfo = LOBE_DEFAULT_MODEL_LIST.find(
|
||||
(m) => m.id === model && m.providerId === provider,
|
||||
);
|
||||
const isModelSupportToolUse = (m: string, p: string) => {
|
||||
const info = LOBE_DEFAULT_MODEL_LIST.find((item) => item.id === m && item.providerId === p);
|
||||
return info?.abilities?.functionCall ?? true;
|
||||
|
|
@ -243,7 +251,7 @@ export class AiAgentService {
|
|||
additionalManifests: [...lobehubSkillManifests, ...klavisManifests],
|
||||
agentConfig: {
|
||||
chatConfig: agentConfig.chatConfig ?? undefined,
|
||||
plugins: agentConfig.plugins ?? undefined,
|
||||
plugins: agentConfig?.plugins ?? undefined,
|
||||
},
|
||||
hasEnabledKnowledgeBases,
|
||||
model,
|
||||
|
|
@ -291,6 +299,98 @@ export class AiAgentService {
|
|||
klavisManifests.length,
|
||||
);
|
||||
|
||||
// 7.5. Build Agent Management context if agent-management tool is enabled
|
||||
const isAgentManagementEnabled = toolsResult.enabledToolIds?.includes('lobe-agent-management');
|
||||
let agentManagementContext;
|
||||
if (isAgentManagementEnabled) {
|
||||
// Query user's enabled models from database
|
||||
const aiModelModel = new AiModelModel(this.db, this.userId);
|
||||
const allUserModels = await aiModelModel.getAllModels();
|
||||
|
||||
// Filter only enabled chat models and group by provider
|
||||
const providerMap = new Map<
|
||||
string,
|
||||
{
|
||||
id: string;
|
||||
models: Array<{ abilities?: any; description?: string; id: string; name: string }>;
|
||||
name: string;
|
||||
}
|
||||
>();
|
||||
|
||||
for (const userModel of allUserModels) {
|
||||
// Only include enabled chat models
|
||||
if (!userModel.enabled || userModel.type !== 'chat') continue;
|
||||
|
||||
// Get model info from LOBE_DEFAULT_MODEL_LIST for full metadata
|
||||
const modelInfo = LOBE_DEFAULT_MODEL_LIST.find(
|
||||
(m) => m.id === userModel.id && m.providerId === userModel.providerId,
|
||||
);
|
||||
|
||||
if (!providerMap.has(userModel.providerId)) {
|
||||
providerMap.set(userModel.providerId, {
|
||||
id: userModel.providerId,
|
||||
models: [],
|
||||
name: userModel.providerId, // TODO: Map to friendly provider name
|
||||
});
|
||||
}
|
||||
|
||||
const provider = providerMap.get(userModel.providerId)!;
|
||||
provider.models.push({
|
||||
abilities: userModel.abilities || modelInfo?.abilities,
|
||||
description: modelInfo?.description,
|
||||
id: userModel.id,
|
||||
name: userModel.displayName || modelInfo?.displayName || userModel.id,
|
||||
});
|
||||
}
|
||||
|
||||
// Build availablePlugins from all plugin sources
|
||||
// Exclude only truly internal tools (agent-management itself, agent-builder, page-agent)
|
||||
const INTERNAL_TOOLS = new Set([
|
||||
'lobe-agent-management', // Don't show agent-management in its own context
|
||||
'lobe-agent-builder', // Used for editing current agent, not for creating new agents
|
||||
'lobe-group-agent-builder', // Used for editing current group, not for creating new agents
|
||||
'lobe-page-agent', // Page-editor specific tool
|
||||
]);
|
||||
|
||||
const availablePlugins = [
|
||||
// All builtin tools (including hidden ones like web-browsing, cloud-sandbox)
|
||||
...builtinTools
|
||||
.filter((tool) => !INTERNAL_TOOLS.has(tool.identifier))
|
||||
.map((tool) => ({
|
||||
description: tool.manifest.meta?.description,
|
||||
identifier: tool.identifier,
|
||||
name: tool.manifest.meta?.title || tool.identifier,
|
||||
type: 'builtin' as const,
|
||||
})),
|
||||
// Lobehub Skills
|
||||
...lobehubSkillManifests.map((manifest) => ({
|
||||
description: manifest.meta?.description,
|
||||
identifier: manifest.identifier,
|
||||
name: manifest.meta?.title || manifest.identifier,
|
||||
type: 'lobehub-skill' as const,
|
||||
})),
|
||||
// Klavis tools
|
||||
...klavisManifests.map((manifest) => ({
|
||||
description: manifest.meta?.description,
|
||||
identifier: manifest.identifier,
|
||||
name: manifest.meta?.title || manifest.identifier,
|
||||
type: 'klavis' as const,
|
||||
})),
|
||||
];
|
||||
|
||||
agentManagementContext = {
|
||||
availablePlugins,
|
||||
// Limit to first 5 providers to avoid context bloat
|
||||
availableProviders: Array.from(providerMap.values()).slice(0, 5),
|
||||
};
|
||||
|
||||
log(
|
||||
'execAgent: built agentManagementContext with %d providers and %d plugins',
|
||||
agentManagementContext.availableProviders.length,
|
||||
agentManagementContext.availablePlugins.length,
|
||||
);
|
||||
}
|
||||
|
||||
// 8. Get existing messages if provided
|
||||
let historyMessages: any[] = [];
|
||||
if (existingMessageIds.length > 0) {
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ const applyParamsFromChatConfig = (
|
|||
chatConfig: LobeAgentChatConfig,
|
||||
): LobeAgentConfig => {
|
||||
// If params is not defined, return agentConfig as-is
|
||||
if (!agentConfig.params) {
|
||||
if (!agentConfig?.params) {
|
||||
return agentConfig;
|
||||
}
|
||||
|
||||
|
|
@ -168,7 +168,7 @@ export const resolveAgentConfig = (ctx: AgentConfigResolverContext): ResolvedAge
|
|||
const chatConfig = chatConfigByIdSelectors.getChatConfigById(agentId)(agentStoreState);
|
||||
|
||||
// Base plugins from agent config
|
||||
const basePlugins = agentConfig.plugins ?? [];
|
||||
const basePlugins = agentConfig?.plugins ?? [];
|
||||
|
||||
// Check if this is a builtin agent
|
||||
// Priority: supervisor check (when in group scope) > agent store slug
|
||||
|
|
@ -186,10 +186,10 @@ export const resolveAgentConfig = (ctx: AgentConfigResolverContext): ResolvedAge
|
|||
ctx.groupId,
|
||||
group
|
||||
? {
|
||||
groupId: group.id,
|
||||
supervisorAgentId: group.supervisorAgentId,
|
||||
title: group.title,
|
||||
}
|
||||
groupId: group.id,
|
||||
supervisorAgentId: group.supervisorAgentId,
|
||||
title: group.title,
|
||||
}
|
||||
: null,
|
||||
agentId,
|
||||
);
|
||||
|
|
@ -292,11 +292,11 @@ export const resolveAgentConfig = (ctx: AgentConfigResolverContext): ResolvedAge
|
|||
ctx.groupId,
|
||||
group
|
||||
? {
|
||||
agentsCount: group.agents?.length,
|
||||
groupId: group.id,
|
||||
supervisorAgentId: group.supervisorAgentId,
|
||||
title: group.title,
|
||||
}
|
||||
agentsCount: group.agents?.length,
|
||||
groupId: group.id,
|
||||
supervisorAgentId: group.supervisorAgentId,
|
||||
title: group.title,
|
||||
}
|
||||
: null,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,28 @@
|
|||
import { AgentBuilderIdentifier } from '@lobechat/builtin-tool-agent-builder';
|
||||
import { AgentManagementIdentifier } from '@lobechat/builtin-tool-agent-management';
|
||||
import { GroupAgentBuilderIdentifier } from '@lobechat/builtin-tool-group-agent-builder';
|
||||
import { GTDIdentifier } from '@lobechat/builtin-tool-gtd';
|
||||
import { LobeToolIdentifier } from '@lobechat/builtin-tool-tools';
|
||||
import { isDesktop, KLAVIS_SERVER_TYPES, LOBEHUB_SKILL_PROVIDERS } from '@lobechat/const';
|
||||
import {
|
||||
type AgentBuilderContext,
|
||||
type AgentGroupConfig,
|
||||
type GroupAgentBuilderContext,
|
||||
type GroupOfficialToolItem,
|
||||
type GTDConfig,
|
||||
type LobeToolManifest,
|
||||
type MemoryContext,
|
||||
type ToolDiscoveryConfig,
|
||||
type UserMemoryData,
|
||||
import type {
|
||||
AgentBuilderContext,
|
||||
AgentGroupConfig,
|
||||
AgentManagementContext,
|
||||
GroupAgentBuilderContext,
|
||||
GroupOfficialToolItem,
|
||||
GTDConfig,
|
||||
LobeToolManifest,
|
||||
MemoryContext,
|
||||
ToolDiscoveryConfig,
|
||||
UserMemoryData,
|
||||
} from '@lobechat/context-engine';
|
||||
import { MessagesEngine } from '@lobechat/context-engine';
|
||||
import { historySummaryPrompt } from '@lobechat/prompts';
|
||||
import {
|
||||
type OpenAIChatMessage,
|
||||
type RuntimeInitialContext,
|
||||
type RuntimeStepContext,
|
||||
type UIChatMessage,
|
||||
import type {
|
||||
OpenAIChatMessage,
|
||||
RuntimeInitialContext,
|
||||
RuntimeStepContext,
|
||||
UIChatMessage,
|
||||
} from '@lobechat/types';
|
||||
import debug from 'debug';
|
||||
|
||||
|
|
@ -31,6 +33,7 @@ import { getAgentStoreState } from '@/store/agent';
|
|||
import { agentSelectors } from '@/store/agent/selectors';
|
||||
import { getChatGroupStoreState } from '@/store/agentGroup';
|
||||
import { agentGroupSelectors } from '@/store/agentGroup/selectors';
|
||||
import { getAiInfraStoreState } from '@/store/aiInfra';
|
||||
import { getChatStoreState } from '@/store/chat';
|
||||
import { getToolStoreState } from '@/store/tool';
|
||||
import {
|
||||
|
|
@ -116,9 +119,12 @@ export const contextEngineering = async ({
|
|||
const isAgentBuilderEnabled = tools?.includes(AgentBuilderIdentifier) ?? false;
|
||||
// Check if Group Agent Builder tool is enabled
|
||||
const isGroupAgentBuilderEnabled = tools?.includes(GroupAgentBuilderIdentifier) ?? false;
|
||||
// Check if Agent Management tool is enabled
|
||||
const isAgentManagementEnabled = tools?.includes(AgentManagementIdentifier) ?? false;
|
||||
|
||||
log('isAgentBuilderEnabled: %s', isAgentBuilderEnabled);
|
||||
log('isGroupAgentBuilderEnabled: %s', isGroupAgentBuilderEnabled);
|
||||
log('isAgentManagementEnabled: %s', isAgentManagementEnabled);
|
||||
|
||||
// Build agent group configuration if groupId is provided
|
||||
let agentGroup: AgentGroupConfig | undefined;
|
||||
|
|
@ -368,6 +374,101 @@ export const contextEngineering = async ({
|
|||
}
|
||||
}
|
||||
|
||||
// Build Agent Management context if Agent Management tool is enabled
|
||||
let agentManagementContext: AgentManagementContext | undefined;
|
||||
if (isAgentManagementEnabled) {
|
||||
// Get enabled providers and models from aiInfra store
|
||||
const aiProviderState = getAiInfraStoreState();
|
||||
const enabledChatModelList = aiProviderState.enabledChatModelList || [];
|
||||
|
||||
// Build availableProviders from enabled chat models (only user-enabled providers)
|
||||
// Limit to first 5 providers to avoid context bloat
|
||||
const availableProviders = enabledChatModelList.slice(0, 5).map((provider) => ({
|
||||
id: provider.id,
|
||||
models: provider.children.map((model) => ({
|
||||
abilities: model.abilities,
|
||||
description: model.description,
|
||||
id: model.id,
|
||||
name: model.displayName || model.id,
|
||||
})),
|
||||
name: provider.name,
|
||||
}));
|
||||
|
||||
// Get tool state for plugins
|
||||
const toolState = getToolStoreState();
|
||||
|
||||
// Build availablePlugins from all plugin sources
|
||||
const availablePlugins = [];
|
||||
|
||||
// Builtin tools (use allMetaList to include hidden tools like web-browsing, cloud-sandbox, etc.)
|
||||
// Exclude only truly internal tools (agent-management itself, agent-builder, page-agent)
|
||||
const allBuiltinTools = builtinToolSelectors.allMetaList(toolState);
|
||||
const klavisIdentifiers = new Set(KLAVIS_SERVER_TYPES.map((t) => t.identifier));
|
||||
const INTERNAL_TOOLS = new Set([
|
||||
'lobe-agent-management', // Don't show agent-management in its own context
|
||||
'lobe-agent-builder', // Used for editing current agent, not for creating new agents
|
||||
'lobe-group-agent-builder', // Used for editing current group, not for creating new agents
|
||||
'lobe-page-agent', // Page-editor specific tool
|
||||
]);
|
||||
|
||||
for (const tool of allBuiltinTools) {
|
||||
// Skip Klavis tools in builtin list (they'll be shown separately)
|
||||
if (klavisIdentifiers.has(tool.identifier)) continue;
|
||||
// Skip internal tools
|
||||
if (INTERNAL_TOOLS.has(tool.identifier)) continue;
|
||||
|
||||
availablePlugins.push({
|
||||
description: tool.meta?.description,
|
||||
identifier: tool.identifier,
|
||||
name: tool.meta?.title || tool.identifier,
|
||||
type: 'builtin' as const,
|
||||
});
|
||||
}
|
||||
|
||||
// Klavis tools (if enabled)
|
||||
const isKlavisEnabled =
|
||||
typeof window !== 'undefined' &&
|
||||
window.global_serverConfigStore?.getState()?.serverConfig?.enableKlavis;
|
||||
|
||||
if (isKlavisEnabled) {
|
||||
for (const klavisType of KLAVIS_SERVER_TYPES) {
|
||||
availablePlugins.push({
|
||||
description: klavisType.description,
|
||||
identifier: klavisType.identifier,
|
||||
name: klavisType.label,
|
||||
type: 'klavis' as const,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// LobehubSkill providers (if enabled)
|
||||
const isLobehubSkillEnabled =
|
||||
typeof window !== 'undefined' &&
|
||||
window.global_serverConfigStore?.getState()?.serverConfig?.enableLobehubSkill;
|
||||
|
||||
if (isLobehubSkillEnabled) {
|
||||
for (const provider of LOBEHUB_SKILL_PROVIDERS) {
|
||||
availablePlugins.push({
|
||||
description: provider.description,
|
||||
identifier: provider.id,
|
||||
name: provider.label,
|
||||
type: 'lobehub-skill' as const,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
agentManagementContext = {
|
||||
availablePlugins,
|
||||
availableProviders,
|
||||
};
|
||||
|
||||
log(
|
||||
'agentManagementContext built: %d providers, %d plugins',
|
||||
agentManagementContext.availableProviders?.length ?? 0,
|
||||
agentManagementContext.availablePlugins?.length ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
// Create MessagesEngine with injected dependencies
|
||||
const engine = new MessagesEngine({
|
||||
// Agent configuration
|
||||
|
|
@ -432,6 +533,7 @@ export const contextEngineering = async ({
|
|||
// Extended contexts - only pass when enabled
|
||||
...(isAgentBuilderEnabled && { agentBuilderContext }),
|
||||
...(isGroupAgentBuilderEnabled && { groupAgentBuilderContext }),
|
||||
...(isAgentManagementEnabled && { agentManagementContext }),
|
||||
...(agentGroup && { agentGroup }),
|
||||
...(gtdConfig && { gtd: gtdConfig }),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1200,8 +1200,9 @@ describe('call_llm executor', () => {
|
|||
// Given
|
||||
const mockStore = createMockStore();
|
||||
const context = createTestContext({
|
||||
agentId: 'supervisor-agent',
|
||||
subAgentId: 'worker-agent',
|
||||
agentId: 'supervisor-agent', // Main agent (supervisor)
|
||||
scope: 'group_agent',
|
||||
subAgentId: 'worker-agent', // Actual executing agent
|
||||
topicId: 'group-topic',
|
||||
});
|
||||
const instruction = createCallLLMInstruction({
|
||||
|
|
@ -1278,6 +1279,7 @@ describe('call_llm executor', () => {
|
|||
const mockStore = createMockStore();
|
||||
const context = createTestContext({
|
||||
agentId: 'supervisor-agent',
|
||||
scope: 'group_agent',
|
||||
subAgentId: 'worker-agent',
|
||||
groupId: 'group-123',
|
||||
topicId: 'group-topic',
|
||||
|
|
@ -1314,6 +1316,48 @@ describe('call_llm executor', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should use subAgentId even without explicit scope when groupId is present (backward compatibility)', async () => {
|
||||
// Given - Group scenario without explicit scope (backward compatibility test)
|
||||
const mockStore = createMockStore();
|
||||
const context = createTestContext({
|
||||
agentId: 'supervisor-agent',
|
||||
subAgentId: 'worker-agent',
|
||||
groupId: 'group-123',
|
||||
topicId: 'group-topic',
|
||||
// No explicit scope - should infer from groupId
|
||||
});
|
||||
const instruction = createCallLLMInstruction({
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
messages: [createUserMessage()],
|
||||
});
|
||||
const state = createInitialState({ operationId: context.operationId });
|
||||
|
||||
mockStreamResponse({ content: 'AI response' });
|
||||
mockStore.dbMessagesMap[context.messageKey] = [];
|
||||
|
||||
// When
|
||||
await executeWithMockContext({
|
||||
executor: 'call_llm',
|
||||
instruction,
|
||||
state,
|
||||
mockStore,
|
||||
context,
|
||||
});
|
||||
|
||||
// Then - should still use subAgentId for backward compatibility
|
||||
expect(mockStore.optimisticCreateMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
agentId: 'worker-agent', // Should use subAgentId even without explicit scope
|
||||
groupId: 'group-123',
|
||||
role: 'assistant',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
operationId: expect.any(String),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not include groupId when not in group chat context', async () => {
|
||||
// Given
|
||||
const mockStore = createMockStore();
|
||||
|
|
|
|||
|
|
@ -2338,6 +2338,7 @@ describe('call_tool executor', () => {
|
|||
const mockStore = createMockStore();
|
||||
const context = createTestContext({
|
||||
agentId: 'supervisor-agent',
|
||||
scope: 'group_agent',
|
||||
subAgentId: 'worker-agent',
|
||||
topicId: 'group-topic',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { type AgentInstruction, type AgentState } from '@lobechat/agent-runtime';
|
||||
import type { AgentInstruction, AgentState } from '@lobechat/agent-runtime';
|
||||
import type { MessageMapScope } from '@lobechat/types';
|
||||
|
||||
import { DEFAULT_AGENT_CHAT_CONFIG, DEFAULT_AGENT_CONFIG } from '@/const/settings';
|
||||
import { type ResolvedAgentConfig } from '@/services/chat/mecha';
|
||||
|
|
@ -42,6 +43,7 @@ export const executeWithMockContext = async ({
|
|||
messageKey: string;
|
||||
operationId: string;
|
||||
parentId: string;
|
||||
scope?: MessageMapScope;
|
||||
subAgentId?: string;
|
||||
topicId?: string | null;
|
||||
};
|
||||
|
|
@ -60,6 +62,7 @@ export const executeWithMockContext = async ({
|
|||
agentId: context.agentId || 'test-session',
|
||||
groupId: context.groupId,
|
||||
messageId: context.parentId,
|
||||
scope: context.scope,
|
||||
subAgentId: context.subAgentId,
|
||||
topicId: context.topicId !== undefined ? context.topicId : 'test-topic',
|
||||
},
|
||||
|
|
@ -141,6 +144,7 @@ export const createTestContext = (
|
|||
messageKey?: string;
|
||||
operationId?: string;
|
||||
parentId?: string;
|
||||
scope?: MessageMapScope;
|
||||
subAgentId?: string;
|
||||
topicId?: string | null;
|
||||
} = {},
|
||||
|
|
@ -153,6 +157,7 @@ export const createTestContext = (
|
|||
`${overrides.agentId || 'test-session'}_${overrides.topicId !== undefined ? overrides.topicId : 'test-topic'}`,
|
||||
operationId: overrides.operationId || 'op_test',
|
||||
parentId: overrides.parentId || 'msg_parent',
|
||||
scope: overrides.scope,
|
||||
subAgentId: overrides.subAgentId,
|
||||
topicId: overrides.topicId !== undefined ? overrides.topicId : 'test-topic',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -92,13 +92,35 @@ export const createAgentExecutors = (context: {
|
|||
};
|
||||
|
||||
/**
|
||||
* Get effective agentId for message creation
|
||||
* In Group Orchestration scenarios, subAgentId is the actual executing agent
|
||||
* Falls back to agentId for normal scenarios
|
||||
* Get effective agentId for message creation - depends on scope
|
||||
* - scope: 'sub_agent': agentId stays unchanged (subAgentId only for config/display)
|
||||
* - Other scopes with subAgentId: use subAgentId for message ownership (e.g., Group mode)
|
||||
* - Default: use agentId
|
||||
*/
|
||||
const getEffectiveAgentId = () => {
|
||||
const opContext = getOperationContext();
|
||||
return opContext.subAgentId || opContext.agentId;
|
||||
|
||||
// Use subAgentId for message ownership except in sub_agent scope
|
||||
// - sub_agent scope: callAgent scenario, message.agentId should stay unchanged
|
||||
// - Other scopes with subAgentId: Group mode, message.agentId should be subAgentId
|
||||
return opContext.subAgentId && opContext.scope !== 'sub_agent'
|
||||
? opContext.subAgentId
|
||||
: opContext.agentId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get subAgentId and scope for metadata (when scope is 'sub_agent')
|
||||
*/
|
||||
const getMetadataForSubAgent = () => {
|
||||
const opContext = getOperationContext();
|
||||
|
||||
if (opContext.scope === 'sub_agent' && opContext.subAgentId) {
|
||||
return {
|
||||
subAgentId: opContext.subAgentId,
|
||||
scope: opContext.scope,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const executors: Partial<Record<AgentInstruction['type'], InstructionExecutor>> = {
|
||||
|
|
@ -133,8 +155,10 @@ export const createAgentExecutors = (context: {
|
|||
} else {
|
||||
// Get context from operation
|
||||
const opContext = getOperationContext();
|
||||
// Get effective agentId (subAgentId for group orchestration, agentId otherwise)
|
||||
// Get effective agentId (depends on scope)
|
||||
const effectiveAgentId = getEffectiveAgentId();
|
||||
// Get subAgentId metadata (for sub_agent scope)
|
||||
const subAgentMetadata = getMetadataForSubAgent();
|
||||
|
||||
// If this is the first regenerated creation of userMessage, llmPayload doesn't have parentMessageId
|
||||
// So we assign it this way
|
||||
|
|
@ -142,13 +166,24 @@ export const createAgentExecutors = (context: {
|
|||
if (!llmPayload.parentMessageId) {
|
||||
llmPayload.parentMessageId = context.parentId;
|
||||
}
|
||||
|
||||
// Build metadata
|
||||
const metadata: Record<string, any> = {};
|
||||
if (opContext.isSupervisor) {
|
||||
metadata.isSupervisor = true;
|
||||
}
|
||||
if (subAgentMetadata) {
|
||||
// Store subAgentId and scope in metadata for sub_agent mode
|
||||
// This will be used by conversation-flow to transform agentId for display
|
||||
Object.assign(metadata, subAgentMetadata);
|
||||
}
|
||||
|
||||
// Create assistant message (following server-side pattern)
|
||||
// If isSupervisor is true, add metadata.isSupervisor for UI rendering
|
||||
const assistantMessageItem = await context.get().optimisticCreateMessage(
|
||||
{
|
||||
content: LOADING_FLAT,
|
||||
groupId: opContext.groupId,
|
||||
metadata: opContext.isSupervisor ? { isSupervisor: true } : undefined,
|
||||
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
||||
model: llmPayload.model,
|
||||
parentId: llmPayload.parentMessageId,
|
||||
provider: llmPayload.provider,
|
||||
|
|
@ -1142,7 +1177,11 @@ export const createAgentExecutors = (context: {
|
|||
const opContext = getOperationContext();
|
||||
const { agentId, topicId } = opContext;
|
||||
|
||||
if (!agentId || !topicId) {
|
||||
// Check for targetAgentId (callAgent mode)
|
||||
const targetAgentId = (task as any).targetAgentId;
|
||||
const executionAgentId = targetAgentId || agentId;
|
||||
|
||||
if (!agentId || !topicId || !executionAgentId) {
|
||||
log('[%s][exec_task] No valid context, cannot execute task', sessionLogId);
|
||||
return {
|
||||
events,
|
||||
|
|
@ -1168,15 +1207,31 @@ export const createAgentExecutors = (context: {
|
|||
};
|
||||
}
|
||||
|
||||
if (targetAgentId) {
|
||||
log(
|
||||
'[%s][exec_task] callAgent mode - current agent: %s, target agent: %s',
|
||||
sessionLogId,
|
||||
agentId,
|
||||
targetAgentId,
|
||||
);
|
||||
}
|
||||
|
||||
const taskLogId = `${sessionLogId}:task`;
|
||||
|
||||
try {
|
||||
// 1. Create task message as placeholder
|
||||
// IMPORTANT: Use operation context's agentId (current agent) for message creation
|
||||
// This ensures the task message appears in the current conversation
|
||||
const taskMessageResult = await context.get().optimisticCreateMessage(
|
||||
{
|
||||
agentId,
|
||||
agentId, // Use current agent's ID (not targetAgentId)
|
||||
content: '',
|
||||
metadata: { instruction: task.instruction, taskTitle: task.description },
|
||||
metadata: {
|
||||
instruction: task.instruction,
|
||||
taskTitle: task.description,
|
||||
// Store targetAgentId in metadata for UI display
|
||||
...(targetAgentId && { targetAgentId }),
|
||||
},
|
||||
parentId: parentMessageId,
|
||||
role: 'task',
|
||||
topicId,
|
||||
|
|
@ -1214,9 +1269,11 @@ export const createAgentExecutors = (context: {
|
|||
log('[%s] Created task message: %s', taskLogId, taskMessageId);
|
||||
|
||||
// 2. Create and execute task on server
|
||||
log('[%s] Using server-side execution', taskLogId);
|
||||
// IMPORTANT: Use executionAgentId here (targetAgentId if in callAgent mode)
|
||||
// This ensures the task executes with the correct agent's config
|
||||
log('[%s] Using server-side execution with agentId: %s', taskLogId, executionAgentId);
|
||||
const createResult = await aiAgentService.execSubAgentTask({
|
||||
agentId,
|
||||
agentId: executionAgentId, // Use targetAgentId for callAgent, or current agentId for GTD
|
||||
instruction: task.instruction,
|
||||
parentMessageId: taskMessageId,
|
||||
title: task.description,
|
||||
|
|
|
|||
|
|
@ -83,6 +83,11 @@ export class StreamingExecutorActionImpl {
|
|||
operationId?: string;
|
||||
initialState?: AgentState;
|
||||
initialContext?: AgentRuntimeContext;
|
||||
/**
|
||||
* Sub Agent ID - behavior depends on scope
|
||||
* - scope: 'group' | 'group_agent': Used for agent config and changes message ownership
|
||||
* - scope: 'sub_agent': Used for agent config but doesn't change message ownership
|
||||
*/
|
||||
subAgentId?: string;
|
||||
isSubTask?: boolean;
|
||||
}): {
|
||||
|
|
@ -97,9 +102,9 @@ export class StreamingExecutorActionImpl {
|
|||
const agentId = paramAgentId || activeAgentId;
|
||||
const topicId = paramTopicId !== undefined ? paramTopicId : activeTopicId;
|
||||
|
||||
// For group orchestration scenarios:
|
||||
// - subAgentId is used for agent config retrieval (model, provider, plugins)
|
||||
// - agentId is used for session ID (message storage location)
|
||||
// Determine effectiveAgentId for agent config retrieval:
|
||||
// - paramSubAgentId: Used for agent config (behavior depends on scope)
|
||||
// - agentId: Default
|
||||
const effectiveAgentId = paramSubAgentId || agentId;
|
||||
|
||||
// Get scope and groupId from operation context if available
|
||||
|
|
@ -118,8 +123,13 @@ export class StreamingExecutorActionImpl {
|
|||
isSubTask, // Filter out lobe-gtd in sub-task context
|
||||
scope, // Pass scope from operation context
|
||||
});
|
||||
|
||||
const { agentConfig: agentConfigData, plugins: pluginIds } = agentConfig;
|
||||
|
||||
if (!agentConfigData || !agentConfigData.model) {
|
||||
throw new Error(`[internal_createAgentState] Agent config not found or incomplete for agentId: ${effectiveAgentId}, scope: ${scope}`);
|
||||
}
|
||||
|
||||
log(
|
||||
'[internal_createAgentState] resolved plugins=%o, isSubTask=%s, disableTools=%s',
|
||||
pluginIds,
|
||||
|
|
@ -276,11 +286,11 @@ export class StreamingExecutorActionImpl {
|
|||
} = params;
|
||||
|
||||
// Extract values from context
|
||||
const { agentId, topicId, threadId, subAgentId, groupId } = context;
|
||||
const { agentId, topicId, threadId, subAgentId, groupId, scope } = context;
|
||||
|
||||
// For group orchestration scenarios:
|
||||
// - subAgentId is used for agent config retrieval (model, provider, plugins)
|
||||
// - agentId is used for message storage location (via messageMapKey)
|
||||
// Determine effectiveAgentId for agent config retrieval:
|
||||
// - subAgentId is used when present (behavior depends on scope)
|
||||
// - agentId: Default
|
||||
const effectiveAgentId = subAgentId || agentId;
|
||||
|
||||
// Generate message key from context
|
||||
|
|
@ -307,10 +317,11 @@ export class StreamingExecutorActionImpl {
|
|||
}
|
||||
|
||||
log(
|
||||
'[internal_execAgentRuntime] start, operationId: %s, agentId: %s, subAgentId: %s, effectiveAgentId: %s, topicId: %s, messageKey: %s, parentMessageId: %s, parentMessageType: %s, messages count: %d, disableTools: %s',
|
||||
'[internal_execAgentRuntime] start, operationId: %s, agentId: %s, subAgentId: %s, scope: %s, effectiveAgentId: %s, topicId: %s, messageKey: %s, parentMessageId: %s, parentMessageType: %s, messages count: %d, disableTools: %s',
|
||||
operationId,
|
||||
agentId,
|
||||
subAgentId,
|
||||
scope,
|
||||
effectiveAgentId,
|
||||
topicId,
|
||||
messageKey,
|
||||
|
|
@ -342,7 +353,7 @@ export class StreamingExecutorActionImpl {
|
|||
initialState: params.initialState,
|
||||
initialContext: params.initialContext,
|
||||
operationId,
|
||||
subAgentId, // Pass subAgentId for agent config retrieval
|
||||
subAgentId, // Pass subAgentId for agent config retrieval (behavior depends on scope)
|
||||
isSubTask, // Pass isSubTask to filter out lobe-gtd tools in sub-task context
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -85,9 +85,10 @@ const toMessageMapContext = (input: MessageMapKeyInput): MessageMapContext => {
|
|||
|
||||
// Default scope (main if not specified)
|
||||
// isNew can be used with any scope (main for new topic, thread for new thread with explicit scope)
|
||||
// Note: sub_agent scope uses same key as main scope (same conversation, just different display)
|
||||
return {
|
||||
isNew,
|
||||
scope: scope ?? 'main',
|
||||
scope: scope === 'sub_agent' ? 'main' : (scope ?? 'main'),
|
||||
scopeId: agentId,
|
||||
topicId,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
* Executors are registered as class instances by identifier.
|
||||
*/
|
||||
import { agentBuilderExecutor } from '@lobechat/builtin-tool-agent-builder/executor';
|
||||
import { agentManagementExecutor } from '@lobechat/builtin-tool-agent-management/executor';
|
||||
import { calculatorExecutor } from '@lobechat/builtin-tool-calculator/executor';
|
||||
import { cloudSandboxExecutor } from '@lobechat/builtin-tool-cloud-sandbox/executor';
|
||||
import { groupAgentBuilderExecutor } from '@lobechat/builtin-tool-group-agent-builder/executor';
|
||||
|
|
@ -125,6 +126,7 @@ const registerExecutors = (executors: IBuiltinToolExecutor[]): void => {
|
|||
// Register all executor instances
|
||||
registerExecutors([
|
||||
agentBuilderExecutor,
|
||||
agentManagementExecutor,
|
||||
calculatorExecutor,
|
||||
cloudSandboxExecutor,
|
||||
groupAgentBuilderExecutor,
|
||||
|
|
|
|||
Loading…
Reference in a new issue