feat: Serverless Functions as AI Tools (#16919)
## Summary This PR enables serverless functions to be exposed as AI tools, allowing them to be used by AI agents. ### Changes - Added new `SERVERLESS_FUNCTION` tool category - Added `toolDescription`, `toolInputSchema`, and `toolOutputSchema` fields to serverless functions - Created database migration for the new schema columns - Added tool index query and resolver for fetching available tools - Added Settings AI page tabs (Skills, Tools, Settings) with new tools table - Added utility to convert tool schema to JSON schema format - Updated frontend to display tools in the settings page ### Implementation Details - Serverless functions can now define tool metadata (description, input/output schemas) - These functions are automatically registered in the tool registry - The tool index endpoint allows querying available tools with their schemas - Settings page now has a dedicated Tools tab showing all available tools
This commit is contained in:
-9
@@ -14,12 +14,3 @@ export const CodeInterpreterInputZodSchema = z.object({
|
||||
.optional()
|
||||
.describe('Files to make available in the execution environment'),
|
||||
});
|
||||
|
||||
export const CodeInterpreterToolParametersZodSchema = z.object({
|
||||
loadingMessage: z
|
||||
.string()
|
||||
.describe(
|
||||
"A clear, human-readable status message describing the code being executed. This will be shown to the user while the tool is running, so phrase it as a present-tense status update (e.g., 'Creating a bar chart from sales data'). Explain what analysis or visualization you are performing in natural language.",
|
||||
),
|
||||
input: CodeInterpreterInputZodSchema,
|
||||
});
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ import { CodeInterpreterService } from 'src/engine/core-modules/code-interpreter
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
import { CodeInterpreterToolParametersZodSchema } from 'src/engine/core-modules/tool/tools/code-interpreter-tool/code-interpreter-tool.schema';
|
||||
import { CodeInterpreterInputZodSchema } from 'src/engine/core-modules/tool/tools/code-interpreter-tool/code-interpreter-tool.schema';
|
||||
import { TWENTY_MCP_HELPER } from 'src/engine/core-modules/tool/tools/code-interpreter-tool/twenty-mcp-helper.const';
|
||||
import { type CodeInterpreterInput } from 'src/engine/core-modules/tool/tools/code-interpreter-tool/types/code-interpreter-input.type';
|
||||
import { type ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type';
|
||||
@@ -44,7 +44,7 @@ export class CodeInterpreterTool implements Tool {
|
||||
description =
|
||||
'Execute Python code in a sandboxed environment for data analysis, CSV processing, calculations, and chart generation. Returns stdout, stderr, and generated files. Input files are available at /home/user/{filename}. Save output files (charts, reports) to /home/user/output/ using plt.savefig() for matplotlib charts.';
|
||||
|
||||
inputSchema = CodeInterpreterToolParametersZodSchema;
|
||||
inputSchema = CodeInterpreterInputZodSchema;
|
||||
|
||||
constructor(
|
||||
private readonly codeInterpreterService: CodeInterpreterService,
|
||||
|
||||
-9
@@ -14,12 +14,3 @@ export const HttpRequestInputZodSchema = z.object({
|
||||
.optional()
|
||||
.describe('Request body for POST, PUT, PATCH requests'),
|
||||
});
|
||||
|
||||
export const HttpToolParametersZodSchema = z.object({
|
||||
loadingMessage: z
|
||||
.string()
|
||||
.describe(
|
||||
"A clear, human-readable status message describing the HTTP request being made. This will be shown to the user while the tool is being called, so phrase it as a present-tense status update (e.g., 'Making a GET request to ...'). Explain what endpoint you are calling and with what parameters in natural language.",
|
||||
),
|
||||
input: HttpRequestInputZodSchema,
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import axios, { type AxiosRequestConfig } from 'axios';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { parseDataFromContentType } from 'twenty-shared/workflow';
|
||||
|
||||
import { HttpToolParametersZodSchema } from 'src/engine/core-modules/tool/tools/http-tool/http-tool.schema';
|
||||
import { HttpRequestInputZodSchema } from 'src/engine/core-modules/tool/tools/http-tool/http-tool.schema';
|
||||
import { type HttpRequestInput } from 'src/engine/core-modules/tool/tools/http-tool/types/http-request-input.type';
|
||||
import { type ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type';
|
||||
import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type';
|
||||
@@ -19,7 +19,7 @@ import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twent
|
||||
export class HttpTool implements Tool {
|
||||
description =
|
||||
'Make an HTTP request to any URL with configurable method, headers, and body.';
|
||||
inputSchema = HttpToolParametersZodSchema;
|
||||
inputSchema = HttpRequestInputZodSchema;
|
||||
|
||||
constructor(private readonly twentyConfigService: TwentyConfigService) {}
|
||||
|
||||
|
||||
-9
@@ -6,15 +6,6 @@ export const SearchHelpCenterInputZodSchema = z.object({
|
||||
.describe('The search query to find relevant help articles about Twenty'),
|
||||
});
|
||||
|
||||
export const SearchHelpCenterToolParametersZodSchema = z.object({
|
||||
loadingMessage: z
|
||||
.string()
|
||||
.describe(
|
||||
'A clear, human-readable status message describing the search being performed. This will be shown to the user while the tool is being called, so phrase it as a present-tense status update (e.g., "Searching help center for..."). Explain what you are searching for in natural language.',
|
||||
),
|
||||
input: SearchHelpCenterInputZodSchema,
|
||||
});
|
||||
|
||||
export type SearchHelpCenterInput = z.infer<
|
||||
typeof SearchHelpCenterInputZodSchema
|
||||
>;
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
import { SearchHelpCenterToolParametersZodSchema } from 'src/engine/core-modules/tool/tools/search-help-center-tool/search-help-center-tool.schema';
|
||||
import { SearchHelpCenterInputZodSchema } from 'src/engine/core-modules/tool/tools/search-help-center-tool/search-help-center-tool.schema';
|
||||
import { type ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type';
|
||||
import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type';
|
||||
import {
|
||||
@@ -15,7 +15,7 @@ import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twent
|
||||
export class SearchHelpCenterTool implements Tool {
|
||||
description =
|
||||
'Search Twenty documentation and help center to find information about features, setup, usage, and troubleshooting.';
|
||||
inputSchema = SearchHelpCenterToolParametersZodSchema;
|
||||
inputSchema = SearchHelpCenterInputZodSchema;
|
||||
|
||||
constructor(private readonly twentyConfigService: TwentyConfigService) {}
|
||||
|
||||
|
||||
+1
-10
@@ -1,6 +1,6 @@
|
||||
import { isValidUuid } from 'twenty-shared/utils';
|
||||
import { z } from 'zod';
|
||||
import { workflowFileSchema } from 'twenty-shared/workflow';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const SendEmailInputZodSchema = z.object({
|
||||
email: z.email().describe('The recipient email address'),
|
||||
@@ -19,12 +19,3 @@ export const SendEmailInputZodSchema = z.object({
|
||||
.optional()
|
||||
.default([]),
|
||||
});
|
||||
|
||||
export const SendEmailToolParametersZodSchema = z.object({
|
||||
loadingMessage: z
|
||||
.string()
|
||||
.describe(
|
||||
"A clear, human-readable status message describing the email being sent. This will be shown to the user while the tool is being called, so phrase it as a present-tense status update (e.g., 'Sending email to customer about order status'). Explain what email you are sending and to whom in natural language.",
|
||||
),
|
||||
input: SendEmailInputZodSchema,
|
||||
});
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ import {
|
||||
SendEmailToolException,
|
||||
SendEmailToolExceptionCode,
|
||||
} from 'src/engine/core-modules/tool/tools/send-email-tool/exceptions/send-email-tool.exception';
|
||||
import { SendEmailToolParametersZodSchema } from 'src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.schema';
|
||||
import { SendEmailInputZodSchema } from 'src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.schema';
|
||||
import { type SendEmailInput } from 'src/engine/core-modules/tool/tools/send-email-tool/types/send-email-input.type';
|
||||
import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type';
|
||||
import {
|
||||
@@ -36,7 +36,7 @@ export class SendEmailTool implements Tool {
|
||||
|
||||
description =
|
||||
'Send an email using a connected account. Requires SEND_EMAIL_TOOL permission.';
|
||||
inputSchema = SendEmailToolParametersZodSchema;
|
||||
inputSchema = SendEmailInputZodSchema;
|
||||
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const DEFAULT_LOADING_MESSAGE_SCHEMA = z
|
||||
.string()
|
||||
.describe(
|
||||
"A brief status message for the user describing what you're doing (e.g., 'Sending email to customer').",
|
||||
);
|
||||
|
||||
// Wraps a flat Zod tool schema with loadingMessage for AI execution
|
||||
export const wrapSchemaForExecution = <T extends z.ZodRawShape>(
|
||||
schema: z.ZodObject<T>,
|
||||
customLoadingMessageSchema?: z.ZodString,
|
||||
): z.ZodObject<T & { loadingMessage: z.ZodString }> => {
|
||||
return z.object({
|
||||
loadingMessage:
|
||||
customLoadingMessageSchema ?? DEFAULT_LOADING_MESSAGE_SCHEMA,
|
||||
...schema.shape,
|
||||
}) as z.ZodObject<T & { loadingMessage: z.ZodString }>;
|
||||
};
|
||||
|
||||
// For non-Zod schemas (serverless functions with JSON Schema)
|
||||
export const wrapJsonSchemaForExecution = (
|
||||
schema: Record<string, unknown>,
|
||||
): Record<string, unknown> => {
|
||||
const properties = (schema.properties as Record<string, unknown>) ?? {};
|
||||
const required = (schema.required as string[]) ?? [];
|
||||
|
||||
return {
|
||||
type: 'object',
|
||||
properties: {
|
||||
loadingMessage: {
|
||||
type: 'string',
|
||||
description: 'A brief status message for the user.',
|
||||
},
|
||||
...properties,
|
||||
},
|
||||
required: ['loadingMessage', ...required],
|
||||
};
|
||||
};
|
||||
|
||||
// Strips loadingMessage from parameters before passing to tool execute
|
||||
export const stripLoadingMessage = <T extends Record<string, unknown>>(
|
||||
parameters: T,
|
||||
): Omit<T, 'loadingMessage'> => {
|
||||
const { loadingMessage: _, ...rest } = parameters;
|
||||
|
||||
return rest;
|
||||
};
|
||||
Reference in New Issue
Block a user