Skip to content

Commit de7dc8c

Browse files
Copilothotlong
andauthored
refactor(ai): split metadata tools into individual .tool.ts metadata files using defineTool()
Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/3267d1f7-7dd6-4d6d-9702-a4acfd67dd00 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent ed7d53d commit de7dc8c

11 files changed

Lines changed: 400 additions & 227 deletions

CHANGELOG.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616

1717
### Added
1818
- **Metadata Management Tools (`service-ai`)** — Added 6 built-in AI tools for metadata
19-
CRUD operations in `packages/services/service-ai/src/tools/metadata-tools.ts`:
20-
`create_object`, `add_field`, `modify_field`, `delete_field`, `list_metadata_objects`,
21-
`describe_metadata_object`. Tool definitions (`AIToolDefinition`) and `ToolHandler` implementations
22-
live together in a single file (matching the `data-tools.ts` pattern). Handlers call
23-
`IMetadataService` CRUD methods (`register`, `getObject`, `listObjects`).
24-
Registered via `registerMetadataTools(registry, { metadataService })`.
25-
37 unit tests covering handler execution, input validation, error handling, dual registration
26-
with data tools (no name collisions), and a full create→add→modify→delete→describe lifecycle.
19+
CRUD operations, each defined as a first-class `Tool` metadata file using `defineTool()`
20+
from `@objectstack/spec/ai`:
21+
- `create-object.tool.ts` — Creates a new data object with schema validation
22+
- `add-field.tool.ts` — Adds a field to an existing object
23+
- `modify-field.tool.ts` — Modifies field properties on an object
24+
- `delete-field.tool.ts` — Removes a field from an object
25+
- `list-metadata-objects.tool.ts` — Lists all registered metadata objects
26+
- `describe-metadata-object.tool.ts` — Returns full schema details of an object
27+
28+
Each `.tool.ts` file is an independent metadata unit with `name`, `label`, `description`,
29+
`category`, `builtIn`, and `parameters` — following the same `.object.ts` / `.view.ts`
30+
metadata file convention. Handler factories remain in `metadata-tools.ts` and bind handlers
31+
at `ai:ready` time via `registerMetadataTools(registry, { metadataService })`.
32+
79 unit tests covering tool metadata properties, handler execution, input validation,
33+
error handling, dual registration with data tools, and a full lifecycle test.
2734
- **Agent Skills — `skills/` directory (agentskills.io)** — Created `skills/` folder at
2835
repository root following the [agentskills.io specification](https://agentskills.io/specification).
2936
Five expert-knowledge skills with hand-written `SKILL.md` files and `references/` quick-lookup

packages/services/service-ai/src/__tests__/metadata-tools.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ import {
1212
} from '../tools/data-tools.js';
1313
import type { MetadataToolContext } from '../tools/metadata-tools.js';
1414

15+
// Individual tool metadata imports
16+
import { createObjectTool } from '../tools/create-object.tool.js';
17+
import { addFieldTool } from '../tools/add-field.tool.js';
18+
import { modifyFieldTool } from '../tools/modify-field.tool.js';
19+
import { deleteFieldTool } from '../tools/delete-field.tool.js';
20+
import { listMetadataObjectsTool } from '../tools/list-metadata-objects.tool.js';
21+
import { describeMetadataObjectTool } from '../tools/describe-metadata-object.tool.js';
22+
1523
// ── Helpers ────────────────────────────────────────────────────────
1624

1725
/** Build a mock IMetadataService with optionally pre-loaded objects. */
@@ -68,6 +76,54 @@ describe('Metadata Tool Definitions', () => {
6876
});
6977
});
7078

79+
// ═══════════════════════════════════════════════════════════════════
80+
// Individual Tool Metadata Files (.tool.ts)
81+
// ═══════════════════════════════════════════════════════════════════
82+
83+
describe('Individual Tool Metadata (.tool.ts)', () => {
84+
const tools = [
85+
{ tool: createObjectTool, expectedName: 'create_object', expectedLabel: 'Create Object' },
86+
{ tool: addFieldTool, expectedName: 'add_field', expectedLabel: 'Add Field' },
87+
{ tool: modifyFieldTool, expectedName: 'modify_field', expectedLabel: 'Modify Field' },
88+
{ tool: deleteFieldTool, expectedName: 'delete_field', expectedLabel: 'Delete Field' },
89+
{ tool: listMetadataObjectsTool, expectedName: 'list_metadata_objects', expectedLabel: 'List Metadata Objects' },
90+
{ tool: describeMetadataObjectTool, expectedName: 'describe_metadata_object', expectedLabel: 'Describe Metadata Object' },
91+
];
92+
93+
for (const { tool, expectedName, expectedLabel } of tools) {
94+
describe(expectedName, () => {
95+
it('should have correct name', () => {
96+
expect(tool.name).toBe(expectedName);
97+
});
98+
99+
it('should have a label', () => {
100+
expect(tool.label).toBe(expectedLabel);
101+
});
102+
103+
it('should be categorized as data', () => {
104+
expect(tool.category).toBe('data');
105+
});
106+
107+
it('should be marked as built-in', () => {
108+
expect(tool.builtIn).toBe(true);
109+
});
110+
111+
it('should have a description', () => {
112+
expect(tool.description).toBeTruthy();
113+
});
114+
115+
it('should have parameters schema', () => {
116+
expect(tool.parameters).toBeDefined();
117+
expect(tool.parameters.type).toBe('object');
118+
});
119+
120+
it('should be included in METADATA_TOOL_DEFINITIONS', () => {
121+
expect(METADATA_TOOL_DEFINITIONS).toContain(tool);
122+
});
123+
});
124+
}
125+
});
126+
71127
// ═══════════════════════════════════════════════════════════════════
72128
// registerMetadataTools + Handlers
73129
// ═══════════════════════════════════════════════════════════════════

