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:
+83
-127
@@ -9,112 +9,76 @@ import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metada
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
|
||||
const GetObjectMetadataInputSchema = z.object({
|
||||
loadingMessage: z
|
||||
id: z
|
||||
.string()
|
||||
.uuid()
|
||||
.optional()
|
||||
.describe('A clear description of the action being performed.'),
|
||||
input: z.object({
|
||||
id: z
|
||||
.string()
|
||||
.uuid()
|
||||
.optional()
|
||||
.describe(
|
||||
'Unique identifier for the object metadata. If provided, returns a single object.',
|
||||
),
|
||||
limit: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(100)
|
||||
.default(100)
|
||||
.describe('Maximum number of objects to return.'),
|
||||
}),
|
||||
.describe(
|
||||
'Unique identifier for the object metadata. If provided, returns a single object.',
|
||||
),
|
||||
limit: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(100)
|
||||
.default(100)
|
||||
.describe('Maximum number of objects to return.'),
|
||||
});
|
||||
|
||||
const CreateObjectMetadataInputSchema = z.object({
|
||||
loadingMessage: z
|
||||
nameSingular: z
|
||||
.string()
|
||||
.describe('Singular name for the object (e.g., "company")'),
|
||||
namePlural: z
|
||||
.string()
|
||||
.describe('Plural name for the object (e.g., "companies")'),
|
||||
labelSingular: z
|
||||
.string()
|
||||
.describe('Display label in singular form (e.g., "Company")'),
|
||||
labelPlural: z
|
||||
.string()
|
||||
.describe('Display label in plural form (e.g., "Companies")'),
|
||||
description: z.string().optional().describe('Description of the object'),
|
||||
icon: z.string().optional().describe('Icon identifier for the object'),
|
||||
shortcut: z.string().optional().describe('Keyboard shortcut for the object'),
|
||||
isRemote: z.boolean().optional().describe('Whether this is a remote object'),
|
||||
isLabelSyncedWithName: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('A clear description of the action being performed.'),
|
||||
input: z.object({
|
||||
nameSingular: z
|
||||
.string()
|
||||
.describe('Singular name for the object (e.g., "company")'),
|
||||
namePlural: z
|
||||
.string()
|
||||
.describe('Plural name for the object (e.g., "companies")'),
|
||||
labelSingular: z
|
||||
.string()
|
||||
.describe('Display label in singular form (e.g., "Company")'),
|
||||
labelPlural: z
|
||||
.string()
|
||||
.describe('Display label in plural form (e.g., "Companies")'),
|
||||
description: z.string().optional().describe('Description of the object'),
|
||||
icon: z.string().optional().describe('Icon identifier for the object'),
|
||||
shortcut: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Keyboard shortcut for the object'),
|
||||
isRemote: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether this is a remote object'),
|
||||
isLabelSyncedWithName: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether label should sync with name changes'),
|
||||
}),
|
||||
.describe('Whether label should sync with name changes'),
|
||||
});
|
||||
|
||||
const UpdateObjectMetadataInputSchema = z.object({
|
||||
loadingMessage: z
|
||||
id: z.string().uuid().describe('ID of the object to update'),
|
||||
labelSingular: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('A clear description of the action being performed.'),
|
||||
input: z.object({
|
||||
id: z.string().uuid().describe('ID of the object to update'),
|
||||
labelSingular: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Display label in singular form'),
|
||||
labelPlural: z.string().optional().describe('Display label in plural form'),
|
||||
nameSingular: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Singular name for the object'),
|
||||
namePlural: z.string().optional().describe('Plural name for the object'),
|
||||
description: z.string().optional().describe('Description of the object'),
|
||||
icon: z.string().optional().describe('Icon identifier for the object'),
|
||||
shortcut: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Keyboard shortcut for the object'),
|
||||
isActive: z.boolean().optional().describe('Whether the object is active'),
|
||||
labelIdentifierFieldMetadataId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.optional()
|
||||
.describe('ID of the field used as label identifier'),
|
||||
imageIdentifierFieldMetadataId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.optional()
|
||||
.describe('ID of the field used as image identifier'),
|
||||
isLabelSyncedWithName: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether label should sync with name changes'),
|
||||
}),
|
||||
.describe('Display label in singular form'),
|
||||
labelPlural: z.string().optional().describe('Display label in plural form'),
|
||||
nameSingular: z.string().optional().describe('Singular name for the object'),
|
||||
namePlural: z.string().optional().describe('Plural name for the object'),
|
||||
description: z.string().optional().describe('Description of the object'),
|
||||
icon: z.string().optional().describe('Icon identifier for the object'),
|
||||
shortcut: z.string().optional().describe('Keyboard shortcut for the object'),
|
||||
isActive: z.boolean().optional().describe('Whether the object is active'),
|
||||
labelIdentifierFieldMetadataId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.optional()
|
||||
.describe('ID of the field used as label identifier'),
|
||||
imageIdentifierFieldMetadataId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.optional()
|
||||
.describe('ID of the field used as image identifier'),
|
||||
isLabelSyncedWithName: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether label should sync with name changes'),
|
||||
});
|
||||
|
||||
const DeleteObjectMetadataInputSchema = z.object({
|
||||
loadingMessage: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('A clear description of the action being performed.'),
|
||||
input: z.object({
|
||||
id: z.string().uuid().describe('ID of the object to delete'),
|
||||
}),
|
||||
id: z.string().uuid().describe('ID of the object to delete'),
|
||||
});
|
||||
|
||||
@Injectable()
|
||||
@@ -127,17 +91,13 @@ export class ObjectMetadataToolsFactory {
|
||||
description:
|
||||
'Find objects metadata. Retrieve information about the data model objects in the workspace.',
|
||||
inputSchema: GetObjectMetadataInputSchema,
|
||||
execute: async (parameters: {
|
||||
input: { id?: string; limit?: number };
|
||||
}) => {
|
||||
execute: async (parameters: { id?: string; limit?: number }) => {
|
||||
const flatObjectMetadatas =
|
||||
await this.objectMetadataService.findManyWithinWorkspace(
|
||||
workspaceId,
|
||||
{
|
||||
...(parameters.input.id
|
||||
? { where: { id: parameters.input.id } }
|
||||
: {}),
|
||||
take: parameters.input.limit ?? 100,
|
||||
...(parameters.id ? { where: { id: parameters.id } } : {}),
|
||||
take: parameters.limit ?? 100,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -151,22 +111,20 @@ export class ObjectMetadataToolsFactory {
|
||||
'Create a new object metadata in the workspace data model.',
|
||||
inputSchema: CreateObjectMetadataInputSchema,
|
||||
execute: async (parameters: {
|
||||
input: {
|
||||
nameSingular: string;
|
||||
namePlural: string;
|
||||
labelSingular: string;
|
||||
labelPlural: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
shortcut?: string;
|
||||
isRemote?: boolean;
|
||||
isLabelSyncedWithName?: boolean;
|
||||
};
|
||||
nameSingular: string;
|
||||
namePlural: string;
|
||||
labelSingular: string;
|
||||
labelPlural: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
shortcut?: string;
|
||||
isRemote?: boolean;
|
||||
isLabelSyncedWithName?: boolean;
|
||||
}) => {
|
||||
try {
|
||||
const flatObjectMetadata =
|
||||
await this.objectMetadataService.createOneObject({
|
||||
createObjectInput: parameters.input as Parameters<
|
||||
createObjectInput: parameters as Parameters<
|
||||
typeof this.objectMetadataService.createOneObject
|
||||
>[0]['createObjectInput'],
|
||||
workspaceId,
|
||||
@@ -188,23 +146,21 @@ export class ObjectMetadataToolsFactory {
|
||||
'Update an existing object metadata. Provide the object ID and the fields to update.',
|
||||
inputSchema: UpdateObjectMetadataInputSchema,
|
||||
execute: async (parameters: {
|
||||
input: {
|
||||
id: string;
|
||||
labelSingular?: string;
|
||||
labelPlural?: string;
|
||||
nameSingular?: string;
|
||||
namePlural?: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
shortcut?: string;
|
||||
isActive?: boolean;
|
||||
labelIdentifierFieldMetadataId?: string;
|
||||
imageIdentifierFieldMetadataId?: string;
|
||||
isLabelSyncedWithName?: boolean;
|
||||
};
|
||||
id: string;
|
||||
labelSingular?: string;
|
||||
labelPlural?: string;
|
||||
nameSingular?: string;
|
||||
namePlural?: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
shortcut?: string;
|
||||
isActive?: boolean;
|
||||
labelIdentifierFieldMetadataId?: string;
|
||||
imageIdentifierFieldMetadataId?: string;
|
||||
isLabelSyncedWithName?: boolean;
|
||||
}) => {
|
||||
try {
|
||||
const { id, ...update } = parameters.input;
|
||||
const { id, ...update } = parameters;
|
||||
|
||||
const flatObjectMetadata =
|
||||
await this.objectMetadataService.updateOneObject({
|
||||
@@ -227,11 +183,11 @@ export class ObjectMetadataToolsFactory {
|
||||
description:
|
||||
'Delete an object metadata by its ID. This will also delete all associated fields.',
|
||||
inputSchema: DeleteObjectMetadataInputSchema,
|
||||
execute: async (parameters: { input: { id: string } }) => {
|
||||
execute: async (parameters: { id: string }) => {
|
||||
try {
|
||||
const flatObjectMetadata =
|
||||
await this.objectMetadataService.deleteOneObject({
|
||||
deleteObjectInput: { id: parameters.input.id },
|
||||
deleteObjectInput: { id: parameters.id },
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user