diff --git a/CHANGELOG.md b/CHANGELOG.md index ce3c100f09..d04fcfaba0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- **Unified `list_objects` / `describe_object` tools (`service-ai`)** — Merged the duplicate + `list_metadata_objects` → `list_objects` and `describe_metadata_object` → `describe_object` + tool pairs. Both `data_chat` and `metadata_assistant` agents now share the same unified tools + with full `filter`, `includeFields`, snake_case validation, and `enableFeatures` support. + `DATA_TOOL_DEFINITIONS` is reduced from 5 to 3 (query-only tools), while + `METADATA_TOOL_DEFINITIONS` retains all 6 tools under the unified names. The duplicate + `ObjectDef`/`FieldDef` type definitions in `data-tools.ts` are removed. + ### Fixed - **Agent Chat: Vercel SSE Data Stream support** — The agent chat endpoint (`/api/v1/ai/agents/:agentName/chat`) now returns Vercel AI SDK v6 UI Message Stream Protocol @@ -157,7 +166,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **Metadata Assistant Agent (`service-ai`)** — New `metadata_assistant` agent definition that binds all 6 metadata management tools (`create_object`, `add_field`, `modify_field`, - `delete_field`, `list_metadata_objects`, `describe_metadata_object`). Includes a tailored + `delete_field`, `list_objects`, `describe_object`). Includes a tailored system prompt that guides the AI to use snake_case naming, verify existing schemas before modifications, and warn about destructive operations. Configured with `react` planning strategy (10 iterations, replan enabled) for multi-step schema design conversations. diff --git a/apps/studio/test/ai-chat-panel.test.tsx b/apps/studio/test/ai-chat-panel.test.tsx index 5c2ed094c0..eec06c90be 100644 --- a/apps/studio/test/ai-chat-panel.test.tsx +++ b/apps/studio/test/ai-chat-panel.test.tsx @@ -164,7 +164,7 @@ describe('Messages with tool invocation parts', () => { role: 'assistant', toolParts: [ { - toolName: 'list_metadata_objects', + toolName: 'list_objects', toolCallId: 'tc_2', state: 'output-available', input: {}, diff --git a/packages/services/service-ai/src/__tests__/chatbot-features.test.ts b/packages/services/service-ai/src/__tests__/chatbot-features.test.ts index ff12501dda..1176a7ec42 100644 --- a/packages/services/service-ai/src/__tests__/chatbot-features.test.ts +++ b/packages/services/service-ai/src/__tests__/chatbot-features.test.ts @@ -304,15 +304,13 @@ describe('AIService.chatWithTools', () => { describe('Data Tools', () => { describe('DATA_TOOL_DEFINITIONS', () => { - it('should define exactly 5 tools', () => { - expect(DATA_TOOL_DEFINITIONS).toHaveLength(5); + it('should define exactly 3 tools', () => { + expect(DATA_TOOL_DEFINITIONS).toHaveLength(3); }); it('should include all expected tool names', () => { const names = DATA_TOOL_DEFINITIONS.map(t => t.name); expect(names).toEqual([ - 'list_objects', - 'describe_object', 'query_records', 'get_record', 'aggregate_data', @@ -336,22 +334,24 @@ describe('Data Tools', () => { registry = new ToolRegistry(); dataEngine = createMockDataEngine(); metadataService = createMockMetadataService(); - registerDataTools(registry, { dataEngine, metadataService }); + registerDataTools(registry, { dataEngine }); }); - it('should register all 5 tools', () => { - expect(registry.size).toBe(5); - expect(registry.has('list_objects')).toBe(true); - expect(registry.has('describe_object')).toBe(true); + it('should register all 3 tools', () => { + expect(registry.size).toBe(3); expect(registry.has('query_records')).toBe(true); expect(registry.has('get_record')).toBe(true); expect(registry.has('aggregate_data')).toBe(true); }); - it('list_objects should return object names and labels', async () => { + it('list_objects should return object names and labels (via metadata tools)', async () => { + // list_objects is now part of metadata tools — register them + const { registerMetadataTools } = await import('../tools/metadata-tools.js'); + registerMetadataTools(registry, { metadataService }); + (metadataService.listObjects as any).mockResolvedValue([ - { name: 'account', label: 'Account' }, - { name: 'contact', label: 'Contact' }, + { name: 'account', label: 'Account', fields: { name: { type: 'text' } } }, + { name: 'contact', label: 'Contact', fields: {} }, ]); const result = await registry.execute({ @@ -362,11 +362,15 @@ describe('Data Tools', () => { }); const parsed = JSON.parse((result.output as any).value); - expect(parsed).toHaveLength(2); - expect(parsed[0]).toEqual({ name: 'account', label: 'Account' }); + expect(parsed.objects).toHaveLength(2); + expect(parsed.objects[0]).toEqual(expect.objectContaining({ name: 'account', label: 'Account' })); }); - it('describe_object should return field schema', async () => { + it('describe_object should return field schema (via metadata tools)', async () => { + // describe_object is now part of metadata tools — register them + const { registerMetadataTools } = await import('../tools/metadata-tools.js'); + registerMetadataTools(registry, { metadataService }); + (metadataService.getObject as any).mockResolvedValue({ name: 'account', label: 'Account', @@ -385,12 +389,19 @@ describe('Data Tools', () => { const parsed = JSON.parse((result.output as any).value); expect(parsed.name).toBe('account'); - expect(parsed.fields.name.type).toBe('text'); - expect(parsed.fields.name.required).toBe(true); - expect(parsed.fields.revenue.type).toBe('number'); + // Unified handler returns fields as array (not object) + const nameField = parsed.fields.find((f: any) => f.name === 'name'); + expect(nameField.type).toBe('text'); + expect(nameField.required).toBe(true); + const revenueField = parsed.fields.find((f: any) => f.name === 'revenue'); + expect(revenueField.type).toBe('number'); }); - it('describe_object should return error for unknown object', async () => { + it('describe_object should return error for unknown object (via metadata tools)', async () => { + // describe_object is now part of metadata tools — register them + const { registerMetadataTools } = await import('../tools/metadata-tools.js'); + registerMetadataTools(registry, { metadataService }); + const result = await registry.execute({ type: 'tool-call' as const, toolCallId: 'c1', @@ -1067,8 +1078,8 @@ describe('METADATA_ASSISTANT_AGENT', () => { expect(toolNames).toContain('add_field'); expect(toolNames).toContain('modify_field'); expect(toolNames).toContain('delete_field'); - expect(toolNames).toContain('list_metadata_objects'); - expect(toolNames).toContain('describe_metadata_object'); + expect(toolNames).toContain('list_objects'); + expect(toolNames).toContain('describe_object'); }); it('should use action type for mutation tools and query type for read tools', () => { @@ -1099,7 +1110,7 @@ describe('METADATA_ASSISTANT_AGENT', () => { it('should have instructions mentioning metadata management capabilities', () => { const instructions = METADATA_ASSISTANT_AGENT.instructions; expect(instructions).toContain('snake_case'); - expect(instructions).toContain('list_metadata_objects'); - expect(instructions).toContain('describe_metadata_object'); + expect(instructions).toContain('list_objects'); + expect(instructions).toContain('describe_object'); }); }); diff --git a/packages/services/service-ai/src/__tests__/metadata-tools.test.ts b/packages/services/service-ai/src/__tests__/metadata-tools.test.ts index 3357191a64..f91362f5e7 100644 --- a/packages/services/service-ai/src/__tests__/metadata-tools.test.ts +++ b/packages/services/service-ai/src/__tests__/metadata-tools.test.ts @@ -17,8 +17,8 @@ import { createObjectTool } from '../tools/create-object.tool.js'; import { addFieldTool } from '../tools/add-field.tool.js'; import { modifyFieldTool } from '../tools/modify-field.tool.js'; import { deleteFieldTool } from '../tools/delete-field.tool.js'; -import { listMetadataObjectsTool } from '../tools/list-metadata-objects.tool.js'; -import { describeMetadataObjectTool } from '../tools/describe-metadata-object.tool.js'; +import { listObjectsTool } from '../tools/list-objects.tool.js'; +import { describeObjectTool } from '../tools/describe-object.tool.js'; // ── Helpers ──────────────────────────────────────────────────────── @@ -63,8 +63,8 @@ describe('Metadata Tool Definitions', () => { 'add_field', 'modify_field', 'delete_field', - 'list_metadata_objects', - 'describe_metadata_object', + 'list_objects', + 'describe_object', ]); }); @@ -86,8 +86,8 @@ describe('Individual Tool Metadata (.tool.ts)', () => { { tool: addFieldTool, expectedName: 'add_field', expectedLabel: 'Add Field' }, { tool: modifyFieldTool, expectedName: 'modify_field', expectedLabel: 'Modify Field' }, { tool: deleteFieldTool, expectedName: 'delete_field', expectedLabel: 'Delete Field' }, - { tool: listMetadataObjectsTool, expectedName: 'list_metadata_objects', expectedLabel: 'List Metadata Objects' }, - { tool: describeMetadataObjectTool, expectedName: 'describe_metadata_object', expectedLabel: 'Describe Metadata Object' }, + { tool: listObjectsTool, expectedName: 'list_objects', expectedLabel: 'List Objects' }, + { tool: describeObjectTool, expectedName: 'describe_object', expectedLabel: 'Describe Object' }, ]; for (const { tool, expectedName, expectedLabel } of tools) { @@ -132,8 +132,8 @@ describe('Individual Tool Metadata (.tool.ts)', () => { }); it('should not mark read-only tools as requiresConfirmation', () => { - expect(listMetadataObjectsTool.requiresConfirmation).toBe(false); - expect(describeMetadataObjectTool.requiresConfirmation).toBe(false); + expect(listObjectsTool.requiresConfirmation).toBe(false); + expect(describeObjectTool.requiresConfirmation).toBe(false); }); it('should not mark add_field and modify_field as requiresConfirmation', () => { @@ -162,8 +162,8 @@ describe('registerMetadataTools', () => { expect(registry.has('add_field')).toBe(true); expect(registry.has('modify_field')).toBe(true); expect(registry.has('delete_field')).toBe(true); - expect(registry.has('list_metadata_objects')).toBe(true); - expect(registry.has('describe_metadata_object')).toBe(true); + expect(registry.has('list_objects')).toBe(true); + expect(registry.has('describe_object')).toBe(true); }); }); @@ -171,8 +171,8 @@ describe('registerMetadataTools', () => { // Dual registration (data tools + metadata tools) // ═══════════════════════════════════════════════════════════════════ -describe('registerDataTools + registerMetadataTools — no collision', () => { - it('should register both tool sets on the same registry without overwriting', () => { +describe('registerDataTools + registerMetadataTools — unified list/describe', () => { + it('should register both tool sets on the same registry with shared list_objects and describe_object', () => { const registry = new ToolRegistry(); const metadataService = createMockMetadataService(); const dataEngine = { @@ -181,26 +181,32 @@ describe('registerDataTools + registerMetadataTools — no collision', () => { aggregate: vi.fn(), } as any; - registerDataTools(registry, { dataEngine, metadataService }); + registerDataTools(registry, { dataEngine }); const sizeAfterData = registry.size; registerMetadataTools(registry, { metadataService }); const sizeAfterBoth = registry.size; - // Data tools define: list_objects, describe_object, query_records, get_record, aggregate_data - // Metadata tools define: create_object, add_field, modify_field, delete_field, list_metadata_objects, describe_metadata_object - // No overlap — total should be sum of both + // Data tools define: query_records, get_record, aggregate_data (3) + // Metadata tools define: create_object, add_field, modify_field, delete_field, list_objects, describe_object (6) + // Total should be 3 + 6 = 9 + expect(sizeAfterData).toBe(3); expect(sizeAfterBoth).toBe(sizeAfterData + 6); - // Data tools should still be present + // Unified list/describe should be present (from metadata tools) expect(registry.has('list_objects')).toBe(true); expect(registry.has('describe_object')).toBe(true); + + // Data-only tools should be present expect(registry.has('query_records')).toBe(true); + expect(registry.has('get_record')).toBe(true); + expect(registry.has('aggregate_data')).toBe(true); - // Metadata tools should also be present with distinct names - expect(registry.has('list_metadata_objects')).toBe(true); - expect(registry.has('describe_metadata_object')).toBe(true); + // Metadata-only tools should be present expect(registry.has('create_object')).toBe(true); + expect(registry.has('add_field')).toBe(true); + expect(registry.has('modify_field')).toBe(true); + expect(registry.has('delete_field')).toBe(true); }); }); @@ -752,7 +758,7 @@ describe('list_metadata_objects handler', () => { const result = await registry.execute({ type: 'tool-call' as const, toolCallId: 'c1', - toolName: 'list_metadata_objects', + toolName: 'list_objects', input: {}, }); @@ -767,7 +773,7 @@ describe('list_metadata_objects handler', () => { const result = await registry.execute({ type: 'tool-call' as const, toolCallId: 'c2', - toolName: 'list_metadata_objects', + toolName: 'list_objects', input: { filter: 'account' }, }); @@ -780,7 +786,7 @@ describe('list_metadata_objects handler', () => { const result = await registry.execute({ type: 'tool-call' as const, toolCallId: 'c3', - toolName: 'list_metadata_objects', + toolName: 'list_objects', input: { includeFields: true }, }); @@ -797,7 +803,7 @@ describe('list_metadata_objects handler', () => { const result = await registry.execute({ type: 'tool-call' as const, toolCallId: 'c4', - toolName: 'list_metadata_objects', + toolName: 'list_objects', input: {}, }); @@ -836,7 +842,7 @@ describe('describe_metadata_object handler', () => { const result = await registry.execute({ type: 'tool-call' as const, toolCallId: 'c1', - toolName: 'describe_metadata_object', + toolName: 'describe_object', input: { objectName: 'account' }, }); @@ -858,7 +864,7 @@ describe('describe_metadata_object handler', () => { const result = await registry.execute({ type: 'tool-call' as const, toolCallId: 'c2', - toolName: 'describe_metadata_object', + toolName: 'describe_object', input: { objectName: 'nonexistent' }, }); @@ -909,7 +915,7 @@ describe('Metadata Tools — full lifecycle', () => { const descResult = await registry.execute({ type: 'tool-call' as const, toolCallId: 's4', - toolName: 'describe_metadata_object', + toolName: 'describe_object', input: { objectName: 'invoice' }, }); const desc = JSON.parse((descResult.output as any).value); @@ -941,7 +947,7 @@ describe('Metadata Tools — full lifecycle', () => { const descResult2 = await registry.execute({ type: 'tool-call' as const, toolCallId: 's7', - toolName: 'describe_metadata_object', + toolName: 'describe_object', input: { objectName: 'invoice' }, }); const desc2 = JSON.parse((descResult2.output as any).value); @@ -954,7 +960,7 @@ describe('Metadata Tools — full lifecycle', () => { const listResult = await registry.execute({ type: 'tool-call' as const, toolCallId: 's8', - toolName: 'list_metadata_objects', + toolName: 'list_objects', input: {}, }); const list = JSON.parse((listResult.output as any).value); diff --git a/packages/services/service-ai/src/agents/metadata-assistant-agent.ts b/packages/services/service-ai/src/agents/metadata-assistant-agent.ts index ebe2a5862b..ed76fdec80 100644 --- a/packages/services/service-ai/src/agents/metadata-assistant-agent.ts +++ b/packages/services/service-ai/src/agents/metadata-assistant-agent.ts @@ -36,8 +36,8 @@ Capabilities: - Describe the full schema of a specific object Guidelines: -1. Before creating a new object, use list_metadata_objects to check if a similar one already exists. -2. Before modifying or deleting fields, use describe_metadata_object to understand the current schema. +1. Before creating a new object, use list_objects to check if a similar one already exists. +2. Before modifying or deleting fields, use describe_object to understand the current schema. 3. Always use snake_case for object names and field names (e.g. project_task, due_date). 4. Suggest meaningful field types based on the user's description (e.g. "deadline" → date, "active" → boolean). 5. When creating objects, propose a reasonable set of initial fields based on the entity type. @@ -59,8 +59,8 @@ Guidelines: { type: 'action', name: 'add_field', description: 'Add a field to an existing object' }, { type: 'action', name: 'modify_field', description: 'Modify an existing field definition' }, { type: 'action', name: 'delete_field', description: 'Delete a field from an object' }, - { type: 'query', name: 'list_metadata_objects', description: 'List all metadata objects' }, - { type: 'query', name: 'describe_metadata_object', description: 'Describe an object schema' }, + { type: 'query', name: 'list_objects', description: 'List all data objects' }, + { type: 'query', name: 'describe_object', description: 'Describe an object schema' }, ], active: true, diff --git a/packages/services/service-ai/src/index.ts b/packages/services/service-ai/src/index.ts index 5f83b14f04..b4266d6709 100644 --- a/packages/services/service-ai/src/index.ts +++ b/packages/services/service-ai/src/index.ts @@ -39,8 +39,8 @@ export { addFieldTool, modifyFieldTool, deleteFieldTool, - listMetadataObjectsTool, - describeMetadataObjectTool, + listObjectsTool, + describeObjectTool, } from './tools/metadata-tools.js'; // Agent runtime diff --git a/packages/services/service-ai/src/plugin.ts b/packages/services/service-ai/src/plugin.ts index a493619b54..7cfcf309b5 100644 --- a/packages/services/service-ai/src/plugin.ts +++ b/packages/services/service-ai/src/plugin.ts @@ -254,24 +254,26 @@ export class AIServicePlugin implements Plugin { ctx.logger.debug('[AI] Metadata service not available'); } - // Data tools require both data engine and metadata service + // Data tools require only the data engine try { const dataEngine = ctx.getService('data'); - if (dataEngine && metadataService) { - registerDataTools(this.service.toolRegistry, { dataEngine, metadataService }); + if (dataEngine) { + registerDataTools(this.service.toolRegistry, { dataEngine }); ctx.logger.info('[AI] Built-in data tools registered'); - // Register the built-in data_chat agent only if it does not already exist - const agentExists = - typeof metadataService.exists === 'function' - ? await metadataService.exists('agent', DATA_CHAT_AGENT.name) - : false; - - if (!agentExists) { - await metadataService.register('agent', DATA_CHAT_AGENT.name, DATA_CHAT_AGENT); - ctx.logger.info('[AI] data_chat agent registered'); - } else { - ctx.logger.debug('[AI] data_chat agent already exists, skipping auto-registration'); + // Register the built-in data_chat agent (requires metadata service) + if (metadataService) { + const agentExists = + typeof metadataService.exists === 'function' + ? await metadataService.exists('agent', DATA_CHAT_AGENT.name) + : false; + + if (!agentExists) { + await metadataService.register('agent', DATA_CHAT_AGENT.name, DATA_CHAT_AGENT); + ctx.logger.info('[AI] data_chat agent registered'); + } else { + ctx.logger.debug('[AI] data_chat agent already exists, skipping auto-registration'); + } } } } catch { diff --git a/packages/services/service-ai/src/tools/data-tools.ts b/packages/services/service-ai/src/tools/data-tools.ts index 9c47429273..560abbb1a2 100644 --- a/packages/services/service-ai/src/tools/data-tools.ts +++ b/packages/services/service-ai/src/tools/data-tools.ts @@ -1,30 +1,9 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. -import type { AIToolDefinition, IDataEngine, IMetadataService } from '@objectstack/spec/contracts'; +import type { AIToolDefinition, IDataEngine } from '@objectstack/spec/contracts'; import type { ToolHandler } from './tool-registry.js'; import type { ToolRegistry } from './tool-registry.js'; -// --------------------------------------------------------------------------- -// Internal type aliases for metadata payloads (returned as `unknown` from -// IMetadataService — we cast to these lightweight shapes for field access). -// --------------------------------------------------------------------------- - -/** Minimal shape of an object definition as returned by IMetadataService. */ -interface ObjectDef { - name: string; - label?: string; - fields?: Record; -} - -/** Minimal shape of a field definition inside an object. */ -interface FieldDef { - type?: string; - label?: string; - required?: boolean; - reference?: string; - options?: unknown; -} - // --------------------------------------------------------------------------- // Data context — injected once at registration time // --------------------------------------------------------------------------- @@ -38,8 +17,6 @@ interface FieldDef { export interface DataToolContext { /** ObjectQL data engine for record-level operations. */ dataEngine: IDataEngine; - /** Metadata service for schema/object introspection. */ - metadataService: IMetadataService; } // --------------------------------------------------------------------------- @@ -52,34 +29,6 @@ const MAX_QUERY_LIMIT = 200; /** Default record limit when not specified. */ const DEFAULT_QUERY_LIMIT = 20; -export const LIST_OBJECTS_TOOL: AIToolDefinition = { - name: 'list_objects', - description: 'List all available data objects (tables) in the system. Returns object names and labels.', - parameters: { - type: 'object', - properties: {}, - additionalProperties: false, - }, -}; - -export const DESCRIBE_OBJECT_TOOL: AIToolDefinition = { - name: 'describe_object', - description: - 'Get the schema (fields, types, labels) of a specific data object. ' + - 'Use this to understand the structure of a table before querying it.', - parameters: { - type: 'object', - properties: { - objectName: { - type: 'string', - description: 'The snake_case name of the object to describe', - }, - }, - required: ['objectName'], - additionalProperties: false, - }, -}; - export const QUERY_RECORDS_TOOL: AIToolDefinition = { name: 'query_records', description: @@ -203,10 +152,8 @@ export const AGGREGATE_DATA_TOOL: AIToolDefinition = { }, }; -/** All built-in data tool definitions. */ +/** All built-in data tools definitions. */ export const DATA_TOOL_DEFINITIONS: AIToolDefinition[] = [ - LIST_OBJECTS_TOOL, - DESCRIBE_OBJECT_TOOL, QUERY_RECORDS_TOOL, GET_RECORD_TOOL, AGGREGATE_DATA_TOOL, @@ -216,46 +163,6 @@ export const DATA_TOOL_DEFINITIONS: AIToolDefinition[] = [ // Handler Factories // --------------------------------------------------------------------------- -function createListObjectsHandler(ctx: DataToolContext): ToolHandler { - return async () => { - const objects = await ctx.metadataService.listObjects(); - const summary = (objects as ObjectDef[]).map(o => ({ - name: o.name, - label: o.label ?? o.name, - })); - return JSON.stringify(summary); - }; -} - -function createDescribeObjectHandler(ctx: DataToolContext): ToolHandler { - return async (args) => { - const { objectName } = args as { objectName: string }; - const objectDef = await ctx.metadataService.getObject(objectName); - if (!objectDef) { - return JSON.stringify({ error: `Object "${objectName}" not found` }); - } - - const def = objectDef as ObjectDef; - const fields = def.fields ?? {}; - const fieldSummary: Record> = {}; - for (const [key, f] of Object.entries(fields)) { - fieldSummary[key] = { - type: f.type, - label: f.label ?? key, - required: f.required ?? false, - ...(f.reference ? { reference: f.reference } : {}), - ...(f.options ? { options: f.options } : {}), - }; - } - - return JSON.stringify({ - name: def.name, - label: def.label ?? def.name, - fields: fieldSummary, - }); - }; -} - function createQueryRecordsHandler(ctx: DataToolContext): ToolHandler { return async (args) => { const { @@ -366,15 +273,13 @@ function createAggregateDataHandler(ctx: DataToolContext): ToolHandler { /** * Register all built-in data tools on the given {@link ToolRegistry}. * - * Typically called from the `ai:ready` hook after both the data engine - * and metadata service are available. + * Typically called from the `ai:ready` hook after the data engine is available. * * @example * ```ts * ctx.hook('ai:ready', async (aiService) => { * const dataEngine = ctx.getService('data'); - * const metadataService = ctx.getService('metadata'); - * registerDataTools(aiService.toolRegistry, { dataEngine, metadataService }); + * registerDataTools(aiService.toolRegistry, { dataEngine }); * }); * ``` */ @@ -382,8 +287,6 @@ export function registerDataTools( registry: ToolRegistry, context: DataToolContext, ): void { - registry.register(LIST_OBJECTS_TOOL, createListObjectsHandler(context)); - registry.register(DESCRIBE_OBJECT_TOOL, createDescribeObjectHandler(context)); registry.register(QUERY_RECORDS_TOOL, createQueryRecordsHandler(context)); registry.register(GET_RECORD_TOOL, createGetRecordHandler(context)); registry.register(AGGREGATE_DATA_TOOL, createAggregateDataHandler(context)); diff --git a/packages/services/service-ai/src/tools/describe-metadata-object.tool.ts b/packages/services/service-ai/src/tools/describe-metadata-object.tool.ts deleted file mode 100644 index 8b5c6f5247..0000000000 --- a/packages/services/service-ai/src/tools/describe-metadata-object.tool.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import { defineTool } from '@objectstack/spec/ai'; - -/** - * describe_metadata_object — AI Tool Metadata - * - * Returns the full metadata schema of a data object including all - * fields, types, relationships, and configuration. Uses a unique name - * (`describe_metadata_object`) to avoid collision with the data-tools - * `describe_object` tool. - */ -export const describeMetadataObjectTool = defineTool({ - name: 'describe_metadata_object', - label: 'Describe Metadata Object', - description: - 'Returns the full metadata schema details of a data object, including all fields, types, relationships, and configuration. ' + - 'Use this when the user wants to inspect or understand the metadata structure of a specific table or entity.', - category: 'data', - builtIn: true, - parameters: { - type: 'object', - properties: { - objectName: { - type: 'string', - description: 'Object machine name to describe (snake_case)', - }, - }, - required: ['objectName'], - additionalProperties: false, - }, -}); diff --git a/packages/services/service-ai/src/tools/describe-object.tool.ts b/packages/services/service-ai/src/tools/describe-object.tool.ts new file mode 100644 index 0000000000..805962317c --- /dev/null +++ b/packages/services/service-ai/src/tools/describe-object.tool.ts @@ -0,0 +1,31 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { defineTool } from '@objectstack/spec/ai'; + +/** + * describe_object — AI Tool Metadata + * + * Returns the full schema of a data object including all fields, types, + * relationships, and configuration. This is the single, unified tool for + * describing objects — used by both data_chat and metadata_assistant agents. + */ +export const describeObjectTool = defineTool({ + name: 'describe_object', + label: 'Describe Object', + description: + 'Returns the full schema details of a data object, including all fields, types, relationships, and configuration. ' + + 'Use this to understand the structure of a table before querying or modifying it.', + category: 'data', + builtIn: true, + parameters: { + type: 'object', + properties: { + objectName: { + type: 'string', + description: 'Object machine name to describe (snake_case)', + }, + }, + required: ['objectName'], + additionalProperties: false, + }, +}); diff --git a/packages/services/service-ai/src/tools/index.ts b/packages/services/service-ai/src/tools/index.ts index 149e76c80c..523561d041 100644 --- a/packages/services/service-ai/src/tools/index.ts +++ b/packages/services/service-ai/src/tools/index.ts @@ -14,5 +14,5 @@ export { createObjectTool } from './create-object.tool.js'; export { addFieldTool } from './add-field.tool.js'; export { modifyFieldTool } from './modify-field.tool.js'; export { deleteFieldTool } from './delete-field.tool.js'; -export { listMetadataObjectsTool } from './list-metadata-objects.tool.js'; -export { describeMetadataObjectTool } from './describe-metadata-object.tool.js'; +export { listObjectsTool } from './list-objects.tool.js'; +export { describeObjectTool } from './describe-object.tool.js'; diff --git a/packages/services/service-ai/src/tools/list-metadata-objects.tool.ts b/packages/services/service-ai/src/tools/list-objects.tool.ts similarity index 56% rename from packages/services/service-ai/src/tools/list-metadata-objects.tool.ts rename to packages/services/service-ai/src/tools/list-objects.tool.ts index 2b37cdd85f..f465c50a27 100644 --- a/packages/services/service-ai/src/tools/list-metadata-objects.tool.ts +++ b/packages/services/service-ai/src/tools/list-objects.tool.ts @@ -3,18 +3,18 @@ import { defineTool } from '@objectstack/spec/ai'; /** - * list_metadata_objects — AI Tool Metadata + * list_objects — AI Tool Metadata * - * Lists all registered metadata objects (tables) with optional filtering. - * Uses a unique name (`list_metadata_objects`) to avoid collision with - * the data-tools `list_objects` tool. + * Lists all registered data objects (tables) with optional filtering + * and field summaries. This is the single, unified tool for listing + * objects — used by both data_chat and metadata_assistant agents. */ -export const listMetadataObjectsTool = defineTool({ - name: 'list_metadata_objects', - label: 'List Metadata Objects', +export const listObjectsTool = defineTool({ + name: 'list_objects', + label: 'List Objects', description: - 'Lists all registered metadata objects (tables) in the current environment. ' + - 'Use this when the user wants to see what tables, entities, or data models are defined in metadata.', + 'Lists all registered data objects (tables) in the current environment. ' + + 'Use this when the user wants to see what tables, entities, or data models are available.', category: 'data', builtIn: true, parameters: { diff --git a/packages/services/service-ai/src/tools/metadata-tools.ts b/packages/services/service-ai/src/tools/metadata-tools.ts index 6a582ef933..e9b5eb18ce 100644 --- a/packages/services/service-ai/src/tools/metadata-tools.ts +++ b/packages/services/service-ai/src/tools/metadata-tools.ts @@ -13,15 +13,15 @@ export { createObjectTool } from './create-object.tool.js'; export { addFieldTool } from './add-field.tool.js'; export { modifyFieldTool } from './modify-field.tool.js'; export { deleteFieldTool } from './delete-field.tool.js'; -export { listMetadataObjectsTool } from './list-metadata-objects.tool.js'; -export { describeMetadataObjectTool } from './describe-metadata-object.tool.js'; +export { listObjectsTool } from './list-objects.tool.js'; +export { describeObjectTool } from './describe-object.tool.js'; import { createObjectTool } from './create-object.tool.js'; import { addFieldTool } from './add-field.tool.js'; import { modifyFieldTool } from './modify-field.tool.js'; import { deleteFieldTool } from './delete-field.tool.js'; -import { listMetadataObjectsTool } from './list-metadata-objects.tool.js'; -import { describeMetadataObjectTool } from './describe-metadata-object.tool.js'; +import { listObjectsTool } from './list-objects.tool.js'; +import { describeObjectTool } from './describe-object.tool.js'; /** All built-in metadata management tool definitions (Tool metadata). */ export const METADATA_TOOL_DEFINITIONS: Tool[] = [ @@ -29,8 +29,8 @@ export const METADATA_TOOL_DEFINITIONS: Tool[] = [ addFieldTool, modifyFieldTool, deleteFieldTool, - listMetadataObjectsTool, - describeMetadataObjectTool, + listObjectsTool, + describeObjectTool, ]; // --------------------------------------------------------------------------- @@ -425,6 +425,6 @@ export function registerMetadataTools( registry.register(addFieldTool, createAddFieldHandler(context)); registry.register(modifyFieldTool, createModifyFieldHandler(context)); registry.register(deleteFieldTool, createDeleteFieldHandler(context)); - registry.register(listMetadataObjectsTool, createListObjectsHandler(context)); - registry.register(describeMetadataObjectTool, createDescribeObjectHandler(context)); + registry.register(listObjectsTool, createListObjectsHandler(context)); + registry.register(describeObjectTool, createDescribeObjectHandler(context)); }