From bb5f294c5a5819f4f045dea9bf6c047d028b8d25 Mon Sep 17 00:00:00 2001 From: nitin <142569587+ehconitin@users.noreply.github.com> Date: Mon, 27 Apr 2026 20:05:59 +0530 Subject: [PATCH] [AI] Collapse NativeToolBinder to a single bind() entry (#20051) The binder doesnt need an agent or full tool context -- just a model and options. Single `bind(model, options)` entry! Builds on #20022. --- .../interfaces/tool-provider-context.type.ts | 2 -- .../native/native-tool-binder.interface.ts | 5 ++-- .../native/native-tool-binder.service.ts | 27 +++---------------- .../services/agent-async-executor.service.ts | 7 ++--- .../services/chat-execution.service.ts | 7 +++-- .../services/ai-model-config.service.ts | 9 ------- 6 files changed, 13 insertions(+), 44 deletions(-) diff --git a/packages/twenty-server/src/engine/core-modules/tool-provider/interfaces/tool-provider-context.type.ts b/packages/twenty-server/src/engine/core-modules/tool-provider/interfaces/tool-provider-context.type.ts index 2d3bbcd9ac..4f4dcc27fe 100644 --- a/packages/twenty-server/src/engine/core-modules/tool-provider/interfaces/tool-provider-context.type.ts +++ b/packages/twenty-server/src/engine/core-modules/tool-provider/interfaces/tool-provider-context.type.ts @@ -2,7 +2,6 @@ import { type ActorMetadata } from 'twenty-shared/types'; import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { type CodeExecutionStreamEmitter } from 'src/engine/core-modules/tool-provider/interfaces/code-execution-stream-emitter.type'; -import { type FlatAgentWithRoleId } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; export type ToolProviderContext = { @@ -13,6 +12,5 @@ export type ToolProviderContext = { actorContext?: ActorMetadata; userId?: string; userWorkspaceId?: string; - agent?: FlatAgentWithRoleId | null; onCodeExecutionUpdate?: CodeExecutionStreamEmitter; }; diff --git a/packages/twenty-server/src/engine/core-modules/tool-provider/native/native-tool-binder.interface.ts b/packages/twenty-server/src/engine/core-modules/tool-provider/native/native-tool-binder.interface.ts index bcf19785ea..a6ca62f5a9 100644 --- a/packages/twenty-server/src/engine/core-modules/tool-provider/native/native-tool-binder.interface.ts +++ b/packages/twenty-server/src/engine/core-modules/tool-provider/native/native-tool-binder.interface.ts @@ -1,6 +1,7 @@ import { type ToolSet } from 'ai'; -import { type ToolProviderContext } from 'src/engine/core-modules/tool-provider/interfaces/tool-provider-context.type'; +import { type RegisteredAiModel } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service'; +import { type NativeModelToolOptions } from 'src/engine/metadata-modules/ai/ai-models/types/native-model-tool-options.type'; // Parallel to ToolProvider, not a variant of it. A binder produces SDK-native // tool objects (Anthropic webSearch, OpenAI webSearch, etc.) that the AI SDK @@ -9,5 +10,5 @@ import { type ToolProviderContext } from 'src/engine/core-modules/tool-provider/ // executed by ToolExecutorService. They're merged directly into the ToolSet // handed to streamText. export interface NativeToolBinder { - bind(context: ToolProviderContext): Promise; + bind(model: RegisteredAiModel, options: NativeModelToolOptions): ToolSet; } diff --git a/packages/twenty-server/src/engine/core-modules/tool-provider/native/native-tool-binder.service.ts b/packages/twenty-server/src/engine/core-modules/tool-provider/native/native-tool-binder.service.ts index bba8ef4d8a..d5fbb93b64 100644 --- a/packages/twenty-server/src/engine/core-modules/tool-provider/native/native-tool-binder.service.ts +++ b/packages/twenty-server/src/engine/core-modules/tool-provider/native/native-tool-binder.service.ts @@ -3,37 +3,16 @@ import { Injectable } from '@nestjs/common'; import { type ToolSet } from 'ai'; import { type NativeToolBinder } from 'src/engine/core-modules/tool-provider/native/native-tool-binder.interface'; -import { type ToolProviderContext } from 'src/engine/core-modules/tool-provider/interfaces/tool-provider-context.type'; import { AiModelConfigService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-config.service'; -import { - AiModelRegistryService, - type RegisteredAiModel, -} from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service'; +import { type RegisteredAiModel } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service'; import { type NativeModelToolOptions } from 'src/engine/metadata-modules/ai/ai-models/types/native-model-tool-options.type'; @Injectable() export class NativeToolBinderService implements NativeToolBinder { - constructor( - private readonly aiModelConfigService: AiModelConfigService, - private readonly aiModelRegistryService: AiModelRegistryService, - ) {} + constructor(private readonly aiModelConfigService: AiModelConfigService) {} - async bind(context: ToolProviderContext): Promise { - if (!context.agent) { - return {}; - } - - const registeredModel = - await this.aiModelRegistryService.resolveModelForAgent(context.agent); - - return this.aiModelConfigService.getNativeModelToolsForAgent( - registeredModel, - context.agent, - ); - } - - bindForModel( + bind( model: RegisteredAiModel, options: NativeModelToolOptions = {}, ): ToolSet { diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts index 9a95d6629f..0f1ad0a937 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts @@ -150,7 +150,6 @@ export class AgentAsyncExecutorService { rolePermissionConfig: effectiveRoleConfig ?? { unionOf: [] }, authContext, actorContext, - agent: agent as unknown as ToolProviderContext['agent'], userId: isDefined(authContext) && isUserAuthContext(authContext) ? authContext.user.id @@ -169,8 +168,10 @@ export class AgentAsyncExecutorService { }, ); - const nativeTools = - await this.nativeToolBinder.bind(toolProviderContext); + const nativeTools = this.nativeToolBinder.bind(registeredModel, { + webSearchEnabled: + agent.modelConfiguration?.webSearch?.enabled === true, + }); tools = { ...registryTools, diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/chat-execution.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/chat-execution.service.ts index d34e60d944..8905a72f0e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/chat-execution.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/chat-execution.service.ts @@ -157,10 +157,9 @@ export class ChatExecutionService { registeredModel.modelId, ); - const nativeModelTools = this.nativeToolBinder.bindForModel( - registeredModel, - { webSearchEnabled: true }, - ); + const nativeModelTools = this.nativeToolBinder.bind(registeredModel, { + webSearchEnabled: true, + }); // Tools the model can call directly: preloaded registry tools (already // serialized by the hydrator) plus SDK-native tools (opaque, never diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/services/ai-model-config.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/services/ai-model-config.service.ts index f45dfd4f83..bcfb8167fc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/services/ai-model-config.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/services/ai-model-config.service.ts @@ -79,15 +79,6 @@ export class AiModelConfigService { return tools; } - getNativeModelToolsForAgent( - model: RegisteredAiModel, - agent: FlatAgentWithRoleId, - ): ToolSet { - return this.getNativeModelTools(model, { - webSearchEnabled: agent.modelConfiguration?.webSearch?.enabled === true, - }); - } - private getXaiProviderOptions(agent: FlatAgentWithRoleId): ProviderOptions { if ( !agent.modelConfiguration ||