refactor(tool-provider): rename NativeModelToolProvider to NativeToolBinderService (#19966)
**Stacked on top of #19962.** ## Summary - `NativeModelToolProvider` lived under `providers/` and had the `*-tool.provider.ts` suffix, but it never implemented `ToolProvider`, wasn't in `TOOL_PROVIDERS`, had no descriptors, and wasn't executed by `ToolExecutorService`. The shape misled readers. - It's actually a **parallel concept**: a binder that produces SDK-native tool objects (Anthropic `webSearch`, OpenAI `webSearch`, etc.) which the AI SDK passes straight to the model. Opaque, not serializable, never in the catalog, never dispatched by the executor. - This PR renames + moves it to reflect that. ## Renames | Before | After | |---|---| | `NativeModelToolProvider` (class) | `NativeToolBinderService` | | `NativeToolProvider` (interface) | `NativeToolBinder` | | `generateTools(context)` (method) | `bind(context)` | | `providers/native-model-tool.provider.ts` | `native/native-tool-binder.service.ts` | | `interfaces/native-tool-provider.interface.ts` | `native/native-tool-binder.interface.ts` | ## What doesn't change - `ToolCategory.NATIVE_MODEL` enum stays (still used by `getToolsByCategories`). - `isAvailable()` signature unchanged. - `WebSearchService.shouldUseNativeSearch()` toggle untouched — that's product-level and belongs to a separate PR that handles the Exa coexistence story. - No behavior change. Pure rename + move. ## Why this matters for the broader architecture This rename makes the native/binder concept **visible in the type system and directory structure**. That's what later enables coexisting native + custom tools (e.g., `web_search` native alongside `exa_web_search` custom) without the current naming collision, because native tools are no longer masquerading as a registry provider. ## Stats - 5 files, +30 / −28. - Blast radius: 4 files modified, 1 file renamed (git tracks as rename). - Typecheck clean (7 pre-existing unrelated errors, zero new). - Prettier clean. ## Test plan - [ ] `npx nx typecheck twenty-server` passes - [ ] AI chat: native `web_search` still works end-to-end when enabled - [ ] Workflow AI agent: `ToolCategory.NATIVE_MODEL` still works (goes through `bind()` now) - [ ] MCP: unaffected (doesn't use native tools) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
-12
@@ -1,12 +0,0 @@
|
||||
import { type ToolSet } from 'ai';
|
||||
import { type ToolCategory } from 'twenty-shared/ai';
|
||||
|
||||
import { type ToolProviderContext } from 'src/engine/core-modules/tool-provider/interfaces/tool-provider-context.type';
|
||||
|
||||
export interface NativeToolProvider {
|
||||
readonly category: ToolCategory;
|
||||
|
||||
isAvailable(context: ToolProviderContext): Promise<boolean>;
|
||||
|
||||
generateTools(context: ToolProviderContext): Promise<ToolSet>;
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { type ToolSet } from 'ai';
|
||||
import { type ToolCategory } from 'twenty-shared/ai';
|
||||
|
||||
import { type ToolProviderContext } from 'src/engine/core-modules/tool-provider/interfaces/tool-provider-context.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
|
||||
// passes straight to the model. These tools are opaque — they can't be
|
||||
// serialized into descriptors, don't appear in the tool catalog, and aren't
|
||||
// executed by ToolExecutorService. They're merged directly into the ToolSet
|
||||
// handed to streamText.
|
||||
export interface NativeToolBinder {
|
||||
readonly category: ToolCategory;
|
||||
|
||||
isAvailable(context: ToolProviderContext): Promise<boolean>;
|
||||
|
||||
bind(context: ToolProviderContext): Promise<ToolSet>;
|
||||
}
|
||||
+3
-5
@@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { type ToolSet } from 'ai';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type NativeToolProvider } from 'src/engine/core-modules/tool-provider/interfaces/native-tool-provider.interface';
|
||||
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 { ToolCategory } from 'twenty-shared/ai';
|
||||
@@ -11,10 +11,8 @@ import { WebSearchService } from 'src/engine/core-modules/web-search/web-search.
|
||||
import { AiModelConfigService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-config.service';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
|
||||
// SDK-native tools (anthropic webSearch, etc.) are opaque and not serializable.
|
||||
// This provider keeps generateTools() and is excluded from the descriptor system.
|
||||
@Injectable()
|
||||
export class NativeModelToolProvider implements NativeToolProvider {
|
||||
export class NativeToolBinderService implements NativeToolBinder {
|
||||
readonly category = ToolCategory.NATIVE_MODEL;
|
||||
|
||||
constructor(
|
||||
@@ -27,7 +25,7 @@ export class NativeModelToolProvider implements NativeToolProvider {
|
||||
return isDefined(context.agent);
|
||||
}
|
||||
|
||||
async generateTools(context: ToolProviderContext): Promise<ToolSet> {
|
||||
async bind(context: ToolProviderContext): Promise<ToolSet> {
|
||||
if (!context.agent) {
|
||||
return {};
|
||||
}
|
||||
+4
-7
@@ -2,15 +2,14 @@ import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { type ToolSet, jsonSchema } from 'ai';
|
||||
|
||||
import { type NativeToolProvider } from 'src/engine/core-modules/tool-provider/interfaces/native-tool-provider.interface';
|
||||
import { type ToolProvider } from 'src/engine/core-modules/tool-provider/interfaces/tool-provider.interface';
|
||||
import { type ToolProviderContext } from 'src/engine/core-modules/tool-provider/interfaces/tool-provider-context.type';
|
||||
import { type ToolRetrievalOptions } from 'src/engine/core-modules/tool-provider/interfaces/tool-retrieval-options.type';
|
||||
|
||||
import { TOOL_PROVIDERS } from 'src/engine/core-modules/tool-provider/constants/tool-providers.token';
|
||||
import { ToolCategory } from 'twenty-shared/ai';
|
||||
import { NativeToolBinderService } from 'src/engine/core-modules/tool-provider/native/native-tool-binder.service';
|
||||
import { compactToolOutput } from 'src/engine/core-modules/tool-provider/output-serialization/compact-tool-output.util';
|
||||
import { NativeModelToolProvider } from 'src/engine/core-modules/tool-provider/providers/native-model-tool.provider';
|
||||
import { ToolExecutorService } from 'src/engine/core-modules/tool-provider/services/tool-executor.service';
|
||||
import { type LearnToolsAspect } from 'src/engine/core-modules/tool-provider/tools/learn-tools.tool';
|
||||
import { type ToolContext } from 'src/engine/core-modules/tool-provider/types/tool-context.type';
|
||||
@@ -31,7 +30,7 @@ export class ToolRegistryService {
|
||||
constructor(
|
||||
@Inject(TOOL_PROVIDERS)
|
||||
private readonly providers: ToolProvider[],
|
||||
private readonly nativeModelToolProvider: NativeModelToolProvider,
|
||||
private readonly nativeToolBinder: NativeToolBinderService,
|
||||
private readonly toolExecutorService: ToolExecutorService,
|
||||
) {}
|
||||
|
||||
@@ -329,10 +328,8 @@ export class ToolRegistryService {
|
||||
});
|
||||
|
||||
if (categories?.includes(ToolCategory.NATIVE_MODEL)) {
|
||||
if (await this.nativeModelToolProvider.isAvailable(context)) {
|
||||
const nativeTools = await (
|
||||
this.nativeModelToolProvider as NativeToolProvider
|
||||
).generateTools(context);
|
||||
if (await this.nativeToolBinder.isAvailable(context)) {
|
||||
const nativeTools = await this.nativeToolBinder.bind(context);
|
||||
|
||||
Object.assign(toolSet, nativeTools);
|
||||
}
|
||||
|
||||
+5
-4
@@ -8,7 +8,7 @@ import { DashboardToolProvider } from 'src/engine/core-modules/tool-provider/pro
|
||||
import { DatabaseToolProvider } from 'src/engine/core-modules/tool-provider/providers/database-tool.provider';
|
||||
import { LogicFunctionToolProvider } from 'src/engine/core-modules/tool-provider/providers/logic-function-tool.provider';
|
||||
import { MetadataToolProvider } from 'src/engine/core-modules/tool-provider/providers/metadata-tool.provider';
|
||||
import { NativeModelToolProvider } from 'src/engine/core-modules/tool-provider/providers/native-model-tool.provider';
|
||||
import { NativeToolBinderService } from 'src/engine/core-modules/tool-provider/native/native-tool-binder.service';
|
||||
import { ViewFieldToolProvider } from 'src/engine/core-modules/tool-provider/providers/view-field-tool.provider';
|
||||
import { ViewToolProvider } from 'src/engine/core-modules/tool-provider/providers/view-tool.provider';
|
||||
import { WorkflowToolProvider } from 'src/engine/core-modules/tool-provider/providers/workflow-tool.provider';
|
||||
@@ -63,14 +63,15 @@ import { ToolRegistryService } from './services/tool-registry.service';
|
||||
DashboardToolProvider,
|
||||
DatabaseToolProvider,
|
||||
MetadataToolProvider,
|
||||
NativeModelToolProvider,
|
||||
NativeToolBinderService,
|
||||
LogicFunctionToolProvider,
|
||||
ViewFieldToolProvider,
|
||||
ViewToolProvider,
|
||||
WorkflowToolProvider,
|
||||
{
|
||||
// TOOL_PROVIDERS contains only providers implementing ToolProvider (generateDescriptors).
|
||||
// NativeModelToolProvider is excluded -- it's injected separately in the registry.
|
||||
// TOOL_PROVIDERS contains only providers implementing ToolProvider
|
||||
// (registry tools with descriptors). The native tool binder is a
|
||||
// parallel concept and is injected directly into ToolRegistryService.
|
||||
provide: TOOL_PROVIDERS,
|
||||
useFactory: (
|
||||
actionProvider: ActionToolProvider,
|
||||
|
||||
Reference in New Issue
Block a user