@objectstack/service-ai 4.0.1 → 4.0.3

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.
Files changed (44) hide show
  1. package/.turbo/turbo-build.log +11 -11
  2. package/CHANGELOG.md +17 -0
  3. package/dist/index.cjs +1632 -355
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +330 -87
  6. package/dist/index.d.ts +330 -87
  7. package/dist/index.js +1623 -352
  8. package/dist/index.js.map +1 -1
  9. package/package.json +27 -5
  10. package/src/__tests__/ai-service.test.ts +260 -27
  11. package/src/__tests__/auth-and-toolcalling.test.ts +81 -29
  12. package/src/__tests__/chatbot-features.test.ts +397 -102
  13. package/src/__tests__/metadata-tools.test.ts +970 -0
  14. package/src/__tests__/objectql-conversation-service.test.ts +34 -16
  15. package/src/__tests__/tool-routes.test.ts +191 -0
  16. package/src/__tests__/vercel-stream-encoder.test.ts +310 -0
  17. package/src/adapters/index.ts +2 -0
  18. package/src/adapters/memory-adapter.ts +17 -9
  19. package/src/adapters/vercel-adapter.ts +148 -0
  20. package/src/agent-runtime.ts +27 -3
  21. package/src/agents/index.ts +1 -0
  22. package/src/agents/metadata-assistant-agent.ts +87 -0
  23. package/src/ai-service.ts +75 -36
  24. package/src/conversation/in-memory-conversation-service.ts +2 -2
  25. package/src/conversation/objectql-conversation-service.ts +67 -18
  26. package/src/index.ts +22 -2
  27. package/src/plugin.ts +237 -30
  28. package/src/routes/agent-routes.ts +68 -12
  29. package/src/routes/ai-routes.ts +93 -14
  30. package/src/routes/index.ts +1 -0
  31. package/src/routes/message-utils.ts +90 -0
  32. package/src/routes/tool-routes.ts +142 -0
  33. package/src/stream/index.ts +3 -0
  34. package/src/stream/vercel-stream-encoder.ts +153 -0
  35. package/src/tools/add-field.tool.ts +70 -0
  36. package/src/tools/create-object.tool.ts +66 -0
  37. package/src/tools/data-tools.ts +4 -101
  38. package/src/tools/delete-field.tool.ts +38 -0
  39. package/src/tools/describe-object.tool.ts +31 -0
  40. package/src/tools/index.ts +12 -1
  41. package/src/tools/list-objects.tool.ts +34 -0
  42. package/src/tools/metadata-tools.ts +430 -0
  43. package/src/tools/modify-field.tool.ts +44 -0
  44. package/src/tools/tool-registry.ts +32 -9
