diff --git a/packages/agent-runtime/src/types/state.ts b/packages/agent-runtime/src/types/state.ts index f62c689719..09df85e834 100644 --- a/packages/agent-runtime/src/types/state.ts +++ b/packages/agent-runtime/src/types/state.ts @@ -2,6 +2,7 @@ import type { ActivatedStepSkill, ActivatedStepTool, OperationToolSet, + ToolExecutor, ToolSource, } from '@lobechat/context-engine'; import type { @@ -122,6 +123,9 @@ export interface AgentState { stepCount: number; systemRole?: string; + /** Tool executor map for routing tool execution between server and client */ + toolExecutorMap?: Record; + toolManifestMap: Record; tools?: any[]; diff --git a/packages/context-engine/src/engine/tools/ToolResolver.ts b/packages/context-engine/src/engine/tools/ToolResolver.ts index 8035517fff..c29d829da2 100644 --- a/packages/context-engine/src/engine/tools/ToolResolver.ts +++ b/packages/context-engine/src/engine/tools/ToolResolver.ts @@ -4,6 +4,7 @@ import type { OperationToolSet, ResolvedToolSet, StepToolDelta, + ToolExecutor, ToolSource, UniformTool, } from './types'; @@ -32,6 +33,7 @@ export class ToolResolver { // Start from operation-level snapshot (shallow copies, with safe defaults) const tools: UniformTool[] = [...(operationToolSet.tools ?? [])]; const sourceMap: Record = { ...operationToolSet.sourceMap }; + const executorMap: Record = { ...operationToolSet.executorMap }; const enabledToolIds: string[] = [...(operationToolSet.enabledToolIds ?? [])]; // Only include manifests for enabled tools to prevent injecting @@ -57,6 +59,7 @@ export class ToolResolver { if (stepDelta.deactivatedToolIds?.includes('*')) { return { enabledToolIds: [], + executorMap, manifestMap, // keep manifests for ToolNameResolver sourceMap, tools: [], @@ -75,6 +78,7 @@ export class ToolResolver { return { enabledToolIds: [...new Set(enabledToolIds)], + executorMap, manifestMap, sourceMap, tools: dedupedTools, diff --git a/packages/context-engine/src/engine/tools/index.ts b/packages/context-engine/src/engine/tools/index.ts index 140ffdb2ca..e3dc3eb33a 100644 --- a/packages/context-engine/src/engine/tools/index.ts +++ b/packages/context-engine/src/engine/tools/index.ts @@ -28,6 +28,7 @@ export type { PluginEnableChecker, ResolvedToolSet, StepToolDelta, + ToolExecutor, ToolNameGenerator, ToolsEngineOptions, ToolsGenerationContext, diff --git a/packages/context-engine/src/engine/tools/types.ts b/packages/context-engine/src/engine/tools/types.ts index 2ea5e9c6c5..4531843e5b 100644 --- a/packages/context-engine/src/engine/tools/types.ts +++ b/packages/context-engine/src/engine/tools/types.ts @@ -160,7 +160,13 @@ export interface UniformTool { // ---- Tool Lifecycle Types ---- -export type ToolSource = 'builtin' | 'client' | 'plugin' | 'mcp' | 'klavis' | 'lobehubSkill'; +export type ToolSource = 'builtin' | 'client' | 'mcp' | 'klavis' | 'lobehubSkill'; + +/** + * Where the tool is executed for a given invocation. + * Orthogonal to ToolSource (origin): executor describes dispatch target. + */ +export type ToolExecutor = 'client' | 'server'; /** * How a tool was activated at step level @@ -172,6 +178,7 @@ export type ActivationSource = 'active_tools' | 'mention' | 'device' | 'discover */ export interface OperationToolSet { enabledToolIds: string[]; + executorMap?: Record; manifestMap: Record; sourceMap: Record; tools: UniformTool[]; @@ -205,6 +212,7 @@ export interface StepToolDelta { */ export interface ResolvedToolSet { enabledToolIds: string[]; + executorMap?: Record; manifestMap: Record; sourceMap: Record; tools: UniformTool[]; diff --git a/packages/types/src/message/common/tools.ts b/packages/types/src/message/common/tools.ts index ff788dcf3e..d6b647c1d2 100644 --- a/packages/types/src/message/common/tools.ts +++ b/packages/types/src/message/common/tools.ts @@ -25,11 +25,21 @@ export interface ChatPluginPayload { /** * Tool source indicates where the tool comes from */ -export type ToolSource = 'builtin' | 'client' | 'plugin' | 'mcp' | 'klavis' | 'lobehubSkill'; +export type ToolSource = 'builtin' | 'client' | 'mcp' | 'klavis' | 'lobehubSkill'; + +/** + * Tool executor indicates where the tool is executed for a given invocation. + * Orthogonal to ToolSource (origin): executor describes dispatch target. + */ +export type ToolExecutor = 'client' | 'server'; export interface ChatToolPayload { apiName: string; arguments: string; + /** + * Tool executor: dispatch target for this invocation. + */ + executor?: ToolExecutor; id: string; identifier: string; intervention?: ToolIntervention; diff --git a/src/server/modules/AgentRuntime/RuntimeExecutors.ts b/src/server/modules/AgentRuntime/RuntimeExecutors.ts index 43519c0fc8..03b90bfe7e 100644 --- a/src/server/modules/AgentRuntime/RuntimeExecutors.ts +++ b/src/server/modules/AgentRuntime/RuntimeExecutors.ts @@ -250,6 +250,7 @@ export const createRuntimeExecutors = ( const activeDeviceId = state.metadata?.activeDeviceId; const operationToolSet: OperationToolSet = state.operationToolSet ?? { enabledToolIds: [], + executorMap: state.toolExecutorMap ?? {}, manifestMap: state.toolManifestMap ?? {}, sourceMap: state.toolSourceMap ?? {}, tools: state.tools ?? [], @@ -769,9 +770,10 @@ export const createRuntimeExecutors = ( }, onToolsCalling: async ({ toolsCalling: raw }) => { const resolvedCalls = new ToolNameResolver().resolve(raw, resolved.manifestMap); - // Add source field from resolved sourceMap for routing tool execution + // Attach source (origin) and executor (dispatch target) for routing const payload = resolvedCalls.map((p) => ({ ...p, + executor: resolved.executorMap?.[p.identifier], source: resolved.sourceMap[p.identifier], })); // log(`[${operationLogId}][toolsCalling]`, payload); diff --git a/src/server/services/agentRuntime/AgentRuntimeService.ts b/src/server/services/agentRuntime/AgentRuntimeService.ts index d8043e9708..b44ec170bd 100644 --- a/src/server/services/agentRuntime/AgentRuntimeService.ts +++ b/src/server/services/agentRuntime/AgentRuntimeService.ts @@ -312,6 +312,7 @@ export class AgentRuntimeService { status: 'idle', stepCount: initialStepCount, // Backward-compat: resolved tool fields read by RuntimeExecutors + toolExecutorMap: operationToolSet.executorMap, toolManifestMap: operationToolSet.manifestMap, toolSourceMap: operationToolSet.sourceMap, tools: operationToolSet.tools, diff --git a/src/server/services/agentRuntime/types.ts b/src/server/services/agentRuntime/types.ts index 5d74bf28b7..357caee6d1 100644 --- a/src/server/services/agentRuntime/types.ts +++ b/src/server/services/agentRuntime/types.ts @@ -1,5 +1,10 @@ import { type AgentRuntimeContext, type AgentState } from '@lobechat/agent-runtime'; -import type { LobeToolManifest, OperationSkillSet, ToolSource } from '@lobechat/context-engine'; +import type { + LobeToolManifest, + OperationSkillSet, + ToolExecutor, + ToolSource, +} from '@lobechat/context-engine'; import { type UserInterventionConfig } from '@lobechat/types'; import { type ServerUserMemoryConfig } from '@/server/modules/Mecha/ContextEngineering/types'; @@ -10,6 +15,7 @@ import { type AgentHook } from './hooks/types'; export interface OperationToolSet { enabledToolIds?: string[]; + executorMap?: Record; manifestMap: Record; sourceMap?: Record; tools?: any[]; diff --git a/src/server/services/aiAgent/index.ts b/src/server/services/aiAgent/index.ts index c4080800d8..6b332cca35 100644 --- a/src/server/services/aiAgent/index.ts +++ b/src/server/services/aiAgent/index.ts @@ -13,6 +13,7 @@ import { LOADING_FLAT } from '@lobechat/const'; import type { AgentManagementContext, LobeToolManifest, + ToolExecutor, ToolSource, } from '@lobechat/context-engine'; import { SkillEngine } from '@lobechat/context-engine'; @@ -455,6 +456,7 @@ export class AiAgentService { }; const toolManifestMap: Record = {}; const toolSourceMap: Record = {}; + const toolExecutorMap: Record = {}; let onlineDevices: DeviceAttachment[] = []; let activeDeviceId: string | undefined; let hasAgentDocuments = false; @@ -1141,6 +1143,7 @@ export class AiAgentService { stream, toolSet: { enabledToolIds: toolsResult.enabledToolIds, + executorMap: toolExecutorMap, manifestMap: toolManifestMap, sourceMap: toolSourceMap, tools, diff --git a/src/store/chat/slices/plugin/actions/internals.ts b/src/store/chat/slices/plugin/actions/internals.ts index 2ac49a5141..6a55e24e97 100644 --- a/src/store/chat/slices/plugin/actions/internals.ts +++ b/src/store/chat/slices/plugin/actions/internals.ts @@ -35,15 +35,14 @@ export class PluginInternalsActionImpl { const manifests: Record = {}; // Track source for each identifier - const sourceMap: Record = {}; + const sourceMap: Record = {}; - // Get all installed plugins + // Get all installed plugins (all treated as MCP now) const installedPlugins = pluginSelectors.installedPlugins(toolStoreState); for (const plugin of installedPlugins) { if (plugin.manifest) { manifests[plugin.identifier] = plugin.manifest as ToolManifest; - // Check if this plugin has MCP params - sourceMap[plugin.identifier] = plugin.customParams?.mcp ? 'mcp' : 'plugin'; + sourceMap[plugin.identifier] = 'mcp'; } }