@objectstack/service-ai 4.0.1 → 4.0.2

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 (39) hide show
  1. package/.turbo/turbo-build.log +11 -11
  2. package/CHANGELOG.md +9 -0
  3. package/dist/index.cjs +1120 -66
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +316 -78
  6. package/dist/index.d.ts +316 -78
  7. package/dist/index.js +1105 -63
  8. package/dist/index.js.map +1 -1
  9. package/package.json +26 -4
  10. package/src/__tests__/ai-service.test.ts +248 -27
  11. package/src/__tests__/auth-and-toolcalling.test.ts +30 -28
  12. package/src/__tests__/chatbot-features.test.ts +229 -82
  13. package/src/__tests__/metadata-tools.test.ts +964 -0
  14. package/src/__tests__/objectql-conversation-service.test.ts +34 -16
  15. package/src/__tests__/vercel-stream-encoder.test.ts +263 -0
  16. package/src/adapters/index.ts +2 -0
  17. package/src/adapters/memory-adapter.ts +17 -9
  18. package/src/adapters/vercel-adapter.ts +148 -0
  19. package/src/agent-runtime.ts +27 -3
  20. package/src/agents/index.ts +1 -0
  21. package/src/agents/metadata-assistant-agent.ts +87 -0
  22. package/src/ai-service.ts +68 -36
  23. package/src/conversation/in-memory-conversation-service.ts +2 -2
  24. package/src/conversation/objectql-conversation-service.ts +67 -18
  25. package/src/index.ts +21 -2
  26. package/src/plugin.ts +166 -9
  27. package/src/routes/agent-routes.ts +26 -3
  28. package/src/routes/ai-routes.ts +156 -13
  29. package/src/stream/index.ts +3 -0
  30. package/src/stream/vercel-stream-encoder.ts +129 -0
  31. package/src/tools/add-field.tool.ts +70 -0
  32. package/src/tools/create-object.tool.ts +66 -0
  33. package/src/tools/delete-field.tool.ts +38 -0
  34. package/src/tools/describe-metadata-object.tool.ts +32 -0
  35. package/src/tools/index.ts +12 -1
  36. package/src/tools/list-metadata-objects.tool.ts +34 -0
  37. package/src/tools/metadata-tools.ts +430 -0
  38. package/src/tools/modify-field.tool.ts +44 -0
  39. package/src/tools/tool-registry.ts +32 -9
