@objectstack/service-ai 4.0.2 → 4.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +16 -0
- package/README.md +293 -0
- package/dist/index.cjs +1871 -1648
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -27
- package/dist/index.d.ts +32 -27
- package/dist/index.js +2137 -1908
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/ai-service.test.ts +30 -1
- package/src/__tests__/auth-and-toolcalling.test.ts +51 -1
- package/src/__tests__/chatbot-features.test.ts +172 -24
- package/src/__tests__/metadata-tools.test.ts +35 -29
- package/src/__tests__/tool-routes.test.ts +191 -0
- package/src/__tests__/vercel-stream-encoder.test.ts +47 -0
- package/src/agents/metadata-assistant-agent.ts +4 -4
- package/src/ai-service.ts +7 -0
- package/src/index.ts +3 -2
- package/src/plugin.ts +85 -35
- package/src/routes/agent-routes.ts +43 -10
- package/src/routes/ai-routes.ts +3 -67
- package/src/routes/index.ts +1 -0
- package/src/routes/message-utils.ts +90 -0
- package/src/routes/tool-routes.ts +142 -0
- package/src/stream/vercel-stream-encoder.ts +24 -0
- package/src/tools/data-tools.ts +4 -101
- package/src/tools/describe-object.tool.ts +31 -0
- package/src/tools/index.ts +2 -2
- package/src/tools/{list-metadata-objects.tool.ts → list-objects.tool.ts} +9 -9
- package/src/tools/metadata-tools.ts +8 -8
- package/vitest.config.ts +23 -0
- package/src/tools/describe-metadata-object.tool.ts +0 -32
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
import type { ModelMessage } from '@objectstack/spec/contracts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Normalize a Vercel AI SDK v6 message (which may use `parts` instead of
|
|
7
|
+
* `content`) into a plain `{ role, content }` ModelMessage.
|
|
8
|
+
*
|
|
9
|
+
* Shared between the general chat routes and agent chat routes.
|
|
10
|
+
*/
|
|
11
|
+
export function normalizeMessage(raw: Record<string, unknown>): ModelMessage {
|
|
12
|
+
const role = raw.role as string;
|
|
13
|
+
|
|
14
|
+
// If content is already a string, use it directly
|
|
15
|
+
if (typeof raw.content === 'string') {
|
|
16
|
+
return { role, content: raw.content } as unknown as ModelMessage;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// If content is an array (multi-part), pass through
|
|
20
|
+
if (Array.isArray(raw.content)) {
|
|
21
|
+
return { role, content: raw.content } as unknown as ModelMessage;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Vercel AI SDK v6: extract text from `parts` array
|
|
25
|
+
if (Array.isArray(raw.parts)) {
|
|
26
|
+
const textParts = (raw.parts as Array<Record<string, unknown>>)
|
|
27
|
+
.filter(p => p.type === 'text' && typeof p.text === 'string')
|
|
28
|
+
.map(p => p.text as string);
|
|
29
|
+
if (textParts.length > 0) {
|
|
30
|
+
return { role, content: textParts.join('') } as unknown as ModelMessage;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Fallback: empty content (e.g. tool-only assistant messages)
|
|
35
|
+
return { role, content: '' } as unknown as ModelMessage;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate message content/parts format (role-agnostic).
|
|
40
|
+
*
|
|
41
|
+
* Returns `null` when the content shape is valid, or an error string
|
|
42
|
+
* describing the first violation found.
|
|
43
|
+
*
|
|
44
|
+
* Accepts:
|
|
45
|
+
* - Simple string `content` (legacy)
|
|
46
|
+
* - Array `content` (e.g. `[{ type: 'text', text: '...' }]`)
|
|
47
|
+
* - Vercel AI SDK v6 `parts` format (content may be absent/null)
|
|
48
|
+
* - Null/undefined `content` for assistant messages (when `allowEmpty` is true)
|
|
49
|
+
*/
|
|
50
|
+
export function validateMessageContent(
|
|
51
|
+
msg: Record<string, unknown>,
|
|
52
|
+
opts?: { allowEmptyContent?: boolean },
|
|
53
|
+
): string | null {
|
|
54
|
+
const content = msg.content;
|
|
55
|
+
|
|
56
|
+
// Vercel AI SDK v6 sends `parts` instead of (or alongside) `content`.
|
|
57
|
+
// Accept any message that carries a `parts` array, even when `content` is absent.
|
|
58
|
+
if (Array.isArray(msg.parts)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// content is a plain string — OK
|
|
63
|
+
if (typeof content === 'string') {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// content is an array of typed parts (legacy multi-part format)
|
|
68
|
+
if (Array.isArray(content)) {
|
|
69
|
+
for (const part of content as unknown[]) {
|
|
70
|
+
if (typeof part !== 'object' || part === null) {
|
|
71
|
+
return 'message.content array elements must be non-null objects';
|
|
72
|
+
}
|
|
73
|
+
const partObj = part as Record<string, unknown>;
|
|
74
|
+
if (typeof partObj.type !== 'string') {
|
|
75
|
+
return 'each message.content array element must have a string "type" property';
|
|
76
|
+
}
|
|
77
|
+
if (partObj.type === 'text' && typeof partObj.text !== 'string') {
|
|
78
|
+
return 'message.content elements with type "text" must have a string "text" property';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Allow empty content for certain roles (e.g. assistant tool-call messages)
|
|
85
|
+
if ((content === null || content === undefined) && opts?.allowEmptyContent) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return 'message.content must be a string, an array, or include parts';
|
|
90
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
import type { Logger } from '@objectstack/spec/contracts';
|
|
4
|
+
import type { AIService } from '../ai-service.js';
|
|
5
|
+
import type { RouteDefinition } from './ai-routes.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extract string value from tool execution result output.
|
|
9
|
+
*/
|
|
10
|
+
function extractOutputValue(output: any): string {
|
|
11
|
+
if (!output) return '';
|
|
12
|
+
if (typeof output === 'string') return output;
|
|
13
|
+
if (typeof output === 'object' && 'value' in output) {
|
|
14
|
+
return String(output.value ?? '');
|
|
15
|
+
}
|
|
16
|
+
return JSON.stringify(output);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Build tool-specific REST routes.
|
|
21
|
+
*
|
|
22
|
+
* | Method | Path | Description |
|
|
23
|
+
* |:---|:---|:---|
|
|
24
|
+
* | GET | /api/v1/ai/tools | List all registered tools |
|
|
25
|
+
* | POST | /api/v1/ai/tools/:toolName/execute | Execute a tool with parameters |
|
|
26
|
+
*/
|
|
27
|
+
export function buildToolRoutes(
|
|
28
|
+
aiService: AIService,
|
|
29
|
+
logger: Logger,
|
|
30
|
+
): RouteDefinition[] {
|
|
31
|
+
return [
|
|
32
|
+
// ── List registered tools ──────────────────────────────────────
|
|
33
|
+
{
|
|
34
|
+
method: 'GET',
|
|
35
|
+
path: '/api/v1/ai/tools',
|
|
36
|
+
description: 'List all registered AI tools',
|
|
37
|
+
auth: true,
|
|
38
|
+
permissions: ['ai:tools'],
|
|
39
|
+
handler: async () => {
|
|
40
|
+
try {
|
|
41
|
+
const tools = aiService.toolRegistry.getAll();
|
|
42
|
+
return {
|
|
43
|
+
status: 200,
|
|
44
|
+
body: {
|
|
45
|
+
tools: tools.map(t => ({
|
|
46
|
+
name: t.name,
|
|
47
|
+
description: t.description,
|
|
48
|
+
category: (t as any).category,
|
|
49
|
+
}))
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
} catch (err) {
|
|
53
|
+
logger.error(
|
|
54
|
+
'[AI Route] /tools list error',
|
|
55
|
+
err instanceof Error ? err : undefined,
|
|
56
|
+
);
|
|
57
|
+
return { status: 500, body: { error: 'Internal AI service error' } };
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// ── Execute a tool ──────────────────────────────────────────────
|
|
63
|
+
//
|
|
64
|
+
// Executes a tool with the provided parameters.
|
|
65
|
+
// This is intended for testing/playground use.
|
|
66
|
+
//
|
|
67
|
+
{
|
|
68
|
+
method: 'POST',
|
|
69
|
+
path: '/api/v1/ai/tools/:toolName/execute',
|
|
70
|
+
description: 'Execute a tool with parameters (playground/testing)',
|
|
71
|
+
auth: true,
|
|
72
|
+
permissions: ['ai:tools', 'ai:execute'],
|
|
73
|
+
handler: async (req) => {
|
|
74
|
+
const toolName = req.params?.toolName;
|
|
75
|
+
if (!toolName) {
|
|
76
|
+
return { status: 400, body: { error: 'toolName parameter is required' } };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Parse request body
|
|
80
|
+
const body = (req.body ?? {}) as Record<string, unknown>;
|
|
81
|
+
const { parameters } = body as {
|
|
82
|
+
parameters?: Record<string, unknown>;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (!parameters || typeof parameters !== 'object') {
|
|
86
|
+
return { status: 400, body: { error: 'parameters object is required' } };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
// Check if tool exists
|
|
91
|
+
if (!aiService.toolRegistry.has(toolName)) {
|
|
92
|
+
return { status: 404, body: { error: `Tool "${toolName}" not found` } };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Execute the tool using ToolRegistry's execute method
|
|
96
|
+
const startTime = Date.now();
|
|
97
|
+
|
|
98
|
+
const toolCallPart = {
|
|
99
|
+
type: 'tool-call' as const,
|
|
100
|
+
toolCallId: `playground-${Date.now()}`,
|
|
101
|
+
toolName,
|
|
102
|
+
input: parameters,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const result = await aiService.toolRegistry.execute(toolCallPart);
|
|
106
|
+
const duration = Date.now() - startTime;
|
|
107
|
+
|
|
108
|
+
// Check if execution resulted in an error
|
|
109
|
+
if (result.isError) {
|
|
110
|
+
const errorMsg = extractOutputValue(result.output);
|
|
111
|
+
logger.error(
|
|
112
|
+
`[AI Route] Tool execution error: ${toolName}`,
|
|
113
|
+
new Error(errorMsg),
|
|
114
|
+
);
|
|
115
|
+
return {
|
|
116
|
+
status: 500,
|
|
117
|
+
body: {
|
|
118
|
+
error: errorMsg,
|
|
119
|
+
duration,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
status: 200,
|
|
126
|
+
body: {
|
|
127
|
+
result: extractOutputValue(result.output),
|
|
128
|
+
duration,
|
|
129
|
+
toolName,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
} catch (err) {
|
|
133
|
+
logger.error(
|
|
134
|
+
'[AI Route] /tools/:toolName/execute error',
|
|
135
|
+
err instanceof Error ? err : undefined,
|
|
136
|
+
);
|
|
137
|
+
return { status: 500, body: { error: 'Internal AI service error' } };
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
}
|
|
@@ -23,6 +23,14 @@ function sse(data: object): string {
|
|
|
23
23
|
return `data: ${JSON.stringify(data)}\n\n`;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Encode data using Vercel AI SDK Data Stream Protocol prefixes.
|
|
28
|
+
* @see https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol
|
|
29
|
+
*/
|
|
30
|
+
function dataStreamLine(prefix: string, data: object): string {
|
|
31
|
+
return `${prefix}:${JSON.stringify(data)}\n`;
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
// ── Public API ──────────────────────────────────────────────────────
|
|
27
35
|
|
|
28
36
|
/**
|
|
@@ -71,8 +79,24 @@ export function encodeStreamPart(part: TextStreamPart<ToolSet>): string {
|
|
|
71
79
|
errorText: String(part.error),
|
|
72
80
|
});
|
|
73
81
|
|
|
82
|
+
// Handle reasoning/thinking streams (DeepSeek R1, o1-style models)
|
|
83
|
+
// Use 'g:' prefix for reasoning content per Vercel AI SDK protocol
|
|
84
|
+
case 'reasoning-start':
|
|
85
|
+
return dataStreamLine('g', { text: '' });
|
|
86
|
+
|
|
87
|
+
case 'reasoning-delta':
|
|
88
|
+
return dataStreamLine('g', { text: part.text });
|
|
89
|
+
|
|
90
|
+
case 'reasoning-end':
|
|
91
|
+
return ''; // No specific end marker needed for reasoning
|
|
92
|
+
|
|
74
93
|
// finish-step and finish are handled by the generator, not here
|
|
75
94
|
default:
|
|
95
|
+
// Pass through any unknown event types that might be custom
|
|
96
|
+
// (e.g., step-start, step-finish from custom providers)
|
|
97
|
+
if ((part as any).type?.startsWith('step-')) {
|
|
98
|
+
return sse(part as any);
|
|
99
|
+
}
|
|
76
100
|
return '';
|
|
77
101
|
}
|
|
78
102
|
}
|
package/src/tools/data-tools.ts
CHANGED
|
@@ -1,30 +1,9 @@
|
|
|
1
1
|
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
2
|
|
|
3
|
-
import type { AIToolDefinition, IDataEngine
|
|
3
|
+
import type { AIToolDefinition, IDataEngine } from '@objectstack/spec/contracts';
|
|
4
4
|
import type { ToolHandler } from './tool-registry.js';
|
|
5
5
|
import type { ToolRegistry } from './tool-registry.js';
|
|
6
6
|
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// Internal type aliases for metadata payloads (returned as `unknown` from
|
|
9
|
-
// IMetadataService — we cast to these lightweight shapes for field access).
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
|
|
12
|
-
/** Minimal shape of an object definition as returned by IMetadataService. */
|
|
13
|
-
interface ObjectDef {
|
|
14
|
-
name: string;
|
|
15
|
-
label?: string;
|
|
16
|
-
fields?: Record<string, FieldDef>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Minimal shape of a field definition inside an object. */
|
|
20
|
-
interface FieldDef {
|
|
21
|
-
type?: string;
|
|
22
|
-
label?: string;
|
|
23
|
-
required?: boolean;
|
|
24
|
-
reference?: string;
|
|
25
|
-
options?: unknown;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
7
|
// ---------------------------------------------------------------------------
|
|
29
8
|
// Data context — injected once at registration time
|
|
30
9
|
// ---------------------------------------------------------------------------
|
|
@@ -38,8 +17,6 @@ interface FieldDef {
|
|
|
38
17
|
export interface DataToolContext {
|
|
39
18
|
/** ObjectQL data engine for record-level operations. */
|
|
40
19
|
dataEngine: IDataEngine;
|
|
41
|
-
/** Metadata service for schema/object introspection. */
|
|
42
|
-
metadataService: IMetadataService;
|
|
43
20
|
}
|
|
44
21
|
|
|
45
22
|
// ---------------------------------------------------------------------------
|
|
@@ -52,34 +29,6 @@ const MAX_QUERY_LIMIT = 200;
|
|
|
52
29
|
/** Default record limit when not specified. */
|
|
53
30
|
const DEFAULT_QUERY_LIMIT = 20;
|
|
54
31
|
|
|
55
|
-
export const LIST_OBJECTS_TOOL: AIToolDefinition = {
|
|
56
|
-
name: 'list_objects',
|
|
57
|
-
description: 'List all available data objects (tables) in the system. Returns object names and labels.',
|
|
58
|
-
parameters: {
|
|
59
|
-
type: 'object',
|
|
60
|
-
properties: {},
|
|
61
|
-
additionalProperties: false,
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export const DESCRIBE_OBJECT_TOOL: AIToolDefinition = {
|
|
66
|
-
name: 'describe_object',
|
|
67
|
-
description:
|
|
68
|
-
'Get the schema (fields, types, labels) of a specific data object. ' +
|
|
69
|
-
'Use this to understand the structure of a table before querying it.',
|
|
70
|
-
parameters: {
|
|
71
|
-
type: 'object',
|
|
72
|
-
properties: {
|
|
73
|
-
objectName: {
|
|
74
|
-
type: 'string',
|
|
75
|
-
description: 'The snake_case name of the object to describe',
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
required: ['objectName'],
|
|
79
|
-
additionalProperties: false,
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
|
|
83
32
|
export const QUERY_RECORDS_TOOL: AIToolDefinition = {
|
|
84
33
|
name: 'query_records',
|
|
85
34
|
description:
|
|
@@ -203,10 +152,8 @@ export const AGGREGATE_DATA_TOOL: AIToolDefinition = {
|
|
|
203
152
|
},
|
|
204
153
|
};
|
|
205
154
|
|
|
206
|
-
/** All built-in data
|
|
155
|
+
/** All built-in data tools definitions. */
|
|
207
156
|
export const DATA_TOOL_DEFINITIONS: AIToolDefinition[] = [
|
|
208
|
-
LIST_OBJECTS_TOOL,
|
|
209
|
-
DESCRIBE_OBJECT_TOOL,
|
|
210
157
|
QUERY_RECORDS_TOOL,
|
|
211
158
|
GET_RECORD_TOOL,
|
|
212
159
|
AGGREGATE_DATA_TOOL,
|
|
@@ -216,46 +163,6 @@ export const DATA_TOOL_DEFINITIONS: AIToolDefinition[] = [
|
|
|
216
163
|
// Handler Factories
|
|
217
164
|
// ---------------------------------------------------------------------------
|
|
218
165
|
|
|
219
|
-
function createListObjectsHandler(ctx: DataToolContext): ToolHandler {
|
|
220
|
-
return async () => {
|
|
221
|
-
const objects = await ctx.metadataService.listObjects();
|
|
222
|
-
const summary = (objects as ObjectDef[]).map(o => ({
|
|
223
|
-
name: o.name,
|
|
224
|
-
label: o.label ?? o.name,
|
|
225
|
-
}));
|
|
226
|
-
return JSON.stringify(summary);
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function createDescribeObjectHandler(ctx: DataToolContext): ToolHandler {
|
|
231
|
-
return async (args) => {
|
|
232
|
-
const { objectName } = args as { objectName: string };
|
|
233
|
-
const objectDef = await ctx.metadataService.getObject(objectName);
|
|
234
|
-
if (!objectDef) {
|
|
235
|
-
return JSON.stringify({ error: `Object "${objectName}" not found` });
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const def = objectDef as ObjectDef;
|
|
239
|
-
const fields = def.fields ?? {};
|
|
240
|
-
const fieldSummary: Record<string, Record<string, unknown>> = {};
|
|
241
|
-
for (const [key, f] of Object.entries(fields)) {
|
|
242
|
-
fieldSummary[key] = {
|
|
243
|
-
type: f.type,
|
|
244
|
-
label: f.label ?? key,
|
|
245
|
-
required: f.required ?? false,
|
|
246
|
-
...(f.reference ? { reference: f.reference } : {}),
|
|
247
|
-
...(f.options ? { options: f.options } : {}),
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return JSON.stringify({
|
|
252
|
-
name: def.name,
|
|
253
|
-
label: def.label ?? def.name,
|
|
254
|
-
fields: fieldSummary,
|
|
255
|
-
});
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
166
|
function createQueryRecordsHandler(ctx: DataToolContext): ToolHandler {
|
|
260
167
|
return async (args) => {
|
|
261
168
|
const {
|
|
@@ -366,15 +273,13 @@ function createAggregateDataHandler(ctx: DataToolContext): ToolHandler {
|
|
|
366
273
|
/**
|
|
367
274
|
* Register all built-in data tools on the given {@link ToolRegistry}.
|
|
368
275
|
*
|
|
369
|
-
* Typically called from the `ai:ready` hook after
|
|
370
|
-
* and metadata service are available.
|
|
276
|
+
* Typically called from the `ai:ready` hook after the data engine is available.
|
|
371
277
|
*
|
|
372
278
|
* @example
|
|
373
279
|
* ```ts
|
|
374
280
|
* ctx.hook('ai:ready', async (aiService) => {
|
|
375
281
|
* const dataEngine = ctx.getService<IDataEngine>('data');
|
|
376
|
-
*
|
|
377
|
-
* registerDataTools(aiService.toolRegistry, { dataEngine, metadataService });
|
|
282
|
+
* registerDataTools(aiService.toolRegistry, { dataEngine });
|
|
378
283
|
* });
|
|
379
284
|
* ```
|
|
380
285
|
*/
|
|
@@ -382,8 +287,6 @@ export function registerDataTools(
|
|
|
382
287
|
registry: ToolRegistry,
|
|
383
288
|
context: DataToolContext,
|
|
384
289
|
): void {
|
|
385
|
-
registry.register(LIST_OBJECTS_TOOL, createListObjectsHandler(context));
|
|
386
|
-
registry.register(DESCRIBE_OBJECT_TOOL, createDescribeObjectHandler(context));
|
|
387
290
|
registry.register(QUERY_RECORDS_TOOL, createQueryRecordsHandler(context));
|
|
388
291
|
registry.register(GET_RECORD_TOOL, createGetRecordHandler(context));
|
|
389
292
|
registry.register(AGGREGATE_DATA_TOOL, createAggregateDataHandler(context));
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
import { defineTool } from '@objectstack/spec/ai';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* describe_object — AI Tool Metadata
|
|
7
|
+
*
|
|
8
|
+
* Returns the full schema of a data object including all fields, types,
|
|
9
|
+
* relationships, and configuration. This is the single, unified tool for
|
|
10
|
+
* describing objects — used by both data_chat and metadata_assistant agents.
|
|
11
|
+
*/
|
|
12
|
+
export const describeObjectTool = defineTool({
|
|
13
|
+
name: 'describe_object',
|
|
14
|
+
label: 'Describe Object',
|
|
15
|
+
description:
|
|
16
|
+
'Returns the full schema details of a data object, including all fields, types, relationships, and configuration. ' +
|
|
17
|
+
'Use this to understand the structure of a table before querying or modifying it.',
|
|
18
|
+
category: 'data',
|
|
19
|
+
builtIn: true,
|
|
20
|
+
parameters: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
objectName: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Object machine name to describe (snake_case)',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ['objectName'],
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
},
|
|
31
|
+
});
|
package/src/tools/index.ts
CHANGED
|
@@ -14,5 +14,5 @@ export { createObjectTool } from './create-object.tool.js';
|
|
|
14
14
|
export { addFieldTool } from './add-field.tool.js';
|
|
15
15
|
export { modifyFieldTool } from './modify-field.tool.js';
|
|
16
16
|
export { deleteFieldTool } from './delete-field.tool.js';
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
17
|
+
export { listObjectsTool } from './list-objects.tool.js';
|
|
18
|
+
export { describeObjectTool } from './describe-object.tool.js';
|
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
import { defineTool } from '@objectstack/spec/ai';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* list_objects — AI Tool Metadata
|
|
7
7
|
*
|
|
8
|
-
* Lists all registered
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Lists all registered data objects (tables) with optional filtering
|
|
9
|
+
* and field summaries. This is the single, unified tool for listing
|
|
10
|
+
* objects — used by both data_chat and metadata_assistant agents.
|
|
11
11
|
*/
|
|
12
|
-
export const
|
|
13
|
-
name: '
|
|
14
|
-
label: 'List
|
|
12
|
+
export const listObjectsTool = defineTool({
|
|
13
|
+
name: 'list_objects',
|
|
14
|
+
label: 'List Objects',
|
|
15
15
|
description:
|
|
16
|
-
'Lists all registered
|
|
17
|
-
'Use this when the user wants to see what tables, entities, or data models are
|
|
16
|
+
'Lists all registered data objects (tables) in the current environment. ' +
|
|
17
|
+
'Use this when the user wants to see what tables, entities, or data models are available.',
|
|
18
18
|
category: 'data',
|
|
19
19
|
builtIn: true,
|
|
20
20
|
parameters: {
|
|
@@ -13,15 +13,15 @@ export { createObjectTool } from './create-object.tool.js';
|
|
|
13
13
|
export { addFieldTool } from './add-field.tool.js';
|
|
14
14
|
export { modifyFieldTool } from './modify-field.tool.js';
|
|
15
15
|
export { deleteFieldTool } from './delete-field.tool.js';
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
16
|
+
export { listObjectsTool } from './list-objects.tool.js';
|
|
17
|
+
export { describeObjectTool } from './describe-object.tool.js';
|
|
18
18
|
|
|
19
19
|
import { createObjectTool } from './create-object.tool.js';
|
|
20
20
|
import { addFieldTool } from './add-field.tool.js';
|
|
21
21
|
import { modifyFieldTool } from './modify-field.tool.js';
|
|
22
22
|
import { deleteFieldTool } from './delete-field.tool.js';
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
23
|
+
import { listObjectsTool } from './list-objects.tool.js';
|
|
24
|
+
import { describeObjectTool } from './describe-object.tool.js';
|
|
25
25
|
|
|
26
26
|
/** All built-in metadata management tool definitions (Tool metadata). */
|
|
27
27
|
export const METADATA_TOOL_DEFINITIONS: Tool[] = [
|
|
@@ -29,8 +29,8 @@ export const METADATA_TOOL_DEFINITIONS: Tool[] = [
|
|
|
29
29
|
addFieldTool,
|
|
30
30
|
modifyFieldTool,
|
|
31
31
|
deleteFieldTool,
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
listObjectsTool,
|
|
33
|
+
describeObjectTool,
|
|
34
34
|
];
|
|
35
35
|
|
|
36
36
|
// ---------------------------------------------------------------------------
|
|
@@ -425,6 +425,6 @@ export function registerMetadataTools(
|
|
|
425
425
|
registry.register(addFieldTool, createAddFieldHandler(context));
|
|
426
426
|
registry.register(modifyFieldTool, createModifyFieldHandler(context));
|
|
427
427
|
registry.register(deleteFieldTool, createDeleteFieldHandler(context));
|
|
428
|
-
registry.register(
|
|
429
|
-
registry.register(
|
|
428
|
+
registry.register(listObjectsTool, createListObjectsHandler(context));
|
|
429
|
+
registry.register(describeObjectTool, createDescribeObjectHandler(context));
|
|
430
430
|
}
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
import { defineConfig } from 'vitest/config';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
test: {
|
|
8
|
+
globals: true,
|
|
9
|
+
environment: 'node',
|
|
10
|
+
},
|
|
11
|
+
resolve: {
|
|
12
|
+
alias: {
|
|
13
|
+
'@objectstack/core': path.resolve(__dirname, '../../core/src/index.ts'),
|
|
14
|
+
'@objectstack/spec/ai': path.resolve(__dirname, '../../spec/src/ai/index.ts'),
|
|
15
|
+
'@objectstack/spec/api': path.resolve(__dirname, '../../spec/src/api/index.ts'),
|
|
16
|
+
'@objectstack/spec/contracts': path.resolve(__dirname, '../../spec/src/contracts/index.ts'),
|
|
17
|
+
'@objectstack/spec/data': path.resolve(__dirname, '../../spec/src/data/index.ts'),
|
|
18
|
+
'@objectstack/spec/kernel': path.resolve(__dirname, '../../spec/src/kernel/index.ts'),
|
|
19
|
+
'@objectstack/spec/system': path.resolve(__dirname, '../../spec/src/system/index.ts'),
|
|
20
|
+
'@objectstack/spec': path.resolve(__dirname, '../../spec/src/index.ts'),
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -1,32 +0,0 @@
|
|
|
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
|
-
});
|