@objectstack/service-ai 4.0.0
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 +22 -0
- package/CHANGELOG.md +25 -0
- package/LICENSE +202 -0
- package/dist/index.cjs +1418 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3406 -0
- package/dist/index.d.ts +3406 -0
- package/dist/index.js +1378 -0
- package/dist/index.js.map +1 -0
- package/package.json +29 -0
- package/src/__tests__/ai-service.test.ts +731 -0
- package/src/__tests__/chatbot-features.test.ts +821 -0
- package/src/__tests__/objectql-conversation-service.test.ts +364 -0
- package/src/adapters/index.ts +4 -0
- package/src/adapters/memory-adapter.ts +64 -0
- package/src/adapters/types.ts +3 -0
- package/src/agent-runtime.ts +130 -0
- package/src/agents/data-chat-agent.ts +79 -0
- package/src/agents/index.ts +3 -0
- package/src/ai-service.ts +205 -0
- package/src/conversation/in-memory-conversation-service.ts +103 -0
- package/src/conversation/index.ts +4 -0
- package/src/conversation/objectql-conversation-service.ts +252 -0
- package/src/index.ts +40 -0
- package/src/objects/ai-conversation.object.ts +86 -0
- package/src/objects/ai-message.object.ts +86 -0
- package/src/objects/index.ts +10 -0
- package/src/plugin.ts +184 -0
- package/src/routes/agent-routes.ts +132 -0
- package/src/routes/ai-routes.ts +286 -0
- package/src/routes/index.ts +4 -0
- package/src/tools/data-tools.ts +390 -0
- package/src/tools/index.ts +7 -0
- package/src/tools/tool-registry.ts +109 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
import type { AIToolDefinition, IDataEngine, IMetadataService } from '@objectstack/spec/contracts';
|
|
4
|
+
import type { ToolHandler } from './tool-registry.js';
|
|
5
|
+
import type { ToolRegistry } from './tool-registry.js';
|
|
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
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Data context — injected once at registration time
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Services required by the built-in data tools.
|
|
34
|
+
*
|
|
35
|
+
* These are provided by the kernel at `ai:ready` time and closed over
|
|
36
|
+
* by the handler functions so they stay framework-agnostic.
|
|
37
|
+
*/
|
|
38
|
+
export interface DataToolContext {
|
|
39
|
+
/** ObjectQL data engine for record-level operations. */
|
|
40
|
+
dataEngine: IDataEngine;
|
|
41
|
+
/** Metadata service for schema/object introspection. */
|
|
42
|
+
metadataService: IMetadataService;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Tool Definitions
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
/** Maximum number of records a single query may return. */
|
|
50
|
+
const MAX_QUERY_LIMIT = 200;
|
|
51
|
+
|
|
52
|
+
/** Default record limit when not specified. */
|
|
53
|
+
const DEFAULT_QUERY_LIMIT = 20;
|
|
54
|
+
|
|
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
|
+
export const QUERY_RECORDS_TOOL: AIToolDefinition = {
|
|
84
|
+
name: 'query_records',
|
|
85
|
+
description:
|
|
86
|
+
'Query records from a data object with optional filters, field selection, ' +
|
|
87
|
+
'sorting, and pagination. Returns an array of matching records.',
|
|
88
|
+
parameters: {
|
|
89
|
+
type: 'object',
|
|
90
|
+
properties: {
|
|
91
|
+
objectName: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
description: 'The snake_case name of the object to query',
|
|
94
|
+
},
|
|
95
|
+
where: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
description:
|
|
98
|
+
'Filter conditions as key-value pairs (e.g. { "status": "active" }) ' +
|
|
99
|
+
'or MongoDB-style operators (e.g. { "amount": { "$gt": 100 } })',
|
|
100
|
+
},
|
|
101
|
+
fields: {
|
|
102
|
+
type: 'array',
|
|
103
|
+
items: { type: 'string' },
|
|
104
|
+
description: 'List of field names to return (omit for all fields)',
|
|
105
|
+
},
|
|
106
|
+
orderBy: {
|
|
107
|
+
type: 'array',
|
|
108
|
+
items: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
field: { type: 'string' },
|
|
112
|
+
order: { type: 'string', enum: ['asc', 'desc'] },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
description: 'Sort order (e.g. [{ "field": "created_at", "order": "desc" }])',
|
|
116
|
+
},
|
|
117
|
+
limit: {
|
|
118
|
+
type: 'number',
|
|
119
|
+
description: `Maximum number of records to return (default ${DEFAULT_QUERY_LIMIT}, max ${MAX_QUERY_LIMIT})`,
|
|
120
|
+
},
|
|
121
|
+
offset: {
|
|
122
|
+
type: 'number',
|
|
123
|
+
description: 'Number of records to skip for pagination',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
required: ['objectName'],
|
|
127
|
+
additionalProperties: false,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const GET_RECORD_TOOL: AIToolDefinition = {
|
|
132
|
+
name: 'get_record',
|
|
133
|
+
description: 'Get a single record by its ID from a data object.',
|
|
134
|
+
parameters: {
|
|
135
|
+
type: 'object',
|
|
136
|
+
properties: {
|
|
137
|
+
objectName: {
|
|
138
|
+
type: 'string',
|
|
139
|
+
description: 'The snake_case name of the object',
|
|
140
|
+
},
|
|
141
|
+
recordId: {
|
|
142
|
+
type: 'string',
|
|
143
|
+
description: 'The unique ID of the record',
|
|
144
|
+
},
|
|
145
|
+
fields: {
|
|
146
|
+
type: 'array',
|
|
147
|
+
items: { type: 'string' },
|
|
148
|
+
description: 'List of field names to return (omit for all fields)',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
required: ['objectName', 'recordId'],
|
|
152
|
+
additionalProperties: false,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export const AGGREGATE_DATA_TOOL: AIToolDefinition = {
|
|
157
|
+
name: 'aggregate_data',
|
|
158
|
+
description:
|
|
159
|
+
'Perform aggregation/statistical operations on a data object. ' +
|
|
160
|
+
'Supports count, sum, avg, min, max with optional groupBy and where filters.',
|
|
161
|
+
parameters: {
|
|
162
|
+
type: 'object',
|
|
163
|
+
properties: {
|
|
164
|
+
objectName: {
|
|
165
|
+
type: 'string',
|
|
166
|
+
description: 'The snake_case name of the object to aggregate',
|
|
167
|
+
},
|
|
168
|
+
aggregations: {
|
|
169
|
+
type: 'array',
|
|
170
|
+
items: {
|
|
171
|
+
type: 'object',
|
|
172
|
+
properties: {
|
|
173
|
+
function: {
|
|
174
|
+
type: 'string',
|
|
175
|
+
enum: ['count', 'sum', 'avg', 'min', 'max', 'count_distinct'],
|
|
176
|
+
description: 'Aggregation function',
|
|
177
|
+
},
|
|
178
|
+
field: {
|
|
179
|
+
type: 'string',
|
|
180
|
+
description: 'Field to aggregate (optional for count)',
|
|
181
|
+
},
|
|
182
|
+
alias: {
|
|
183
|
+
type: 'string',
|
|
184
|
+
description: 'Result column alias',
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
required: ['function', 'alias'],
|
|
188
|
+
},
|
|
189
|
+
description: 'Aggregation definitions',
|
|
190
|
+
},
|
|
191
|
+
groupBy: {
|
|
192
|
+
type: 'array',
|
|
193
|
+
items: { type: 'string' },
|
|
194
|
+
description: 'Fields to group by',
|
|
195
|
+
},
|
|
196
|
+
where: {
|
|
197
|
+
type: 'object',
|
|
198
|
+
description: 'Filter conditions applied before aggregation',
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
required: ['objectName', 'aggregations'],
|
|
202
|
+
additionalProperties: false,
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/** All built-in data tool definitions. */
|
|
207
|
+
export const DATA_TOOL_DEFINITIONS: AIToolDefinition[] = [
|
|
208
|
+
LIST_OBJECTS_TOOL,
|
|
209
|
+
DESCRIBE_OBJECT_TOOL,
|
|
210
|
+
QUERY_RECORDS_TOOL,
|
|
211
|
+
GET_RECORD_TOOL,
|
|
212
|
+
AGGREGATE_DATA_TOOL,
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// Handler Factories
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
|
|
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
|
+
function createQueryRecordsHandler(ctx: DataToolContext): ToolHandler {
|
|
260
|
+
return async (args) => {
|
|
261
|
+
const {
|
|
262
|
+
objectName,
|
|
263
|
+
where,
|
|
264
|
+
fields,
|
|
265
|
+
orderBy,
|
|
266
|
+
limit,
|
|
267
|
+
offset,
|
|
268
|
+
} = args as {
|
|
269
|
+
objectName: string;
|
|
270
|
+
where?: Record<string, unknown>;
|
|
271
|
+
fields?: string[];
|
|
272
|
+
orderBy?: Array<{ field: string; order: 'asc' | 'desc' }>;
|
|
273
|
+
limit?: number;
|
|
274
|
+
offset?: number;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// Validate and clamp limit to [1, MAX_QUERY_LIMIT]
|
|
278
|
+
const rawLimit = limit ?? DEFAULT_QUERY_LIMIT;
|
|
279
|
+
const safeLimit = Number.isFinite(rawLimit) && rawLimit > 0
|
|
280
|
+
? Math.min(Math.floor(rawLimit), MAX_QUERY_LIMIT)
|
|
281
|
+
: DEFAULT_QUERY_LIMIT;
|
|
282
|
+
|
|
283
|
+
// Validate offset: must be a non-negative finite integer
|
|
284
|
+
const safeOffset = (Number.isFinite(offset) && (offset as number) >= 0)
|
|
285
|
+
? Math.floor(offset as number)
|
|
286
|
+
: undefined;
|
|
287
|
+
|
|
288
|
+
const records = await ctx.dataEngine.find(objectName, {
|
|
289
|
+
where,
|
|
290
|
+
fields,
|
|
291
|
+
orderBy,
|
|
292
|
+
limit: safeLimit,
|
|
293
|
+
offset: safeOffset,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
return JSON.stringify({ count: records.length, records });
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function createGetRecordHandler(ctx: DataToolContext): ToolHandler {
|
|
301
|
+
return async (args) => {
|
|
302
|
+
const { objectName, recordId, fields } = args as {
|
|
303
|
+
objectName: string;
|
|
304
|
+
recordId: string;
|
|
305
|
+
fields?: string[];
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const record = await ctx.dataEngine.findOne(objectName, {
|
|
309
|
+
where: { id: recordId },
|
|
310
|
+
fields,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
if (!record) {
|
|
314
|
+
return JSON.stringify({ error: `Record "${recordId}" not found in "${objectName}"` });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return JSON.stringify(record);
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Aggregation function names supported by the data engine. */
|
|
322
|
+
type AggFn = 'count' | 'sum' | 'avg' | 'min' | 'max' | 'count_distinct';
|
|
323
|
+
|
|
324
|
+
/** Set of valid aggregation function names for runtime validation. */
|
|
325
|
+
const VALID_AGG_FUNCTIONS = new Set<string>([
|
|
326
|
+
'count', 'sum', 'avg', 'min', 'max', 'count_distinct',
|
|
327
|
+
]);
|
|
328
|
+
|
|
329
|
+
function createAggregateDataHandler(ctx: DataToolContext): ToolHandler {
|
|
330
|
+
return async (args) => {
|
|
331
|
+
const { objectName, aggregations, groupBy, where } = args as {
|
|
332
|
+
objectName: string;
|
|
333
|
+
aggregations: Array<{ function: string; field?: string; alias: string }>;
|
|
334
|
+
groupBy?: string[];
|
|
335
|
+
where?: Record<string, unknown>;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Validate aggregation functions at runtime
|
|
339
|
+
for (const a of aggregations) {
|
|
340
|
+
if (!VALID_AGG_FUNCTIONS.has(a.function)) {
|
|
341
|
+
return JSON.stringify({
|
|
342
|
+
error: `Invalid aggregation function "${a.function}". ` +
|
|
343
|
+
`Allowed: ${[...VALID_AGG_FUNCTIONS].join(', ')}`,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const result = await ctx.dataEngine.aggregate(objectName, {
|
|
349
|
+
where,
|
|
350
|
+
groupBy,
|
|
351
|
+
aggregations: aggregations.map(a => ({
|
|
352
|
+
function: a.function as AggFn,
|
|
353
|
+
field: a.field,
|
|
354
|
+
alias: a.alias,
|
|
355
|
+
})),
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
return JSON.stringify(result);
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
// Public Registration Helper
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Register all built-in data tools on the given {@link ToolRegistry}.
|
|
368
|
+
*
|
|
369
|
+
* Typically called from the `ai:ready` hook after both the data engine
|
|
370
|
+
* and metadata service are available.
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```ts
|
|
374
|
+
* ctx.hook('ai:ready', async (aiService) => {
|
|
375
|
+
* const dataEngine = ctx.getService<IDataEngine>('data');
|
|
376
|
+
* const metadataService = ctx.getService<IMetadataService>('metadata');
|
|
377
|
+
* registerDataTools(aiService.toolRegistry, { dataEngine, metadataService });
|
|
378
|
+
* });
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
export function registerDataTools(
|
|
382
|
+
registry: ToolRegistry,
|
|
383
|
+
context: DataToolContext,
|
|
384
|
+
): void {
|
|
385
|
+
registry.register(LIST_OBJECTS_TOOL, createListObjectsHandler(context));
|
|
386
|
+
registry.register(DESCRIBE_OBJECT_TOOL, createDescribeObjectHandler(context));
|
|
387
|
+
registry.register(QUERY_RECORDS_TOOL, createQueryRecordsHandler(context));
|
|
388
|
+
registry.register(GET_RECORD_TOOL, createGetRecordHandler(context));
|
|
389
|
+
registry.register(AGGREGATE_DATA_TOOL, createAggregateDataHandler(context));
|
|
390
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
export { ToolRegistry } from './tool-registry.js';
|
|
4
|
+
export type { ToolHandler } from './tool-registry.js';
|
|
5
|
+
|
|
6
|
+
export { registerDataTools, DATA_TOOL_DEFINITIONS } from './data-tools.js';
|
|
7
|
+
export type { DataToolContext } from './data-tools.js';
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
import type { AIToolDefinition, AIToolCall, AIToolResult } from '@objectstack/spec/contracts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Handler function for a registered tool.
|
|
7
|
+
*
|
|
8
|
+
* Receives parsed arguments and returns the tool output as a string.
|
|
9
|
+
*/
|
|
10
|
+
export type ToolHandler = (args: Record<string, unknown>) => Promise<string> | string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* ToolRegistry — Central registry for AI-callable tools.
|
|
14
|
+
*
|
|
15
|
+
* Plugins register tools (metadata helpers, data queries, business actions)
|
|
16
|
+
* during the `ai:ready` hook. The AI service resolves tool calls against
|
|
17
|
+
* this registry and feeds the results back to the LLM.
|
|
18
|
+
*/
|
|
19
|
+
export class ToolRegistry {
|
|
20
|
+
private readonly definitions = new Map<string, AIToolDefinition>();
|
|
21
|
+
private readonly handlers = new Map<string, ToolHandler>();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Register a tool with its definition and handler.
|
|
25
|
+
* @param definition - Tool definition (name, description, parameters schema)
|
|
26
|
+
* @param handler - Async function that executes the tool
|
|
27
|
+
*/
|
|
28
|
+
register(definition: AIToolDefinition, handler: ToolHandler): void {
|
|
29
|
+
this.definitions.set(definition.name, definition);
|
|
30
|
+
this.handlers.set(definition.name, handler);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Unregister a tool by name.
|
|
35
|
+
*/
|
|
36
|
+
unregister(name: string): void {
|
|
37
|
+
this.definitions.delete(name);
|
|
38
|
+
this.handlers.delete(name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check whether a tool is registered.
|
|
43
|
+
*/
|
|
44
|
+
has(name: string): boolean {
|
|
45
|
+
return this.definitions.has(name);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get the definition for a registered tool.
|
|
50
|
+
*/
|
|
51
|
+
getDefinition(name: string): AIToolDefinition | undefined {
|
|
52
|
+
return this.definitions.get(name);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Return all registered tool definitions.
|
|
57
|
+
*/
|
|
58
|
+
getAll(): AIToolDefinition[] {
|
|
59
|
+
return Array.from(this.definitions.values());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Number of registered tools. */
|
|
63
|
+
get size(): number {
|
|
64
|
+
return this.definitions.size;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** All registered tool names. */
|
|
68
|
+
names(): string[] {
|
|
69
|
+
return Array.from(this.definitions.keys());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Execute a tool call and return the result.
|
|
74
|
+
*/
|
|
75
|
+
async execute(toolCall: AIToolCall): Promise<AIToolResult> {
|
|
76
|
+
const handler = this.handlers.get(toolCall.name);
|
|
77
|
+
if (!handler) {
|
|
78
|
+
return {
|
|
79
|
+
toolCallId: toolCall.id,
|
|
80
|
+
content: `Tool "${toolCall.name}" is not registered`,
|
|
81
|
+
isError: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const args: Record<string, unknown> = JSON.parse(toolCall.arguments);
|
|
87
|
+
const content = await handler(args);
|
|
88
|
+
return { toolCallId: toolCall.id, content };
|
|
89
|
+
} catch (err) {
|
|
90
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
91
|
+
return { toolCallId: toolCall.id, content: message, isError: true };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Execute multiple tool calls in parallel.
|
|
97
|
+
*/
|
|
98
|
+
async executeAll(toolCalls: AIToolCall[]): Promise<AIToolResult[]> {
|
|
99
|
+
return Promise.all(toolCalls.map(tc => this.execute(tc)));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Clear all registered tools.
|
|
104
|
+
*/
|
|
105
|
+
clear(): void {
|
|
106
|
+
this.definitions.clear();
|
|
107
|
+
this.handlers.clear();
|
|
108
|
+
}
|
|
109
|
+
}
|
package/tsconfig.json
ADDED