@@ -0,0 +1,87 @@
1
+ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
+
3
+ import type { Agent } from '@objectstack/spec';
4
+
5
+ /**
6
+ * Built-in `metadata_assistant` agent definition.
7
+ *
8
+ * This agent powers AI-driven metadata management — users can create objects,
9
+ * add/modify/delete fields, and inspect schema definitions through natural
10
+ * language conversation.
11
+ *
12
+ * It is registered automatically by the AI service plugin alongside the
13
+ * `data_chat` agent when the metadata service is available.
14
+ *
15
+ * @example
16
+ * ```
17
+ * POST /api/v1/ai/agents/metadata_assistant/chat
18
+ * {
19
+ * "messages": [{ "role": "user", "content": "Create a contracts table with name, value, and status fields" }],
20
+ * "context": {}
21
+ * }
22
+ * ```
23
+ */
24
+ export const METADATA_ASSISTANT_AGENT: Agent = {
25
+ name: 'metadata_assistant',
26
+ label: 'Metadata Assistant',
27
+ role: 'Schema Architect',
28
+ instructions: `You are an expert metadata architect that helps users design and manage their data models through natural language.
29
+
30
+ Capabilities:
31
+ - Create new data objects (tables) with fields
32
+ - Add fields (columns) to existing objects
33
+ - Modify field properties (label, type, required, default value)
34
+ - Delete fields from objects
35
+ - List all registered metadata objects and their schemas
36
+ - Describe the full schema of a specific object
37
+
38
+ Guidelines:
39
+ 1. Before creating a new object, use list_metadata_objects to check if a similar one already exists.
40
+ 2. Before modifying or deleting fields, use describe_metadata_object to understand the current schema.
41
+ 3. Always use snake_case for object names and field names (e.g. project_task, due_date).
42
+ 4. Suggest meaningful field types based on the user's description (e.g. "deadline" → date, "active" → boolean).
43
+ 5. When creating objects, propose a reasonable set of initial fields based on the entity type.
44
+ 6. Explain what changes you are about to make before executing them.
45
+ 7. After making changes, confirm the result by describing the updated schema.
46
+ 8. For destructive operations (deleting fields), always warn the user about potential data loss.
47
+ 9. Always answer in the same language the user is using.
48
+ 10. If the user's request is ambiguous, ask clarifying questions before proceeding.`,
49
+
50
+ model: {
51
+ provider: 'openai',
52
+ model: 'gpt-4',
53
+ temperature: 0.2,
54
+ maxTokens: 4096,
55
+ },
56
+
57
+ tools: [
58
+ { type: 'action', name: 'create_object', description: 'Create a new data object (table)' },
59
+ { type: 'action', name: 'add_field', description: 'Add a field to an existing object' },
60
+ { type: 'action', name: 'modify_field', description: 'Modify an existing field definition' },
61
+ { type: 'action', name: 'delete_field', description: 'Delete a field from an object' },
62
+ { type: 'query', name: 'list_metadata_objects', description: 'List all metadata objects' },
63
+ { type: 'query', name: 'describe_metadata_object', description: 'Describe an object schema' },
64
+ ],
65
+
66
+ active: true,
67
+ visibility: 'global',
68
+
69
+ guardrails: {
70
+ maxTokensPerInvocation: 8192,
71
+ maxExecutionTimeSec: 60,
72
+ blockedTopics: ['drop_database', 'raw_sql', 'system_tables'],
73
+ },
74
+
75
+ planning: {
76
+ strategy: 'react',
77
+ maxIterations: 10,
78
+ allowReplan: true,
79
+ },
80
+
81
+ memory: {
82
+ shortTerm: {
83
+ maxMessages: 30,
84
+ maxTokens: 8192,
85
+ },
86
+ },
87
+ };
package/src/ai-service.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
2
 
