refactor(agents): split tool resolution into native and action rails (#20331)

## Summary

Splits AI agent tool resolution into two independent rails:

- **Native tools** — capabilities baked into the model SDK
(Anthropic/OpenAI `web_search`, xAI `web`/`x` provider options). Bound
by `NativeToolBinderService`, controlled by per-agent
`modelConfiguration` toggles. Opaque to Twenty — executed on the model
provider's servers.
- **Action tools** — registry-scoped tools from `ToolRegistryService`
(code interpreter, send email, record CRUD, etc.). Permission-gated via
the agent's role. Executed on Twenty's server.

Both rails merge into a single `ToolSet` at call time. When both
surfaces expose a search tool the model picks at runtime — coexistence
is intentional (relevant once Exa returns as an action, see below).

## Notable changes worth calling out

**Contract change: `AgentAsyncExecutorService.executeAgent` no longer
accepts `rolePermissionConfig`.** Workflow agents now scope exclusively
by the agent's own permission-tab role (`unionOf: [agentRoleId]`). The
previous role-merging path (caller role intersected with agent role) is
removed. No agent role → no registry tools (fail-closed by design).

**`NativeToolBinderService` relocated** from
`core-modules/tool-provider/native/` →
`metadata-modules/ai/ai-models/services/`. The binder needs SDK-package
knowledge, which lives in `ai-models`. Old location created a backwards
module dependency.

**`NATIVE_MODEL_TOOLS_BY_SDK_PACKAGE` is exhaustive over
`AiSdkPackage`** (`Record<>`, not `Partial<Record<>>`). Adding a new SDK
without thinking about native tools now fails the build. SDKs without
native tools (Bedrock, Google, Mistral, Azure, OpenAI-compatible) get
explicit `{}` entries.

**Discriminated union `kind: 'sdk-tool' | 'provider-option'`** lets one
registry describe both function tools (Anthropic/OpenAI) and runtime
sources (xAI). Follows the local `tool-provider` convention from #19321.

## Deferred to follow-ups

- **Exa web search is dropped from this PR** (along with its
`WEB_SEARCH_TOOL` permission flag and the Exa-specific gating). Exa
comes back as an **action/app tool** once apps can define permission
flags through the SDK — ongoing work in #20481.
- **xAI native search currently errors.** xAI deprecated its Live Search
API (the `web`/`x` provider-option sources this rail maps to), so xAI
returns `410` when native search is actually exercised. The code path
itself is clear — it's only hit if you test xAI native tools. Fixed
separately alongside the broader xAI model fixes.

## Conscious non-decisions

- **No "twenty-native" category.** `native` is reserved for
model/provider SDK features; everything Twenty-owned is just a
tool/action.
- **Coexistence over precedence.** No rule forcing an action search tool
to override native search (or vice-versa) — when both exist, it's the
user's choice in workflow agents and the model's choice in chat.

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
nitin
2026-05-29 01:38:05 +05:30
committed by GitHub
parent 1d84695fb0
commit 996cdaf3ff
22 changed files with 691 additions and 156 deletions
@@ -69,7 +69,10 @@ export const SettingsAgentModelCapabilities = ({
return null;
}
if (!nativeCapabilities.webSearch && !nativeCapabilities.twitterSearch) {
const showNativeWebSearch = nativeCapabilities.webSearch;
const showNativeTwitterSearch = nativeCapabilities.twitterSearch;
if (!showNativeWebSearch && !showNativeTwitterSearch) {
return null;
}
@@ -91,7 +94,7 @@ export const SettingsAgentModelCapabilities = ({
};
const capabilities = [
...(nativeCapabilities.webSearch
...(showNativeWebSearch
? [
{
key: 'webSearch' as const,
@@ -101,7 +104,7 @@ export const SettingsAgentModelCapabilities = ({
},
]
: []),
...(nativeCapabilities.twitterSearch
...(showNativeTwitterSearch
? [
{
key: 'twitterSearch' as const,
@@ -4,6 +4,7 @@ import { useMemo } from 'react';
import {
IconApi,
IconAt,
IconCode,
IconDownload,
IconFileExport,
IconFileImport,
@@ -86,6 +87,16 @@ export const useActionRolePermissionFlagConfig = ({
isRelevantForApiKeys: false,
isRelevantForUsers: false,
},
{
key: PermissionFlagType.CODE_INTERPRETER_TOOL,
name: t`Code Interpreter`,
description: t`Run code to analyze files and data`,
Icon: IconCode,
isToolPermission: true,
isRelevantForAgents: true,
isRelevantForApiKeys: false,
isRelevantForUsers: true,
},
{
key: PermissionFlagType.IMPORT_CSV,
name: t`Import CSV`,