fix(ai) - optimize metadata CRUD tools (#21235)
Reduces output tokens for all 13 metadata tools by (~49%) based on
production sampling data.
GET tools (field + object metadata)
System fields are now returned as compact {id, name, type} instead of
the full ~20-key payload (opt-in includeFullSystemFields to get full
payload). System objects are similarly compacted to {id, nameSingular,
namePlural}.
Internal fields the agent never uses (searchVector, deletedAt, position,
updatedBy) are excluded entirely.
workspaceId and applicationId are hoisted into a response envelope
instead of being repeated on every record.
Null/default-false properties are stripped from custom field and object
payloads (e.g. options: null, settings: null, isUIReadOnly: false).
CUD tools (create/update/delete)
Create and update field tools now return {id, name, type, label} instead
of the full DTO.
Create and update object tools now return {id, nameSingular,
labelSingular} instead of the full DTO.
Delete tools return {id, success: true} instead of the full DTO of the
deleted entity.
Validation errors are grouped by message — e.g. 10 fields failing the
same check produce one line with all names instead of 10 identical
lines.
Learn schemas (all tools)
UUID pattern regex stripped from JSON schemas (keeps format: "uuid").
$schema and additionalProperties: false stripped from all generated
schemas.
All Zod .describe() annotations and tool descriptions shortened.
Skill & tool description updates:
All references to the removed list_object_metadata_items tool replaced
with get_object_metadata / get_field_metadata across skill instructions,
dashboard tools, view filter/sort tools, and MCP server instructions.
This commit is contained in:
+1
-1
@@ -9,7 +9,7 @@ For ANY non-trivial task, follow this order:
|
||||
|
||||
1. **Plan**: Identify what the user needs. Determine which domain is involved (workflows, dashboards, metadata, data, documents, etc.).
|
||||
2. **Load the relevant skill FIRST**: Call \`load_skills\` to get detailed instructions, correct schemas, and parameter formats BEFORE doing anything else. Skills contain critical knowledge you don't have built-in — skipping this step leads to incorrect parameters and failed tool calls.
|
||||
3. **Learn the required tools**: Call \`learn_tools\` to discover tool schemas and descriptions before using them.
|
||||
3. **Learn the required tools**: Call \`learn_tools\` to discover tool schemas and descriptions before using them. Pass every tool you need in a single \`learn_tools\` call (\`toolNames\` is an array) — do not make one call per tool.
|
||||
4. **Execute**: Call \`execute_tool\` to run the tools following the instructions from the skill.
|
||||
|
||||
⚠️ NEVER call a specialized tool (workflow, dashboard, metadata, etc.) without loading its matching skill first. The Available Skills section below lists all skills — look for the one that matches the user's task domain and load it.
|
||||
|
||||
+107
-106
@@ -4,104 +4,93 @@ import { type ToolSet } from 'ai';
|
||||
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { compactMetadataOutput } from 'src/engine/core-modules/tool-provider/utils/compact-metadata-output.util';
|
||||
import { formatValidationErrors } from 'src/engine/core-modules/tool-provider/utils/format-validation-errors.util';
|
||||
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service';
|
||||
import { fromFlatFieldMetadataToFieldMetadataDto } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-flat-field-metadata-to-field-metadata-dto.util';
|
||||
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
|
||||
|
||||
const EXCLUDED_FIELD_NAMES = new Set(['searchVector', 'position', 'updatedBy']);
|
||||
|
||||
const FIELD_STRIP_WHEN_NULLISH = [
|
||||
'options',
|
||||
'settings',
|
||||
'defaultValue',
|
||||
'description',
|
||||
'icon',
|
||||
'deletedAt',
|
||||
];
|
||||
|
||||
const FIELD_STRIP_WHEN_FALSE = ['isLabelSyncedWithName', 'isUIReadOnly'];
|
||||
|
||||
const GetFieldMetadataInputSchema = z.object({
|
||||
id: z
|
||||
.string()
|
||||
.uuid()
|
||||
.optional()
|
||||
.describe(
|
||||
'Unique identifier for the field metadata. If provided, returns a single field.',
|
||||
),
|
||||
.describe('Field ID. Returns one field if set.'),
|
||||
objectMetadataId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.optional()
|
||||
.describe('Filter fields by object metadata ID.'),
|
||||
.describe('Filter by object ID.'),
|
||||
includeFullSystemFields: z
|
||||
.boolean()
|
||||
.default(false)
|
||||
.describe(
|
||||
"Keep false (default) for listing or inspecting fields — system fields then return as compact {id, name, type}, which is enough to know which fields exist and their types. Only set true when you specifically need a system field's full configuration (settings, defaultValue, relation targets).",
|
||||
),
|
||||
limit: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(100)
|
||||
.default(100)
|
||||
.describe('Maximum number of fields to return.'),
|
||||
.describe('Max fields to return.'),
|
||||
});
|
||||
|
||||
const CreateFieldMetadataInputSchema = z.object({
|
||||
objectMetadataId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.describe('ID of the object to add the field to'),
|
||||
type: z
|
||||
.nativeEnum(FieldMetadataType)
|
||||
.describe(
|
||||
'Field type (e.g., TEXT, NUMBER, BOOLEAN, DATE_TIME, RELATION, etc.)',
|
||||
),
|
||||
name: z.string().describe('Internal name of the field (camelCase)'),
|
||||
label: z.string().describe('Display label of the field'),
|
||||
description: z.string().optional().describe('Description of the field'),
|
||||
icon: z.string().optional().describe('Icon identifier for the field'),
|
||||
isNullable: z.boolean().optional().describe('Whether the field can be null'),
|
||||
isUnique: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether the field value must be unique'),
|
||||
defaultValue: z.unknown().optional().describe('Default value for the field'),
|
||||
options: z
|
||||
.unknown()
|
||||
.optional()
|
||||
.describe('Options for SELECT/MULTI_SELECT fields'),
|
||||
settings: z
|
||||
.unknown()
|
||||
.optional()
|
||||
.describe('Additional settings for the field'),
|
||||
objectMetadataId: z.string().uuid().describe('Target object ID'),
|
||||
type: z.nativeEnum(FieldMetadataType).describe('Field type'),
|
||||
name: z.string().describe('Field name (camelCase)'),
|
||||
label: z.string().describe('Display label'),
|
||||
description: z.string().optional().describe('Description'),
|
||||
icon: z.string().optional().describe('Icon name'),
|
||||
isNullable: z.boolean().optional().describe('Nullable'),
|
||||
isUnique: z.boolean().optional().describe('Unique constraint'),
|
||||
defaultValue: z.unknown().optional().describe('Default value'),
|
||||
options: z.unknown().optional().describe('SELECT/MULTI_SELECT options'),
|
||||
settings: z.unknown().optional().describe('Field settings'),
|
||||
isLabelSyncedWithName: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether label should sync with name changes'),
|
||||
isRemoteCreation: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether this is a remote field creation'),
|
||||
.describe('Sync label with name'),
|
||||
isRemoteCreation: z.boolean().optional().describe('Remote field creation'),
|
||||
relationCreationPayload: z
|
||||
.unknown()
|
||||
.optional()
|
||||
.describe('Payload for creating relation fields'),
|
||||
.describe('Relation creation payload'),
|
||||
});
|
||||
|
||||
const UpdateFieldMetadataInputSchema = z.object({
|
||||
id: z.string().uuid().describe('ID of the field to update'),
|
||||
name: z.string().optional().describe('Internal name of the field'),
|
||||
label: z.string().optional().describe('Display label of the field'),
|
||||
description: z.string().optional().describe('Description of the field'),
|
||||
icon: z.string().optional().describe('Icon identifier for the field'),
|
||||
isActive: z.boolean().optional().describe('Whether the field is active'),
|
||||
isNullable: z.boolean().optional().describe('Whether the field can be null'),
|
||||
isUnique: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether the field value must be unique'),
|
||||
defaultValue: z.unknown().optional().describe('Default value for the field'),
|
||||
options: z
|
||||
.unknown()
|
||||
.optional()
|
||||
.describe('Options for SELECT/MULTI_SELECT fields'),
|
||||
settings: z
|
||||
.unknown()
|
||||
.optional()
|
||||
.describe('Additional settings for the field'),
|
||||
id: z.string().uuid().describe('Field ID'),
|
||||
name: z.string().optional().describe('Field name'),
|
||||
label: z.string().optional().describe('Display label'),
|
||||
description: z.string().optional().describe('Description'),
|
||||
icon: z.string().optional().describe('Icon name'),
|
||||
isActive: z.boolean().optional().describe('Active state'),
|
||||
isNullable: z.boolean().optional().describe('Nullable'),
|
||||
isUnique: z.boolean().optional().describe('Unique constraint'),
|
||||
defaultValue: z.unknown().optional().describe('Default value'),
|
||||
options: z.unknown().optional().describe('SELECT/MULTI_SELECT options'),
|
||||
settings: z.unknown().optional().describe('Field settings'),
|
||||
isLabelSyncedWithName: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether label should sync with name changes'),
|
||||
.describe('Sync label with name'),
|
||||
});
|
||||
|
||||
const DeleteFieldMetadataInputSchema = z.object({
|
||||
id: z.string().uuid().describe('ID of the field to delete'),
|
||||
id: z.string().uuid().describe('Field ID'),
|
||||
});
|
||||
|
||||
const CreateManyFieldMetadataInputSchema = z.object({
|
||||
@@ -109,7 +98,7 @@ const CreateManyFieldMetadataInputSchema = z.object({
|
||||
.array(CreateFieldMetadataInputSchema)
|
||||
.min(1)
|
||||
.max(20)
|
||||
.describe('Array of field metadata to create (1-20 items).'),
|
||||
.describe('Fields to create (max 20).'),
|
||||
});
|
||||
|
||||
const UpdateManyFieldMetadataInputSchema = z.object({
|
||||
@@ -117,49 +106,27 @@ const UpdateManyFieldMetadataInputSchema = z.object({
|
||||
.array(UpdateFieldMetadataInputSchema)
|
||||
.min(1)
|
||||
.max(20)
|
||||
.describe('Array of field metadata updates to apply (1-20 items).'),
|
||||
.describe('Fields to update (max 20).'),
|
||||
});
|
||||
|
||||
const CreateManyRelationFieldsInputSchema = z.object({
|
||||
relations: z
|
||||
.array(
|
||||
z.object({
|
||||
objectMetadataId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.describe('ID of the source object to add the relation field to'),
|
||||
name: z
|
||||
.string()
|
||||
.describe('Internal name of the relation field (camelCase)'),
|
||||
label: z.string().describe('Display label of the relation field'),
|
||||
description: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Description of the relation field'),
|
||||
icon: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Icon identifier for the relation field'),
|
||||
type: z
|
||||
.nativeEnum(RelationType)
|
||||
.describe('Relation type: MANY_TO_ONE or ONE_TO_MANY'),
|
||||
targetObjectMetadataId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.describe('ID of the target object this relation points to'),
|
||||
targetFieldLabel: z
|
||||
.string()
|
||||
.describe(
|
||||
'Display label for the inverse relation field on the target object',
|
||||
),
|
||||
targetFieldIcon: z
|
||||
.string()
|
||||
.describe('Icon for the inverse relation field (e.g. IconSomething)'),
|
||||
objectMetadataId: z.string().uuid().describe('Source object ID'),
|
||||
name: z.string().describe('Field name (camelCase)'),
|
||||
label: z.string().describe('Display label'),
|
||||
description: z.string().optional().describe('Description'),
|
||||
icon: z.string().optional().describe('Icon name'),
|
||||
type: z.nativeEnum(RelationType).describe('MANY_TO_ONE or ONE_TO_MANY'),
|
||||
targetObjectMetadataId: z.string().uuid().describe('Target object ID'),
|
||||
targetFieldLabel: z.string().describe('Inverse field label'),
|
||||
targetFieldIcon: z.string().describe('Inverse field icon'),
|
||||
}),
|
||||
)
|
||||
.min(1)
|
||||
.max(20)
|
||||
.describe('Array of relation fields to create (1-20 items).'),
|
||||
.describe('Relations to create (max 20).'),
|
||||
});
|
||||
|
||||
@Injectable()
|
||||
@@ -170,14 +137,15 @@ export class FieldMetadataToolsFactory {
|
||||
return {
|
||||
get_field_metadata: {
|
||||
description:
|
||||
'Find fields metadata. Retrieve information about the fields of objects in the workspace data model.',
|
||||
"Returns an array of fields. System fields are returned as compact {id, name, type} — enough to know which fields exist and their types. Keep includeFullSystemFields at its default (false); only set it true when you specifically need a system field's full configuration (settings, defaultValue, relation targets). Internal fields (searchVector, position, updatedBy) are excluded.",
|
||||
inputSchema: GetFieldMetadataInputSchema,
|
||||
execute: async (parameters: {
|
||||
id?: string;
|
||||
objectMetadataId?: string;
|
||||
includeFullSystemFields?: boolean;
|
||||
limit?: number;
|
||||
}) => {
|
||||
return this.fieldMetadataService.query({
|
||||
const rawResults = await this.fieldMetadataService.query({
|
||||
filter: {
|
||||
workspaceId: { eq: workspaceId },
|
||||
...(parameters.id ? { id: { eq: parameters.id } } : {}),
|
||||
@@ -189,11 +157,34 @@ export class FieldMetadataToolsFactory {
|
||||
},
|
||||
paging: { limit: parameters.limit ?? 100 },
|
||||
});
|
||||
|
||||
const compactedFields = (
|
||||
rawResults as unknown as Record<string, unknown>[]
|
||||
)
|
||||
.filter((field) => !EXCLUDED_FIELD_NAMES.has(field.name as string))
|
||||
.map((field) => {
|
||||
if (field.isSystem && !parameters.includeFullSystemFields) {
|
||||
return {
|
||||
id: field.id,
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
};
|
||||
}
|
||||
|
||||
return compactMetadataOutput(
|
||||
{ ...field },
|
||||
{
|
||||
stripWhenNullish: FIELD_STRIP_WHEN_NULLISH,
|
||||
stripWhenFalse: FIELD_STRIP_WHEN_FALSE,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return compactedFields;
|
||||
},
|
||||
},
|
||||
create_field_metadata: {
|
||||
description:
|
||||
'Create a new field metadata on an object. Specify the objectMetadataId and field properties.',
|
||||
description: 'Create a new field on an object.',
|
||||
inputSchema: CreateFieldMetadataInputSchema,
|
||||
execute: async (parameters: {
|
||||
objectMetadataId: string;
|
||||
@@ -220,7 +211,13 @@ export class FieldMetadataToolsFactory {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatFieldMetadataToFieldMetadataDto(flatFieldMetadata);
|
||||
return {
|
||||
id: flatFieldMetadata.id,
|
||||
name: flatFieldMetadata.name,
|
||||
label: flatFieldMetadata.label,
|
||||
type: flatFieldMetadata.type,
|
||||
objectMetadataId: flatFieldMetadata.objectMetadataId,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof WorkspaceMigrationBuilderException) {
|
||||
throw new Error(formatValidationErrors(error));
|
||||
@@ -231,7 +228,7 @@ export class FieldMetadataToolsFactory {
|
||||
},
|
||||
update_field_metadata: {
|
||||
description:
|
||||
'Update an existing field metadata. Provide the field ID and the properties to update.',
|
||||
'Update a field. Provide field ID and properties to change.',
|
||||
inputSchema: UpdateFieldMetadataInputSchema,
|
||||
execute: async (parameters: {
|
||||
id: string;
|
||||
@@ -258,7 +255,12 @@ export class FieldMetadataToolsFactory {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatFieldMetadataToFieldMetadataDto(flatFieldMetadata);
|
||||
return {
|
||||
id: flatFieldMetadata.id,
|
||||
name: flatFieldMetadata.name,
|
||||
label: flatFieldMetadata.label,
|
||||
type: flatFieldMetadata.type,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof WorkspaceMigrationBuilderException) {
|
||||
throw new Error(formatValidationErrors(error));
|
||||
@@ -268,7 +270,7 @@ export class FieldMetadataToolsFactory {
|
||||
},
|
||||
},
|
||||
delete_field_metadata: {
|
||||
description: 'Delete a field metadata by its ID.',
|
||||
description: 'Delete a field by ID.',
|
||||
inputSchema: DeleteFieldMetadataInputSchema,
|
||||
execute: async (parameters: { id: string }) => {
|
||||
try {
|
||||
@@ -278,7 +280,7 @@ export class FieldMetadataToolsFactory {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatFieldMetadataToFieldMetadataDto(flatFieldMetadata);
|
||||
return { id: flatFieldMetadata.id, success: true };
|
||||
} catch (error) {
|
||||
if (error instanceof WorkspaceMigrationBuilderException) {
|
||||
throw new Error(formatValidationErrors(error));
|
||||
@@ -368,8 +370,7 @@ export class FieldMetadataToolsFactory {
|
||||
},
|
||||
},
|
||||
create_many_relation_fields: {
|
||||
description:
|
||||
'Create multiple relation fields between objects at once. This is the recommended way to add relations after creating objects and non-relation fields. Each item specifies the source object, target object, relation type, and labels for both sides of the relation.',
|
||||
description: 'Create multiple relation fields between objects at once.',
|
||||
inputSchema: CreateManyRelationFieldsInputSchema,
|
||||
execute: async (parameters: {
|
||||
relations: Array<{
|
||||
|
||||
+1
-2
@@ -18,7 +18,6 @@ const commonOptionalFields = {
|
||||
.optional()
|
||||
.describe('Position among siblings; defaults to the end.'),
|
||||
folderId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.optional()
|
||||
.describe('Parent folder id, if the item should live inside a folder.'),
|
||||
@@ -84,7 +83,7 @@ const createNavigationMenuItemSchema = z.discriminatedUnion('type', [
|
||||
z.object({
|
||||
type: z.literal(NavigationMenuItemType.PAGE_LAYOUT),
|
||||
scope: navigationMenuItemScopeSchema,
|
||||
pageLayoutId: z.string().uuid().describe('Id of the page layout to pin'),
|
||||
pageLayoutId: z.string().describe('Id of the page layout to pin'),
|
||||
name: requiredNameField,
|
||||
...commonOptionalFields,
|
||||
}),
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { type NavigationMenuItemToolContext } from 'src/engine/metadata-modules/
|
||||
import { type NavigationMenuItemToolDependencies } from 'src/engine/metadata-modules/navigation-menu-item/tools/types/navigation-menu-item-tool-dependencies.type';
|
||||
|
||||
const deleteNavigationMenuItemSchema = z.object({
|
||||
id: z.string().uuid().describe('Id of the navigation menu item to delete'),
|
||||
id: z.uuid().describe('Id of the navigation menu item to delete'),
|
||||
});
|
||||
|
||||
type DeleteNavigationMenuItemParams = z.infer<
|
||||
|
||||
-1
@@ -16,7 +16,6 @@ const listNavigationMenuItemsSchema = z.object({
|
||||
"'workspace' = shared navigation, 'user' = current user's favorites, 'all' = both merged (default).",
|
||||
),
|
||||
folderId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.optional()
|
||||
.describe('Only return items inside this folder.'),
|
||||
|
||||
+1
-3
@@ -5,7 +5,7 @@ import { type NavigationMenuItemToolContext } from 'src/engine/metadata-modules/
|
||||
import { type NavigationMenuItemToolDependencies } from 'src/engine/metadata-modules/navigation-menu-item/tools/types/navigation-menu-item-tool-dependencies.type';
|
||||
|
||||
const updateNavigationMenuItemSchema = z.object({
|
||||
id: z.string().uuid().describe('Id of the navigation menu item to update'),
|
||||
id: z.uuid().describe('Id of the navigation menu item to update'),
|
||||
name: z
|
||||
.string()
|
||||
.trim()
|
||||
@@ -19,7 +19,6 @@ const updateNavigationMenuItemSchema = z.object({
|
||||
position: z.number().optional().describe('New position among siblings'),
|
||||
folderId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe(
|
||||
@@ -32,7 +31,6 @@ const updateNavigationMenuItemSchema = z.object({
|
||||
.describe('New URL (only meaningful for LINK items)'),
|
||||
pageLayoutId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.optional()
|
||||
.describe('New page layout id (only meaningful for PAGE_LAYOUT items)'),
|
||||
});
|
||||
|
||||
+80
-58
@@ -3,18 +3,33 @@ import { Injectable } from '@nestjs/common';
|
||||
import { type ToolSet } from 'ai';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { compactMetadataOutput } from 'src/engine/core-modules/tool-provider/utils/compact-metadata-output.util';
|
||||
import { formatValidationErrors } from 'src/engine/core-modules/tool-provider/utils/format-validation-errors.util';
|
||||
import { fromFlatObjectMetadataToObjectMetadataDto } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-object-metadata-dto.util';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
|
||||
|
||||
const OBJECT_STRIP_WHEN_NULLISH = [
|
||||
'standardOverrides',
|
||||
'color',
|
||||
'duplicateCriteria',
|
||||
'shortcut',
|
||||
'imageIdentifierFieldMetadataId',
|
||||
'description',
|
||||
'icon',
|
||||
];
|
||||
|
||||
const GetObjectMetadataInputSchema = z.object({
|
||||
id: z
|
||||
.string()
|
||||
.uuid()
|
||||
.optional()
|
||||
.describe('Object ID. Returns one object if set.'),
|
||||
includeFullSystemObjects: z
|
||||
.boolean()
|
||||
.default(false)
|
||||
.describe(
|
||||
'Unique identifier for the object metadata. If provided, returns a single object.',
|
||||
"Keep false (default) for listing or locating objects — system objects then return as compact {id, nameSingular, namePlural}, which is enough to find an object and read its id. Only set true when you specifically need a system object's full configuration (e.g. building a relation to workspaceMember).",
|
||||
),
|
||||
limit: z
|
||||
.number()
|
||||
@@ -22,63 +37,52 @@ const GetObjectMetadataInputSchema = z.object({
|
||||
.min(1)
|
||||
.max(100)
|
||||
.default(100)
|
||||
.describe('Maximum number of objects to return.'),
|
||||
.describe('Max objects to return.'),
|
||||
});
|
||||
|
||||
const CreateObjectMetadataInputSchema = 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'),
|
||||
nameSingular: z.string().describe('Singular name (e.g. "company")'),
|
||||
namePlural: z.string().describe('Plural name (e.g. "companies")'),
|
||||
labelSingular: z.string().describe('Singular label (e.g. "Company")'),
|
||||
labelPlural: z.string().describe('Plural label (e.g. "Companies")'),
|
||||
description: z.string().optional().describe('Description'),
|
||||
icon: z.string().optional().describe('Icon name'),
|
||||
shortcut: z.string().optional().describe('Keyboard shortcut'),
|
||||
isRemote: z.boolean().optional().describe('Remote object'),
|
||||
isLabelSyncedWithName: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether label should sync with name changes'),
|
||||
.describe('Sync label with name'),
|
||||
});
|
||||
|
||||
const UpdateObjectMetadataInputSchema = 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'),
|
||||
id: z.uuid().describe('Object ID'),
|
||||
labelSingular: z.string().optional().describe('Singular label'),
|
||||
labelPlural: z.string().optional().describe('Plural label'),
|
||||
nameSingular: z.string().optional().describe('Singular name'),
|
||||
namePlural: z.string().optional().describe('Plural name'),
|
||||
description: z.string().optional().describe('Description'),
|
||||
icon: z.string().optional().describe('Icon name'),
|
||||
shortcut: z.string().optional().describe('Keyboard shortcut'),
|
||||
isActive: z.boolean().optional().describe('Active state'),
|
||||
labelIdentifierFieldMetadataId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.optional()
|
||||
.describe('ID of the field used as label identifier'),
|
||||
.describe('Label identifier field ID'),
|
||||
imageIdentifierFieldMetadataId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.optional()
|
||||
.describe('ID of the field used as image identifier'),
|
||||
.describe('Image identifier field ID'),
|
||||
isLabelSyncedWithName: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether label should sync with name changes'),
|
||||
.describe('Sync label with name'),
|
||||
});
|
||||
|
||||
const DeleteObjectMetadataInputSchema = z.object({
|
||||
id: z.string().uuid().describe('ID of the object to delete'),
|
||||
id: z.string().uuid().describe('Object ID'),
|
||||
});
|
||||
|
||||
const CreateManyObjectMetadataInputSchema = z.object({
|
||||
@@ -86,7 +90,7 @@ const CreateManyObjectMetadataInputSchema = z.object({
|
||||
.array(CreateObjectMetadataInputSchema)
|
||||
.min(1)
|
||||
.max(20)
|
||||
.describe('Array of object metadata to create (1-20 items).'),
|
||||
.describe('Objects to create (max 20).'),
|
||||
});
|
||||
|
||||
const UpdateManyObjectMetadataInputSchema = z.object({
|
||||
@@ -94,7 +98,7 @@ const UpdateManyObjectMetadataInputSchema = z.object({
|
||||
.array(UpdateObjectMetadataInputSchema)
|
||||
.min(1)
|
||||
.max(20)
|
||||
.describe('Array of object metadata updates to apply (1-20 items).'),
|
||||
.describe('Objects to update (max 20).'),
|
||||
});
|
||||
|
||||
@Injectable()
|
||||
@@ -105,9 +109,13 @@ export class ObjectMetadataToolsFactory {
|
||||
return {
|
||||
get_object_metadata: {
|
||||
description:
|
||||
'Find objects metadata. Retrieve information about the data model objects in the workspace.',
|
||||
"List object metadata as an array. System objects are returned as compact {id, nameSingular, namePlural} — enough to locate an object by name and read its id. Keep includeFullSystemObjects at its default (false); only set it true when you specifically need a system object's full configuration.",
|
||||
inputSchema: GetObjectMetadataInputSchema,
|
||||
execute: async (parameters: { id?: string; limit?: number }) => {
|
||||
execute: async (parameters: {
|
||||
id?: string;
|
||||
includeFullSystemObjects?: boolean;
|
||||
limit?: number;
|
||||
}) => {
|
||||
const flatObjectMetadatas =
|
||||
await this.objectMetadataService.findManyWithinWorkspace(
|
||||
workspaceId,
|
||||
@@ -117,14 +125,27 @@ export class ObjectMetadataToolsFactory {
|
||||
},
|
||||
);
|
||||
|
||||
return flatObjectMetadatas.map((flatObjectMetadata) =>
|
||||
fromFlatObjectMetadataToObjectMetadataDto(flatObjectMetadata),
|
||||
);
|
||||
return flatObjectMetadatas.map((flatObjectMetadata) => {
|
||||
const dto =
|
||||
fromFlatObjectMetadataToObjectMetadataDto(flatObjectMetadata);
|
||||
|
||||
if (dto.isSystem && !parameters.includeFullSystemObjects) {
|
||||
return {
|
||||
id: dto.id,
|
||||
nameSingular: dto.nameSingular,
|
||||
namePlural: dto.namePlural,
|
||||
};
|
||||
}
|
||||
|
||||
return compactMetadataOutput(
|
||||
{ ...dto },
|
||||
{ stripWhenNullish: OBJECT_STRIP_WHEN_NULLISH },
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
create_object_metadata: {
|
||||
description:
|
||||
'Create a new object metadata in the workspace data model.',
|
||||
description: 'Create a new object in the workspace data model.',
|
||||
inputSchema: CreateObjectMetadataInputSchema,
|
||||
execute: async (parameters: {
|
||||
nameSingular: string;
|
||||
@@ -146,9 +167,11 @@ export class ObjectMetadataToolsFactory {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatObjectMetadataToObjectMetadataDto(
|
||||
flatObjectMetadata,
|
||||
);
|
||||
return {
|
||||
id: flatObjectMetadata.id,
|
||||
nameSingular: flatObjectMetadata.nameSingular,
|
||||
labelSingular: flatObjectMetadata.labelSingular,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof WorkspaceMigrationBuilderException) {
|
||||
throw new Error(formatValidationErrors(error));
|
||||
@@ -159,7 +182,7 @@ export class ObjectMetadataToolsFactory {
|
||||
},
|
||||
update_object_metadata: {
|
||||
description:
|
||||
'Update an existing object metadata. Provide the object ID and the fields to update.',
|
||||
'Update an object. Provide object ID and properties to change.',
|
||||
inputSchema: UpdateObjectMetadataInputSchema,
|
||||
execute: async (parameters: {
|
||||
id: string;
|
||||
@@ -184,9 +207,11 @@ export class ObjectMetadataToolsFactory {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatObjectMetadataToObjectMetadataDto(
|
||||
flatObjectMetadata,
|
||||
);
|
||||
return {
|
||||
id: flatObjectMetadata.id,
|
||||
nameSingular: flatObjectMetadata.nameSingular,
|
||||
labelSingular: flatObjectMetadata.labelSingular,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof WorkspaceMigrationBuilderException) {
|
||||
throw new Error(formatValidationErrors(error));
|
||||
@@ -196,8 +221,7 @@ export class ObjectMetadataToolsFactory {
|
||||
},
|
||||
},
|
||||
delete_object_metadata: {
|
||||
description:
|
||||
'Delete an object metadata by its ID. This will also delete all associated fields.',
|
||||
description: 'Delete an object by ID. Also deletes associated fields.',
|
||||
inputSchema: DeleteObjectMetadataInputSchema,
|
||||
execute: async (parameters: { id: string }) => {
|
||||
try {
|
||||
@@ -207,9 +231,7 @@ export class ObjectMetadataToolsFactory {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatObjectMetadataToObjectMetadataDto(
|
||||
flatObjectMetadata,
|
||||
);
|
||||
return { id: flatObjectMetadata.id, success: true };
|
||||
} catch (error) {
|
||||
if (error instanceof WorkspaceMigrationBuilderException) {
|
||||
throw new Error(formatValidationErrors(error));
|
||||
@@ -220,7 +242,7 @@ export class ObjectMetadataToolsFactory {
|
||||
},
|
||||
create_many_object_metadata: {
|
||||
description:
|
||||
'Create multiple object metadata at once in the workspace data model. More efficient than calling create_object_metadata multiple times. Each item follows the same schema as create_object_metadata.',
|
||||
'Create multiple objects at once. Batch version of create_object_metadata.',
|
||||
inputSchema: CreateManyObjectMetadataInputSchema,
|
||||
execute: async (parameters: {
|
||||
objects: Array<{
|
||||
@@ -258,7 +280,7 @@ export class ObjectMetadataToolsFactory {
|
||||
},
|
||||
update_many_object_metadata: {
|
||||
description:
|
||||
'Update multiple object metadata at once. More efficient than calling update_object_metadata multiple times. Each item must include the object ID and the properties to update.',
|
||||
'Update multiple objects at once. Batch version of update_object_metadata.',
|
||||
inputSchema: UpdateManyObjectMetadataInputSchema,
|
||||
execute: async (parameters: {
|
||||
objects: Array<{
|
||||
|
||||
-1
@@ -21,7 +21,6 @@ const GetViewFieldsInputSchema = z.object({
|
||||
const CreateViewFieldInputSchema = z.object({
|
||||
viewId: z.string().uuid().describe('The ID of the view to add the field to.'),
|
||||
fieldMetadataId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.describe(
|
||||
'The ID of the field metadata to add. Use get_field_metadata to find available fields.',
|
||||
|
||||
+3
-3
@@ -26,7 +26,7 @@ const CreateViewFilterInputSchema = z.object({
|
||||
.string()
|
||||
.uuid()
|
||||
.describe(
|
||||
'ID of the field to filter on. Use list_object_metadata_items to find field IDs.',
|
||||
'ID of the field to filter on. Use get_field_metadata to find field IDs.',
|
||||
),
|
||||
operand: z
|
||||
.enum(VIEW_FILTER_OPERAND_OPTIONS)
|
||||
@@ -122,7 +122,7 @@ export class ViewFilterToolsFactory {
|
||||
return {
|
||||
create_view_filter: {
|
||||
description:
|
||||
'Add a filter to a view. Use list_object_metadata_items to get fieldMetadataId values.',
|
||||
'Add a filter to a view. Use get_field_metadata to get fieldMetadataId values.',
|
||||
inputSchema: CreateViewFilterInputSchema,
|
||||
execute: async (parameters: {
|
||||
viewId: string;
|
||||
@@ -161,7 +161,7 @@ export class ViewFilterToolsFactory {
|
||||
},
|
||||
create_many_view_filters: {
|
||||
description:
|
||||
'Add multiple filters to a view in one call. Use list_object_metadata_items to get fieldMetadataId values.',
|
||||
'Add multiple filters to a view in one call. Use get_field_metadata to get fieldMetadataId values.',
|
||||
inputSchema: CreateManyViewFiltersInputSchema,
|
||||
execute: async (parameters: {
|
||||
filters: Array<{
|
||||
|
||||
+3
-4
@@ -20,10 +20,9 @@ const GetViewSortsInputSchema = z.object({
|
||||
const CreateViewSortInputSchema = z.object({
|
||||
viewId: z.string().uuid().describe('ID of the view to add the sort to'),
|
||||
fieldMetadataId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.describe(
|
||||
'ID of the field to sort by. Use list_object_metadata_items to find field IDs.',
|
||||
'ID of the field to sort by. Use get_field_metadata to find field IDs.',
|
||||
),
|
||||
direction: z
|
||||
.enum(VIEW_SORT_DIRECTION_OPTIONS)
|
||||
@@ -82,7 +81,7 @@ export class ViewSortToolsFactory {
|
||||
return {
|
||||
create_view_sort: {
|
||||
description:
|
||||
'Add a sort to a view. Use list_object_metadata_items to get fieldMetadataId values.',
|
||||
'Add a sort to a view. Use get_field_metadata to get fieldMetadataId values.',
|
||||
inputSchema: CreateViewSortInputSchema,
|
||||
execute: async (parameters: {
|
||||
viewId: string;
|
||||
@@ -116,7 +115,7 @@ export class ViewSortToolsFactory {
|
||||
},
|
||||
create_many_view_sorts: {
|
||||
description:
|
||||
'Add multiple sorts to a view in one call. Use list_object_metadata_items to get fieldMetadataId values.',
|
||||
'Add multiple sorts to a view in one call. Use get_field_metadata to get fieldMetadataId values.',
|
||||
inputSchema: CreateManyViewSortsInputSchema,
|
||||
execute: async (parameters: {
|
||||
sorts: Array<{
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { type WebhookToolContext } from 'src/engine/metadata-modules/webhook/too
|
||||
import { type WebhookToolDependencies } from 'src/engine/metadata-modules/webhook/tools/types/webhook-tool-dependencies.type';
|
||||
|
||||
const deleteWebhookSchema = z.object({
|
||||
id: z.string().uuid().describe('The id of the webhook to delete'),
|
||||
id: z.uuid().describe('The id of the webhook to delete'),
|
||||
});
|
||||
|
||||
type DeleteWebhookParams = z.infer<typeof deleteWebhookSchema>;
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import { type WebhookToolDependencies } from 'src/engine/metadata-modules/webhoo
|
||||
import { compileWebhookOperations } from 'src/engine/metadata-modules/webhook/tools/utils/compile-webhook-operations.util';
|
||||
|
||||
const updateWebhookSchema = z.object({
|
||||
id: z.string().uuid().describe('The id of the webhook to update'),
|
||||
id: z.uuid().describe('The id of the webhook to update'),
|
||||
targetUrl: z
|
||||
.string()
|
||||
.url()
|
||||
|
||||
Reference in New Issue
Block a user