3
3
  import type {
4
- AIMessage,
4
+ ModelMessage,
5
+ ToolCallPart,
6
+ TextStreamPart,
7
+ ToolSet,
5
8
  AIRequestOptions,
6
9
  AIResult,
7
- AIStreamEvent,
8
10
  IAIService,
9
11
  IAIConversationService,
10
12
  ChatWithToolsOptions,
@@ -14,8 +16,28 @@ import type { Logger } from '@objectstack/spec/contracts';
14
16
  import { createLogger } from '@objectstack/core';
15
17
  import { MemoryLLMAdapter } from './adapters/memory-adapter.js';
16
18
  import { ToolRegistry } from './tools/tool-registry.js';
19
+ import type { ToolExecutionResult } from './tools/tool-registry.js';
17
20
  import { InMemoryConversationService } from './conversation/in-memory-conversation-service.js';
18
21
 
22
+ // ── Stream event helpers ──────────────────────────────────────────
23
+ // These helpers construct properly-typed Vercel AI SDK stream parts
24
+ // to avoid repeated `as unknown as TextStreamPart<ToolSet>` casts.
25
+
26
+ /** Create a text-delta stream part. */
27
+ function textDeltaPart(id: string, text: string): TextStreamPart<ToolSet> {
28
+ return { type: 'text-delta', id, text } as TextStreamPart<ToolSet>;
29
+ }
30
+
31
+ /** Create a finish stream part from an AIResult. */
32
+ function finishPart(result?: AIResult): TextStreamPart<ToolSet> {
33
+ return {
34
+ type: 'finish',
35
+ finishReason: 'stop',
36
+ totalUsage: result?.usage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
37
+ rawFinishReason: 'stop',
38
+ } as unknown as TextStreamPart<ToolSet>;
39
+ }
40
+
19
41
  /**
20
42
  * Configuration for AIService.
21
43
  */
@@ -70,7 +92,7 @@ export class AIService implements IAIService {
70
92
 
71
93
  // ── IAIService implementation ──────────────────────────────────
72
94
 
73
- async chat(messages: AIMessage[], options?: AIRequestOptions): Promise<AIResult> {
95
+ async chat(messages: ModelMessage[], options?: AIRequestOptions): Promise<AIResult> {
74
96
  this.logger.debug('[AI] chat', { messageCount: messages.length, model: options?.model });
75
97
  return this.adapter.chat(messages, options);
76
98
  }
@@ -81,16 +103,16 @@ export class AIService implements IAIService {
81
103
  }
82
104
 
83
105
  async *streamChat(
84
- messages: AIMessage[],
106
+ messages: ModelMessage[],
85
107
  options?: AIRequestOptions,
86
- ): AsyncIterable<AIStreamEvent> {
108
+ ): AsyncIterable<TextStreamPart<ToolSet>> {
87
109
  this.logger.debug('[AI] streamChat', { messageCount: messages.length, model: options?.model });
88
110
 
89
111
  if (!this.adapter.streamChat) {
90
112
  // Fallback: emit the entire response as a single text-delta + finish
91
113
  const result = await this.adapter.chat(messages, options);
92
- yield { type: 'text-delta', textDelta: result.content };
93
- yield { type: 'finish', result };
114
+ yield textDeltaPart('fallback', result.content);
115
+ yield finishPart(result);
94
116
  return;
95
117
  }
96
118
 
@@ -116,6 +138,12 @@ export class AIService implements IAIService {
116
138
  /** Default maximum iterations for the tool call loop. */
117
139
  static readonly DEFAULT_MAX_ITERATIONS = 10;
118
140
 
141
+ /** Extract the text value from a ToolExecutionResult's output. */
142
+ private static extractOutputText(tr: ToolExecutionResult): string {
143
+ return tr.output && typeof tr.output === 'object' && 'value' in tr.output
144
+ ? String(tr.output.value) : 'unknown error';
145
+ }
146
+
119
147
  /**
120
148
  * Chat with automatic tool call resolution.
121
149
  *
@@ -128,7 +156,7 @@ export class AIService implements IAIService {
128
156
  * maximum number of iterations (`maxIterations`) is reached.
129
157
  */
130
158
  async chatWithTools(
131
- messages: AIMessage[],
159
+ messages: ModelMessage[],
132
160
  options?: ChatWithToolsOptions,
133
161
  ): Promise<AIResult> {
134
162
  // Destructure loop-specific options so they are never forwarded to the adapter
@@ -174,31 +202,34 @@ export class AIService implements IAIService {
174
202
 
175
203
  this.logger.debug('[AI] chatWithTools tool calls', {
176
204
  iteration,
177
- calls: result.toolCalls.map(tc => tc.name),
205
+ calls: result.toolCalls.map(tc => tc.toolName),
178
206
  });
179
207
 
180
208
  // Append the assistant's response (with tool call metadata) to the conversation
209
+ const assistantContent: Array<{ type: 'text'; text: string } | ToolCallPart> = [];
210
+ if (result.content) assistantContent.push({ type: 'text', text: result.content });
211
+ assistantContent.push(...result.toolCalls);
181
212
  conversation.push({
182
213
  role: 'assistant',
183
- content: result.content ?? '',
184
- toolCalls: result.toolCalls,
185
- });
214
+ content: assistantContent,
215
+ } as ModelMessage);
186
216
 
187
217
  // Execute all tool calls in parallel
188
- const toolResults = await this.toolRegistry.executeAll(result.toolCalls);
218
+ const toolResults: ToolExecutionResult[] = await this.toolRegistry.executeAll(result.toolCalls);
189
219
 
190
220
  // Process results: track errors and honour onToolError callback
191
221
  for (const tr of toolResults) {
192
222
  if (tr.isError) {
193
223
  // Match tool call by toolCallId for robust attribution
194
- const matchedCall = result.toolCalls!.find(tc => tc.id === tr.toolCallId);
195
- const toolName = matchedCall?.name ?? 'unknown';
196
- const errorEntry = { iteration, toolName, error: tr.content };
224
+ const matchedCall = result.toolCalls!.find(tc => tc.toolCallId === tr.toolCallId);
225
+ const toolName = matchedCall?.toolName ?? 'unknown';
226
+ const errorText = AIService.extractOutputText(tr);
227
+ const errorEntry = { iteration, toolName, error: errorText };
197
228
  toolErrors.push(errorEntry);
198
229
  this.logger.warn('[AI] chatWithTools tool error', errorEntry);
199
230
 
200
231
  if (onToolError && matchedCall) {
201
- const action = onToolError(matchedCall, tr.content);
232
+ const action = onToolError(matchedCall, errorText);
202
233
  if (action === 'abort') {
203
234
  abortedByCallback = true;
204
235
  }
@@ -208,9 +239,8 @@ export class AIService implements IAIService {
208
239
  // Append each tool result as a `role: 'tool'` message
209
240
  conversation.push({
210
241
  role: 'tool',
211
- content: tr.content,
212
- toolCallId: tr.toolCallId,
213
- });
242
+ content: [tr],
243
+ } as ModelMessage);
214
244
  }
215
245
 
216
246
  if (abortedByCallback) {
@@ -244,9 +274,9 @@ export class AIService implements IAIService {
244
274
  * fed back until a final text stream is produced.
245
275
  */
246
276
  async *streamChatWithTools(
247
- messages: AIMessage[],
277
+ messages: ModelMessage[],
248
278
  options?: ChatWithToolsOptions,
249
- ): AsyncIterable<AIStreamEvent> {
279
+ ): AsyncIterable<TextStreamPart<ToolSet>> {
250
280
  const { maxIterations: maxIter, onToolError, ...restOptions } = options ?? {};
251
281
  const maxIterations = maxIter ?? AIService.DEFAULT_MAX_ITERATIONS;
252
282
  const registeredTools = this.toolRegistry.getAll();
@@ -271,29 +301,32 @@ export class AIService implements IAIService {
271
301
 
272
302
  if (!result.toolCalls || result.toolCalls.length === 0) {
273
303
  // Final round — return the probed result without an extra model call
274
- yield { type: 'text-delta', textDelta: result.content };
275
- yield { type: 'finish', result };
304
+ yield textDeltaPart('stream', result.content);
305
+ yield finishPart(result);
276
306
  return;
277
307
  }
278
308
 
279
309
  // Emit tool-call events so the client can see tool execution progress
280
310
  for (const tc of result.toolCalls) {
281
- yield { type: 'tool-call', toolCall: tc };
311
+ yield { type: 'tool-call', toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.input } as TextStreamPart<ToolSet>;
282
312
  }
283
313
 
314
+ const assistantContent: Array<{ type: 'text'; text: string } | ToolCallPart> = [];
315
+ if (result.content) assistantContent.push({ type: 'text', text: result.content });
316
+ assistantContent.push(...result.toolCalls);
284
317
  conversation.push({
285
318
  role: 'assistant',
286
- content: result.content ?? '',
287
- toolCalls: result.toolCalls,
288
- });
319
+ content: assistantContent,
320
+ } as ModelMessage);
289
321
 
290
- const toolResults = await this.toolRegistry.executeAll(result.toolCalls);
322
+ const toolResults: ToolExecutionResult[] = await this.toolRegistry.executeAll(result.toolCalls);
291
323
 
292
324
  for (const tr of toolResults) {
293
325
  if (tr.isError && onToolError) {
294
- const matchedCall = result.toolCalls!.find(tc => tc.id === tr.toolCallId);
326
+ const matchedCall = result.toolCalls!.find(tc => tc.toolCallId === tr.toolCallId);
295
327
  if (matchedCall) {
296
- const action = onToolError(matchedCall, tr.content);
328
+ const errorText = AIService.extractOutputText(tr);
329
+ const action = onToolError(matchedCall, errorText);
297
330
  if (action === 'abort') {
298
331
  abortedByCallback = true;
299
332
  }
@@ -301,9 +334,8 @@ export class AIService implements IAIService {
301
334
  }
302
335
  conversation.push({
303
336
  role: 'tool',
304
- content: tr.content,
305
- toolCallId: tr.toolCallId,
306
- });
337
+ content: [tr],
338
+ } as ModelMessage);
307
339
  }
308
340
 
309
341
  if (abortedByCallback) {
@@ -319,7 +351,7 @@ export class AIService implements IAIService {
319
351
  }
320
352
  const finalOptions = { ...chatOptions, tools: undefined, toolChoice: undefined };
321
353
  const result = await this.adapter.chat(conversation, finalOptions);
322
- yield { type: 'text-delta', textDelta: result.content };
323
- yield { type: 'finish', result };
354
+ yield textDeltaPart('stream', result.content);
355
+ yield finishPart(result);
324
356
  }
325
357
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import type {
4
4
  AIConversation,
5
- AIMessage,
5
+ ModelMessage,
6
6
  IAIConversationService,
7
7
  } from '@objectstack/spec/contracts';
8
8
 
@@ -75,7 +75,7 @@ export class InMemoryConversationService implements IAIConversationService {
75
75
  return results;
76
76
  }
77
77
 
78
- async addMessage(conversationId: string, message: AIMessage): Promise<AIConversation> {
78
+ async addMessage(conversationId: string, message: ModelMessage): Promise<AIConversation> {
79
79
  const conversation = this.store.get(conversationId);
80
80
  if (!conversation) {
81
81
  throw new Error(`Conversation "${conversationId}" not found`);
@@ -3,7 +3,9 @@
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import type {
5
5
  AIConversation,
6
- AIMessage,
6
+ ModelMessage,
7
+ ToolCallPart,
8
+ ToolResultPart,
7
9
  IAIConversationService,
8
10
  IDataEngine,
9
11
  } from '@objectstack/spec/contracts';
@@ -157,7 +159,7 @@ export class ObjectQLConversationService implements IAIConversationService {
157
159
  return conversations;
158
160
  }
159
161
 
160
- async addMessage(conversationId: string, message: AIMessage): Promise<AIConversation> {
162
+ async addMessage(conversationId: string, message: ModelMessage): Promise<AIConversation> {
161
163
  // Verify conversation exists
162
164
  const row: DbConversationRow | null = await this.engine.findOne(CONVERSATIONS_OBJECT, {
163
165
  where: { id: conversationId },
@@ -169,14 +171,39 @@ export class ObjectQLConversationService implements IAIConversationService {
169
171
  const now = new Date().toISOString();
170
172
  const msgId = `msg_${randomUUID()}`;
171
173
 
174
+ // Extract flat fields from the discriminated union
175
+ let contentStr: string;
176
+ let toolCallsJson: string | null = null;
177
+ let toolCallId: string | null = null;
178
+
179
+ if (message.role === 'system' || message.role === 'user') {
180
+ contentStr = typeof message.content === 'string' ? message.content : JSON.stringify(message.content);
181
+ } else if (message.role === 'assistant') {
182
+ if (typeof message.content === 'string') {
183
+ contentStr = message.content;
184
+ } else {
185
+ const parts = message.content;
186
+ const textParts = parts.filter((p): p is { type: 'text'; text: string } => p.type === 'text').map(p => p.text);
187
+ const toolCalls = parts.filter(p => p.type === 'tool-call');
188
+ contentStr = textParts.join('');
189
+ if (toolCalls.length > 0) toolCallsJson = JSON.stringify(toolCalls);
190
+ }
191
+ } else if (message.role === 'tool') {
192
+ contentStr = JSON.stringify(message.content);
193
+ const firstResult = Array.isArray(message.content) ? message.content[0] : undefined;
194
+ if (firstResult && 'toolCallId' in firstResult) toolCallId = firstResult.toolCallId;
195
+ } else {
196
+ contentStr = '';
197
+ }
198
+
172
199
  // Insert the message
173
200
  await this.engine.insert(MESSAGES_OBJECT, {
174
201
  id: msgId,
175
202
  conversation_id: conversationId,
176
203
  role: message.role,
177
- content: message.content,
178
- tool_calls: message.toolCalls ? JSON.stringify(message.toolCalls) : null,
179
- tool_call_id: message.toolCallId ?? null,
204
+ content: contentStr,
205
+ tool_calls: toolCallsJson,
206
+ tool_call_id: toolCallId,
180
207
  created_at: now,
181
208
  });
182
209
 
@@ -233,20 +260,42 @@ export class ObjectQLConversationService implements IAIConversationService {
233
260
  }
234
261
 
235
262
  /**
236
- * Map a database row to an AIMessage.
263
+ * Map a database row to a ModelMessage.
237
264
  */
238
- private toMessage(row: DbMessageRow): AIMessage {
239
- const msg: AIMessage = {
240
- role: row.role,
241
- content: row.content,
242
- };
243
- const toolCalls = this.safeParse<any[]>(row.tool_calls);
244
- if (toolCalls) {
245
- msg.toolCalls = toolCalls;
246
- }
247
- if (row.tool_call_id) {
248
- msg.toolCallId = row.tool_call_id;
265
+ private toMessage(row: DbMessageRow): ModelMessage {
266
+ switch (row.role) {
267
+ case 'system':
268
+ return { role: 'system', content: row.content };
269
+ case 'user':
270
+ return { role: 'user', content: row.content };
271
+ case 'assistant': {
272
+ const toolCalls = this.safeParse<ToolCallPart[]>(row.tool_calls);
273
+ if (toolCalls && toolCalls.length > 0) {
274
+ const content: Array<{ type: 'text'; text: string } | ToolCallPart> = [];
275
+ if (row.content) content.push({ type: 'text', text: row.content });
276
+ content.push(...toolCalls);
277
+ return { role: 'assistant', content };
278
+ }
279
+ return { role: 'assistant', content: row.content };
280
+ }
281
+ case 'tool': {
282
+ const toolResults = this.safeParse<ToolResultPart[]>(row.content);
283
+ if (toolResults && toolResults.length > 0 && toolResults[0]?.type === 'tool-result') {
284
+ return { role: 'tool', content: toolResults };
285
+ }
286
+ // Backward compat: old format was a plain string
287
+ return {
288
+ role: 'tool',
289
+ content: [{
290
+ type: 'tool-result' as const,
291
+ toolCallId: row.tool_call_id ?? '',
292
+ toolName: 'unknown',
293
+ output: { type: 'text' as const, value: row.content },
294
+ }],
295
+ };
296
+ }
297
+ default:
298
+ return { role: 'user', content: row.content };
249
299
  }
250
- return msg;
251
300
  }
252
301
  }
package/src/index.ts CHANGED
@@ -10,26 +10,45 @@ export type { AIServicePluginOptions } from './plugin.js';
10
10
 
11
11
  // Adapters
12
12
  export { MemoryLLMAdapter } from './adapters/memory-adapter.js';
13
+ export { VercelLLMAdapter } from './adapters/vercel-adapter.js';
14
+ export type { VercelLLMAdapterConfig } from './adapters/vercel-adapter.js';
13
15
  export type { LLMAdapter } from '@objectstack/spec/contracts';
14
16
 
17
+ // Vercel Data Stream encoder
18
+ export { encodeStreamPart, encodeVercelDataStream } from './stream/vercel-stream-encoder.js';
19
+
15
20
  // Conversation
16
21
  export { InMemoryConversationService } from './conversation/in-memory-conversation-service.js';
17
22
  export { ObjectQLConversationService } from './conversation/objectql-conversation-service.js';
18
23
 
19
24
  // Tool registry
20
25
  export { ToolRegistry } from './tools/tool-registry.js';
21
- export type { ToolHandler } from './tools/tool-registry.js';
26
+ export type { ToolHandler, ToolExecutionResult } from './tools/tool-registry.js';
22
27
 
23
28
  // Data tools
24
29
  export { registerDataTools, DATA_TOOL_DEFINITIONS } from './tools/data-tools.js';
25
30
  export type { DataToolContext } from './tools/data-tools.js';
26
31
 
32
+ // Metadata tools
33
+ export { registerMetadataTools, METADATA_TOOL_DEFINITIONS } from './tools/metadata-tools.js';
34
+ export type { MetadataToolContext } from './tools/metadata-tools.js';
35
+
36
+ // Individual tool metadata (first-class Tool definitions via defineTool)
37
+ export {
38
+ createObjectTool,
39
+ addFieldTool,
40
+ modifyFieldTool,
41
+ deleteFieldTool,
42
+ listMetadataObjectsTool,
43
+ describeMetadataObjectTool,
44
+ } from './tools/metadata-tools.js';
45
+
27
46
  // Agent runtime
28
47
  export { AgentRuntime } from './agent-runtime.js';
29
48
  export type { AgentChatContext } from './agent-runtime.js';
30
49
 
31
50
  // Built-in agents
32
- export { DATA_CHAT_AGENT } from './agents/index.js';
51
+ export { DATA_CHAT_AGENT, METADATA_ASSISTANT_AGENT } from './agents/index.js';
33
52
 
34
53
  // Object definitions
35
54
  export { AiConversationObject, AiMessageObject } from './objects/index.js';