@objectstack/service-ai 5.2.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +719 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4245 -6
- package/dist/index.d.ts +4245 -6
- package/dist/index.js +710 -11
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.cjs
CHANGED
|
@@ -124,7 +124,9 @@ var init_data_tools = __esm({
|
|
|
124
124
|
properties: {
|
|
125
125
|
field: { type: "string" },
|
|
126
126
|
order: { type: "string", enum: ["asc", "desc"] }
|
|
127
|
-
}
|
|
127
|
+
},
|
|
128
|
+
required: ["field", "order"],
|
|
129
|
+
additionalProperties: false
|
|
128
130
|
},
|
|
129
131
|
description: 'Sort order (e.g. [{ "field": "created_at", "order": "desc" }])'
|
|
130
132
|
},
|
|
@@ -194,7 +196,8 @@ var init_data_tools = __esm({
|
|
|
194
196
|
description: "Result column alias"
|
|
195
197
|
}
|
|
196
198
|
},
|
|
197
|
-
required: ["function", "alias"]
|
|
199
|
+
required: ["function", "alias"],
|
|
200
|
+
additionalProperties: false
|
|
198
201
|
},
|
|
199
202
|
description: "Aggregation definitions"
|
|
200
203
|
},
|
|
@@ -850,14 +853,20 @@ __export(index_exports, {
|
|
|
850
853
|
AgentRuntime: () => AgentRuntime,
|
|
851
854
|
AiConversationObject: () => AiConversationObject,
|
|
852
855
|
AiMessageObject: () => AiMessageObject,
|
|
856
|
+
AiTraceObject: () => AiTraceObject,
|
|
853
857
|
DATA_CHAT_AGENT: () => DATA_CHAT_AGENT,
|
|
854
858
|
DATA_TOOL_DEFINITIONS: () => DATA_TOOL_DEFINITIONS,
|
|
855
859
|
InMemoryConversationService: () => InMemoryConversationService,
|
|
856
860
|
METADATA_ASSISTANT_AGENT: () => METADATA_ASSISTANT_AGENT,
|
|
857
861
|
METADATA_TOOL_DEFINITIONS: () => METADATA_TOOL_DEFINITIONS,
|
|
858
862
|
MemoryLLMAdapter: () => MemoryLLMAdapter,
|
|
863
|
+
ModelRegistry: () => ModelRegistry,
|
|
864
|
+
NullTraceRecorder: () => NullTraceRecorder,
|
|
859
865
|
ObjectQLConversationService: () => ObjectQLConversationService,
|
|
866
|
+
ObjectQLTraceRecorder: () => ObjectQLTraceRecorder,
|
|
860
867
|
PACKAGE_TOOL_DEFINITIONS: () => PACKAGE_TOOL_DEFINITIONS,
|
|
868
|
+
QUERY_DATA_TOOL: () => QUERY_DATA_TOOL,
|
|
869
|
+
SchemaRetriever: () => SchemaRetriever,
|
|
861
870
|
SkillRegistry: () => SkillRegistry,
|
|
862
871
|
ToolRegistry: () => ToolRegistry,
|
|
863
872
|
VercelLLMAdapter: () => VercelLLMAdapter,
|
|
@@ -866,8 +875,11 @@ __export(index_exports, {
|
|
|
866
875
|
buildAgentRoutes: () => buildAgentRoutes,
|
|
867
876
|
buildAssistantRoutes: () => buildAssistantRoutes,
|
|
868
877
|
buildToolRoutes: () => buildToolRoutes,
|
|
878
|
+
buildTraceEvent: () => buildTraceEvent,
|
|
879
|
+
computeCost: () => computeCost,
|
|
869
880
|
createObjectTool: () => createObjectTool,
|
|
870
881
|
createPackageTool: () => createPackageTool,
|
|
882
|
+
createQueryDataHandler: () => createQueryDataHandler,
|
|
871
883
|
deleteFieldTool: () => deleteFieldTool,
|
|
872
884
|
describeObjectTool: () => describeObjectTool,
|
|
873
885
|
encodeStreamPart: () => encodeStreamPart,
|
|
@@ -880,6 +892,7 @@ __export(index_exports, {
|
|
|
880
892
|
registerDataTools: () => registerDataTools,
|
|
881
893
|
registerMetadataTools: () => registerMetadataTools,
|
|
882
894
|
registerPackageTools: () => registerPackageTools,
|
|
895
|
+
registerQueryDataTool: () => registerQueryDataTool,
|
|
883
896
|
setActivePackageTool: () => setActivePackageTool
|
|
884
897
|
});
|
|
885
898
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -931,6 +944,59 @@ var MemoryLLMAdapter = class {
|
|
|
931
944
|
async listModels() {
|
|
932
945
|
return ["memory"];
|
|
933
946
|
}
|
|
947
|
+
/**
|
|
948
|
+
* Heuristic structured-output for testing & demos — NOT a real LLM.
|
|
949
|
+
*
|
|
950
|
+
* Strategy:
|
|
951
|
+
* 1. Extract candidate object names from the system messages by matching
|
|
952
|
+
* schema-context headers (`### name — Label`) emitted by
|
|
953
|
+
* {@link SchemaRetriever.renderSnippet}.
|
|
954
|
+
* 2. Pick the candidate whose tokens overlap most with the last user
|
|
955
|
+
* message (falls back to the first candidate).
|
|
956
|
+
* 3. Try `schema.safeParse({ objectName, limit: 20 })` — this satisfies the
|
|
957
|
+
* `QueryPlanSchema` used by the built-in `query_data` tool.
|
|
958
|
+
* 4. If that fails, fall back to `schema.safeParse({})` for schemas that
|
|
959
|
+
* accept defaults.
|
|
960
|
+
* 5. Otherwise throw with a clear message — the demo needs a real provider.
|
|
961
|
+
*/
|
|
962
|
+
async generateObject(messages, schema, options) {
|
|
963
|
+
const sys = messages.filter((m) => m.role === "system").map((m) => typeof m.content === "string" ? m.content : "").join("\n");
|
|
964
|
+
const headerRe = /^###\s+([a-z0-9_]+)\b/gim;
|
|
965
|
+
const candidates = [];
|
|
966
|
+
for (const match of sys.matchAll(headerRe)) {
|
|
967
|
+
if (match[1]) candidates.push(match[1]);
|
|
968
|
+
}
|
|
969
|
+
const lastUser = [...messages].reverse().find((m) => m.role === "user");
|
|
970
|
+
const userText = typeof lastUser?.content === "string" ? lastUser.content.toLowerCase() : "";
|
|
971
|
+
const userTokens = new Set(
|
|
972
|
+
userText.split(/[^a-z0-9_]+/).filter((t) => t.length > 1)
|
|
973
|
+
);
|
|
974
|
+
let chosen = candidates[0];
|
|
975
|
+
let bestScore = -1;
|
|
976
|
+
for (const name of candidates) {
|
|
977
|
+
const score = name.split(/[^a-z0-9]+/).reduce((acc, tok) => acc + (tok && userTokens.has(tok) ? 1 : 0), 0);
|
|
978
|
+
if (score > bestScore) {
|
|
979
|
+
bestScore = score;
|
|
980
|
+
chosen = name;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
const attempts = [];
|
|
984
|
+
if (chosen) attempts.push({ objectName: chosen, limit: 20 });
|
|
985
|
+
attempts.push({});
|
|
986
|
+
for (const attempt of attempts) {
|
|
987
|
+
const result = schema.safeParse(attempt);
|
|
988
|
+
if (result.success) {
|
|
989
|
+
return {
|
|
990
|
+
object: result.data,
|
|
991
|
+
model: options?.model ?? "memory",
|
|
992
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
throw new Error(
|
|
997
|
+
"MemoryLLMAdapter.generateObject: unable to synthesise a value for the requested schema. The memory adapter only handles QueryPlan-shaped schemas \u2014 wire a real LLM adapter (OpenAI / Anthropic / Google) for arbitrary structured output."
|
|
998
|
+
);
|
|
999
|
+
}
|
|
934
1000
|
};
|
|
935
1001
|
|
|
936
1002
|
// src/tools/tool-registry.ts
|
|
@@ -1097,6 +1163,70 @@ var InMemoryConversationService = class {
|
|
|
1097
1163
|
}
|
|
1098
1164
|
};
|
|
1099
1165
|
|
|
1166
|
+
// src/trace-recorder.ts
|
|
1167
|
+
var import_node_crypto = require("crypto");
|
|
1168
|
+
var TRACE_OBJECT = "ai_traces";
|
|
1169
|
+
var NullTraceRecorder = class {
|
|
1170
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1171
|
+
record(_event) {
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
var ObjectQLTraceRecorder = class {
|
|
1175
|
+
constructor(engine, options = {}) {
|
|
1176
|
+
this.engine = engine;
|
|
1177
|
+
this.logger = options.logger;
|
|
1178
|
+
}
|
|
1179
|
+
async record(event) {
|
|
1180
|
+
const row = {
|
|
1181
|
+
id: `trace_${(0, import_node_crypto.randomUUID)()}`,
|
|
1182
|
+
conversation_id: event.conversationId ?? null,
|
|
1183
|
+
agent_id: event.agentId ?? null,
|
|
1184
|
+
operation: event.operation,
|
|
1185
|
+
model: event.model ?? null,
|
|
1186
|
+
adapter: event.adapter,
|
|
1187
|
+
prompt_tokens: event.promptTokens,
|
|
1188
|
+
completion_tokens: event.completionTokens,
|
|
1189
|
+
total_tokens: event.totalTokens,
|
|
1190
|
+
input_cost: event.cost?.inputCost ?? null,
|
|
1191
|
+
output_cost: event.cost?.outputCost ?? null,
|
|
1192
|
+
total_cost: event.cost?.totalCost ?? null,
|
|
1193
|
+
currency: event.cost?.currency ?? null,
|
|
1194
|
+
latency_ms: event.latencyMs,
|
|
1195
|
+
status: event.status,
|
|
1196
|
+
error: event.error ?? null,
|
|
1197
|
+
metadata: event.metadata ? JSON.stringify(event.metadata) : null,
|
|
1198
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1199
|
+
};
|
|
1200
|
+
try {
|
|
1201
|
+
await this.engine.insert(TRACE_OBJECT, row);
|
|
1202
|
+
} catch (err) {
|
|
1203
|
+
this.logger?.warn(
|
|
1204
|
+
"[AI] Failed to record trace (non-fatal)",
|
|
1205
|
+
err instanceof Error ? { error: err.message } : { error: String(err) }
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
function buildTraceEvent(input) {
|
|
1211
|
+
const usage = input.usage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
1212
|
+
const cost = input.model && input.registry ? input.registry.estimateCost(input.model, usage) : void 0;
|
|
1213
|
+
return {
|
|
1214
|
+
operation: input.operation,
|
|
1215
|
+
adapter: input.adapter,
|
|
1216
|
+
model: input.model,
|
|
1217
|
+
agentId: input.agentId,
|
|
1218
|
+
conversationId: input.conversationId,
|
|
1219
|
+
promptTokens: usage.promptTokens,
|
|
1220
|
+
completionTokens: usage.completionTokens,
|
|
1221
|
+
totalTokens: usage.totalTokens,
|
|
1222
|
+
latencyMs: input.latencyMs,
|
|
1223
|
+
status: input.status,
|
|
1224
|
+
error: input.error,
|
|
1225
|
+
cost,
|
|
1226
|
+
metadata: input.metadata
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1100
1230
|
// src/ai-service.ts
|
|
1101
1231
|
function textDeltaPart(id, text) {
|
|
1102
1232
|
return { type: "text-delta", id, text };
|
|
@@ -1115,22 +1245,83 @@ var _AIService = class _AIService {
|
|
|
1115
1245
|
this.logger = config.logger ?? (0, import_core.createLogger)({ level: "info", format: "pretty" });
|
|
1116
1246
|
this.toolRegistry = config.toolRegistry ?? new ToolRegistry();
|
|
1117
1247
|
this.conversationService = config.conversationService ?? new InMemoryConversationService();
|
|
1248
|
+
this.modelRegistry = config.modelRegistry;
|
|
1249
|
+
this.traceRecorder = config.traceRecorder ?? new NullTraceRecorder();
|
|
1118
1250
|
this.logger.info(
|
|
1119
|
-
`[AI] Service initialized with adapter="${this.adapter.name}", tools=${this.toolRegistry.size}`
|
|
1251
|
+
`[AI] Service initialized with adapter="${this.adapter.name}", tools=${this.toolRegistry.size}, models=${this.modelRegistry?.size ?? 0}`
|
|
1120
1252
|
);
|
|
1121
1253
|
}
|
|
1122
1254
|
/** The name of the active LLM adapter. */
|
|
1123
1255
|
get adapterName() {
|
|
1124
1256
|
return this.adapter.name;
|
|
1125
1257
|
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Run an adapter call and emit a trace event.
|
|
1260
|
+
*
|
|
1261
|
+
* Records both success and failure. Tracing failures never escape — the
|
|
1262
|
+
* recorder is expected to be defensive.
|
|
1263
|
+
*/
|
|
1264
|
+
async instrument(operation, options, fn) {
|
|
1265
|
+
const started = Date.now();
|
|
1266
|
+
try {
|
|
1267
|
+
const result = await fn();
|
|
1268
|
+
void this.traceRecorder.record(buildTraceEvent({
|
|
1269
|
+
operation,
|
|
1270
|
+
adapter: this.adapter.name,
|
|
1271
|
+
model: result.model ?? options?.model,
|
|
1272
|
+
usage: result.usage,
|
|
1273
|
+
latencyMs: Date.now() - started,
|
|
1274
|
+
status: "success",
|
|
1275
|
+
registry: this.modelRegistry
|
|
1276
|
+
}));
|
|
1277
|
+
return result;
|
|
1278
|
+
} catch (err) {
|
|
1279
|
+
void this.traceRecorder.record(buildTraceEvent({
|
|
1280
|
+
operation,
|
|
1281
|
+
adapter: this.adapter.name,
|
|
1282
|
+
model: options?.model,
|
|
1283
|
+
latencyMs: Date.now() - started,
|
|
1284
|
+
status: "error",
|
|
1285
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1286
|
+
registry: this.modelRegistry
|
|
1287
|
+
}));
|
|
1288
|
+
throw err;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1126
1291
|
// ── IAIService implementation ──────────────────────────────────
|
|
1127
1292
|
async chat(messages, options) {
|
|
1128
1293
|
this.logger.debug("[AI] chat", { messageCount: messages.length, model: options?.model });
|
|
1129
|
-
return this.adapter.chat(messages, options);
|
|
1294
|
+
return this.instrument("chat", options, () => this.adapter.chat(messages, options));
|
|
1130
1295
|
}
|
|
1131
1296
|
async complete(prompt, options) {
|
|
1132
1297
|
this.logger.debug("[AI] complete", { promptLength: prompt.length, model: options?.model });
|
|
1133
|
-
return this.adapter.complete(prompt, options);
|
|
1298
|
+
return this.instrument("complete", options, () => this.adapter.complete(prompt, options));
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Generate a strongly-typed object validated against a Zod schema.
|
|
1302
|
+
*
|
|
1303
|
+
* Delegates to the adapter's `generateObject` when supported; throws a
|
|
1304
|
+
* descriptive error when the adapter does not implement structured output.
|
|
1305
|
+
*
|
|
1306
|
+
* @example
|
|
1307
|
+
* ```ts
|
|
1308
|
+
* import { z } from 'zod';
|
|
1309
|
+
* const Schema = z.object({ name: z.string(), priority: z.number().int() });
|
|
1310
|
+
* const { object } = await ai.generateObject(messages, Schema);
|
|
1311
|
+
* ```
|
|
1312
|
+
*/
|
|
1313
|
+
async generateObject(messages, schema, options) {
|
|
1314
|
+
this.logger.debug("[AI] generateObject", { messageCount: messages.length, model: options?.model });
|
|
1315
|
+
if (!this.adapter.generateObject) {
|
|
1316
|
+
throw new Error(
|
|
1317
|
+
`[AI] Adapter "${this.adapter.name}" does not support generateObject. Use VercelLLMAdapter with a structured-output-capable model.`
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
return this.instrument(
|
|
1321
|
+
"generate_object",
|
|
1322
|
+
options,
|
|
1323
|
+
() => this.adapter.generateObject(messages, schema, options)
|
|
1324
|
+
);
|
|
1134
1325
|
}
|
|
1135
1326
|
async *streamChat(messages, options) {
|
|
1136
1327
|
this.logger.debug("[AI] streamChat", { messageCount: messages.length, model: options?.model });
|
|
@@ -2212,7 +2403,7 @@ function buildToolRoutes(aiService, logger) {
|
|
|
2212
2403
|
}
|
|
2213
2404
|
|
|
2214
2405
|
// src/conversation/objectql-conversation-service.ts
|
|
2215
|
-
var
|
|
2406
|
+
var import_node_crypto2 = require("crypto");
|
|
2216
2407
|
var CONVERSATIONS_OBJECT = "ai_conversations";
|
|
2217
2408
|
var MESSAGES_OBJECT = "ai_messages";
|
|
2218
2409
|
var CONVERSATION_ORDER = [
|
|
@@ -2229,7 +2420,7 @@ var ObjectQLConversationService = class {
|
|
|
2229
2420
|
}
|
|
2230
2421
|
async create(options = {}) {
|
|
2231
2422
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2232
|
-
const id = `conv_${(0,
|
|
2423
|
+
const id = `conv_${(0, import_node_crypto2.randomUUID)()}`;
|
|
2233
2424
|
const record = {
|
|
2234
2425
|
id,
|
|
2235
2426
|
title: options.title ?? null,
|
|
@@ -2302,7 +2493,7 @@ var ObjectQLConversationService = class {
|
|
|
2302
2493
|
throw new Error(`Conversation "${conversationId}" not found`);
|
|
2303
2494
|
}
|
|
2304
2495
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2305
|
-
const msgId = `msg_${(0,
|
|
2496
|
+
const msgId = `msg_${(0, import_node_crypto2.randomUUID)()}`;
|
|
2306
2497
|
let contentStr;
|
|
2307
2498
|
let toolCallsJson = null;
|
|
2308
2499
|
let toolCallId = null;
|
|
@@ -2547,10 +2738,395 @@ var AiMessageObject = import_data2.ObjectSchema.create({
|
|
|
2547
2738
|
}
|
|
2548
2739
|
});
|
|
2549
2740
|
|
|
2741
|
+
// src/objects/ai-trace.object.ts
|
|
2742
|
+
var import_data3 = require("@objectstack/spec/data");
|
|
2743
|
+
var AiTraceObject = import_data3.ObjectSchema.create({
|
|
2744
|
+
name: "ai_traces",
|
|
2745
|
+
label: "AI Trace",
|
|
2746
|
+
pluralLabel: "AI Traces",
|
|
2747
|
+
icon: "activity",
|
|
2748
|
+
isSystem: true,
|
|
2749
|
+
description: "Per-call LLM invocation trace with token usage and cost",
|
|
2750
|
+
fields: {
|
|
2751
|
+
id: import_data3.Field.text({
|
|
2752
|
+
label: "Trace ID",
|
|
2753
|
+
required: true,
|
|
2754
|
+
readonly: true
|
|
2755
|
+
}),
|
|
2756
|
+
conversation_id: import_data3.Field.lookup("ai_conversations", {
|
|
2757
|
+
label: "Conversation",
|
|
2758
|
+
required: false,
|
|
2759
|
+
description: "Parent conversation, if any"
|
|
2760
|
+
}),
|
|
2761
|
+
agent_id: import_data3.Field.text({
|
|
2762
|
+
label: "Agent",
|
|
2763
|
+
required: false,
|
|
2764
|
+
maxLength: 128,
|
|
2765
|
+
description: "Agent metadata name that originated the call"
|
|
2766
|
+
}),
|
|
2767
|
+
operation: import_data3.Field.select({
|
|
2768
|
+
label: "Operation",
|
|
2769
|
+
required: true,
|
|
2770
|
+
options: [
|
|
2771
|
+
{ label: "Chat", value: "chat" },
|
|
2772
|
+
{ label: "Complete", value: "complete" },
|
|
2773
|
+
{ label: "Stream Chat", value: "stream_chat" },
|
|
2774
|
+
{ label: "Chat With Tools", value: "chat_with_tools" },
|
|
2775
|
+
{ label: "Generate Object", value: "generate_object" },
|
|
2776
|
+
{ label: "Embed", value: "embed" }
|
|
2777
|
+
]
|
|
2778
|
+
}),
|
|
2779
|
+
model: import_data3.Field.text({
|
|
2780
|
+
label: "Model",
|
|
2781
|
+
required: false,
|
|
2782
|
+
maxLength: 128,
|
|
2783
|
+
description: "Model identifier reported by the adapter"
|
|
2784
|
+
}),
|
|
2785
|
+
adapter: import_data3.Field.text({
|
|
2786
|
+
label: "Adapter",
|
|
2787
|
+
required: false,
|
|
2788
|
+
maxLength: 64,
|
|
2789
|
+
description: 'LLM adapter name (e.g. "vercel", "memory")'
|
|
2790
|
+
}),
|
|
2791
|
+
prompt_tokens: import_data3.Field.number({
|
|
2792
|
+
label: "Prompt Tokens",
|
|
2793
|
+
required: false,
|
|
2794
|
+
defaultValue: 0
|
|
2795
|
+
}),
|
|
2796
|
+
completion_tokens: import_data3.Field.number({
|
|
2797
|
+
label: "Completion Tokens",
|
|
2798
|
+
required: false,
|
|
2799
|
+
defaultValue: 0
|
|
2800
|
+
}),
|
|
2801
|
+
total_tokens: import_data3.Field.number({
|
|
2802
|
+
label: "Total Tokens",
|
|
2803
|
+
required: false,
|
|
2804
|
+
defaultValue: 0
|
|
2805
|
+
}),
|
|
2806
|
+
input_cost: import_data3.Field.number({
|
|
2807
|
+
label: "Input Cost",
|
|
2808
|
+
required: false,
|
|
2809
|
+
description: "Cost attributable to prompt tokens (currency in `currency` field)"
|
|
2810
|
+
}),
|
|
2811
|
+
output_cost: import_data3.Field.number({
|
|
2812
|
+
label: "Output Cost",
|
|
2813
|
+
required: false,
|
|
2814
|
+
description: "Cost attributable to completion tokens"
|
|
2815
|
+
}),
|
|
2816
|
+
total_cost: import_data3.Field.number({
|
|
2817
|
+
label: "Total Cost",
|
|
2818
|
+
required: false,
|
|
2819
|
+
description: "input_cost + output_cost"
|
|
2820
|
+
}),
|
|
2821
|
+
currency: import_data3.Field.text({
|
|
2822
|
+
label: "Currency",
|
|
2823
|
+
required: false,
|
|
2824
|
+
maxLength: 8,
|
|
2825
|
+
defaultValue: "USD"
|
|
2826
|
+
}),
|
|
2827
|
+
latency_ms: import_data3.Field.number({
|
|
2828
|
+
label: "Latency (ms)",
|
|
2829
|
+
required: true,
|
|
2830
|
+
defaultValue: 0,
|
|
2831
|
+
description: "Wall-clock duration of the LLM call"
|
|
2832
|
+
}),
|
|
2833
|
+
status: import_data3.Field.select({
|
|
2834
|
+
label: "Status",
|
|
2835
|
+
required: true,
|
|
2836
|
+
options: [
|
|
2837
|
+
{ label: "Success", value: "success" },
|
|
2838
|
+
{ label: "Error", value: "error" }
|
|
2839
|
+
]
|
|
2840
|
+
}),
|
|
2841
|
+
error: import_data3.Field.textarea({
|
|
2842
|
+
label: "Error",
|
|
2843
|
+
required: false,
|
|
2844
|
+
description: "Error message when status=error"
|
|
2845
|
+
}),
|
|
2846
|
+
metadata: import_data3.Field.textarea({
|
|
2847
|
+
label: "Metadata",
|
|
2848
|
+
required: false,
|
|
2849
|
+
description: "JSON-serialized extra fields (request id, user id, \u2026)"
|
|
2850
|
+
}),
|
|
2851
|
+
created_at: import_data3.Field.datetime({
|
|
2852
|
+
label: "Created At",
|
|
2853
|
+
required: true,
|
|
2854
|
+
defaultValue: "NOW()",
|
|
2855
|
+
readonly: true
|
|
2856
|
+
})
|
|
2857
|
+
},
|
|
2858
|
+
indexes: [
|
|
2859
|
+
{ fields: ["conversation_id"] },
|
|
2860
|
+
{ fields: ["agent_id"] },
|
|
2861
|
+
{ fields: ["model"] },
|
|
2862
|
+
{ fields: ["status"] },
|
|
2863
|
+
{ fields: ["created_at"] }
|
|
2864
|
+
],
|
|
2865
|
+
enable: {
|
|
2866
|
+
trackHistory: false,
|
|
2867
|
+
searchable: false,
|
|
2868
|
+
apiEnabled: true,
|
|
2869
|
+
apiMethods: ["get", "list"],
|
|
2870
|
+
trash: false,
|
|
2871
|
+
mru: false
|
|
2872
|
+
}
|
|
2873
|
+
});
|
|
2874
|
+
|
|
2550
2875
|
// src/plugin.ts
|
|
2551
2876
|
init_data_tools();
|
|
2552
2877
|
init_metadata_tools();
|
|
2553
2878
|
|
|
2879
|
+
// src/tools/query-data.tool.ts
|
|
2880
|
+
var import_zod = require("zod");
|
|
2881
|
+
|
|
2882
|
+
// src/schema-retriever.ts
|
|
2883
|
+
var SchemaRetriever = class {
|
|
2884
|
+
constructor(metadata, options = {}) {
|
|
2885
|
+
this.metadata = metadata;
|
|
2886
|
+
this.options = {
|
|
2887
|
+
limit: options.limit ?? 3,
|
|
2888
|
+
minScore: options.minScore ?? 1,
|
|
2889
|
+
maxFieldsPerObject: options.maxFieldsPerObject ?? 12
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2892
|
+
/**
|
|
2893
|
+
* Find object definitions whose name/label/fields match terms in the query.
|
|
2894
|
+
*
|
|
2895
|
+
* Returns matches sorted by score (descending) capped at `limit`. When
|
|
2896
|
+
* the query yields no matches, returns an empty array — callers may
|
|
2897
|
+
* fall back to a generic "describe what data exists" tool call.
|
|
2898
|
+
*/
|
|
2899
|
+
async retrieve(query) {
|
|
2900
|
+
const terms = tokenise(query);
|
|
2901
|
+
if (terms.length === 0) return [];
|
|
2902
|
+
const objects = await this.metadata.listObjects();
|
|
2903
|
+
const hits = [];
|
|
2904
|
+
for (const raw of objects) {
|
|
2905
|
+
const obj = raw;
|
|
2906
|
+
if (!obj?.name) continue;
|
|
2907
|
+
const score = scoreObject(obj, terms);
|
|
2908
|
+
if (score >= this.options.minScore) {
|
|
2909
|
+
hits.push({ object: obj, score });
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
hits.sort((a, b) => b.score - a.score);
|
|
2913
|
+
return hits.slice(0, this.options.limit);
|
|
2914
|
+
}
|
|
2915
|
+
/**
|
|
2916
|
+
* Render hits as a compact Markdown schema snippet.
|
|
2917
|
+
*
|
|
2918
|
+
* Designed to be appended to the system message — every line carries
|
|
2919
|
+
* exactly the information a model needs to choose object/field names
|
|
2920
|
+
* for query construction.
|
|
2921
|
+
*/
|
|
2922
|
+
static renderSnippet(hits, maxFieldsPerObject = 12) {
|
|
2923
|
+
if (hits.length === 0) return "";
|
|
2924
|
+
const lines = ["## Schema context (auto-injected)"];
|
|
2925
|
+
for (const hit of hits) {
|
|
2926
|
+
const obj = hit.object;
|
|
2927
|
+
const label = obj.label ? ` \u2014 ${obj.label}` : "";
|
|
2928
|
+
lines.push(`### ${obj.name}${label}`);
|
|
2929
|
+
const fields = Object.entries(obj.fields ?? {}).slice(0, maxFieldsPerObject);
|
|
2930
|
+
for (const [name, field] of fields) {
|
|
2931
|
+
lines.push(` - ${name}: ${describeField(field)}`);
|
|
2932
|
+
}
|
|
2933
|
+
const total = Object.keys(obj.fields ?? {}).length;
|
|
2934
|
+
if (total > fields.length) {
|
|
2935
|
+
lines.push(` - \u2026${total - fields.length} more field(s)`);
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
return lines.join("\n");
|
|
2939
|
+
}
|
|
2940
|
+
};
|
|
2941
|
+
function tokenise(query) {
|
|
2942
|
+
const raw = query.toLowerCase().match(/[a-z0-9]+/g) ?? [];
|
|
2943
|
+
return raw.filter((t) => t.length >= 2 && !STOPWORDS.has(t));
|
|
2944
|
+
}
|
|
2945
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
2946
|
+
"the",
|
|
2947
|
+
"and",
|
|
2948
|
+
"for",
|
|
2949
|
+
"with",
|
|
2950
|
+
"from",
|
|
2951
|
+
"are",
|
|
2952
|
+
"has",
|
|
2953
|
+
"have",
|
|
2954
|
+
"had",
|
|
2955
|
+
"was",
|
|
2956
|
+
"were",
|
|
2957
|
+
"this",
|
|
2958
|
+
"that",
|
|
2959
|
+
"these",
|
|
2960
|
+
"those",
|
|
2961
|
+
"all",
|
|
2962
|
+
"any",
|
|
2963
|
+
"how",
|
|
2964
|
+
"what",
|
|
2965
|
+
"when",
|
|
2966
|
+
"where",
|
|
2967
|
+
"who",
|
|
2968
|
+
"why",
|
|
2969
|
+
"which",
|
|
2970
|
+
"show",
|
|
2971
|
+
"list",
|
|
2972
|
+
"find",
|
|
2973
|
+
"get",
|
|
2974
|
+
"count",
|
|
2975
|
+
"of",
|
|
2976
|
+
"in",
|
|
2977
|
+
"on",
|
|
2978
|
+
"at",
|
|
2979
|
+
"to",
|
|
2980
|
+
"as",
|
|
2981
|
+
"by",
|
|
2982
|
+
"is",
|
|
2983
|
+
"it",
|
|
2984
|
+
"an",
|
|
2985
|
+
"or",
|
|
2986
|
+
"be",
|
|
2987
|
+
"me"
|
|
2988
|
+
]);
|
|
2989
|
+
function scoreObject(obj, terms) {
|
|
2990
|
+
let score = 0;
|
|
2991
|
+
const nameTokens = splitSnake(obj.name);
|
|
2992
|
+
const labelTokens = obj.label ? tokenise(obj.label) : [];
|
|
2993
|
+
const pluralTokens = obj.pluralLabel ? tokenise(obj.pluralLabel) : [];
|
|
2994
|
+
const descTokens = obj.description ? tokenise(obj.description) : [];
|
|
2995
|
+
for (const term of terms) {
|
|
2996
|
+
if (nameTokens.includes(term)) score += 3;
|
|
2997
|
+
else if (labelTokens.includes(term) || pluralTokens.includes(term)) score += 2;
|
|
2998
|
+
else if (descTokens.includes(term)) score += 1;
|
|
2999
|
+
}
|
|
3000
|
+
for (const [fieldName, field] of Object.entries(obj.fields ?? {})) {
|
|
3001
|
+
const fnTokens = splitSnake(fieldName);
|
|
3002
|
+
const flTokens = field.label ? tokenise(field.label) : [];
|
|
3003
|
+
for (const term of terms) {
|
|
3004
|
+
if (fnTokens.includes(term)) score += 2;
|
|
3005
|
+
else if (flTokens.includes(term)) score += 1;
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
return score;
|
|
3009
|
+
}
|
|
3010
|
+
function splitSnake(name) {
|
|
3011
|
+
return name.toLowerCase().split("_").filter(Boolean);
|
|
3012
|
+
}
|
|
3013
|
+
function describeField(field) {
|
|
3014
|
+
const t = field.type ?? "unknown";
|
|
3015
|
+
if (t === "lookup" && field.reference) return `lookup \u2192 ${field.reference}`;
|
|
3016
|
+
if (t === "select" && Array.isArray(field.options)) {
|
|
3017
|
+
const values = field.options.map(
|
|
3018
|
+
(o) => typeof o === "string" ? o : o.value
|
|
3019
|
+
).filter(Boolean).slice(0, 6);
|
|
3020
|
+
return `select(${values.join("|")})`;
|
|
3021
|
+
}
|
|
3022
|
+
return t;
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
// src/tools/query-data.tool.ts
|
|
3026
|
+
var QueryPlanSchema = import_zod.z.object({
|
|
3027
|
+
objectName: import_zod.z.string().min(1).describe('The snake_case object name to query (e.g. "task", "account").'),
|
|
3028
|
+
where: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional().describe(
|
|
3029
|
+
'Filter conditions as key-value pairs. Use MongoDB-style operators for ranges, e.g. {"amount": {"$gt": 100}}.'
|
|
3030
|
+
),
|
|
3031
|
+
fields: import_zod.z.array(import_zod.z.string()).optional().describe("Field names to return. Omit to return all fields."),
|
|
3032
|
+
orderBy: import_zod.z.array(
|
|
3033
|
+
import_zod.z.object({
|
|
3034
|
+
field: import_zod.z.string(),
|
|
3035
|
+
order: import_zod.z.enum(["asc", "desc"])
|
|
3036
|
+
})
|
|
3037
|
+
).optional().describe("Sort order. First entry is primary sort key."),
|
|
3038
|
+
limit: import_zod.z.number().int().min(1).max(200).optional().describe("Maximum number of records (default 20, max 200).")
|
|
3039
|
+
});
|
|
3040
|
+
var QUERY_DATA_TOOL = {
|
|
3041
|
+
name: "query_data",
|
|
3042
|
+
description: "Answer a natural-language question about the user's data. Internally retrieves the relevant object schema, generates an ObjectQL query, executes it, and returns the matching records. Prefer this tool over `query_records` / `aggregate_data` when the user's intent is expressed in plain language.",
|
|
3043
|
+
parameters: {
|
|
3044
|
+
type: "object",
|
|
3045
|
+
properties: {
|
|
3046
|
+
request: {
|
|
3047
|
+
type: "string",
|
|
3048
|
+
description: "The natural-language question to answer (paraphrase the user's request if needed for clarity)."
|
|
3049
|
+
},
|
|
3050
|
+
model: {
|
|
3051
|
+
type: "string",
|
|
3052
|
+
description: "Optional model id to use for query planning. Defaults to the AI service's default model."
|
|
3053
|
+
}
|
|
3054
|
+
},
|
|
3055
|
+
required: ["request"],
|
|
3056
|
+
additionalProperties: false
|
|
3057
|
+
}
|
|
3058
|
+
};
|
|
3059
|
+
function createQueryDataHandler(ctx) {
|
|
3060
|
+
const retriever = new SchemaRetriever(ctx.metadata);
|
|
3061
|
+
const maxLimit = ctx.maxLimit ?? 100;
|
|
3062
|
+
return async (args) => {
|
|
3063
|
+
const { request, model } = args;
|
|
3064
|
+
if (!request || typeof request !== "string") {
|
|
3065
|
+
return JSON.stringify({ error: "query_data: `request` is required" });
|
|
3066
|
+
}
|
|
3067
|
+
if (!ctx.ai.generateObject) {
|
|
3068
|
+
return JSON.stringify({
|
|
3069
|
+
error: "query_data requires structured-output support. Configure a Vercel-AI-SDK-backed adapter (OpenAI, Anthropic, Google)."
|
|
3070
|
+
});
|
|
3071
|
+
}
|
|
3072
|
+
const hits = await retriever.retrieve(request);
|
|
3073
|
+
if (hits.length === 0) {
|
|
3074
|
+
return JSON.stringify({
|
|
3075
|
+
error: "No matching objects in metadata. Ask the user which object(s) to query, or list available objects via list_objects."
|
|
3076
|
+
});
|
|
3077
|
+
}
|
|
3078
|
+
const snippet = SchemaRetriever.renderSnippet(hits);
|
|
3079
|
+
const planMessages = [
|
|
3080
|
+
{
|
|
3081
|
+
role: "system",
|
|
3082
|
+
content: "You translate user data questions into a single ObjectQL query plan. Use ONLY the objects and fields listed in the schema context below. Never invent field names. If the question is ambiguous, pick the most likely interpretation and use a reasonable `limit`.\n\n" + snippet
|
|
3083
|
+
},
|
|
3084
|
+
{ role: "user", content: request }
|
|
3085
|
+
];
|
|
3086
|
+
let plan;
|
|
3087
|
+
try {
|
|
3088
|
+
const generated = await ctx.ai.generateObject(planMessages, QueryPlanSchema, {
|
|
3089
|
+
model,
|
|
3090
|
+
schemaName: "ObjectQLQueryPlan",
|
|
3091
|
+
schemaDescription: "A single ObjectQL find() query to answer the user request."
|
|
3092
|
+
});
|
|
3093
|
+
plan = generated.object;
|
|
3094
|
+
} catch (err) {
|
|
3095
|
+
return JSON.stringify({
|
|
3096
|
+
error: `Failed to plan query: ${err instanceof Error ? err.message : String(err)}`
|
|
3097
|
+
});
|
|
3098
|
+
}
|
|
3099
|
+
const matchedObject = hits.find((h) => h.object.name === plan.objectName)?.object ?? hits[0].object;
|
|
3100
|
+
if (matchedObject.name !== plan.objectName) {
|
|
3101
|
+
return JSON.stringify({
|
|
3102
|
+
error: `Planned object "${plan.objectName}" is not in the retrieved schema. Available: ${hits.map((h) => h.object.name).join(", ")}`
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
3105
|
+
const limit = Math.min(plan.limit ?? 20, maxLimit);
|
|
3106
|
+
try {
|
|
3107
|
+
const records = await ctx.dataEngine.find(plan.objectName, {
|
|
3108
|
+
where: plan.where,
|
|
3109
|
+
fields: plan.fields,
|
|
3110
|
+
orderBy: plan.orderBy,
|
|
3111
|
+
limit
|
|
3112
|
+
});
|
|
3113
|
+
return JSON.stringify({
|
|
3114
|
+
plan,
|
|
3115
|
+
count: records.length,
|
|
3116
|
+
records
|
|
3117
|
+
});
|
|
3118
|
+
} catch (err) {
|
|
3119
|
+
return JSON.stringify({
|
|
3120
|
+
plan,
|
|
3121
|
+
error: `Query execution failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3122
|
+
});
|
|
3123
|
+
}
|
|
3124
|
+
};
|
|
3125
|
+
}
|
|
3126
|
+
function registerQueryDataTool(registry, context) {
|
|
3127
|
+
registry.register(QUERY_DATA_TOOL, createQueryDataHandler(context));
|
|
3128
|
+
}
|
|
3129
|
+
|
|
2554
3130
|
// src/agent-runtime.ts
|
|
2555
3131
|
var import_ai7 = require("@objectstack/spec/ai");
|
|
2556
3132
|
var AgentRuntime = class {
|
|
@@ -3148,11 +3724,102 @@ var VercelLLMAdapter = class {
|
|
|
3148
3724
|
"[VercelLLMAdapter] Embeddings require a dedicated EmbeddingModel. Configure an embedding adapter instead."
|
|
3149
3725
|
);
|
|
3150
3726
|
}
|
|
3727
|
+
async generateObject(messages, schema, options) {
|
|
3728
|
+
const { schemaName, schemaDescription, ...rest } = options ?? {};
|
|
3729
|
+
const result = await (0, import_ai9.generateObject)({
|
|
3730
|
+
model: this.model,
|
|
3731
|
+
messages,
|
|
3732
|
+
schema,
|
|
3733
|
+
schemaName,
|
|
3734
|
+
schemaDescription,
|
|
3735
|
+
...buildVercelOptions(rest)
|
|
3736
|
+
});
|
|
3737
|
+
return {
|
|
3738
|
+
object: result.object,
|
|
3739
|
+
model: result.response?.modelId,
|
|
3740
|
+
usage: result.usage ? {
|
|
3741
|
+
promptTokens: result.usage.inputTokens ?? 0,
|
|
3742
|
+
completionTokens: result.usage.outputTokens ?? 0,
|
|
3743
|
+
totalTokens: result.usage.totalTokens ?? 0
|
|
3744
|
+
} : void 0
|
|
3745
|
+
};
|
|
3746
|
+
}
|
|
3151
3747
|
async listModels() {
|
|
3152
3748
|
return [];
|
|
3153
3749
|
}
|
|
3154
3750
|
};
|
|
3155
3751
|
|
|
3752
|
+
// src/model-registry.ts
|
|
3753
|
+
var ModelRegistry = class {
|
|
3754
|
+
constructor(config = {}) {
|
|
3755
|
+
this.models = /* @__PURE__ */ new Map();
|
|
3756
|
+
for (const model of config.models ?? []) {
|
|
3757
|
+
this.models.set(model.id, model);
|
|
3758
|
+
}
|
|
3759
|
+
this.defaultModelId = config.defaultModelId;
|
|
3760
|
+
}
|
|
3761
|
+
/** Register or replace a model. */
|
|
3762
|
+
register(model) {
|
|
3763
|
+
this.models.set(model.id, model);
|
|
3764
|
+
}
|
|
3765
|
+
/** Look up a model by id. */
|
|
3766
|
+
get(id) {
|
|
3767
|
+
return this.models.get(id);
|
|
3768
|
+
}
|
|
3769
|
+
/** Look up a model by id, throwing if missing. */
|
|
3770
|
+
getOrThrow(id) {
|
|
3771
|
+
const model = this.models.get(id);
|
|
3772
|
+
if (!model) {
|
|
3773
|
+
throw new Error(
|
|
3774
|
+
`[ModelRegistry] Unknown model "${id}". Registered: ${[...this.models.keys()].join(", ") || "(none)"}`
|
|
3775
|
+
);
|
|
3776
|
+
}
|
|
3777
|
+
return model;
|
|
3778
|
+
}
|
|
3779
|
+
/** Resolve the default model (explicit > first registered > undefined). */
|
|
3780
|
+
getDefault() {
|
|
3781
|
+
if (this.defaultModelId) {
|
|
3782
|
+
return this.models.get(this.defaultModelId);
|
|
3783
|
+
}
|
|
3784
|
+
return this.models.values().next().value;
|
|
3785
|
+
}
|
|
3786
|
+
/** Set the default model id (must already be registered). */
|
|
3787
|
+
setDefault(id) {
|
|
3788
|
+
this.getOrThrow(id);
|
|
3789
|
+
this.defaultModelId = id;
|
|
3790
|
+
}
|
|
3791
|
+
/** All registered models. */
|
|
3792
|
+
list() {
|
|
3793
|
+
return [...this.models.values()];
|
|
3794
|
+
}
|
|
3795
|
+
/** Number of registered models. */
|
|
3796
|
+
get size() {
|
|
3797
|
+
return this.models.size;
|
|
3798
|
+
}
|
|
3799
|
+
/**
|
|
3800
|
+
* Estimate cost in the model's currency (defaults to USD).
|
|
3801
|
+
*
|
|
3802
|
+
* Returns `undefined` when the model is unknown or has no pricing data.
|
|
3803
|
+
* Costs are computed as `(tokens / 1000) * pricePer1kTokens` for input and
|
|
3804
|
+
* output independently, then summed.
|
|
3805
|
+
*/
|
|
3806
|
+
estimateCost(modelId, usage) {
|
|
3807
|
+
const model = this.models.get(modelId);
|
|
3808
|
+
if (!model?.pricing) return void 0;
|
|
3809
|
+
return computeCost(model.pricing, usage);
|
|
3810
|
+
}
|
|
3811
|
+
};
|
|
3812
|
+
function computeCost(pricing, usage) {
|
|
3813
|
+
const inputCost = pricing.inputCostPer1kTokens != null ? usage.promptTokens / 1e3 * pricing.inputCostPer1kTokens : 0;
|
|
3814
|
+
const outputCost = pricing.outputCostPer1kTokens != null ? usage.completionTokens / 1e3 * pricing.outputCostPer1kTokens : 0;
|
|
3815
|
+
return {
|
|
3816
|
+
inputCost,
|
|
3817
|
+
outputCost,
|
|
3818
|
+
totalCost: inputCost + outputCost,
|
|
3819
|
+
currency: pricing.currency ?? "USD"
|
|
3820
|
+
};
|
|
3821
|
+
}
|
|
3822
|
+
|
|
3156
3823
|
// src/plugin.ts
|
|
3157
3824
|
var AIServicePlugin = class {
|
|
3158
3825
|
constructor(options = {}) {
|
|
@@ -3274,10 +3941,34 @@ var AIServicePlugin = class {
|
|
|
3274
3941
|
adapterDescription = detected.description;
|
|
3275
3942
|
}
|
|
3276
3943
|
ctx.logger.info(`[AI] Using LLM adapter: ${adapterDescription}`);
|
|
3944
|
+
const modelRegistry = new ModelRegistry({
|
|
3945
|
+
models: this.options.models,
|
|
3946
|
+
defaultModelId: this.options.defaultModelId
|
|
3947
|
+
});
|
|
3948
|
+
if (modelRegistry.size > 0) {
|
|
3949
|
+
ctx.logger.info(`[AI] ModelRegistry initialised with ${modelRegistry.size} model(s)`);
|
|
3950
|
+
}
|
|
3951
|
+
let traceRecorder;
|
|
3952
|
+
if (this.options.traceRecorder === null) {
|
|
3953
|
+
ctx.logger.debug("[AI] Tracing disabled (traceRecorder=null)");
|
|
3954
|
+
} else if (this.options.traceRecorder) {
|
|
3955
|
+
traceRecorder = this.options.traceRecorder;
|
|
3956
|
+
} else {
|
|
3957
|
+
try {
|
|
3958
|
+
const engine = ctx.getService("data");
|
|
3959
|
+
if (engine && typeof engine.insert === "function") {
|
|
3960
|
+
traceRecorder = new ObjectQLTraceRecorder(engine, { logger: ctx.logger });
|
|
3961
|
+
ctx.logger.info("[AI] Using ObjectQLTraceRecorder (IDataEngine detected)");
|
|
3962
|
+
}
|
|
3963
|
+
} catch {
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3277
3966
|
const config = {
|
|
3278
3967
|
adapter,
|
|
3279
3968
|
logger: ctx.logger,
|
|
3280
|
-
conversationService
|
|
3969
|
+
conversationService,
|
|
3970
|
+
modelRegistry,
|
|
3971
|
+
traceRecorder
|
|
3281
3972
|
};
|
|
3282
3973
|
this.service = new AIService(config);
|
|
3283
3974
|
if (hasExisting) {
|
|
@@ -3292,7 +3983,7 @@ var AIServicePlugin = class {
|
|
|
3292
3983
|
type: "plugin",
|
|
3293
3984
|
scope: "project",
|
|
3294
3985
|
namespace: "ai",
|
|
3295
|
-
objects: [AiConversationObject, AiMessageObject]
|
|
3986
|
+
objects: [AiConversationObject, AiMessageObject, AiTraceObject]
|
|
3296
3987
|
});
|
|
3297
3988
|
if (this.options.debug) {
|
|
3298
3989
|
ctx.hook("ai:beforeChat", async (messages) => {
|
|
@@ -3324,6 +4015,14 @@ var AIServicePlugin = class {
|
|
|
3324
4015
|
if (dataEngine) {
|
|
3325
4016
|
registerDataTools(this.service.toolRegistry, { dataEngine });
|
|
3326
4017
|
ctx.logger.info("[AI] Built-in data tools registered");
|
|
4018
|
+
if (metadataService) {
|
|
4019
|
+
registerQueryDataTool(this.service.toolRegistry, {
|
|
4020
|
+
ai: this.service,
|
|
4021
|
+
metadata: metadataService,
|
|
4022
|
+
dataEngine
|
|
4023
|
+
});
|
|
4024
|
+
ctx.logger.info("[AI] query_data tool registered");
|
|
4025
|
+
}
|
|
3327
4026
|
if (metadataService) {
|
|
3328
4027
|
const { DATA_TOOL_DEFINITIONS: DATA_TOOL_DEFINITIONS2 } = await Promise.resolve().then(() => (init_data_tools(), data_tools_exports));
|
|
3329
4028
|
for (const toolDef of DATA_TOOL_DEFINITIONS2) {
|
|
@@ -3839,14 +4538,20 @@ function registerPackageTools(registry, context) {
|
|
|
3839
4538
|
AgentRuntime,
|
|
3840
4539
|
AiConversationObject,
|
|
3841
4540
|
AiMessageObject,
|
|
4541
|
+
AiTraceObject,
|
|
3842
4542
|
DATA_CHAT_AGENT,
|
|
3843
4543
|
DATA_TOOL_DEFINITIONS,
|
|
3844
4544
|
InMemoryConversationService,
|
|
3845
4545
|
METADATA_ASSISTANT_AGENT,
|
|
3846
4546
|
METADATA_TOOL_DEFINITIONS,
|
|
3847
4547
|
MemoryLLMAdapter,
|
|
4548
|
+
ModelRegistry,
|
|
4549
|
+
NullTraceRecorder,
|
|
3848
4550
|
ObjectQLConversationService,
|
|
4551
|
+
ObjectQLTraceRecorder,
|
|
3849
4552
|
PACKAGE_TOOL_DEFINITIONS,
|
|
4553
|
+
QUERY_DATA_TOOL,
|
|
4554
|
+
SchemaRetriever,
|
|
3850
4555
|
SkillRegistry,
|
|
3851
4556
|
ToolRegistry,
|
|
3852
4557
|
VercelLLMAdapter,
|
|
@@ -3855,8 +4560,11 @@ function registerPackageTools(registry, context) {
|
|
|
3855
4560
|
buildAgentRoutes,
|
|
3856
4561
|
buildAssistantRoutes,
|
|
3857
4562
|
buildToolRoutes,
|
|
4563
|
+
buildTraceEvent,
|
|
4564
|
+
computeCost,
|
|
3858
4565
|
createObjectTool,
|
|
3859
4566
|
createPackageTool,
|
|
4567
|
+
createQueryDataHandler,
|
|
3860
4568
|
deleteFieldTool,
|
|
3861
4569
|
describeObjectTool,
|
|
3862
4570
|
encodeStreamPart,
|
|
@@ -3869,6 +4577,7 @@ function registerPackageTools(registry, context) {
|
|
|
3869
4577
|
registerDataTools,
|
|
3870
4578
|
registerMetadataTools,
|
|
3871
4579
|
registerPackageTools,
|
|
4580
|
+
registerQueryDataTool,
|
|
3872
4581
|
setActivePackageTool
|
|
3873
4582
|
});
|
|
3874
4583
|
//# sourceMappingURL=index.cjs.map
|