packages/services/service-ai/src/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ export type { DataToolContext } from './tools/data-tools.js';
3333
export { registerMetadataTools, METADATA_TOOL_DEFINITIONS } from './tools/metadata-tools.js';
3434
export type { MetadataToolContext } from './tools/metadata-tools.js';
3535

36+
// Individual tool metadata (first-class Tool definitions via defineTool)
37+
export {
38+
createObjectTool,
39+
addFieldTool,
40+
modifyFieldTool,
41+
deleteFieldTool,
42+
listMetadataObjectsTool,
43+
describeMetadataObjectTool,
44+
} from './tools/metadata-tools.js';
45+
3646
// Agent runtime
3747
export { AgentRuntime } from './agent-runtime.js';
3848
export type { AgentChatContext } from './agent-runtime.js';
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { defineTool } from '@objectstack/spec/ai';
4+
5+
/**
6+
* add_field — AI Tool Metadata
7+
*
8+
* Adds a new field (column) to an existing data object.
9+
* Validates snake_case for objectName, field name, reference,
10+
* and select option values before merging into the definition.
11+
*/
12+
export const addFieldTool = defineTool({
13+
name: 'add_field',
14+
label: 'Add Field',
15+
description:
16+
'Adds a new field (column) to an existing data object. ' +
17+
'Use this when the user wants to add a property, column, or attribute to a table.',
18+
category: 'data',
19+
builtIn: true,
20+
parameters: {
21+
type: 'object',
22+
properties: {
23+
objectName: {
24+
type: 'string',
25+
description: 'Target object machine name (snake_case)',
26+
},
27+
name: {
28+
type: 'string',
29+
description: 'Field machine name (snake_case, e.g. due_date)',
30+
},
31+
label: {
32+
type: 'string',
33+
description: 'Human-readable field label (e.g. Due Date)',
34+
},
35+
type: {
36+
type: 'string',
37+
description: 'Field data type',
38+
enum: ['text', 'textarea', 'number', 'boolean', 'date', 'datetime', 'select', 'lookup', 'formula', 'autonumber'],
39+
},
40+
required: {
41+
type: 'boolean',
42+
description: 'Whether the field is required',
43+
},
44+
defaultValue: {
45+
description: 'Default value for the field',
46+
},
47+
options: {
48+
type: 'array',
49+
description: 'Options for select/picklist fields',
50+
items: {
51+
type: 'object',
52+
properties: {
53+
label: { type: 'string' },
54+
value: {
55+
type: 'string',
56+
description: 'Option machine identifier (lowercase snake_case, e.g. high_priority)',
57+
pattern: '^[a-z_][a-z0-9_]*$',
58+
},
59+
},
60+
},
61+
},
62+
reference: {
63+
type: 'string',
64+
description: 'Referenced object name for lookup fields (snake_case, e.g. account)',
65+
},
66+
},
67+
required: ['objectName', 'name', 'type'],
68+
additionalProperties: false,
69+
},
70+
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { defineTool } from '@objectstack/spec/ai';
4+
5+
/**
6+
* create_object — AI Tool Metadata
7+
*
8+
* Creates a new data object (table) with schema validation.
9+
* Validates snake_case naming for object and initial fields,
10+
* checks for duplicates, and registers the object definition.
11+
*/
12+
export const createObjectTool = defineTool({
13+
name: 'create_object',
14+
label: 'Create Object',
15+
description:
16+
'Creates a new data object (table) with the specified name, label, and optional field definitions. ' +
17+
'Use this when the user wants to create a new entity, table, or data model.',
18+
category: 'data',
19+
builtIn: true,
20+
parameters: {
21+
type: 'object',
22+
properties: {
23+
name: {
24+
type: 'string',
25+
description: 'Machine name for the object (snake_case, e.g. project_task)',
26+
},
27+
label: {
28+
type: 'string',
29+
description: 'Human-readable display name (e.g. Project Task)',
30+
},
31+
fields: {
32+
type: 'array',
33+
description: 'Initial fields to create with the object',
34+
items: {
35+
type: 'object',
36+
properties: {
37+
name: { type: 'string', description: 'Field machine name (snake_case)' },
38+
label: { type: 'string', description: 'Field display name' },
39+
type: {
40+
type: 'string',
41+
description: 'Field data type',
42+
enum: ['text', 'textarea', 'number', 'boolean', 'date', 'datetime', 'select', 'lookup', 'formula', 'autonumber'],
43+
},
44+
required: { type: 'boolean', description: 'Whether the field is required' },
45+
},
46+
required: ['name', 'type'],
47+
},
48+
},
49+
enableFeatures: {
50+
type: 'object',
51+
description: 'Object capability flags',
52+
properties: {
53+
trackHistory: { type: 'boolean' },
54+
apiEnabled: { type: 'boolean' },
55+
},
56+
},
57+
},
58+
required: ['name', 'label'],
59+
additionalProperties: false,
60+
},
61+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { defineTool } from '@objectstack/spec/ai';
4+
5+
/**
6+
* delete_field — AI Tool Metadata
7+
*
8+
* Removes a field (column) from an existing data object.
9+
* This is a destructive operation.
10+
*/
11+
export const deleteFieldTool = defineTool({
12+
name: 'delete_field',
13+
label: 'Delete Field',
14+
description:
15+
'Removes a field (column) from an existing data object. This is a destructive operation. ' +
16+
'Use this when the user explicitly wants to remove an attribute or column from a table.',
17+
category: 'data',
18+
builtIn: true,
19+
parameters: {
20+
type: 'object',
21+
properties: {
22+
objectName: {
23+
type: 'string',
24+
description: 'Target object machine name (snake_case)',
25+
},
26+
fieldName: {
27+
type: 'string',
28+
description: 'Field machine name to delete (snake_case)',
29+
},
30+
},
31+
required: ['objectName', 'fieldName'],
32+
additionalProperties: false,
33+
},
34+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { defineTool } from '@objectstack/spec/ai';
4+
5+
/**
6+
* describe_metadata_object — AI Tool Metadata
7+
*
8+
* Returns the full metadata schema of a data object including all
9+
* fields, types, relationships, and configuration. Uses a unique name
10+
* (`describe_metadata_object`) to avoid collision with the data-tools
11+
* `describe_object` tool.
12+
*/
13+
export const describeMetadataObjectTool = defineTool({
14+
name: 'describe_metadata_object',
15+
label: 'Describe Metadata Object',
16+
description:
17+
'Returns the full metadata schema details of a data object, including all fields, types, relationships, and configuration. ' +
18+
'Use this when the user wants to inspect or understand the metadata structure of a specific table or entity.',
19+
category: 'data',
20+
builtIn: true,
21+
parameters: {
22+
type: 'object',
23+
properties: {
24+
objectName: {
25+
type: 'string',
26+
description: 'Object machine name to describe (snake_case)',
27+
},
28+
},
29+
required: ['objectName'],
30+
additionalProperties: false,
31+
},
32+
});

packages/services/service-ai/src/tools/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,11 @@ export type { DataToolContext } from './data-tools.js';
88

99
export { registerMetadataTools, METADATA_TOOL_DEFINITIONS } from './metadata-tools.js';
1010
export type { MetadataToolContext } from './metadata-tools.js';
11+
12+
// Individual tool metadata exports
13+
export { createObjectTool } from './create-object.tool.js';
14+
export { addFieldTool } from './add-field.tool.js';
15+
export { modifyFieldTool } from './modify-field.tool.js';
16+
export { deleteFieldTool } from './delete-field.tool.js';
17+
export { listMetadataObjectsTool } from './list-metadata-objects.tool.js';
18+
export { describeMetadataObjectTool } from './describe-metadata-object.tool.js';
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { defineTool } from '@objectstack/spec/ai';
4+
5+
/**
6+
* list_metadata_objects — AI Tool Metadata
7+
*
8+
* Lists all registered metadata objects (tables) with optional filtering.
9+
* Uses a unique name (`list_metadata_objects`) to avoid collision with
10+
* the data-tools `list_objects` tool.
11+
*/
12+
export const listMetadataObjectsTool = defineTool({
13+
name: 'list_metadata_objects',
14+
label: 'List Metadata Objects',
15+
description:
16+
'Lists all registered metadata objects (tables) in the current environment. ' +
17+
'Use this when the user wants to see what tables, entities, or data models are defined in metadata.',
18+
category: 'data',
19+
builtIn: true,
20+
parameters: {
21+
type: 'object',
22+
properties: {
23+
filter: {
24+
type: 'string',
25+
description: 'Optional name or label substring to filter objects',
26+
},
27+
includeFields: {
28+
type: 'boolean',
29+
description: 'Whether to include field summaries for each object (default: false)',
30+
},
31+
},
32+
additionalProperties: false,
33+
},
34+
});

0 commit comments

Comments
 (0)