@objectstack/service-ai 4.0.4 → 4.0.5
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/dist/index.cjs +1176 -135
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1225 -430
- package/dist/index.d.ts +1225 -430
- package/dist/index.js +1160 -128
- package/dist/index.js.map +1 -1
- package/package.json +35 -8
- package/.turbo/turbo-build.log +0 -22
- package/CHANGELOG.md +0 -61
- package/src/__tests__/ai-service.test.ts +0 -981
- package/src/__tests__/auth-and-toolcalling.test.ts +0 -677
- package/src/__tests__/chatbot-features.test.ts +0 -1116
- package/src/__tests__/metadata-tools.test.ts +0 -970
- package/src/__tests__/objectql-conversation-service.test.ts +0 -382
- package/src/__tests__/tool-routes.test.ts +0 -191
- package/src/__tests__/vercel-stream-encoder.test.ts +0 -310
- package/src/adapters/index.ts +0 -6
- package/src/adapters/memory-adapter.ts +0 -72
- package/src/adapters/types.ts +0 -3
- package/src/adapters/vercel-adapter.ts +0 -148
- package/src/agent-runtime.ts +0 -154
- package/src/agents/data-chat-agent.ts +0 -79
- package/src/agents/index.ts +0 -4
- package/src/agents/metadata-assistant-agent.ts +0 -87
- package/src/ai-service.ts +0 -364
- package/src/conversation/in-memory-conversation-service.ts +0 -103
- package/src/conversation/index.ts +0 -4
- package/src/conversation/objectql-conversation-service.ts +0 -301
- package/src/index.ts +0 -60
- package/src/objects/ai-conversation.object.ts +0 -86
- package/src/objects/ai-message.object.ts +0 -86
- package/src/objects/index.ts +0 -10
- package/src/plugin.ts +0 -391
- package/src/routes/agent-routes.ts +0 -190
- package/src/routes/ai-routes.ts +0 -439
- package/src/routes/index.ts +0 -5
- package/src/routes/message-utils.ts +0 -90
- package/src/routes/tool-routes.ts +0 -142
- package/src/stream/index.ts +0 -3
- package/src/stream/vercel-stream-encoder.ts +0 -153
- package/src/tools/add-field.tool.ts +0 -70
- package/src/tools/create-object.tool.ts +0 -66
- package/src/tools/data-tools.ts +0 -293
- package/src/tools/delete-field.tool.ts +0 -38
- package/src/tools/describe-object.tool.ts +0 -31
- package/src/tools/index.ts +0 -18
- package/src/tools/list-objects.tool.ts +0 -34
- package/src/tools/metadata-tools.ts +0 -430
- package/src/tools/modify-field.tool.ts +0 -44
- package/src/tools/tool-registry.ts +0 -132
- package/tsconfig.json +0 -17
- package/vitest.config.ts +0 -23
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Vercel AI SDK v6 — UI Message Stream Encoder
|
|
5
|
-
*
|
|
6
|
-
* Converts `AsyncIterable<TextStreamPart<ToolSet>>` (the internal ObjectStack
|
|
7
|
-
* streaming format) into the Vercel AI SDK v6 **UI Message Stream Protocol**.
|
|
8
|
-
*
|
|
9
|
-
* Wire format: Server-Sent Events (SSE) with JSON payloads.
|
|
10
|
-
* `data: {"type":"text-delta","id":"0","delta":"Hello"}\n\n`
|
|
11
|
-
*
|
|
12
|
-
* The client-side `DefaultChatTransport` from `ai` v6 uses
|
|
13
|
-
* `parseJsonEventStream` to parse these SSE events.
|
|
14
|
-
*
|
|
15
|
-
* @see https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import type { TextStreamPart, ToolSet } from 'ai';
|
|
19
|
-
|
|
20
|
-
// ── SSE helpers ──────────────────────────────────────────────────────
|
|
21
|
-
|
|
22
|
-
function sse(data: object): string {
|
|
23
|
-
return `data: ${JSON.stringify(data)}\n\n`;
|
|
24
|
-
}
|
|
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
|
-
|
|
34
|
-
// ── Public API ──────────────────────────────────────────────────────
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Encode a single `TextStreamPart` event into SSE-formatted UI Message
|
|
38
|
-
* Stream chunk(s).
|
|
39
|
-
*
|
|
40
|
-
* Returns an empty string for event types that have no wire-format mapping.
|
|
41
|
-
*/
|
|
42
|
-
export function encodeStreamPart(part: TextStreamPart<ToolSet>): string {
|
|
43
|
-
switch (part.type) {
|
|
44
|
-
case 'text-delta':
|
|
45
|
-
return sse({ type: 'text-delta', id: '0', delta: part.text });
|
|
46
|
-
|
|
47
|
-
case 'tool-input-start':
|
|
48
|
-
return sse({
|
|
49
|
-
type: 'tool-input-start',
|
|
50
|
-
toolCallId: part.id,
|
|
51
|
-
toolName: part.toolName,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
case 'tool-input-delta':
|
|
55
|
-
return sse({
|
|
56
|
-
type: 'tool-input-delta',
|
|
57
|
-
toolCallId: part.id,
|
|
58
|
-
inputTextDelta: part.delta,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
case 'tool-call':
|
|
62
|
-
return sse({
|
|
63
|
-
type: 'tool-input-available',
|
|
64
|
-
toolCallId: part.toolCallId,
|
|
65
|
-
toolName: part.toolName,
|
|
66
|
-
input: part.input,
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
case 'tool-result':
|
|
70
|
-
return sse({
|
|
71
|
-
type: 'tool-output-available',
|
|
72
|
-
toolCallId: part.toolCallId,
|
|
73
|
-
output: part.output,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
case 'error':
|
|
77
|
-
return sse({
|
|
78
|
-
type: 'error',
|
|
79
|
-
errorText: String(part.error),
|
|
80
|
-
});
|
|
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
|
-
|
|
93
|
-
// finish-step and finish are handled by the generator, not here
|
|
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
|
-
}
|
|
100
|
-
return '';
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Transform an `AsyncIterable<TextStreamPart>` into an `AsyncIterable<string>`
|
|
106
|
-
* where each yielded string is an SSE-formatted UI Message Stream chunk.
|
|
107
|
-
*
|
|
108
|
-
* Lifecycle order required by the client:
|
|
109
|
-
* start → start-step → text-start → text-delta* → text-end → finish-step → finish → [DONE]
|
|
110
|
-
*/
|
|
111
|
-
export async function* encodeVercelDataStream(
|
|
112
|
-
events: AsyncIterable<TextStreamPart<ToolSet>>,
|
|
113
|
-
): AsyncIterable<string> {
|
|
114
|
-
// Preamble
|
|
115
|
-
yield sse({ type: 'start' });
|
|
116
|
-
yield sse({ type: 'start-step' });
|
|
117
|
-
yield sse({ type: 'text-start', id: '0' });
|
|
118
|
-
|
|
119
|
-
let textOpen = true;
|
|
120
|
-
let finishReason = 'stop';
|
|
121
|
-
|
|
122
|
-
for await (const part of events) {
|
|
123
|
-
// Capture finish reason
|
|
124
|
-
if (part.type === 'finish') {
|
|
125
|
-
finishReason = part.finishReason ?? 'stop';
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Before finish-step/finish, close the text part first
|
|
129
|
-
if (part.type === 'finish-step' || part.type === 'finish') {
|
|
130
|
-
if (textOpen) {
|
|
131
|
-
yield sse({ type: 'text-end', id: '0' });
|
|
132
|
-
textOpen = false;
|
|
133
|
-
}
|
|
134
|
-
// Don't emit these via encodeStreamPart — we handle them in postamble
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const frame = encodeStreamPart(part);
|
|
139
|
-
if (frame) {
|
|
140
|
-
yield frame;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Close text if still open (safety)
|
|
145
|
-
if (textOpen) {
|
|
146
|
-
yield sse({ type: 'text-end', id: '0' });
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Postamble
|
|
150
|
-
yield sse({ type: 'finish-step' });
|
|
151
|
-
yield sse({ type: 'finish', finishReason });
|
|
152
|
-
yield 'data: [DONE]\n\n';
|
|
153
|
-
}
|
|
@@ -1,70 +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
|
-
* 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
|
-
});
|
|
@@ -1,66 +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
|
-
* 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
|
-
// NOTE: requiresConfirmation is intentionally false (default) because the
|
|
21
|
-
// server-side tool-call loop in AIService.chatWithTools/streamChatWithTools
|
|
22
|
-
// executes tool calls immediately without checking this flag. The flag
|
|
23
|
-
// should only be set once server-side approval gating is implemented to
|
|
24
|
-
// avoid giving users a false sense of safety.
|
|
25
|
-
parameters: {
|
|
26
|
-
type: 'object',
|
|
27
|
-
properties: {
|
|
28
|
-
name: {
|
|
29
|
-
type: 'string',
|
|
30
|
-
description: 'Machine name for the object (snake_case, e.g. project_task)',
|
|
31
|
-
},
|
|
32
|
-
label: {
|
|
33
|
-
type: 'string',
|
|
34
|
-
description: 'Human-readable display name (e.g. Project Task)',
|
|
35
|
-
},
|
|
36
|
-
fields: {
|
|
37
|
-
type: 'array',
|
|
38
|
-
description: 'Initial fields to create with the object',
|
|
39
|
-
items: {
|
|
40
|
-
type: 'object',
|
|
41
|
-
properties: {
|
|
42
|
-
name: { type: 'string', description: 'Field machine name (snake_case)' },
|
|
43
|
-
label: { type: 'string', description: 'Field display name' },
|
|
44
|
-
type: {
|
|
45
|
-
type: 'string',
|
|
46
|
-
description: 'Field data type',
|
|
47
|
-
enum: ['text', 'textarea', 'number', 'boolean', 'date', 'datetime', 'select', 'lookup', 'formula', 'autonumber'],
|
|
48
|
-
},
|
|
49
|
-
required: { type: 'boolean', description: 'Whether the field is required' },
|
|
50
|
-
},
|
|
51
|
-
required: ['name', 'type'],
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
enableFeatures: {
|
|
55
|
-
type: 'object',
|
|
56
|
-
description: 'Object capability flags',
|
|
57
|
-
properties: {
|
|
58
|
-
trackHistory: { type: 'boolean' },
|
|
59
|
-
apiEnabled: { type: 'boolean' },
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
required: ['name', 'label'],
|
|
64
|
-
additionalProperties: false,
|
|
65
|
-
},
|
|
66
|
-
});
|
package/src/tools/data-tools.ts
DELETED
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import type { AIToolDefinition, IDataEngine } from '@objectstack/spec/contracts';
|
|
4
|
-
import type { ToolHandler } from './tool-registry.js';
|
|
5
|
-
import type { ToolRegistry } from './tool-registry.js';
|
|
6
|
-
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// Data context — injected once at registration time
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Services required by the built-in data tools.
|
|
13
|
-
*
|
|
14
|
-
* These are provided by the kernel at `ai:ready` time and closed over
|
|
15
|
-
* by the handler functions so they stay framework-agnostic.
|
|
16
|
-
*/
|
|
17
|
-
export interface DataToolContext {
|
|
18
|
-
/** ObjectQL data engine for record-level operations. */
|
|
19
|
-
dataEngine: IDataEngine;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
// Tool Definitions
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
/** Maximum number of records a single query may return. */
|
|
27
|
-
const MAX_QUERY_LIMIT = 200;
|
|
28
|
-
|
|
29
|
-
/** Default record limit when not specified. */
|
|
30
|
-
const DEFAULT_QUERY_LIMIT = 20;
|
|
31
|
-
|
|
32
|
-
export const QUERY_RECORDS_TOOL: AIToolDefinition = {
|
|
33
|
-
name: 'query_records',
|
|
34
|
-
description:
|
|
35
|
-
'Query records from a data object with optional filters, field selection, ' +
|
|
36
|
-
'sorting, and pagination. Returns an array of matching records.',
|
|
37
|
-
parameters: {
|
|
38
|
-
type: 'object',
|
|
39
|
-
properties: {
|
|
40
|
-
objectName: {
|
|
41
|
-
type: 'string',
|
|
42
|
-
description: 'The snake_case name of the object to query',
|
|
43
|
-
},
|
|
44
|
-
where: {
|
|
45
|
-
type: 'object',
|
|
46
|
-
description:
|
|
47
|
-
'Filter conditions as key-value pairs (e.g. { "status": "active" }) ' +
|
|
48
|
-
'or MongoDB-style operators (e.g. { "amount": { "$gt": 100 } })',
|
|
49
|
-
},
|
|
50
|
-
fields: {
|
|
51
|
-
type: 'array',
|
|
52
|
-
items: { type: 'string' },
|
|
53
|
-
description: 'List of field names to return (omit for all fields)',
|
|
54
|
-
},
|
|
55
|
-
orderBy: {
|
|
56
|
-
type: 'array',
|
|
57
|
-
items: {
|
|
58
|
-
type: 'object',
|
|
59
|
-
properties: {
|
|
60
|
-
field: { type: 'string' },
|
|
61
|
-
order: { type: 'string', enum: ['asc', 'desc'] },
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
description: 'Sort order (e.g. [{ "field": "created_at", "order": "desc" }])',
|
|
65
|
-
},
|
|
66
|
-
limit: {
|
|
67
|
-
type: 'number',
|
|
68
|
-
description: `Maximum number of records to return (default ${DEFAULT_QUERY_LIMIT}, max ${MAX_QUERY_LIMIT})`,
|
|
69
|
-
},
|
|
70
|
-
offset: {
|
|
71
|
-
type: 'number',
|
|
72
|
-
description: 'Number of records to skip for pagination',
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
required: ['objectName'],
|
|
76
|
-
additionalProperties: false,
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export const GET_RECORD_TOOL: AIToolDefinition = {
|
|
81
|
-
name: 'get_record',
|
|
82
|
-
description: 'Get a single record by its ID from a data object.',
|
|
83
|
-
parameters: {
|
|
84
|
-
type: 'object',
|
|
85
|
-
properties: {
|
|
86
|
-
objectName: {
|
|
87
|
-
type: 'string',
|
|
88
|
-
description: 'The snake_case name of the object',
|
|
89
|
-
},
|
|
90
|
-
recordId: {
|
|
91
|
-
type: 'string',
|
|
92
|
-
description: 'The unique ID of the record',
|
|
93
|
-
},
|
|
94
|
-
fields: {
|
|
95
|
-
type: 'array',
|
|
96
|
-
items: { type: 'string' },
|
|
97
|
-
description: 'List of field names to return (omit for all fields)',
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
required: ['objectName', 'recordId'],
|
|
101
|
-
additionalProperties: false,
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export const AGGREGATE_DATA_TOOL: AIToolDefinition = {
|
|
106
|
-
name: 'aggregate_data',
|
|
107
|
-
description:
|
|
108
|
-
'Perform aggregation/statistical operations on a data object. ' +
|
|
109
|
-
'Supports count, sum, avg, min, max with optional groupBy and where filters.',
|
|
110
|
-
parameters: {
|
|
111
|
-
type: 'object',
|
|
112
|
-
properties: {
|
|
113
|
-
objectName: {
|
|
114
|
-
type: 'string',
|
|
115
|
-
description: 'The snake_case name of the object to aggregate',
|
|
116
|
-
},
|
|
117
|
-
aggregations: {
|
|
118
|
-
type: 'array',
|
|
119
|
-
items: {
|
|
120
|
-
type: 'object',
|
|
121
|
-
properties: {
|
|
122
|
-
function: {
|
|
123
|
-
type: 'string',
|
|
124
|
-
enum: ['count', 'sum', 'avg', 'min', 'max', 'count_distinct'],
|
|
125
|
-
description: 'Aggregation function',
|
|
126
|
-
},
|
|
127
|
-
field: {
|
|
128
|
-
type: 'string',
|
|
129
|
-
description: 'Field to aggregate (optional for count)',
|
|
130
|
-
},
|
|
131
|
-
alias: {
|
|
132
|
-
type: 'string',
|
|
133
|
-
description: 'Result column alias',
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
required: ['function', 'alias'],
|
|
137
|
-
},
|
|
138
|
-
description: 'Aggregation definitions',
|
|
139
|
-
},
|
|
140
|
-
groupBy: {
|
|
141
|
-
type: 'array',
|
|
142
|
-
items: { type: 'string' },
|
|
143
|
-
description: 'Fields to group by',
|
|
144
|
-
},
|
|
145
|
-
where: {
|
|
146
|
-
type: 'object',
|
|
147
|
-
description: 'Filter conditions applied before aggregation',
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
required: ['objectName', 'aggregations'],
|
|
151
|
-
additionalProperties: false,
|
|
152
|
-
},
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
/** All built-in data tools definitions. */
|
|
156
|
-
export const DATA_TOOL_DEFINITIONS: AIToolDefinition[] = [
|
|
157
|
-
QUERY_RECORDS_TOOL,
|
|
158
|
-
GET_RECORD_TOOL,
|
|
159
|
-
AGGREGATE_DATA_TOOL,
|
|
160
|
-
];
|
|
161
|
-
|
|
162
|
-
// ---------------------------------------------------------------------------
|
|
163
|
-
// Handler Factories
|
|
164
|
-
// ---------------------------------------------------------------------------
|
|
165
|
-
|
|
166
|
-
function createQueryRecordsHandler(ctx: DataToolContext): ToolHandler {
|
|
167
|
-
return async (args) => {
|
|
168
|
-
const {
|
|
169
|
-
objectName,
|
|
170
|
-
where,
|
|
171
|
-
fields,
|
|
172
|
-
orderBy,
|
|
173
|
-
limit,
|
|
174
|
-
offset,
|
|
175
|
-
} = args as {
|
|
176
|
-
objectName: string;
|
|
177
|
-
where?: Record<string, unknown>;
|
|
178
|
-
fields?: string[];
|
|
179
|
-
orderBy?: Array<{ field: string; order: 'asc' | 'desc' }>;
|
|
180
|
-
limit?: number;
|
|
181
|
-
offset?: number;
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
// Validate and clamp limit to [1, MAX_QUERY_LIMIT]
|
|
185
|
-
const rawLimit = limit ?? DEFAULT_QUERY_LIMIT;
|
|
186
|
-
const safeLimit = Number.isFinite(rawLimit) && rawLimit > 0
|
|
187
|
-
? Math.min(Math.floor(rawLimit), MAX_QUERY_LIMIT)
|
|
188
|
-
: DEFAULT_QUERY_LIMIT;
|
|
189
|
-
|
|
190
|
-
// Validate offset: must be a non-negative finite integer
|
|
191
|
-
const safeOffset = (Number.isFinite(offset) && (offset as number) >= 0)
|
|
192
|
-
? Math.floor(offset as number)
|
|
193
|
-
: undefined;
|
|
194
|
-
|
|
195
|
-
const records = await ctx.dataEngine.find(objectName, {
|
|
196
|
-
where,
|
|
197
|
-
fields,
|
|
198
|
-
orderBy,
|
|
199
|
-
limit: safeLimit,
|
|
200
|
-
offset: safeOffset,
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
return JSON.stringify({ count: records.length, records });
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function createGetRecordHandler(ctx: DataToolContext): ToolHandler {
|
|
208
|
-
return async (args) => {
|
|
209
|
-
const { objectName, recordId, fields } = args as {
|
|
210
|
-
objectName: string;
|
|
211
|
-
recordId: string;
|
|
212
|
-
fields?: string[];
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
const record = await ctx.dataEngine.findOne(objectName, {
|
|
216
|
-
where: { id: recordId },
|
|
217
|
-
fields,
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
if (!record) {
|
|
221
|
-
return JSON.stringify({ error: `Record "${recordId}" not found in "${objectName}"` });
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return JSON.stringify(record);
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/** Aggregation function names supported by the data engine. */
|
|
229
|
-
type AggFn = 'count' | 'sum' | 'avg' | 'min' | 'max' | 'count_distinct';
|
|
230
|
-
|
|
231
|
-
/** Set of valid aggregation function names for runtime validation. */
|
|
232
|
-
const VALID_AGG_FUNCTIONS = new Set<string>([
|
|
233
|
-
'count', 'sum', 'avg', 'min', 'max', 'count_distinct',
|
|
234
|
-
]);
|
|
235
|
-
|
|
236
|
-
function createAggregateDataHandler(ctx: DataToolContext): ToolHandler {
|
|
237
|
-
return async (args) => {
|
|
238
|
-
const { objectName, aggregations, groupBy, where } = args as {
|
|
239
|
-
objectName: string;
|
|
240
|
-
aggregations: Array<{ function: string; field?: string; alias: string }>;
|
|
241
|
-
groupBy?: string[];
|
|
242
|
-
where?: Record<string, unknown>;
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
// Validate aggregation functions at runtime
|
|
246
|
-
for (const a of aggregations) {
|
|
247
|
-
if (!VALID_AGG_FUNCTIONS.has(a.function)) {
|
|
248
|
-
return JSON.stringify({
|
|
249
|
-
error: `Invalid aggregation function "${a.function}". ` +
|
|
250
|
-
`Allowed: ${[...VALID_AGG_FUNCTIONS].join(', ')}`,
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const result = await ctx.dataEngine.aggregate(objectName, {
|
|
256
|
-
where,
|
|
257
|
-
groupBy,
|
|
258
|
-
aggregations: aggregations.map(a => ({
|
|
259
|
-
function: a.function as AggFn,
|
|
260
|
-
field: a.field,
|
|
261
|
-
alias: a.alias,
|
|
262
|
-
})),
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
return JSON.stringify(result);
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// ---------------------------------------------------------------------------
|
|
270
|
-
// Public Registration Helper
|
|
271
|
-
// ---------------------------------------------------------------------------
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Register all built-in data tools on the given {@link ToolRegistry}.
|
|
275
|
-
*
|
|
276
|
-
* Typically called from the `ai:ready` hook after the data engine is available.
|
|
277
|
-
*
|
|
278
|
-
* @example
|
|
279
|
-
* ```ts
|
|
280
|
-
* ctx.hook('ai:ready', async (aiService) => {
|
|
281
|
-
* const dataEngine = ctx.getService<IDataEngine>('data');
|
|
282
|
-
* registerDataTools(aiService.toolRegistry, { dataEngine });
|
|
283
|
-
* });
|
|
284
|
-
* ```
|
|
285
|
-
*/
|
|
286
|
-
export function registerDataTools(
|
|
287
|
-
registry: ToolRegistry,
|
|
288
|
-
context: DataToolContext,
|
|
289
|
-
): void {
|
|
290
|
-
registry.register(QUERY_RECORDS_TOOL, createQueryRecordsHandler(context));
|
|
291
|
-
registry.register(GET_RECORD_TOOL, createGetRecordHandler(context));
|
|
292
|
-
registry.register(AGGREGATE_DATA_TOOL, createAggregateDataHandler(context));
|
|
293
|
-
}
|
|
@@ -1,38 +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
|
-
* 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
|
-
// NOTE: requiresConfirmation is intentionally false (default) because the
|
|
20
|
-
// server-side tool-call loop in AIService.chatWithTools/streamChatWithTools
|
|
21
|
-
// executes tool calls immediately without checking this flag. The flag
|
|
22
|
-
// should only be set once server-side approval gating is implemented.
|
|
23
|
-
parameters: {
|
|
24
|
-
type: 'object',
|
|
25
|
-
properties: {
|
|
26
|
-
objectName: {
|
|
27
|
-
type: 'string',
|
|
28
|
-
description: 'Target object machine name (snake_case)',
|
|
29
|
-
},
|
|
30
|
-
fieldName: {
|
|
31
|
-
type: 'string',
|
|
32
|
-
description: 'Field machine name to delete (snake_case)',
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
required: ['objectName', 'fieldName'],
|
|
36
|
-
additionalProperties: false,
|
|
37
|
-
},
|
|
38
|
-
});
|
|
@@ -1,31 +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_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
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
export { ToolRegistry } from './tool-registry.js';
|
|
4
|
-
export type { ToolHandler, ToolExecutionResult } from './tool-registry.js';
|
|
5
|
-
|
|
6
|
-
export { registerDataTools, DATA_TOOL_DEFINITIONS } from './data-tools.js';
|
|
7
|
-
export type { DataToolContext } from './data-tools.js';
|
|
8
|
-
|
|
9
|
-
export { registerMetadataTools, METADATA_TOOL_DEFINITIONS } from './metadata-tools.js';
|
|
10
|
-
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 { listObjectsTool } from './list-objects.tool.js';
|
|
18
|
-
export { describeObjectTool } from './describe-object.tool.js';
|