package/dist/index.cjs CHANGED
@@ -3,6 +3,9 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
6
9
  var __export = (target, all) => {
7
10
  for (var name in all)
8
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -17,6 +20,752 @@ var __copyProps = (to, from, except, desc) => {
17
20
  };
18
21
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
22
 
23
+ // src/tools/data-tools.ts
24
+ var data_tools_exports = {};
25
+ __export(data_tools_exports, {
26
+ AGGREGATE_DATA_TOOL: () => AGGREGATE_DATA_TOOL,
27
+ DATA_TOOL_DEFINITIONS: () => DATA_TOOL_DEFINITIONS,
28
+ GET_RECORD_TOOL: () => GET_RECORD_TOOL,
29
+ QUERY_RECORDS_TOOL: () => QUERY_RECORDS_TOOL,
30
+ registerDataTools: () => registerDataTools
31
+ });
32
+ function createQueryRecordsHandler(ctx) {
33
+ return async (args) => {
34
+ const {
35
+ objectName,
36
+ where,
37
+ fields,
38
+ orderBy,
39
+ limit,
40
+ offset
41
+ } = args;
42
+ const rawLimit = limit ?? DEFAULT_QUERY_LIMIT;
43
+ const safeLimit = Number.isFinite(rawLimit) && rawLimit > 0 ? Math.min(Math.floor(rawLimit), MAX_QUERY_LIMIT) : DEFAULT_QUERY_LIMIT;
44
+ const safeOffset = Number.isFinite(offset) && offset >= 0 ? Math.floor(offset) : void 0;
45
+ const records = await ctx.dataEngine.find(objectName, {
46
+ where,
47
+ fields,
48
+ orderBy,
49
+ limit: safeLimit,
50
+ offset: safeOffset
51
+ });
52
+ return JSON.stringify({ count: records.length, records });
53
+ };
54
+ }
55
+ function createGetRecordHandler(ctx) {
56
+ return async (args) => {
57
+ const { objectName, recordId, fields } = args;
58
+ const record = await ctx.dataEngine.findOne(objectName, {
59
+ where: { id: recordId },
60
+ fields
61
+ });
62
+ if (!record) {
63
+ return JSON.stringify({ error: `Record "${recordId}" not found in "${objectName}"` });
64
+ }
65
+ return JSON.stringify(record);
66
+ };
67
+ }
68
+ function createAggregateDataHandler(ctx) {
69
+ return async (args) => {
70
+ const { objectName, aggregations, groupBy, where } = args;
71
+ for (const a of aggregations) {
72
+ if (!VALID_AGG_FUNCTIONS.has(a.function)) {
73
+ return JSON.stringify({
74
+ error: `Invalid aggregation function "${a.function}". Allowed: ${[...VALID_AGG_FUNCTIONS].join(", ")}`
75
+ });
76
+ }
77
+ }
78
+ const result = await ctx.dataEngine.aggregate(objectName, {
79
+ where,
80
+ groupBy,
81
+ aggregations: aggregations.map((a) => ({
82
+ function: a.function,
83
+ field: a.field,
84
+ alias: a.alias
85
+ }))
86
+ });
87
+ return JSON.stringify(result);
88
+ };
89
+ }
90
+ function registerDataTools(registry, context) {
91
+ registry.register(QUERY_RECORDS_TOOL, createQueryRecordsHandler(context));
92
+ registry.register(GET_RECORD_TOOL, createGetRecordHandler(context));
93
+ registry.register(AGGREGATE_DATA_TOOL, createAggregateDataHandler(context));
94
+ }
95
+ var MAX_QUERY_LIMIT, DEFAULT_QUERY_LIMIT, QUERY_RECORDS_TOOL, GET_RECORD_TOOL, AGGREGATE_DATA_TOOL, DATA_TOOL_DEFINITIONS, VALID_AGG_FUNCTIONS;
96
+ var init_data_tools = __esm({
97
+ "src/tools/data-tools.ts"() {
98
+ "use strict";
99
+ MAX_QUERY_LIMIT = 200;
100
+ DEFAULT_QUERY_LIMIT = 20;
101
+ QUERY_RECORDS_TOOL = {
102
+ name: "query_records",
103
+ description: "Query records from a data object with optional filters, field selection, sorting, and pagination. Returns an array of matching records.",
104
+ parameters: {
105
+ type: "object",
106
+ properties: {
107
+ objectName: {
108
+ type: "string",
109
+ description: "The snake_case name of the object to query"
110
+ },
111
+ where: {
112
+ type: "object",
113
+ description: 'Filter conditions as key-value pairs (e.g. { "status": "active" }) or MongoDB-style operators (e.g. { "amount": { "$gt": 100 } })'
114
+ },
115
+ fields: {
116
+ type: "array",
117
+ items: { type: "string" },
118
+ description: "List of field names to return (omit for all fields)"
119
+ },
120
+ orderBy: {
121
+ type: "array",
122
+ items: {
123
+ type: "object",
124
+ properties: {
125
+ field: { type: "string" },
126
+ order: { type: "string", enum: ["asc", "desc"] }
127
+ }
128
+ },
129
+ description: 'Sort order (e.g. [{ "field": "created_at", "order": "desc" }])'
130
+ },
131
+ limit: {
132
+ type: "number",
133
+ description: `Maximum number of records to return (default ${DEFAULT_QUERY_LIMIT}, max ${MAX_QUERY_LIMIT})`
134
+ },
135
+ offset: {
136
+ type: "number",
137
+ description: "Number of records to skip for pagination"
138
+ }
139
+ },
140
+ required: ["objectName"],
141
+ additionalProperties: false
142
+ }
143
+ };
144
+ GET_RECORD_TOOL = {
145
+ name: "get_record",
146
+ description: "Get a single record by its ID from a data object.",
147
+ parameters: {
148
+ type: "object",
149
+ properties: {
150
+ objectName: {
151
+ type: "string",
152
+ description: "The snake_case name of the object"
153
+ },
154
+ recordId: {
155
+ type: "string",
156
+ description: "The unique ID of the record"
157
+ },
158
+ fields: {
159
+ type: "array",
160
+ items: { type: "string" },
161
+ description: "List of field names to return (omit for all fields)"
162
+ }
163
+ },
164
+ required: ["objectName", "recordId"],
165
+ additionalProperties: false
166
+ }
167
+ };
168
+ AGGREGATE_DATA_TOOL = {
169
+ name: "aggregate_data",
170
+ description: "Perform aggregation/statistical operations on a data object. Supports count, sum, avg, min, max with optional groupBy and where filters.",
171
+ parameters: {
172
+ type: "object",
173
+ properties: {
174
+ objectName: {
175
+ type: "string",
176
+ description: "The snake_case name of the object to aggregate"
177
+ },
178
+ aggregations: {
179
+ type: "array",
180
+ items: {
181
+ type: "object",
182
+ properties: {
183
+ function: {
184
+ type: "string",
185
+ enum: ["count", "sum", "avg", "min", "max", "count_distinct"],
186
+ description: "Aggregation function"
187
+ },
188
+ field: {
189
+ type: "string",
190
+ description: "Field to aggregate (optional for count)"
191
+ },
192
+ alias: {
193
+ type: "string",
194
+ description: "Result column alias"
195
+ }
196
+ },
197
+ required: ["function", "alias"]
198
+ },
199
+ description: "Aggregation definitions"
200
+ },
201
+ groupBy: {
202
+ type: "array",
203
+ items: { type: "string" },
204
+ description: "Fields to group by"
205
+ },
206
+ where: {
207
+ type: "object",
208
+ description: "Filter conditions applied before aggregation"
209
+ }
210
+ },
211
+ required: ["objectName", "aggregations"],
212
+ additionalProperties: false
213
+ }
214
+ };
215
+ DATA_TOOL_DEFINITIONS = [
216
+ QUERY_RECORDS_TOOL,
217
+ GET_RECORD_TOOL,
218
+ AGGREGATE_DATA_TOOL
219
+ ];
220
+ VALID_AGG_FUNCTIONS = /* @__PURE__ */ new Set([
221
+ "count",
222
+ "sum",
223
+ "avg",
224
+ "min",
225
+ "max",
226
+ "count_distinct"
227
+ ]);
228
+ }
229
+ });
230
+
231
+ // src/tools/create-object.tool.ts
232
+ var import_ai, createObjectTool;
233
+ var init_create_object_tool = __esm({
234
+ "src/tools/create-object.tool.ts"() {
235
+ "use strict";
236
+ import_ai = require("@objectstack/spec/ai");
237
+ createObjectTool = (0, import_ai.defineTool)({
238
+ name: "create_object",
239
+ label: "Create Object",
240
+ description: "Creates a new data object (table) with the specified name, label, and optional field definitions. Use this when the user wants to create a new entity, table, or data model.",
241
+ category: "data",
242
+ builtIn: true,
243
+ // NOTE: requiresConfirmation is intentionally false (default) because the
244
+ // server-side tool-call loop in AIService.chatWithTools/streamChatWithTools
245
+ // executes tool calls immediately without checking this flag. The flag
246
+ // should only be set once server-side approval gating is implemented to
247
+ // avoid giving users a false sense of safety.
248
+ parameters: {
249
+ type: "object",
250
+ properties: {
251
+ name: {
252
+ type: "string",
253
+ description: "Machine name for the object (snake_case, e.g. project_task)"
254
+ },
255
+ label: {
256
+ type: "string",
257
+ description: "Human-readable display name (e.g. Project Task)"
258
+ },
259
+ fields: {
260
+ type: "array",
261
+ description: "Initial fields to create with the object",
262
+ items: {
263
+ type: "object",
264
+ properties: {
265
+ name: { type: "string", description: "Field machine name (snake_case)" },
266
+ label: { type: "string", description: "Field display name" },
267
+ type: {
268
+ type: "string",
269
+ description: "Field data type",
270
+ enum: ["text", "textarea", "number", "boolean", "date", "datetime", "select", "lookup", "formula", "autonumber"]
271
+ },
272
+ required: { type: "boolean", description: "Whether the field is required" }
273
+ },
274
+ required: ["name", "type"]
275
+ }
276
+ },
277
+ enableFeatures: {
278
+ type: "object",
279
+ description: "Object capability flags",
280
+ properties: {
281
+ trackHistory: { type: "boolean" },
282
+ apiEnabled: { type: "boolean" }
283
+ }
284
+ }
285
+ },
286
+ required: ["name", "label"],
287
+ additionalProperties: false
288
+ }
289
+ });
290
+ }
291
+ });
292
+
293
+ // src/tools/add-field.tool.ts
294
+ var import_ai2, addFieldTool;
295
+ var init_add_field_tool = __esm({
296
+ "src/tools/add-field.tool.ts"() {
297
+ "use strict";
298
+ import_ai2 = require("@objectstack/spec/ai");
299
+ addFieldTool = (0, import_ai2.defineTool)({
300
+ name: "add_field",
301
+ label: "Add Field",
302
+ description: "Adds a new field (column) to an existing data object. Use this when the user wants to add a property, column, or attribute to a table.",
303
+ category: "data",
304
+ builtIn: true,
305
+ parameters: {
306
+ type: "object",
307
+ properties: {
308
+ objectName: {
309
+ type: "string",
310
+ description: "Target object machine name (snake_case)"
311
+ },
312
+ name: {
313
+ type: "string",
314
+ description: "Field machine name (snake_case, e.g. due_date)"
315
+ },
316
+ label: {
317
+ type: "string",
318
+ description: "Human-readable field label (e.g. Due Date)"
319
+ },
320
+ type: {
321
+ type: "string",
322
+ description: "Field data type",
323
+ enum: ["text", "textarea", "number", "boolean", "date", "datetime", "select", "lookup", "formula", "autonumber"]
324
+ },
325
+ required: {
326
+ type: "boolean",
327
+ description: "Whether the field is required"
328
+ },
329
+ defaultValue: {
330
+ description: "Default value for the field"
331
+ },
332
+ options: {
333
+ type: "array",
334
+ description: "Options for select/picklist fields",
335
+ items: {
336
+ type: "object",
337
+ properties: {
338
+ label: { type: "string" },
339
+ value: {
340
+ type: "string",
341
+ description: "Option machine identifier (lowercase snake_case, e.g. high_priority)",
342
+ pattern: "^[a-z_][a-z0-9_]*$"
343
+ }
344
+ }
345
+ }
346
+ },
347
+ reference: {
348
+ type: "string",
349
+ description: "Referenced object name for lookup fields (snake_case, e.g. account)"
350
+ }
351
+ },
352
+ required: ["objectName", "name", "type"],
353
+ additionalProperties: false
354
+ }
355
+ });
356
+ }
357
+ });
358
+
359
+ // src/tools/modify-field.tool.ts
360
+ var import_ai3, modifyFieldTool;
361
+ var init_modify_field_tool = __esm({
362
+ "src/tools/modify-field.tool.ts"() {
363
+ "use strict";
364
+ import_ai3 = require("@objectstack/spec/ai");
365
+ modifyFieldTool = (0, import_ai3.defineTool)({
366
+ name: "modify_field",
367
+ label: "Modify Field",
368
+ description: "Modifies an existing field definition (label, type, required, default value, etc.) on a data object. Use this when the user wants to change or reconfigure an existing column or attribute (not rename it).",
369
+ category: "data",
370
+ builtIn: true,
371
+ parameters: {
372
+ type: "object",
373
+ properties: {
374
+ objectName: {
375
+ type: "string",
376
+ description: "Target object machine name (snake_case)"
377
+ },
378
+ fieldName: {
379
+ type: "string",
380
+ description: "Existing field machine name to modify (snake_case)"
381
+ },
382
+ changes: {
383
+ type: "object",
384
+ description: "Field properties to update (partial patch)",
385
+ properties: {
386
+ label: { type: "string", description: "New display label" },
387
+ type: { type: "string", description: "New field type" },
388
+ required: { type: "boolean", description: "Update required constraint" },
389
+ defaultValue: { description: "New default value" }
390
+ }
391
+ }
392
+ },
393
+ required: ["objectName", "fieldName", "changes"],
394
+ additionalProperties: false
395
+ }
396
+ });
397
+ }
398
+ });
399
+
400
+ // src/tools/delete-field.tool.ts
401
+ var import_ai4, deleteFieldTool;
402
+ var init_delete_field_tool = __esm({
403
+ "src/tools/delete-field.tool.ts"() {
404
+ "use strict";
405
+ import_ai4 = require("@objectstack/spec/ai");
406
+ deleteFieldTool = (0, import_ai4.defineTool)({
407
+ name: "delete_field",
408
+ label: "Delete Field",
409
+ description: "Removes a field (column) from an existing data object. This is a destructive operation. Use this when the user explicitly wants to remove an attribute or column from a table.",
410
+ category: "data",
411
+ builtIn: true,
412
+ // NOTE: requiresConfirmation is intentionally false (default) because the
413
+ // server-side tool-call loop in AIService.chatWithTools/streamChatWithTools
414
+ // executes tool calls immediately without checking this flag. The flag
415
+ // should only be set once server-side approval gating is implemented.
416
+ parameters: {
417
+ type: "object",
418
+ properties: {
419
+ objectName: {
420
+ type: "string",
421
+ description: "Target object machine name (snake_case)"
422
+ },
423
+ fieldName: {
424
+ type: "string",
425
+ description: "Field machine name to delete (snake_case)"
426
+ }
427
+ },
428
+ required: ["objectName", "fieldName"],
429
+ additionalProperties: false
430
+ }
431
+ });
432
+ }
433
+ });
434
+
435
+ // src/tools/list-objects.tool.ts
436
+ var import_ai5, listObjectsTool;
437
+ var init_list_objects_tool = __esm({
438
+ "src/tools/list-objects.tool.ts"() {
439
+ "use strict";
440
+ import_ai5 = require("@objectstack/spec/ai");
441
+ listObjectsTool = (0, import_ai5.defineTool)({
442
+ name: "list_objects",
443
+ label: "List Objects",
444
+ description: "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.",
445
+ category: "data",
446
+ builtIn: true,
447
+ parameters: {
448
+ type: "object",
449
+ properties: {
450
+ filter: {
451
+ type: "string",
452
+ description: "Optional name or label substring to filter objects"
453
+ },
454
+ includeFields: {
455
+ type: "boolean",
456
+ description: "Whether to include field summaries for each object (default: false)"
457
+ }
458
+ },
459
+ additionalProperties: false
460
+ }
461
+ });
462
+ }
463
+ });
464
+
465
+ // src/tools/describe-object.tool.ts
466
+ var import_ai6, describeObjectTool;
467
+ var init_describe_object_tool = __esm({
468
+ "src/tools/describe-object.tool.ts"() {
469
+ "use strict";
470
+ import_ai6 = require("@objectstack/spec/ai");
471
+ describeObjectTool = (0, import_ai6.defineTool)({
472
+ name: "describe_object",
473
+ label: "Describe Object",
474
+ 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.",
475
+ category: "data",
476
+ builtIn: true,
477
+ parameters: {
478
+ type: "object",
479
+ properties: {
480
+ objectName: {
481
+ type: "string",
482
+ description: "Object machine name to describe (snake_case)"
483
+ }
484
+ },
485
+ required: ["objectName"],
486
+ additionalProperties: false
487
+ }
488
+ });
489
+ }
490
+ });
491
+
492
+ // src/tools/metadata-tools.ts
493
+ var metadata_tools_exports = {};
494
+ __export(metadata_tools_exports, {
495
+ METADATA_TOOL_DEFINITIONS: () => METADATA_TOOL_DEFINITIONS,
496
+ addFieldTool: () => addFieldTool,
497
+ createObjectTool: () => createObjectTool,
498
+ deleteFieldTool: () => deleteFieldTool,
499
+ describeObjectTool: () => describeObjectTool,
500
+ listObjectsTool: () => listObjectsTool,
501
+ modifyFieldTool: () => modifyFieldTool,
502
+ registerMetadataTools: () => registerMetadataTools
503
+ });
504
+ function isSnakeCase(value) {
505
+ return SNAKE_CASE_RE.test(value);
506
+ }
507
+ function createCreateObjectHandler(ctx) {
508
+ return async (args) => {
509
+ const { name, label, fields, enableFeatures } = args;
510
+ if (!name || !label) {
511
+ return JSON.stringify({ error: 'Both "name" and "label" are required' });
512
+ }
513
+ if (!isSnakeCase(name)) {
514
+ return JSON.stringify({ error: `Invalid object name "${name}". Must be snake_case.` });
515
+ }
516
+ const existing = await ctx.metadataService.getObject(name);
517
+ if (existing) {
518
+ return JSON.stringify({ error: `Object "${name}" already exists` });
519
+ }
520
+ const fieldMap = {};
521
+ if (fields && Array.isArray(fields)) {
522
+ const seenNames = /* @__PURE__ */ new Set();
523
+ for (const f of fields) {
524
+ if (!f.name) {
525
+ return JSON.stringify({ error: 'Each field must have a "name" property' });
526
+ }
527
+ if (!isSnakeCase(f.name)) {
528
+ return JSON.stringify({ error: `Invalid field name "${f.name}". Must be snake_case.` });
529
+ }
530
+ if (seenNames.has(f.name)) {
531
+ return JSON.stringify({ error: `Duplicate field name "${f.name}" in initial fields` });
532
+ }
533
+ seenNames.add(f.name);
534
+ fieldMap[f.name] = {
535
+ type: f.type,
536
+ ...f.label ? { label: f.label } : {},
537
+ ...f.required !== void 0 ? { required: f.required } : {}
538
+ };
539
+ }
540
+ }
541
+ const objectDef = {
542
+ name,
543
+ label,
544
+ ...Object.keys(fieldMap).length > 0 ? { fields: fieldMap } : {},
545
+ ...enableFeatures ? { enable: enableFeatures } : {}
546
+ };
547
+ await ctx.metadataService.register("object", name, objectDef);
548
+ return JSON.stringify({
549
+ name,
550
+ label,
551
+ fieldCount: Object.keys(fieldMap).length
552
+ });
553
+ };
554
+ }
555
+ function createAddFieldHandler(ctx) {
556
+ return async (args) => {
557
+ const { objectName, name, label, type, required, defaultValue, options, reference } = args;
558
+ if (!objectName || !name || !type) {
559
+ return JSON.stringify({ error: '"objectName", "name", and "type" are required' });
560
+ }
561
+ if (!isSnakeCase(objectName)) {
562
+ return JSON.stringify({ error: `Invalid object name "${objectName}". Must be snake_case.` });
563
+ }
564
+ if (!isSnakeCase(name)) {
565
+ return JSON.stringify({ error: `Invalid field name "${name}". Must be snake_case.` });
566
+ }
567
+ if (reference && !isSnakeCase(reference)) {
568
+ return JSON.stringify({ error: `Invalid reference "${reference}". Must be a snake_case object name.` });
569
+ }
570
+ if (options && Array.isArray(options)) {
571
+ for (const opt of options) {
572
+ if (opt.value && !isSnakeCase(opt.value)) {
573
+ return JSON.stringify({ error: `Invalid option value "${opt.value}". Must be lowercase snake_case.` });
574
+ }
575
+ }
576
+ }
577
+ const objectDef = await ctx.metadataService.getObject(objectName);
578
+ if (!objectDef) {
579
+ return JSON.stringify({ error: `Object "${objectName}" not found` });
580
+ }
581
+ const def = objectDef;
582
+ if (def.fields && def.fields[name]) {
583
+ return JSON.stringify({ error: `Field "${name}" already exists on object "${objectName}"` });
584
+ }
585
+ const fieldDef = {
586
+ type,
587
+ ...label ? { label } : {},
588
+ ...required !== void 0 ? { required } : {},
589
+ ...defaultValue !== void 0 ? { defaultValue } : {},
590
+ ...options ? { options } : {},
591
+ ...reference ? { reference } : {}
592
+ };
593
+ const updatedFields = { ...def.fields ?? {}, [name]: fieldDef };
594
+ await ctx.metadataService.register("object", objectName, {
595
+ ...def,
596
+ fields: updatedFields
597
+ });
598
+ return JSON.stringify({
599
+ objectName,
600
+ fieldName: name,
601
+ fieldType: type
602
+ });
603
+ };
604
+ }
605
+ function createModifyFieldHandler(ctx) {
606
+ return async (args) => {
607
+ const { objectName, fieldName, changes } = args;
608
+ if (!objectName || !fieldName || !changes) {
609
+ return JSON.stringify({ error: '"objectName", "fieldName", and "changes" are required' });
610
+ }
611
+ if (!isSnakeCase(objectName)) {
612
+ return JSON.stringify({ error: `Invalid object name "${objectName}". Must be snake_case.` });
613
+ }
614
+ if (!isSnakeCase(fieldName)) {
615
+ return JSON.stringify({ error: `Invalid field name "${fieldName}". Must be snake_case.` });
616
+ }
617
+ const objectDef = await ctx.metadataService.getObject(objectName);
618
+ if (!objectDef) {
619
+ return JSON.stringify({ error: `Object "${objectName}" not found` });
620
+ }
621
+ const def = objectDef;
622
+ if (!def.fields || !def.fields[fieldName]) {
623
+ return JSON.stringify({ error: `Field "${fieldName}" not found on object "${objectName}"` });
624
+ }
625
+ const existingField = def.fields[fieldName];
626
+ const updatedField = { ...existingField, ...changes };
627
+ const updatedFields = { ...def.fields, [fieldName]: updatedField };
628
+ await ctx.metadataService.register("object", objectName, {
629
+ ...def,
630
+ fields: updatedFields
631
+ });
632
+ return JSON.stringify({
633
+ objectName,
634
+ fieldName,
635
+ updatedProperties: Object.keys(changes)
636
+ });
637
+ };
638
+ }
639
+ function createDeleteFieldHandler(ctx) {
640
+ return async (args) => {
641
+ const { objectName, fieldName } = args;
642
+ if (!objectName || !fieldName) {
643
+ return JSON.stringify({ error: '"objectName" and "fieldName" are required' });
644
+ }
645
+ if (!isSnakeCase(objectName)) {
646
+ return JSON.stringify({ error: `Invalid object name "${objectName}". Must be snake_case.` });
647
+ }
648
+ if (!isSnakeCase(fieldName)) {
649
+ return JSON.stringify({ error: `Invalid field name "${fieldName}". Must be snake_case.` });
650
+ }
651
+ const objectDef = await ctx.metadataService.getObject(objectName);
652
+ if (!objectDef) {
653
+ return JSON.stringify({ error: `Object "${objectName}" not found` });
654
+ }
655
+ const def = objectDef;
656
+ if (!def.fields || !def.fields[fieldName]) {
657
+ return JSON.stringify({ error: `Field "${fieldName}" not found on object "${objectName}"` });
658
+ }
659
+ const { [fieldName]: _removed, ...remainingFields } = def.fields;
660
+ await ctx.metadataService.register("object", objectName, {
661
+ ...def,
662
+ fields: remainingFields
663
+ });
664
+ return JSON.stringify({
665
+ objectName,
666
+ fieldName,
667
+ success: true
668
+ });
669
+ };
670
+ }
671
+ function createListObjectsHandler(ctx) {
672
+ return async (args) => {
673
+ const { filter, includeFields } = args ?? {};
674
+ const objects = await ctx.metadataService.listObjects();
675
+ let result = objects.map((o) => {
676
+ const base = {
677
+ name: o.name,
678
+ label: o.label ?? o.name,
679
+ fieldCount: o.fields ? Object.keys(o.fields).length : 0
680
+ };
681
+ if (includeFields && o.fields) {
682
+ base.fields = Object.entries(o.fields).map(([key, f]) => ({
683
+ name: key,
684
+ type: f.type,
685
+ label: f.label ?? key
686
+ }));
687
+ }
688
+ return base;
689
+ });
690
+ if (filter) {
691
+ const lower = filter.toLowerCase();
692
+ result = result.filter(
693
+ (o) => o.name.toLowerCase().includes(lower) || o.label.toLowerCase().includes(lower)
694
+ );
695
+ }
696
+ return JSON.stringify({
697
+ objects: result,
698
+ totalCount: result.length
699
+ });
700
+ };
701
+ }
702
+ function createDescribeObjectHandler(ctx) {
703
+ return async (args) => {
704
+ const { objectName } = args;
705
+ if (!objectName) {
706
+ return JSON.stringify({ error: '"objectName" is required' });
707
+ }
708
+ if (!isSnakeCase(objectName)) {
709
+ return JSON.stringify({ error: `Invalid object name "${objectName}". Must be snake_case.` });
710
+ }
711
+ const objectDef = await ctx.metadataService.getObject(objectName);
712
+ if (!objectDef) {
713
+ return JSON.stringify({ error: `Object "${objectName}" not found` });
714
+ }
715
+ const def = objectDef;
716
+ const fields = def.fields ?? {};
717
+ const fieldSummary = Object.entries(fields).map(([key, f]) => ({
718
+ name: key,
719
+ type: f.type,
720
+ label: f.label ?? key,
721
+ required: f.required ?? false,
722
+ ...f.reference ? { reference: f.reference } : {},
723
+ ...f.options ? { options: f.options } : {}
724
+ }));
725
+ return JSON.stringify({
726
+ name: def.name,
727
+ label: def.label ?? def.name,
728
+ fields: fieldSummary,
729
+ enableFeatures: def.enable ?? {}
730
+ });
731
+ };
732
+ }
733
+ function registerMetadataTools(registry, context) {
734
+ registry.register(createObjectTool, createCreateObjectHandler(context));
735
+ registry.register(addFieldTool, createAddFieldHandler(context));
736
+ registry.register(modifyFieldTool, createModifyFieldHandler(context));
737
+ registry.register(deleteFieldTool, createDeleteFieldHandler(context));
738
+ registry.register(listObjectsTool, createListObjectsHandler(context));
739
+ registry.register(describeObjectTool, createDescribeObjectHandler(context));
740
+ }
741
+ var METADATA_TOOL_DEFINITIONS, SNAKE_CASE_RE;
742
+ var init_metadata_tools = __esm({
743
+ "src/tools/metadata-tools.ts"() {
744
+ "use strict";
745
+ init_create_object_tool();
746
+ init_add_field_tool();
747
+ init_modify_field_tool();
748
+ init_delete_field_tool();
749
+ init_list_objects_tool();
750
+ init_describe_object_tool();
751
+ init_create_object_tool();
752
+ init_add_field_tool();
753
+ init_modify_field_tool();
754
+ init_delete_field_tool();
755
+ init_list_objects_tool();
756
+ init_describe_object_tool();
757
+ METADATA_TOOL_DEFINITIONS = [
758
+ createObjectTool,
759
+ addFieldTool,
760
+ modifyFieldTool,
761
+ deleteFieldTool,
762
+ listObjectsTool,
763
+ describeObjectTool
764
+ ];
765
+ SNAKE_CASE_RE = /^[a-z_][a-z0-9_]*$/;
766
+ }
767
+ });
768
+
20
769
  // src/index.ts
21
770
  var index_exports = {};
22
771
  __export(index_exports, {
@@ -28,12 +777,25 @@ __export(index_exports, {
28
777
  DATA_CHAT_AGENT: () => DATA_CHAT_AGENT,
29
778
  DATA_TOOL_DEFINITIONS: () => DATA_TOOL_DEFINITIONS,
30
779
  InMemoryConversationService: () => InMemoryConversationService,
780
+ METADATA_ASSISTANT_AGENT: () => METADATA_ASSISTANT_AGENT,
781
+ METADATA_TOOL_DEFINITIONS: () => METADATA_TOOL_DEFINITIONS,
31
782
  MemoryLLMAdapter: () => MemoryLLMAdapter,
32
783
  ObjectQLConversationService: () => ObjectQLConversationService,
33
784
  ToolRegistry: () => ToolRegistry,
785
+ VercelLLMAdapter: () => VercelLLMAdapter,
786
+ addFieldTool: () => addFieldTool,
34
787
  buildAIRoutes: () => buildAIRoutes,
35
788
  buildAgentRoutes: () => buildAgentRoutes,
36
- registerDataTools: () => registerDataTools
789
+ buildToolRoutes: () => buildToolRoutes,
790
+ createObjectTool: () => createObjectTool,
791
+ deleteFieldTool: () => deleteFieldTool,
792
+ describeObjectTool: () => describeObjectTool,
793
+ encodeStreamPart: () => encodeStreamPart,
794
+ encodeVercelDataStream: () => encodeVercelDataStream,
795
+ listObjectsTool: () => listObjectsTool,
796
+ modifyFieldTool: () => modifyFieldTool,
797
+ registerDataTools: () => registerDataTools,
798
+ registerMetadataTools: () => registerMetadataTools
37
799
  });
38
800
  module.exports = __toCommonJS(index_exports);
39
801
 
@@ -47,7 +809,9 @@ var MemoryLLMAdapter = class {
47
809
  }
48
810
  async chat(messages, options) {
49
811
  const lastUserMessage = [...messages].reverse().find((m) => m.role === "user");
50
- const content = lastUserMessage ? `[memory] ${lastUserMessage.content}` : "[memory] (no user message)";
812
+ const userContent = lastUserMessage?.content;
813
+ const text = typeof userContent === "string" ? userContent : "(complex content)";
814
+ const content = lastUserMessage ? `[memory] ${text}` : "[memory] (no user message)";
51
815
  return {
52
816
  content,
53
817
  model: options?.model ?? "memory",
@@ -65,10 +829,15 @@ var MemoryLLMAdapter = class {
65
829
  const result = await this.chat(messages);
66
830
  const words = result.content.split(" ");
67
831
  for (let i = 0; i < words.length; i++) {
68
- const textDelta = i === 0 ? words[i] : ` ${words[i]}`;
69
- yield { type: "text-delta", textDelta };
832
+ const wordText = i === 0 ? words[i] : ` ${words[i]}`;
833
+ yield { type: "text-delta", id: `delta_${i}`, text: wordText };
70
834
  }
71
- yield { type: "finish", result };
835
+ yield {
836
+ type: "finish",
837
+ finishReason: "stop",
838
+ totalUsage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
839
+ rawFinishReason: "stop"
840
+ };
72
841
  }
73
842
  async embed(input) {
74
843
  const texts = Array.isArray(input) ? input : [input];
@@ -131,21 +900,34 @@ var ToolRegistry = class {
131
900
  * Execute a tool call and return the result.
132
901
  */
133
902
  async execute(toolCall) {
134
- const handler = this.handlers.get(toolCall.name);
903
+ const handler = this.handlers.get(toolCall.toolName);
135
904
  if (!handler) {
136
905
  return {
137
- toolCallId: toolCall.id,
138
- content: `Tool "${toolCall.name}" is not registered`,
906
+ type: "tool-result",
907
+ toolCallId: toolCall.toolCallId,
908
+ toolName: toolCall.toolName,
909
+ output: { type: "text", value: `Tool "${toolCall.toolName}" is not registered` },
139
910
  isError: true
140
911
  };
141
912
  }
142
913
  try {
143
- const args = JSON.parse(toolCall.arguments);
914
+ const args = typeof toolCall.input === "string" ? JSON.parse(toolCall.input) : toolCall.input ?? {};
144
915
  const content = await handler(args);
145
- return { toolCallId: toolCall.id, content };
916
+ return {
917
+ type: "tool-result",
918
+ toolCallId: toolCall.toolCallId,
919
+ toolName: toolCall.toolName,
920
+ output: { type: "text", value: content }
921
+ };
146
922
  } catch (err) {
147
923
  const message = err instanceof Error ? err.message : String(err);
148
- return { toolCallId: toolCall.id, content: message, isError: true };
924
+ return {
925
+ type: "tool-result",
926
+ toolCallId: toolCall.toolCallId,
927
+ toolName: toolCall.toolName,
928
+ output: { type: "text", value: message },
929
+ isError: true
930
+ };
149
931
  }
150
932
  }
151
933
  /**
@@ -231,6 +1013,17 @@ var InMemoryConversationService = class {
231
1013
  };
232
1014
 
233
1015
  // src/ai-service.ts
1016
+ function textDeltaPart(id, text) {
1017
+ return { type: "text-delta", id, text };
1018
+ }
1019
+ function finishPart(result) {
1020
+ return {
1021
+ type: "finish",
1022
+ finishReason: "stop",
1023
+ totalUsage: result?.usage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
1024
+ rawFinishReason: "stop"
1025
+ };
1026
+ }
234
1027
  var _AIService = class _AIService {
235
1028
  constructor(config = {}) {
236
1029
  this.adapter = config.adapter ?? new MemoryLLMAdapter();
@@ -258,8 +1051,8 @@ var _AIService = class _AIService {
258
1051
  this.logger.debug("[AI] streamChat", { messageCount: messages.length, model: options?.model });
259
1052
  if (!this.adapter.streamChat) {
260
1053
  const result = await this.adapter.chat(messages, options);
261
- yield { type: "text-delta", textDelta: result.content };
262
- yield { type: "finish", result };
1054
+ yield textDeltaPart("fallback", result.content);
1055
+ yield finishPart(result);
263
1056
  return;
264
1057
  }
265
1058
  yield* this.adapter.streamChat(messages, options);
@@ -276,6 +1069,10 @@ var _AIService = class _AIService {
276
1069
  }
277
1070
  return this.adapter.listModels();
278
1071
  }
1072
+ /** Extract the text value from a ToolExecutionResult's output. */
1073
+ static extractOutputText(tr) {
1074
+ return tr.output && typeof tr.output === "object" && "value" in tr.output ? String(tr.output.value) : "unknown error";
1075
+ }
279
1076
  /**
280
1077
  * Chat with automatic tool call resolution.
281
1078
  *
@@ -316,23 +1113,26 @@ var _AIService = class _AIService {
316
1113
  }
317
1114
  this.logger.debug("[AI] chatWithTools tool calls", {
318
1115
  iteration,
319
- calls: result.toolCalls.map((tc) => tc.name)
1116
+ calls: result.toolCalls.map((tc) => tc.toolName)
320
1117
  });
1118
+ const assistantContent = [];
1119
+ if (result.content) assistantContent.push({ type: "text", text: result.content });
1120
+ assistantContent.push(...result.toolCalls);
321
1121
  conversation.push({
322
1122
  role: "assistant",
323
- content: result.content ?? "",
324
- toolCalls: result.toolCalls
1123
+ content: assistantContent
325
1124
  });
326
1125
  const toolResults = await this.toolRegistry.executeAll(result.toolCalls);
327
1126
  for (const tr of toolResults) {
328
1127
  if (tr.isError) {
329
- const matchedCall = result.toolCalls.find((tc) => tc.id === tr.toolCallId);
330
- const toolName = matchedCall?.name ?? "unknown";
331
- const errorEntry = { iteration, toolName, error: tr.content };
1128
+ const matchedCall = result.toolCalls.find((tc) => tc.toolCallId === tr.toolCallId);
1129
+ const toolName = matchedCall?.toolName ?? "unknown";
1130
+ const errorText = _AIService.extractOutputText(tr);
1131
+ const errorEntry = { iteration, toolName, error: errorText };
332
1132
  toolErrors.push(errorEntry);
333
1133
  this.logger.warn("[AI] chatWithTools tool error", errorEntry);
334
1134
  if (onToolError && matchedCall) {
335
- const action = onToolError(matchedCall, tr.content);
1135
+ const action = onToolError(matchedCall, errorText);
336
1136
  if (action === "abort") {
337
1137
  abortedByCallback = true;
338
1138
  }
@@ -340,8 +1140,7 @@ var _AIService = class _AIService {
340
1140
  }
341
1141
  conversation.push({
342
1142
  role: "tool",
343
- content: tr.content,
344
- toolCallId: tr.toolCallId
1143
+ content: [tr]
345
1144
  });
346
1145
  }
347
1146
  if (abortedByCallback) {
@@ -387,33 +1186,41 @@ var _AIService = class _AIService {
387
1186
  for (let iteration = 0; iteration < maxIterations; iteration++) {
388
1187
  const result2 = await this.adapter.chat(conversation, chatOptions);
389
1188
  if (!result2.toolCalls || result2.toolCalls.length === 0) {
390
- yield { type: "text-delta", textDelta: result2.content };
391
- yield { type: "finish", result: result2 };
1189
+ yield textDeltaPart("stream", result2.content);
1190
+ yield finishPart(result2);
392
1191
  return;
393
1192
  }
394
1193
  for (const tc of result2.toolCalls) {
395
- yield { type: "tool-call", toolCall: tc };
1194
+ yield { type: "tool-call", toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.input };
396
1195
  }
1196
+ const assistantContent = [];
1197
+ if (result2.content) assistantContent.push({ type: "text", text: result2.content });
1198
+ assistantContent.push(...result2.toolCalls);
397
1199
  conversation.push({
398
1200
  role: "assistant",
399
- content: result2.content ?? "",
400
- toolCalls: result2.toolCalls
1201
+ content: assistantContent
401
1202
  });
402
1203
  const toolResults = await this.toolRegistry.executeAll(result2.toolCalls);
403
1204
  for (const tr of toolResults) {
404
1205
  if (tr.isError && onToolError) {
405
- const matchedCall = result2.toolCalls.find((tc) => tc.id === tr.toolCallId);
1206
+ const matchedCall = result2.toolCalls.find((tc) => tc.toolCallId === tr.toolCallId);
406
1207
  if (matchedCall) {
407
- const action = onToolError(matchedCall, tr.content);
1208
+ const errorText = _AIService.extractOutputText(tr);
1209
+ const action = onToolError(matchedCall, errorText);
408
1210
  if (action === "abort") {
409
1211
  abortedByCallback = true;
410
1212
  }
411
1213
  }
412
1214
  }
1215
+ yield {
1216
+ type: "tool-result",
1217
+ toolCallId: tr.toolCallId,
1218
+ toolName: tr.toolName,
1219
+ output: tr.output
1220
+ };
413
1221
  conversation.push({
414
1222
  role: "tool",
415
- content: tr.content,
416
- toolCallId: tr.toolCallId
1223
+ content: [tr]
417
1224
  });
418
1225
  }
419
1226
  if (abortedByCallback) {
@@ -427,8 +1234,8 @@ var _AIService = class _AIService {
427
1234
  }
428
1235
  const finalOptions = { ...chatOptions, tools: void 0, toolChoice: void 0 };
429
1236
  const result = await this.adapter.chat(conversation, finalOptions);
430
- yield { type: "text-delta", textDelta: result.content };
431
- yield { type: "finish", result };
1237
+ yield textDeltaPart("stream", result.content);
1238
+ yield finishPart(result);
432
1239
  }
433
1240
  };
434
1241
  // ── Tool Call Loop ────────────────────────────────────────────
@@ -436,6 +1243,143 @@ var _AIService = class _AIService {
436
1243
  _AIService.DEFAULT_MAX_ITERATIONS = 10;
437
1244
  var AIService = _AIService;
438
1245
 
1246
+ // src/stream/vercel-stream-encoder.ts
1247
+ function sse(data) {
1248
+ return `data: ${JSON.stringify(data)}
1249
+
1250
+ `;
1251
+ }
1252
+ function dataStreamLine(prefix, data) {
1253
+ return `${prefix}:${JSON.stringify(data)}
1254
+ `;
1255
+ }
1256
+ function encodeStreamPart(part) {
1257
+ switch (part.type) {
1258
+ case "text-delta":
1259
+ return sse({ type: "text-delta", id: "0", delta: part.text });
1260
+ case "tool-input-start":
1261
+ return sse({
1262
+ type: "tool-input-start",
1263
+ toolCallId: part.id,
1264
+ toolName: part.toolName
1265
+ });
1266
+ case "tool-input-delta":
1267
+ return sse({
1268
+ type: "tool-input-delta",
1269
+ toolCallId: part.id,
1270
+ inputTextDelta: part.delta
1271
+ });
1272
+ case "tool-call":
1273
+ return sse({
1274
+ type: "tool-input-available",
1275
+ toolCallId: part.toolCallId,
1276
+ toolName: part.toolName,
1277
+ input: part.input
1278
+ });
1279
+ case "tool-result":
1280
+ return sse({
1281
+ type: "tool-output-available",
1282
+ toolCallId: part.toolCallId,
1283
+ output: part.output
1284
+ });
1285
+ case "error":
1286
+ return sse({
1287
+ type: "error",
1288
+ errorText: String(part.error)
1289
+ });
1290
+ // Handle reasoning/thinking streams (DeepSeek R1, o1-style models)
1291
+ // Use 'g:' prefix for reasoning content per Vercel AI SDK protocol
1292
+ case "reasoning-start":
1293
+ return dataStreamLine("g", { text: "" });
1294
+ case "reasoning-delta":
1295
+ return dataStreamLine("g", { text: part.text });
1296
+ case "reasoning-end":
1297
+ return "";
1298
+ // No specific end marker needed for reasoning
1299
+ // finish-step and finish are handled by the generator, not here
1300
+ default:
1301
+ if (part.type?.startsWith("step-")) {
1302
+ return sse(part);
1303
+ }
1304
+ return "";
1305
+ }
1306
+ }
1307
+ async function* encodeVercelDataStream(events) {
1308
+ yield sse({ type: "start" });
1309
+ yield sse({ type: "start-step" });
1310
+ yield sse({ type: "text-start", id: "0" });
1311
+ let textOpen = true;
1312
+ let finishReason = "stop";
1313
+ for await (const part of events) {
1314
+ if (part.type === "finish") {
1315
+ finishReason = part.finishReason ?? "stop";
1316
+ }
1317
+ if (part.type === "finish-step" || part.type === "finish") {
1318
+ if (textOpen) {
1319
+ yield sse({ type: "text-end", id: "0" });
1320
+ textOpen = false;
1321
+ }
1322
+ continue;
1323
+ }
1324
+ const frame = encodeStreamPart(part);
1325
+ if (frame) {
1326
+ yield frame;
1327
+ }
1328
+ }
1329
+ if (textOpen) {
1330
+ yield sse({ type: "text-end", id: "0" });
1331
+ }
1332
+ yield sse({ type: "finish-step" });
1333
+ yield sse({ type: "finish", finishReason });
1334
+ yield "data: [DONE]\n\n";
1335
+ }
1336
+
1337
+ // src/routes/message-utils.ts
1338
+ function normalizeMessage(raw) {
1339
+ const role = raw.role;
1340
+ if (typeof raw.content === "string") {
1341
+ return { role, content: raw.content };
1342
+ }
1343
+ if (Array.isArray(raw.content)) {
1344
+ return { role, content: raw.content };
1345
+ }
1346
+ if (Array.isArray(raw.parts)) {
1347
+ const textParts = raw.parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text);
1348
+ if (textParts.length > 0) {
1349
+ return { role, content: textParts.join("") };
1350
+ }
1351
+ }
1352
+ return { role, content: "" };
1353
+ }
1354
+ function validateMessageContent(msg, opts) {
1355
+ const content = msg.content;
1356
+ if (Array.isArray(msg.parts)) {
1357
+ return null;
1358
+ }
1359
+ if (typeof content === "string") {
1360
+ return null;
1361
+ }
1362
+ if (Array.isArray(content)) {
1363
+ for (const part of content) {
1364
+ if (typeof part !== "object" || part === null) {
1365
+ return "message.content array elements must be non-null objects";
1366
+ }
1367
+ const partObj = part;
1368
+ if (typeof partObj.type !== "string") {
1369
+ return 'each message.content array element must have a string "type" property';
1370
+ }
1371
+ if (partObj.type === "text" && typeof partObj.text !== "string") {
1372
+ return 'message.content elements with type "text" must have a string "text" property';
1373
+ }
1374
+ }
1375
+ return null;
1376
+ }
1377
+ if ((content === null || content === void 0) && opts?.allowEmptyContent) {
1378
+ return null;
1379
+ }
1380
+ return "message.content must be a string, an array, or include parts";
1381
+ }
1382
+
439
1383
  // src/routes/ai-routes.ts
440
1384
  var VALID_ROLES = /* @__PURE__ */ new Set(["system", "user", "assistant", "tool"]);
441
1385
  function validateMessage(raw) {
@@ -446,22 +1390,30 @@ function validateMessage(raw) {
446
1390
  if (typeof msg.role !== "string" || !VALID_ROLES.has(msg.role)) {
447
1391
  return `message.role must be one of ${[...VALID_ROLES].map((r) => `"${r}"`).join(", ")}`;
448
1392
  }
449
- if (typeof msg.content !== "string") {
450
- return "message.content must be a string";
451
- }
452
- return null;
1393
+ const allowEmpty = msg.role === "assistant" || msg.role === "tool";
1394
+ return validateMessageContent(msg, { allowEmptyContent: allowEmpty });
453
1395
  }
454
1396
  function buildAIRoutes(aiService, conversationService, logger) {
455
1397
  return [
456
1398
  // ── Chat ────────────────────────────────────────────────────
1399
+ //
1400
+ // Dual-mode endpoint compatible with both the legacy ObjectStack
1401
+ // format (`{ messages, options }`) and the Vercel AI SDK useChat
1402
+ // flat format (`{ messages, system, model, stream, … }`).
1403
+ //
1404
+ // Behaviour:
1405
+ // • `stream !== false` → Vercel Data Stream Protocol (SSE)
1406
+ // • `stream === false` → JSON response (legacy)
1407
+ //
457
1408
  {
458
1409
  method: "POST",
459
1410
  path: "/api/v1/ai/chat",
460
- description: "Synchronous chat completion",
1411
+ description: "Chat completion (supports Vercel AI Data Stream Protocol)",
461
1412
  auth: true,
462
1413
  permissions: ["ai:chat"],
463
1414
  handler: async (req) => {
464
- const { messages, options } = req.body ?? {};
1415
+ const body = req.body ?? {};
1416
+ const messages = body.messages;
465
1417
  if (!Array.isArray(messages) || messages.length === 0) {
466
1418
  return { status: 400, body: { error: "messages array is required" } };
467
1419
  }
@@ -469,8 +1421,49 @@ function buildAIRoutes(aiService, conversationService, logger) {
469
1421
  const err = validateMessage(msg);
470
1422
  if (err) return { status: 400, body: { error: err } };
471
1423
  }
1424
+ const nested = body.options ?? {};
1425
+ const resolvedOptions = {
1426
+ ...nested,
1427
+ ...body.model != null && { model: body.model },
1428
+ ...body.temperature != null && { temperature: body.temperature },
1429
+ ...body.maxTokens != null && { maxTokens: body.maxTokens }
1430
+ };
1431
+ const rawSystemPrompt = body.system ?? body.systemPrompt;
1432
+ if (rawSystemPrompt != null && typeof rawSystemPrompt !== "string") {
1433
+ return { status: 400, body: { error: "system/systemPrompt must be a string" } };
1434
+ }
1435
+ const systemPrompt = rawSystemPrompt;
1436
+ const finalMessages = [
1437
+ ...systemPrompt ? [{ role: "system", content: systemPrompt }] : [],
1438
+ ...messages.map((m) => normalizeMessage(m))
1439
+ ];
1440
+ const wantStream = body.stream !== false;
1441
+ if (wantStream) {
1442
+ try {
1443
+ if (!aiService.streamChatWithTools) {
1444
+ return { status: 501, body: { error: "Streaming is not supported by the configured AI service" } };
1445
+ }
1446
+ const events = aiService.streamChatWithTools(finalMessages, resolvedOptions);
1447
+ return {
1448
+ status: 200,
1449
+ stream: true,
1450
+ vercelDataStream: true,
1451
+ contentType: "text/event-stream",
1452
+ headers: {
1453
+ "Content-Type": "text/event-stream",
1454
+ "Cache-Control": "no-cache",
1455
+ "Connection": "keep-alive",
1456
+ "x-vercel-ai-ui-message-stream": "v1"
1457
+ },
1458
+ events: encodeVercelDataStream(events)
1459
+ };
1460
+ } catch (err) {
1461
+ logger.error("[AI Route] /chat stream error", err instanceof Error ? err : void 0);
1462
+ return { status: 500, body: { error: "Internal AI service error" } };
1463
+ }
1464
+ }
472
1465
  try {
473
- const result = await aiService.chat(messages, options);
1466
+ const result = await aiService.chatWithTools(finalMessages, resolvedOptions);
474
1467
  return { status: 200, body: result };
475
1468
  } catch (err) {
476
1469
  logger.error("[AI Route] /chat error", err instanceof Error ? err : void 0);
@@ -498,7 +1491,7 @@ function buildAIRoutes(aiService, conversationService, logger) {
498
1491
  if (!aiService.streamChat) {
499
1492
  return { status: 501, body: { error: "Streaming is not supported by the configured AI service" } };
500
1493
  }
501
- const events = aiService.streamChat(messages, options);
1494
+ const events = aiService.streamChat(messages.map((m) => normalizeMessage(m)), options);
502
1495
  return { status: 200, stream: true, events };
503
1496
  } catch (err) {
504
1497
  logger.error("[AI Route] /chat/stream error", err instanceof Error ? err : void 0);
@@ -676,17 +1669,41 @@ function validateAgentMessage(raw) {
676
1669
  if (typeof msg.role !== "string" || !ALLOWED_AGENT_ROLES.has(msg.role)) {
677
1670
  return `message.role must be one of ${[...ALLOWED_AGENT_ROLES].map((r) => `"${r}"`).join(", ")} for agent chat`;
678
1671
  }
679
- if (typeof msg.content !== "string") {
680
- return "message.content must be a string";
681
- }
682
- return null;
1672
+ const allowEmpty = msg.role === "assistant";
1673
+ return validateMessageContent(msg, { allowEmptyContent: allowEmpty });
683
1674
  }
684
1675
  function buildAgentRoutes(aiService, agentRuntime, logger) {
685
1676
  return [
1677
+ // ── List active agents ──────────────────────────────────────
1678
+ {
1679
+ method: "GET",
1680
+ path: "/api/v1/ai/agents",
1681
+ description: "List all active AI agents",
1682
+ auth: true,
1683
+ permissions: ["ai:chat"],
1684
+ handler: async () => {
1685
+ try {
1686
+ const agents = await agentRuntime.listAgents();
1687
+ return { status: 200, body: { agents } };
1688
+ } catch (err) {
1689
+ logger.error(
1690
+ "[AI Route] /agents list error",
1691
+ err instanceof Error ? err : void 0
1692
+ );
1693
+ return { status: 500, body: { error: "Internal AI service error" } };
1694
+ }
1695
+ }
1696
+ },
1697
+ // ── Chat with a specific agent ──────────────────────────────
1698
+ //
1699
+ // Dual-mode endpoint matching the general chat route behaviour:
1700
+ // • `stream !== false` → Vercel Data Stream Protocol (SSE)
1701
+ // • `stream === false` → JSON response (legacy)
1702
+ //
686
1703
  {
687
1704
  method: "POST",
688
1705
  path: "/api/v1/ai/agents/:agentName/chat",
689
- description: "Chat with a specific AI agent",
1706
+ description: "Chat with a specific AI agent (supports Vercel AI Data Stream Protocol)",
690
1707
  auth: true,
691
1708
  permissions: ["ai:chat", "ai:agents"],
692
1709
  handler: async (req) => {
@@ -694,11 +1711,12 @@ function buildAgentRoutes(aiService, agentRuntime, logger) {
694
1711
  if (!agentName) {
695
1712
  return { status: 400, body: { error: "agentName parameter is required" } };
696
1713
  }
1714
+ const body = req.body ?? {};
697
1715
  const {
698
1716
  messages: rawMessages,
699
1717
  context: chatContext,
700
1718
  options: extraOptions
701
- } = req.body ?? {};
1719
+ } = body;
702
1720
  if (!Array.isArray(rawMessages) || rawMessages.length === 0) {
703
1721
  return { status: 400, body: { error: "messages array is required" } };
704
1722
  }
@@ -726,21 +1744,150 @@ function buildAgentRoutes(aiService, agentRuntime, logger) {
726
1744
  if (ALLOWED_KEYS.has(key)) {
727
1745
  safeOverrides[key] = extraOptions[key];
728
1746
  }
729
- }
1747
+ }
1748
+ }
1749
+ const mergedOptions = { ...agentOptions, ...safeOverrides };
1750
+ const fullMessages = [
1751
+ ...systemMessages,
1752
+ ...rawMessages.map((m) => normalizeMessage(m))
1753
+ ];
1754
+ const chatWithToolsOptions = {
1755
+ ...mergedOptions,
1756
+ maxIterations: agent.planning?.maxIterations
1757
+ };
1758
+ const wantStream = body.stream !== false;
1759
+ if (wantStream) {
1760
+ if (!aiService.streamChatWithTools) {
1761
+ return { status: 501, body: { error: "Streaming is not supported by the configured AI service" } };
1762
+ }
1763
+ const events = aiService.streamChatWithTools(fullMessages, chatWithToolsOptions);
1764
+ return {
1765
+ status: 200,
1766
+ stream: true,
1767
+ vercelDataStream: true,
1768
+ contentType: "text/event-stream",
1769
+ headers: {
1770
+ "Content-Type": "text/event-stream",
1771
+ "Cache-Control": "no-cache",
1772
+ "Connection": "keep-alive",
1773
+ "x-vercel-ai-ui-message-stream": "v1"
1774
+ },
1775
+ events: encodeVercelDataStream(events)
1776
+ };
1777
+ }
1778
+ const result = await aiService.chatWithTools(fullMessages, chatWithToolsOptions);
1779
+ return { status: 200, body: result };
1780
+ } catch (err) {
1781
+ logger.error(
1782
+ "[AI Route] /agents/:agentName/chat error",
1783
+ err instanceof Error ? err : void 0
1784
+ );
1785
+ return { status: 500, body: { error: "Internal AI service error" } };
1786
+ }
1787
+ }
1788
+ }
1789
+ ];
1790
+ }
1791
+
1792
+ // src/routes/tool-routes.ts
1793
+ function extractOutputValue(output) {
1794
+ if (!output) return "";
1795
+ if (typeof output === "string") return output;
1796
+ if (typeof output === "object" && "value" in output) {
1797
+ return String(output.value ?? "");
1798
+ }
1799
+ return JSON.stringify(output);
1800
+ }
1801
+ function buildToolRoutes(aiService, logger) {
1802
+ return [
1803
+ // ── List registered tools ──────────────────────────────────────
1804
+ {
1805
+ method: "GET",
1806
+ path: "/api/v1/ai/tools",
1807
+ description: "List all registered AI tools",
1808
+ auth: true,
1809
+ permissions: ["ai:tools"],
1810
+ handler: async () => {
1811
+ try {
1812
+ const tools = aiService.toolRegistry.getAll();
1813
+ return {
1814
+ status: 200,
1815
+ body: {
1816
+ tools: tools.map((t) => ({
1817
+ name: t.name,
1818
+ description: t.description,
1819
+ category: t.category
1820
+ }))
1821
+ }
1822
+ };
1823
+ } catch (err) {
1824
+ logger.error(
1825
+ "[AI Route] /tools list error",
1826
+ err instanceof Error ? err : void 0
1827
+ );
1828
+ return { status: 500, body: { error: "Internal AI service error" } };
1829
+ }
1830
+ }
1831
+ },
1832
+ // ── Execute a tool ──────────────────────────────────────────────
1833
+ //
1834
+ // Executes a tool with the provided parameters.
1835
+ // This is intended for testing/playground use.
1836
+ //
1837
+ {
1838
+ method: "POST",
1839
+ path: "/api/v1/ai/tools/:toolName/execute",
1840
+ description: "Execute a tool with parameters (playground/testing)",
1841
+ auth: true,
1842
+ permissions: ["ai:tools", "ai:execute"],
1843
+ handler: async (req) => {
1844
+ const toolName = req.params?.toolName;
1845
+ if (!toolName) {
1846
+ return { status: 400, body: { error: "toolName parameter is required" } };
1847
+ }
1848
+ const body = req.body ?? {};
1849
+ const { parameters } = body;
1850
+ if (!parameters || typeof parameters !== "object") {
1851
+ return { status: 400, body: { error: "parameters object is required" } };
1852
+ }
1853
+ try {
1854
+ if (!aiService.toolRegistry.has(toolName)) {
1855
+ return { status: 404, body: { error: `Tool "${toolName}" not found` } };
1856
+ }
1857
+ const startTime = Date.now();
1858
+ const toolCallPart = {
1859
+ type: "tool-call",
1860
+ toolCallId: `playground-${Date.now()}`,
1861
+ toolName,
1862
+ input: parameters
1863
+ };
1864
+ const result = await aiService.toolRegistry.execute(toolCallPart);
1865
+ const duration = Date.now() - startTime;
1866
+ if (result.isError) {
1867
+ const errorMsg = extractOutputValue(result.output);
1868
+ logger.error(
1869
+ `[AI Route] Tool execution error: ${toolName}`,
1870
+ new Error(errorMsg)
1871
+ );
1872
+ return {
1873
+ status: 500,
1874
+ body: {
1875
+ error: errorMsg,
1876
+ duration
1877
+ }
1878
+ };
730
1879
  }
731
- const mergedOptions = { ...agentOptions, ...safeOverrides };
732
- const fullMessages = [
733
- ...systemMessages,
734
- ...rawMessages
735
- ];
736
- const result = await aiService.chatWithTools(fullMessages, {
737
- ...mergedOptions,
738
- maxIterations: agent.planning?.maxIterations
739
- });
740
- return { status: 200, body: result };
1880
+ return {
1881
+ status: 200,
1882
+ body: {
1883
+ result: extractOutputValue(result.output),
1884
+ duration,
1885
+ toolName
1886
+ }
1887
+ };
741
1888
  } catch (err) {
742
1889
  logger.error(
743
- "[AI Route] /agents/:agentName/chat error",
1890
+ "[AI Route] /tools/:toolName/execute error",
744
1891
  err instanceof Error ? err : void 0
745
1892
  );
746
1893
  return { status: 500, body: { error: "Internal AI service error" } };
@@ -842,13 +1989,35 @@ var ObjectQLConversationService = class {
842
1989
  }
843
1990
  const now = (/* @__PURE__ */ new Date()).toISOString();
844
1991
  const msgId = `msg_${(0, import_node_crypto.randomUUID)()}`;
1992
+ let contentStr;
1993
+ let toolCallsJson = null;
1994
+ let toolCallId = null;
1995
+ if (message.role === "system" || message.role === "user") {
1996
+ contentStr = typeof message.content === "string" ? message.content : JSON.stringify(message.content);
1997
+ } else if (message.role === "assistant") {
1998
+ if (typeof message.content === "string") {
1999
+ contentStr = message.content;
2000
+ } else {
2001
+ const parts = message.content;
2002
+ const textParts = parts.filter((p) => p.type === "text").map((p) => p.text);
2003
+ const toolCalls = parts.filter((p) => p.type === "tool-call");
2004
+ contentStr = textParts.join("");
2005
+ if (toolCalls.length > 0) toolCallsJson = JSON.stringify(toolCalls);
2006
+ }
2007
+ } else if (message.role === "tool") {
2008
+ contentStr = JSON.stringify(message.content);
2009
+ const firstResult = Array.isArray(message.content) ? message.content[0] : void 0;
2010
+ if (firstResult && "toolCallId" in firstResult) toolCallId = firstResult.toolCallId;
2011
+ } else {
2012
+ contentStr = "";
2013
+ }
845
2014
  await this.engine.insert(MESSAGES_OBJECT, {
846
2015
  id: msgId,
847
2016
  conversation_id: conversationId,
848
2017
  role: message.role,
849
- content: message.content,
850
- tool_calls: message.toolCalls ? JSON.stringify(message.toolCalls) : null,
851
- tool_call_id: message.toolCallId ?? null,
2018
+ content: contentStr,
2019
+ tool_calls: toolCallsJson,
2020
+ tool_call_id: toolCallId,
852
2021
  created_at: now
853
2022
  });
854
2023
  await this.engine.update(CONVERSATIONS_OBJECT, { id: conversationId, updated_at: now }, {
@@ -893,21 +2062,42 @@ var ObjectQLConversationService = class {
893
2062
  };
894
2063
  }
895
2064
  /**
896
- * Map a database row to an AIMessage.
2065
+ * Map a database row to a ModelMessage.
897
2066
  */
898
2067
  toMessage(row) {
899
- const msg = {
900
- role: row.role,
901
- content: row.content
902
- };
903
- const toolCalls = this.safeParse(row.tool_calls);
904
- if (toolCalls) {
905
- msg.toolCalls = toolCalls;
906
- }
907
- if (row.tool_call_id) {
908
- msg.toolCallId = row.tool_call_id;
2068
+ switch (row.role) {
2069
+ case "system":
2070
+ return { role: "system", content: row.content };
2071
+ case "user":
2072
+ return { role: "user", content: row.content };
2073
+ case "assistant": {
2074
+ const toolCalls = this.safeParse(row.tool_calls);
2075
+ if (toolCalls && toolCalls.length > 0) {
2076
+ const content = [];
2077
+ if (row.content) content.push({ type: "text", text: row.content });
2078
+ content.push(...toolCalls);
2079
+ return { role: "assistant", content };
2080
+ }
2081
+ return { role: "assistant", content: row.content };
2082
+ }
2083
+ case "tool": {
2084
+ const toolResults = this.safeParse(row.content);
2085
+ if (toolResults && toolResults.length > 0 && toolResults[0]?.type === "tool-result") {
2086
+ return { role: "tool", content: toolResults };
2087
+ }
2088
+ return {
2089
+ role: "tool",
2090
+ content: [{
2091
+ type: "tool-result",
2092
+ toolCallId: row.tool_call_id ?? "",
2093
+ toolName: "unknown",
2094
+ output: { type: "text", value: row.content }
2095
+ }]
2096
+ };
2097
+ }
2098
+ default:
2099
+ return { role: "user", content: row.content };
909
2100
  }
910
- return msg;
911
2101
  }
912
2102
  };
913
2103
 
@@ -1046,271 +2236,38 @@ var AiMessageObject = import_data2.ObjectSchema.create({
1046
2236
  }
1047
2237
  });
1048
2238
 
1049
- // src/tools/data-tools.ts
1050
- var MAX_QUERY_LIMIT = 200;
1051
- var DEFAULT_QUERY_LIMIT = 20;
1052
- var LIST_OBJECTS_TOOL = {
1053
- name: "list_objects",
1054
- description: "List all available data objects (tables) in the system. Returns object names and labels.",
1055
- parameters: {
1056
- type: "object",
1057
- properties: {},
1058
- additionalProperties: false
1059
- }
1060
- };
1061
- var DESCRIBE_OBJECT_TOOL = {
1062
- name: "describe_object",
1063
- description: "Get the schema (fields, types, labels) of a specific data object. Use this to understand the structure of a table before querying it.",
1064
- parameters: {
1065
- type: "object",
1066
- properties: {
1067
- objectName: {
1068
- type: "string",
1069
- description: "The snake_case name of the object to describe"
1070
- }
1071
- },
1072
- required: ["objectName"],
1073
- additionalProperties: false
1074
- }
1075
- };
1076
- var QUERY_RECORDS_TOOL = {
1077
- name: "query_records",
1078
- description: "Query records from a data object with optional filters, field selection, sorting, and pagination. Returns an array of matching records.",
1079
- parameters: {
1080
- type: "object",
1081
- properties: {
1082
- objectName: {
1083
- type: "string",
1084
- description: "The snake_case name of the object to query"
1085
- },
1086
- where: {
1087
- type: "object",
1088
- description: 'Filter conditions as key-value pairs (e.g. { "status": "active" }) or MongoDB-style operators (e.g. { "amount": { "$gt": 100 } })'
1089
- },
1090
- fields: {
1091
- type: "array",
1092
- items: { type: "string" },
1093
- description: "List of field names to return (omit for all fields)"
1094
- },
1095
- orderBy: {
1096
- type: "array",
1097
- items: {
1098
- type: "object",
1099
- properties: {
1100
- field: { type: "string" },
1101
- order: { type: "string", enum: ["asc", "desc"] }
1102
- }
1103
- },
1104
- description: 'Sort order (e.g. [{ "field": "created_at", "order": "desc" }])'
1105
- },
1106
- limit: {
1107
- type: "number",
1108
- description: `Maximum number of records to return (default ${DEFAULT_QUERY_LIMIT}, max ${MAX_QUERY_LIMIT})`
1109
- },
1110
- offset: {
1111
- type: "number",
1112
- description: "Number of records to skip for pagination"
1113
- }
1114
- },
1115
- required: ["objectName"],
1116
- additionalProperties: false
1117
- }
1118
- };
1119
- var GET_RECORD_TOOL = {
1120
- name: "get_record",
1121
- description: "Get a single record by its ID from a data object.",
1122
- parameters: {
1123
- type: "object",
1124
- properties: {
1125
- objectName: {
1126
- type: "string",
1127
- description: "The snake_case name of the object"
1128
- },
1129
- recordId: {
1130
- type: "string",
1131
- description: "The unique ID of the record"
1132
- },
1133
- fields: {
1134
- type: "array",
1135
- items: { type: "string" },
1136
- description: "List of field names to return (omit for all fields)"
1137
- }
1138
- },
1139
- required: ["objectName", "recordId"],
1140
- additionalProperties: false
1141
- }
1142
- };
1143
- var AGGREGATE_DATA_TOOL = {
1144
- name: "aggregate_data",
1145
- description: "Perform aggregation/statistical operations on a data object. Supports count, sum, avg, min, max with optional groupBy and where filters.",
1146
- parameters: {
1147
- type: "object",
1148
- properties: {
1149
- objectName: {
1150
- type: "string",
1151
- description: "The snake_case name of the object to aggregate"
1152
- },
1153
- aggregations: {
1154
- type: "array",
1155
- items: {
1156
- type: "object",
1157
- properties: {
1158
- function: {
1159
- type: "string",
1160
- enum: ["count", "sum", "avg", "min", "max", "count_distinct"],
1161
- description: "Aggregation function"
1162
- },
1163
- field: {
1164
- type: "string",
1165
- description: "Field to aggregate (optional for count)"
1166
- },
1167
- alias: {
1168
- type: "string",
1169
- description: "Result column alias"
1170
- }
1171
- },
1172
- required: ["function", "alias"]
1173
- },
1174
- description: "Aggregation definitions"
1175
- },
1176
- groupBy: {
1177
- type: "array",
1178
- items: { type: "string" },
1179
- description: "Fields to group by"
1180
- },
1181
- where: {
1182
- type: "object",
1183
- description: "Filter conditions applied before aggregation"
1184
- }
1185
- },
1186
- required: ["objectName", "aggregations"],
1187
- additionalProperties: false
1188
- }
1189
- };
1190
- var DATA_TOOL_DEFINITIONS = [
1191
- LIST_OBJECTS_TOOL,
1192
- DESCRIBE_OBJECT_TOOL,
1193
- QUERY_RECORDS_TOOL,
1194
- GET_RECORD_TOOL,
1195
- AGGREGATE_DATA_TOOL
1196
- ];
1197
- function createListObjectsHandler(ctx) {
1198
- return async () => {
1199
- const objects = await ctx.metadataService.listObjects();
1200
- const summary = objects.map((o) => ({
1201
- name: o.name,
1202
- label: o.label ?? o.name
1203
- }));
1204
- return JSON.stringify(summary);
1205
- };
1206
- }
1207
- function createDescribeObjectHandler(ctx) {
1208
- return async (args) => {
1209
- const { objectName } = args;
1210
- const objectDef = await ctx.metadataService.getObject(objectName);
1211
- if (!objectDef) {
1212
- return JSON.stringify({ error: `Object "${objectName}" not found` });
1213
- }
1214
- const def = objectDef;
1215
- const fields = def.fields ?? {};
1216
- const fieldSummary = {};
1217
- for (const [key, f] of Object.entries(fields)) {
1218
- fieldSummary[key] = {
1219
- type: f.type,
1220
- label: f.label ?? key,
1221
- required: f.required ?? false,
1222
- ...f.reference ? { reference: f.reference } : {},
1223
- ...f.options ? { options: f.options } : {}
1224
- };
1225
- }
1226
- return JSON.stringify({
1227
- name: def.name,
1228
- label: def.label ?? def.name,
1229
- fields: fieldSummary
1230
- });
1231
- };
1232
- }
1233
- function createQueryRecordsHandler(ctx) {
1234
- return async (args) => {
1235
- const {
1236
- objectName,
1237
- where,
1238
- fields,
1239
- orderBy,
1240
- limit,
1241
- offset
1242
- } = args;
1243
- const rawLimit = limit ?? DEFAULT_QUERY_LIMIT;
1244
- const safeLimit = Number.isFinite(rawLimit) && rawLimit > 0 ? Math.min(Math.floor(rawLimit), MAX_QUERY_LIMIT) : DEFAULT_QUERY_LIMIT;
1245
- const safeOffset = Number.isFinite(offset) && offset >= 0 ? Math.floor(offset) : void 0;
1246
- const records = await ctx.dataEngine.find(objectName, {
1247
- where,
1248
- fields,
1249
- orderBy,
1250
- limit: safeLimit,
1251
- offset: safeOffset
1252
- });
1253
- return JSON.stringify({ count: records.length, records });
1254
- };
1255
- }
1256
- function createGetRecordHandler(ctx) {
1257
- return async (args) => {
1258
- const { objectName, recordId, fields } = args;
1259
- const record = await ctx.dataEngine.findOne(objectName, {
1260
- where: { id: recordId },
1261
- fields
1262
- });
1263
- if (!record) {
1264
- return JSON.stringify({ error: `Record "${recordId}" not found in "${objectName}"` });
1265
- }
1266
- return JSON.stringify(record);
1267
- };
1268
- }
1269
- var VALID_AGG_FUNCTIONS = /* @__PURE__ */ new Set([
1270
- "count",
1271
- "sum",
1272
- "avg",
1273
- "min",
1274
- "max",
1275
- "count_distinct"
1276
- ]);
1277
- function createAggregateDataHandler(ctx) {
1278
- return async (args) => {
1279
- const { objectName, aggregations, groupBy, where } = args;
1280
- for (const a of aggregations) {
1281
- if (!VALID_AGG_FUNCTIONS.has(a.function)) {
1282
- return JSON.stringify({
1283
- error: `Invalid aggregation function "${a.function}". Allowed: ${[...VALID_AGG_FUNCTIONS].join(", ")}`
1284
- });
1285
- }
1286
- }
1287
- const result = await ctx.dataEngine.aggregate(objectName, {
1288
- where,
1289
- groupBy,
1290
- aggregations: aggregations.map((a) => ({
1291
- function: a.function,
1292
- field: a.field,
1293
- alias: a.alias
1294
- }))
1295
- });
1296
- return JSON.stringify(result);
1297
- };
1298
- }
1299
- function registerDataTools(registry, context) {
1300
- registry.register(LIST_OBJECTS_TOOL, createListObjectsHandler(context));
1301
- registry.register(DESCRIBE_OBJECT_TOOL, createDescribeObjectHandler(context));
1302
- registry.register(QUERY_RECORDS_TOOL, createQueryRecordsHandler(context));
1303
- registry.register(GET_RECORD_TOOL, createGetRecordHandler(context));
1304
- registry.register(AGGREGATE_DATA_TOOL, createAggregateDataHandler(context));
1305
- }
2239
+ // src/plugin.ts
2240
+ init_data_tools();
2241
+ init_metadata_tools();
1306
2242
 
1307
2243
  // src/agent-runtime.ts
1308
- var import_ai = require("@objectstack/spec/ai");
2244
+ var import_ai7 = require("@objectstack/spec/ai");
1309
2245
  var AgentRuntime = class {
1310
2246
  constructor(metadataService) {
1311
2247
  this.metadataService = metadataService;
1312
2248
  }
1313
2249
  // ── Public API ────────────────────────────────────────────────
2250
+ /**
2251
+ * List all active agents registered in the metadata service.
2252
+ *
2253
+ * Returns a summary for each agent (name, label, role) suitable
2254
+ * for populating an agent selector dropdown in the UI.
2255
+ */
2256
+ async listAgents() {
2257
+ const rawItems = await this.metadataService.list("agent");
2258
+ const agents = [];
2259
+ for (const raw of rawItems) {
2260
+ const result = import_ai7.AgentSchema.safeParse(raw);
2261
+ if (result.success && result.data.active) {
2262
+ agents.push({
2263
+ name: result.data.name,
2264
+ label: result.data.label,
2265
+ role: result.data.role
2266
+ });
2267
+ }
2268
+ }
2269
+ return agents;
2270
+ }
1314
2271
  /**
1315
2272
  * Load and validate an agent definition by name.
1316
2273
  *
@@ -1322,7 +2279,7 @@ var AgentRuntime = class {
1322
2279
  async loadAgent(agentName) {
1323
2280
  const raw = await this.metadataService.get("agent", agentName);
1324
2281
  if (!raw) return void 0;
1325
- const result = import_ai.AgentSchema.safeParse(raw);
2282
+ const result = import_ai7.AgentSchema.safeParse(raw);
1326
2283
  if (!result.success) {
1327
2284
  return void 0;
1328
2285
  }
@@ -1439,15 +2396,233 @@ Guidelines:
1439
2396
  }
1440
2397
  };
1441
2398
 
2399
+ // src/agents/metadata-assistant-agent.ts
2400
+ var METADATA_ASSISTANT_AGENT = {
2401
+ name: "metadata_assistant",
2402
+ label: "Metadata Assistant",
2403
+ role: "Schema Architect",
2404
+ instructions: `You are an expert metadata architect that helps users design and manage their data models through natural language.
2405
+
2406
+ Capabilities:
2407
+ - Create new data objects (tables) with fields
2408
+ - Add fields (columns) to existing objects
2409
+ - Modify field properties (label, type, required, default value)
2410
+ - Delete fields from objects
2411
+ - List all registered metadata objects and their schemas
2412
+ - Describe the full schema of a specific object
2413
+
2414
+ Guidelines:
2415
+ 1. Before creating a new object, use list_objects to check if a similar one already exists.
2416
+ 2. Before modifying or deleting fields, use describe_object to understand the current schema.
2417
+ 3. Always use snake_case for object names and field names (e.g. project_task, due_date).
2418
+ 4. Suggest meaningful field types based on the user's description (e.g. "deadline" \u2192 date, "active" \u2192 boolean).
2419
+ 5. When creating objects, propose a reasonable set of initial fields based on the entity type.
2420
+ 6. Explain what changes you are about to make before executing them.
2421
+ 7. After making changes, confirm the result by describing the updated schema.
2422
+ 8. For destructive operations (deleting fields), always warn the user about potential data loss.
2423
+ 9. Always answer in the same language the user is using.
2424
+ 10. If the user's request is ambiguous, ask clarifying questions before proceeding.`,
2425
+ model: {
2426
+ provider: "openai",
2427
+ model: "gpt-4",
2428
+ temperature: 0.2,
2429
+ maxTokens: 4096
2430
+ },
2431
+ tools: [
2432
+ { type: "action", name: "create_object", description: "Create a new data object (table)" },
2433
+ { type: "action", name: "add_field", description: "Add a field to an existing object" },
2434
+ { type: "action", name: "modify_field", description: "Modify an existing field definition" },
2435
+ { type: "action", name: "delete_field", description: "Delete a field from an object" },
2436
+ { type: "query", name: "list_objects", description: "List all data objects" },
2437
+ { type: "query", name: "describe_object", description: "Describe an object schema" }
2438
+ ],
2439
+ active: true,
2440
+ visibility: "global",
2441
+ guardrails: {
2442
+ maxTokensPerInvocation: 8192,
2443
+ maxExecutionTimeSec: 60,
2444
+ blockedTopics: ["drop_database", "raw_sql", "system_tables"]
2445
+ },
2446
+ planning: {
2447
+ strategy: "react",
2448
+ maxIterations: 10,
2449
+ allowReplan: true
2450
+ },
2451
+ memory: {
2452
+ shortTerm: {
2453
+ maxMessages: 30,
2454
+ maxTokens: 8192
2455
+ }
2456
+ }
2457
+ };
2458
+
2459
+ // src/adapters/vercel-adapter.ts
2460
+ var import_ai8 = require("ai");
2461
+ function buildVercelOptions(options) {
2462
+ if (!options) return {};
2463
+ const opts = {};
2464
+ if (options.temperature != null) opts.temperature = options.temperature;
2465
+ if (options.maxTokens != null) opts.maxTokens = options.maxTokens;
2466
+ if (options.stop?.length) opts.stopSequences = options.stop;
2467
+ if (options.tools?.length) {
2468
+ const tools = {};
2469
+ for (const t of options.tools) {
2470
+ tools[t.name] = (0, import_ai8.tool)({
2471
+ description: t.description,
2472
+ inputSchema: (0, import_ai8.jsonSchema)(t.parameters)
2473
+ });
2474
+ }
2475
+ opts.tools = tools;
2476
+ }
2477
+ if (options.toolChoice != null) {
2478
+ opts.toolChoice = options.toolChoice;
2479
+ }
2480
+ return opts;
2481
+ }
2482
+ var VercelLLMAdapter = class {
2483
+ constructor(config) {
2484
+ this.name = "vercel";
2485
+ this.model = config.model;
2486
+ }
2487
+ async chat(messages, options) {
2488
+ const result = await (0, import_ai8.generateText)({
2489
+ model: this.model,
2490
+ messages,
2491
+ ...buildVercelOptions(options)
2492
+ });
2493
+ return {
2494
+ content: result.text,
2495
+ model: result.response?.modelId,
2496
+ toolCalls: result.toolCalls?.length ? result.toolCalls : void 0,
2497
+ usage: result.usage ? {
2498
+ promptTokens: result.usage.inputTokens ?? 0,
2499
+ completionTokens: result.usage.outputTokens ?? 0,
2500
+ totalTokens: result.usage.totalTokens ?? 0
2501
+ } : void 0
2502
+ };
2503
+ }
2504
+ async complete(prompt, options) {
2505
+ const result = await (0, import_ai8.generateText)({
2506
+ model: this.model,
2507
+ prompt,
2508
+ ...buildVercelOptions(options)
2509
+ });
2510
+ return {
2511
+ content: result.text,
2512
+ model: result.response?.modelId,
2513
+ usage: result.usage ? {
2514
+ promptTokens: result.usage.inputTokens ?? 0,
2515
+ completionTokens: result.usage.outputTokens ?? 0,
2516
+ totalTokens: result.usage.totalTokens ?? 0
2517
+ } : void 0
2518
+ };
2519
+ }
2520
+ async *streamChat(messages, options) {
2521
+ const result = (0, import_ai8.streamText)({
2522
+ model: this.model,
2523
+ messages,
2524
+ ...buildVercelOptions(options)
2525
+ });
2526
+ for await (const part of result.fullStream) {
2527
+ yield part;
2528
+ }
2529
+ }
2530
+ async embed(_input) {
2531
+ throw new Error(
2532
+ "[VercelLLMAdapter] Embeddings require a dedicated EmbeddingModel. Configure an embedding adapter instead."
2533
+ );
2534
+ }
2535
+ async listModels() {
2536
+ return [];
2537
+ }
2538
+ };
2539
+
1442
2540
  // src/plugin.ts
1443
2541
  var AIServicePlugin = class {
1444
2542
  constructor(options = {}) {
1445
2543
  this.name = "com.objectstack.service-ai";
1446
2544
  this.version = "1.0.0";
1447
2545
  this.type = "standard";
1448
- this.dependencies = [];
2546
+ this.dependencies = ["com.objectstack.engine.objectql"];
1449
2547
  this.options = options;
1450
2548
  }
2549
+ /**
2550
+ * Auto-detect LLM provider from environment variables.
2551
+ *
2552
+ * Priority order:
2553
+ * 1. AI_GATEWAY_MODEL → Vercel AI Gateway
2554
+ * 2. OPENAI_API_KEY → OpenAI
2555
+ * 3. ANTHROPIC_API_KEY → Anthropic
2556
+ * 4. GOOGLE_GENERATIVE_AI_API_KEY → Google
2557
+ * 5. Fallback → MemoryLLMAdapter
2558
+ *
2559
+ * Returns the adapter and a description for logging.
2560
+ */
2561
+ async detectAdapter(ctx) {
2562
+ const gatewayModel = process.env.AI_GATEWAY_MODEL;
2563
+ if (gatewayModel) {
2564
+ try {
2565
+ const gatewayPkg = "@ai-sdk/gateway";
2566
+ const { gateway } = await import(
2567
+ /* webpackIgnore: true */
2568
+ gatewayPkg
2569
+ );
2570
+ const adapter = new VercelLLMAdapter({ model: gateway(gatewayModel) });
2571
+ return { adapter, description: `Vercel AI Gateway (model: ${gatewayModel})` };
2572
+ } catch (err) {
2573
+ ctx.logger.warn(
2574
+ `[AI] Failed to load @ai-sdk/gateway for AI_GATEWAY_MODEL=${gatewayModel}, trying next provider`,
2575
+ err instanceof Error ? { error: err.message } : void 0
2576
+ );
2577
+ }
2578
+ }
2579
+ const providerConfigs = [
2580
+ {
2581
+ envKey: "OPENAI_API_KEY",
2582
+ pkg: "@ai-sdk/openai",
2583
+ factory: "openai",
2584
+ defaultModel: "gpt-4o",
2585
+ displayName: "OpenAI"
2586
+ },
2587
+ {
2588
+ envKey: "ANTHROPIC_API_KEY",
2589
+ pkg: "@ai-sdk/anthropic",
2590
+ factory: "anthropic",
2591
+ defaultModel: "claude-sonnet-4-20250514",
2592
+ displayName: "Anthropic"
2593
+ },
2594
+ {
2595
+ envKey: "GOOGLE_GENERATIVE_AI_API_KEY",
2596
+ pkg: "@ai-sdk/google",
2597
+ factory: "google",
2598
+ defaultModel: "gemini-2.0-flash",
2599
+ displayName: "Google"
2600
+ }
2601
+ ];
2602
+ for (const { envKey, pkg, factory, defaultModel, displayName } of providerConfigs) {
2603
+ if (process.env[envKey]) {
2604
+ try {
2605
+ const mod = await import(
2606
+ /* webpackIgnore: true */
2607
+ pkg
2608
+ );
2609
+ const createModel = mod[factory] ?? mod.default;
2610
+ if (typeof createModel === "function") {
2611
+ const modelId = process.env.AI_MODEL ?? defaultModel;
2612
+ const adapter = new VercelLLMAdapter({ model: createModel(modelId) });
2613
+ return { adapter, description: `${displayName} (model: ${modelId})` };
2614
+ }
2615
+ } catch (err) {
2616
+ ctx.logger.warn(
2617
+ `[AI] Failed to load ${pkg} for ${envKey}, trying next provider`,
2618
+ err instanceof Error ? { error: err.message } : void 0
2619
+ );
2620
+ }
2621
+ }
2622
+ }
2623
+ ctx.logger.warn("[AI] No LLM provider configured via environment variables. Falling back to MemoryLLMAdapter (echo mode). Set AI_GATEWAY_MODEL, OPENAI_API_KEY, ANTHROPIC_API_KEY, or GOOGLE_GENERATIVE_AI_API_KEY to use a real LLM.");
2624
+ return { adapter: new MemoryLLMAdapter(), description: "MemoryLLMAdapter (echo mode - for testing only)" };
2625
+ }
1451
2626
  async init(ctx) {
1452
2627
  let hasExisting = false;
1453
2628
  try {
@@ -1469,8 +2644,19 @@ var AIServicePlugin = class {
1469
2644
  } catch {
1470
2645
  }
1471
2646
  }
2647
+ let adapter;
2648
+ let adapterDescription;
2649
+ if (this.options.adapter) {
2650
+ adapter = this.options.adapter;
2651
+ adapterDescription = `${adapter.name} (explicitly configured)`;
2652
+ } else {
2653
+ const detected = await this.detectAdapter(ctx);
2654
+ adapter = detected.adapter;
2655
+ adapterDescription = detected.description;
2656
+ }
2657
+ ctx.logger.info(`[AI] Using LLM adapter: ${adapterDescription}`);
1472
2658
  const config = {
1473
- adapter: this.options.adapter,
2659
+ adapter,
1474
2660
  logger: ctx.logger,
1475
2661
  conversationService
1476
2662
  };
@@ -1480,7 +2666,7 @@ var AIServicePlugin = class {
1480
2666
  } else {
1481
2667
  ctx.registerService("ai", this.service);
1482
2668
  }
1483
- ctx.registerService("app.com.objectstack.service-ai", {
2669
+ ctx.getService("manifest").register({
1484
2670
  id: "com.objectstack.service-ai",
1485
2671
  name: "AI Service",
1486
2672
  version: "1.0.0",
@@ -1493,40 +2679,113 @@ var AIServicePlugin = class {
1493
2679
  ctx.logger.debug("[AI] Before chat", { messages });
1494
2680
  });
1495
2681
  }
2682
+ try {
2683
+ const setupNav = ctx.getService("setupNav");
2684
+ if (setupNav) {
2685
+ setupNav.contribute({
2686
+ areaId: "area_ai",
2687
+ items: [
2688
+ { id: "nav_ai_conversations", type: "object", label: { key: "setup.nav.ai_conversations", defaultValue: "Conversations" }, objectName: "conversations", icon: "message-square", order: 10 },
2689
+ { id: "nav_ai_messages", type: "object", label: { key: "setup.nav.ai_messages", defaultValue: "Messages" }, objectName: "messages", icon: "messages-square", order: 20 }
2690
+ ]
2691
+ });
2692
+ ctx.logger.info("[AI] Navigation items contributed to Setup App");
2693
+ }
2694
+ } catch {
2695
+ }
1496
2696
  ctx.logger.info("[AI] Service initialized");
1497
2697
  }
1498
2698
  async start(ctx) {
1499
2699
  if (!this.service) return;
2700
+ let metadataService;
2701
+ try {
2702
+ metadataService = ctx.getService("metadata");
2703
+ console.log("[AI Plugin] Retrieved metadata service:", !!metadataService, "has getRegisteredTypes:", typeof metadataService?.getRegisteredTypes);
2704
+ } catch (e) {
2705
+ console.log("[AI] Metadata service not available:", e.message);
2706
+ ctx.logger.debug("[AI] Metadata service not available");
2707
+ }
1500
2708
  try {
1501
2709
  const dataEngine = ctx.getService("data");
1502
- const metadataService = ctx.getService("metadata");
1503
- if (dataEngine && metadataService) {
1504
- registerDataTools(this.service.toolRegistry, { dataEngine, metadataService });
2710
+ if (dataEngine) {
2711
+ registerDataTools(this.service.toolRegistry, { dataEngine });
1505
2712
  ctx.logger.info("[AI] Built-in data tools registered");
1506
- const agentExists = typeof metadataService.exists === "function" ? await metadataService.exists("agent", DATA_CHAT_AGENT.name) : false;
1507
- if (!agentExists) {
1508
- await metadataService.register("agent", DATA_CHAT_AGENT.name, DATA_CHAT_AGENT);
1509
- ctx.logger.info("[AI] data_chat agent registered");
1510
- } else {
1511
- ctx.logger.debug("[AI] data_chat agent already exists, skipping auto-registration");
2713
+ if (metadataService) {
2714
+ const { DATA_TOOL_DEFINITIONS: DATA_TOOL_DEFINITIONS2 } = await Promise.resolve().then(() => (init_data_tools(), data_tools_exports));
2715
+ for (const toolDef of DATA_TOOL_DEFINITIONS2) {
2716
+ const toolExists = typeof metadataService.exists === "function" ? await metadataService.exists("tool", toolDef.name) : false;
2717
+ if (!toolExists) {
2718
+ await metadataService.register("tool", toolDef.name, toolDef);
2719
+ }
2720
+ }
2721
+ ctx.logger.info(`[AI] ${DATA_TOOL_DEFINITIONS2.length} data tools registered as metadata`);
2722
+ }
2723
+ if (metadataService) {
2724
+ try {
2725
+ const agentExists = typeof metadataService.exists === "function" ? await metadataService.exists("agent", DATA_CHAT_AGENT.name) : false;
2726
+ if (!agentExists) {
2727
+ await metadataService.register("agent", DATA_CHAT_AGENT.name, DATA_CHAT_AGENT);
2728
+ console.log("[AI] Registered data_chat agent to metadataService");
2729
+ ctx.logger.info("[AI] data_chat agent registered");
2730
+ } else {
2731
+ console.log("[AI] data_chat agent already exists, skipping");
2732
+ ctx.logger.debug("[AI] data_chat agent already exists, skipping auto-registration");
2733
+ }
2734
+ } catch (err) {
2735
+ ctx.logger.warn("[AI] Failed to register data_chat agent", err instanceof Error ? { error: err.message, stack: err.stack } : { error: String(err) });
2736
+ }
1512
2737
  }
1513
2738
  }
1514
2739
  } catch {
1515
- ctx.logger.debug("[AI] Data engine or metadata service not available, skipping data tools");
2740
+ ctx.logger.debug("[AI] Data engine not available, skipping data tools");
2741
+ }
2742
+ if (metadataService) {
2743
+ try {
2744
+ registerMetadataTools(this.service.toolRegistry, { metadataService });
2745
+ ctx.logger.info("[AI] Built-in metadata tools registered");
2746
+ const { METADATA_TOOL_DEFINITIONS: METADATA_TOOL_DEFINITIONS2 } = await Promise.resolve().then(() => (init_metadata_tools(), metadata_tools_exports));
2747
+ for (const toolDef of METADATA_TOOL_DEFINITIONS2) {
2748
+ const toolExists = typeof metadataService.exists === "function" ? await metadataService.exists("tool", toolDef.name) : false;
2749
+ if (!toolExists) {
2750
+ await metadataService.register("tool", toolDef.name, toolDef);
2751
+ }
2752
+ }
2753
+ ctx.logger.info(`[AI] ${METADATA_TOOL_DEFINITIONS2.length} metadata tools registered as metadata`);
2754
+ try {
2755
+ const agentExists = typeof metadataService.exists === "function" ? await metadataService.exists("agent", METADATA_ASSISTANT_AGENT.name) : false;
2756
+ if (!agentExists) {
2757
+ await metadataService.register("agent", METADATA_ASSISTANT_AGENT.name, METADATA_ASSISTANT_AGENT);
2758
+ console.log("[AI] Registered metadata_assistant agent to metadataService");
2759
+ ctx.logger.info("[AI] metadata_assistant agent registered");
2760
+ } else {
2761
+ console.log("[AI] metadata_assistant agent already exists, skipping");
2762
+ ctx.logger.debug("[AI] metadata_assistant agent already exists, skipping auto-registration");
2763
+ }
2764
+ } catch (err) {
2765
+ ctx.logger.warn("[AI] Failed to register metadata_assistant agent", err instanceof Error ? { error: err.message, stack: err.stack } : { error: String(err) });
2766
+ }
2767
+ } catch (err) {
2768
+ ctx.logger.debug("[AI] Failed to register metadata tools", err instanceof Error ? err : void 0);
2769
+ }
1516
2770
  }
1517
2771
  await ctx.trigger("ai:ready", this.service);
1518
2772
  const routes = buildAIRoutes(this.service, this.service.conversationService, ctx.logger);
1519
- try {
1520
- const metadataService = ctx.getService("metadata");
1521
- if (metadataService) {
1522
- const agentRuntime = new AgentRuntime(metadataService);
1523
- const agentRoutes = buildAgentRoutes(this.service, agentRuntime, ctx.logger);
1524
- routes.push(...agentRoutes);
1525
- }
1526
- } catch {
2773
+ const toolRoutes = buildToolRoutes(this.service, ctx.logger);
2774
+ routes.push(...toolRoutes);
2775
+ ctx.logger.info(`[AI] Tool routes registered (${toolRoutes.length} routes)`);
2776
+ if (metadataService) {
2777
+ const agentRuntime = new AgentRuntime(metadataService);
2778
+ const agentRoutes = buildAgentRoutes(this.service, agentRuntime, ctx.logger);
2779
+ routes.push(...agentRoutes);
2780
+ ctx.logger.info(`[AI] Agent routes registered (${agentRoutes.length} routes)`);
2781
+ } else {
1527
2782
  ctx.logger.debug("[AI] Metadata service not available, skipping agent routes");
1528
2783
  }
1529
2784
  await ctx.trigger("ai:routes", routes);
2785
+ const kernel = ctx.getKernel();
2786
+ if (kernel) {
2787
+ kernel.__aiRoutes = routes;
2788
+ }
1530
2789
  ctx.logger.info(
1531
2790
  `[AI] Service started \u2014 adapter="${this.service.adapterName}", tools=${this.service.toolRegistry.size}, routes=${routes.length}`
1532
2791
  );
@@ -1535,6 +2794,11 @@ var AIServicePlugin = class {
1535
2794
  this.service = void 0;
1536
2795
  }
1537
2796
  };
2797
+
2798
+ // src/index.ts
2799
+ init_data_tools();
2800
+ init_metadata_tools();
2801
+ init_metadata_tools();
1538
2802
  // Annotate the CommonJS export names for ESM import in node:
1539
2803
  0 && (module.exports = {
1540
2804
  AIService,
@@ -1545,11 +2809,24 @@ var AIServicePlugin = class {
1545
2809
  DATA_CHAT_AGENT,
1546
2810
  DATA_TOOL_DEFINITIONS,
1547
2811
  InMemoryConversationService,
2812
+ METADATA_ASSISTANT_AGENT,
2813
+ METADATA_TOOL_DEFINITIONS,
1548
2814
  MemoryLLMAdapter,
1549
2815
  ObjectQLConversationService,
1550
2816
  ToolRegistry,
2817
+ VercelLLMAdapter,
2818
+ addFieldTool,
1551
2819
  buildAIRoutes,
1552
2820
  buildAgentRoutes,
1553
- registerDataTools
2821
+ buildToolRoutes,
2822
+ createObjectTool,
2823
+ deleteFieldTool,
2824
+ describeObjectTool,
2825
+ encodeStreamPart,
2826
+ encodeVercelDataStream,
2827
+ listObjectsTool,
2828
+ modifyFieldTool,
2829
+ registerDataTools,
2830
+ registerMetadataTools
1554
2831
  });
1555
2832
  //# sourceMappingURL=index.cjs.map