@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.js
CHANGED
|
@@ -112,7 +112,9 @@ var init_data_tools = __esm({
|
|
|
112
112
|
properties: {
|
|
113
113
|
field: { type: "string" },
|
|
114
114
|
order: { type: "string", enum: ["asc", "desc"] }
|
|
115
|
-
}
|
|
115
|
+
},
|
|
116
|
+
required: ["field", "order"],
|
|
117
|
+
additionalProperties: false
|
|
116
118
|
},
|
|
117
119
|
description: 'Sort order (e.g. [{ "field": "created_at", "order": "desc" }])'
|
|
118
120
|
},
|
|
@@ -182,7 +184,8 @@ var init_data_tools = __esm({
|
|
|
182
184
|
description: "Result column alias"
|
|
183
185
|
}
|
|
184
186
|
},
|
|
185
|
-
required: ["function", "alias"]
|
|
187
|
+
required: ["function", "alias"],
|
|
188
|
+
additionalProperties: false
|
|
186
189
|
},
|
|
187
190
|
description: "Aggregation definitions"
|
|
188
191
|
},
|
|
@@ -877,6 +880,59 @@ var MemoryLLMAdapter = class {
|
|
|
877
880
|
async listModels() {
|
|
878
881
|
return ["memory"];
|
|
879
882
|
}
|
|
883
|
+
/**
|
|
884
|
+
* Heuristic structured-output for testing & demos — NOT a real LLM.
|
|
885
|
+
*
|
|
886
|
+
* Strategy:
|
|
887
|
+
* 1. Extract candidate object names from the system messages by matching
|
|
888
|
+
* schema-context headers (`### name — Label`) emitted by
|
|
889
|
+
* {@link SchemaRetriever.renderSnippet}.
|
|
890
|
+
* 2. Pick the candidate whose tokens overlap most with the last user
|
|
891
|
+
* message (falls back to the first candidate).
|
|
892
|
+
* 3. Try `schema.safeParse({ objectName, limit: 20 })` — this satisfies the
|
|
893
|
+
* `QueryPlanSchema` used by the built-in `query_data` tool.
|
|
894
|
+
* 4. If that fails, fall back to `schema.safeParse({})` for schemas that
|
|
895
|
+
* accept defaults.
|
|
896
|
+
* 5. Otherwise throw with a clear message — the demo needs a real provider.
|
|
897
|
+
*/
|
|
898
|
+
async generateObject(messages, schema, options) {
|
|
899
|
+
const sys = messages.filter((m) => m.role === "system").map((m) => typeof m.content === "string" ? m.content : "").join("\n");
|
|
900
|
+
const headerRe = /^###\s+([a-z0-9_]+)\b/gim;
|
|
901
|
+
const candidates = [];
|
|
902
|
+
for (const match of sys.matchAll(headerRe)) {
|
|
903
|
+
if (match[1]) candidates.push(match[1]);
|
|
904
|
+
}
|
|
905
|
+
const lastUser = [...messages].reverse().find((m) => m.role === "user");
|
|
906
|
+
const userText = typeof lastUser?.content === "string" ? lastUser.content.toLowerCase() : "";
|
|
907
|
+
const userTokens = new Set(
|
|
908
|
+
userText.split(/[^a-z0-9_]+/).filter((t) => t.length > 1)
|
|
909
|
+
);
|
|
910
|
+
let chosen = candidates[0];
|
|
911
|
+
let bestScore = -1;
|
|
912
|
+
for (const name of candidates) {
|
|
913
|
+
const score = name.split(/[^a-z0-9]+/).reduce((acc, tok) => acc + (tok && userTokens.has(tok) ? 1 : 0), 0);
|
|
914
|
+
if (score > bestScore) {
|
|
915
|
+
bestScore = score;
|
|
916
|
+
chosen = name;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
const attempts = [];
|
|
920
|
+
if (chosen) attempts.push({ objectName: chosen, limit: 20 });
|
|
921
|
+
attempts.push({});
|
|
922
|
+
for (const attempt of attempts) {
|
|
923
|
+
const result = schema.safeParse(attempt);
|
|
924
|
+
if (result.success) {
|
|
925
|
+
return {
|
|
926
|
+
object: result.data,
|
|
927
|
+
model: options?.model ?? "memory",
|
|
928
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
throw new Error(
|
|
933
|
+
"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."
|
|
934
|
+
);
|
|
935
|
+
}
|
|
880
936
|
};
|
|
881
937
|
|
|
882
938
|
// src/tools/tool-registry.ts
|
|
@@ -1043,6 +1099,70 @@ var InMemoryConversationService = class {
|
|
|
1043
1099
|
}
|
|
1044
1100
|
};
|
|
1045
1101
|
|
|
1102
|
+
// src/trace-recorder.ts
|
|
1103
|
+
import { randomUUID } from "crypto";
|
|
1104
|
+
var TRACE_OBJECT = "ai_traces";
|
|
1105
|
+
var NullTraceRecorder = class {
|
|
1106
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1107
|
+
record(_event) {
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
var ObjectQLTraceRecorder = class {
|
|
1111
|
+
constructor(engine, options = {}) {
|
|
1112
|
+
this.engine = engine;
|
|
1113
|
+
this.logger = options.logger;
|
|
1114
|
+
}
|
|
1115
|
+
async record(event) {
|
|
1116
|
+
const row = {
|
|
1117
|
+
id: `trace_${randomUUID()}`,
|
|
1118
|
+
conversation_id: event.conversationId ?? null,
|
|
1119
|
+
agent_id: event.agentId ?? null,
|
|
1120
|
+
operation: event.operation,
|
|
1121
|
+
model: event.model ?? null,
|
|
1122
|
+
adapter: event.adapter,
|
|
1123
|
+
prompt_tokens: event.promptTokens,
|
|
1124
|
+
completion_tokens: event.completionTokens,
|
|
1125
|
+
total_tokens: event.totalTokens,
|
|
1126
|
+
input_cost: event.cost?.inputCost ?? null,
|
|
1127
|
+
output_cost: event.cost?.outputCost ?? null,
|
|
1128
|
+
total_cost: event.cost?.totalCost ?? null,
|
|
1129
|
+
currency: event.cost?.currency ?? null,
|
|
1130
|
+
latency_ms: event.latencyMs,
|
|
1131
|
+
status: event.status,
|
|
1132
|
+
error: event.error ?? null,
|
|
1133
|
+
metadata: event.metadata ? JSON.stringify(event.metadata) : null,
|
|
1134
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1135
|
+
};
|
|
1136
|
+
try {
|
|
1137
|
+
await this.engine.insert(TRACE_OBJECT, row);
|
|
1138
|
+
} catch (err) {
|
|
1139
|
+
this.logger?.warn(
|
|
1140
|
+
"[AI] Failed to record trace (non-fatal)",
|
|
1141
|
+
err instanceof Error ? { error: err.message } : { error: String(err) }
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
function buildTraceEvent(input) {
|
|
1147
|
+
const usage = input.usage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
1148
|
+
const cost = input.model && input.registry ? input.registry.estimateCost(input.model, usage) : void 0;
|
|
1149
|
+
return {
|
|
1150
|
+
operation: input.operation,
|
|
1151
|
+
adapter: input.adapter,
|
|
1152
|
+
model: input.model,
|
|
1153
|
+
agentId: input.agentId,
|
|
1154
|
+
conversationId: input.conversationId,
|
|
1155
|
+
promptTokens: usage.promptTokens,
|
|
1156
|
+
completionTokens: usage.completionTokens,
|
|
1157
|
+
totalTokens: usage.totalTokens,
|
|
1158
|
+
latencyMs: input.latencyMs,
|
|
1159
|
+
status: input.status,
|
|
1160
|
+
error: input.error,
|
|
1161
|
+
cost,
|
|
1162
|
+
metadata: input.metadata
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1046
1166
|
// src/ai-service.ts
|
|
1047
1167
|
function textDeltaPart(id, text) {
|
|
1048
1168
|
return { type: "text-delta", id, text };
|
|
@@ -1061,22 +1181,83 @@ var _AIService = class _AIService {
|
|
|
1061
1181
|
this.logger = config.logger ?? createLogger({ level: "info", format: "pretty" });
|
|
1062
1182
|
this.toolRegistry = config.toolRegistry ?? new ToolRegistry();
|
|
1063
1183
|
this.conversationService = config.conversationService ?? new InMemoryConversationService();
|
|
1184
|
+
this.modelRegistry = config.modelRegistry;
|
|
1185
|
+
this.traceRecorder = config.traceRecorder ?? new NullTraceRecorder();
|
|
1064
1186
|
this.logger.info(
|
|
1065
|
-
`[AI] Service initialized with adapter="${this.adapter.name}", tools=${this.toolRegistry.size}`
|
|
1187
|
+
`[AI] Service initialized with adapter="${this.adapter.name}", tools=${this.toolRegistry.size}, models=${this.modelRegistry?.size ?? 0}`
|
|
1066
1188
|
);
|
|
1067
1189
|
}
|
|
1068
1190
|
/** The name of the active LLM adapter. */
|
|
1069
1191
|
get adapterName() {
|
|
1070
1192
|
return this.adapter.name;
|
|
1071
1193
|
}
|
|
1194
|
+
/**
|
|
1195
|
+
* Run an adapter call and emit a trace event.
|
|
1196
|
+
*
|
|
1197
|
+
* Records both success and failure. Tracing failures never escape — the
|
|
1198
|
+
* recorder is expected to be defensive.
|
|
1199
|
+
*/
|
|
1200
|
+
async instrument(operation, options, fn) {
|
|
1201
|
+
const started = Date.now();
|
|
1202
|
+
try {
|
|
1203
|
+
const result = await fn();
|
|
1204
|
+
void this.traceRecorder.record(buildTraceEvent({
|
|
1205
|
+
operation,
|
|
1206
|
+
adapter: this.adapter.name,
|
|
1207
|
+
model: result.model ?? options?.model,
|
|
1208
|
+
usage: result.usage,
|
|
1209
|
+
latencyMs: Date.now() - started,
|
|
1210
|
+
status: "success",
|
|
1211
|
+
registry: this.modelRegistry
|
|
1212
|
+
}));
|
|
1213
|
+
return result;
|
|
1214
|
+
} catch (err) {
|
|
1215
|
+
void this.traceRecorder.record(buildTraceEvent({
|
|
1216
|
+
operation,
|
|
1217
|
+
adapter: this.adapter.name,
|
|
1218
|
+
model: options?.model,
|
|
1219
|
+
latencyMs: Date.now() - started,
|
|
1220
|
+
status: "error",
|
|
1221
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1222
|
+
registry: this.modelRegistry
|
|
1223
|
+
}));
|
|
1224
|
+
throw err;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1072
1227
|
// ── IAIService implementation ──────────────────────────────────
|
|
1073
1228
|
async chat(messages, options) {
|
|
1074
1229
|
this.logger.debug("[AI] chat", { messageCount: messages.length, model: options?.model });
|
|
1075
|
-
return this.adapter.chat(messages, options);
|
|
1230
|
+
return this.instrument("chat", options, () => this.adapter.chat(messages, options));
|
|
1076
1231
|
}
|
|
1077
1232
|
async complete(prompt, options) {
|
|
1078
1233
|
this.logger.debug("[AI] complete", { promptLength: prompt.length, model: options?.model });
|
|
1079
|
-
return this.adapter.complete(prompt, options);
|
|
1234
|
+
return this.instrument("complete", options, () => this.adapter.complete(prompt, options));
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Generate a strongly-typed object validated against a Zod schema.
|
|
1238
|
+
*
|
|
1239
|
+
* Delegates to the adapter's `generateObject` when supported; throws a
|
|
1240
|
+
* descriptive error when the adapter does not implement structured output.
|
|
1241
|
+
*
|
|
1242
|
+
* @example
|
|
1243
|
+
* ```ts
|
|
1244
|
+
* import { z } from 'zod';
|
|
1245
|
+
* const Schema = z.object({ name: z.string(), priority: z.number().int() });
|
|
1246
|
+
* const { object } = await ai.generateObject(messages, Schema);
|
|
1247
|
+
* ```
|
|
1248
|
+
*/
|
|
1249
|
+
async generateObject(messages, schema, options) {
|
|
1250
|
+
this.logger.debug("[AI] generateObject", { messageCount: messages.length, model: options?.model });
|
|
1251
|
+
if (!this.adapter.generateObject) {
|
|
1252
|
+
throw new Error(
|
|
1253
|
+
`[AI] Adapter "${this.adapter.name}" does not support generateObject. Use VercelLLMAdapter with a structured-output-capable model.`
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
return this.instrument(
|
|
1257
|
+
"generate_object",
|
|
1258
|
+
options,
|
|
1259
|
+
() => this.adapter.generateObject(messages, schema, options)
|
|
1260
|
+
);
|
|
1080
1261
|
}
|
|
1081
1262
|
async *streamChat(messages, options) {
|
|
1082
1263
|
this.logger.debug("[AI] streamChat", { messageCount: messages.length, model: options?.model });
|
|
@@ -2158,7 +2339,7 @@ function buildToolRoutes(aiService, logger) {
|
|
|
2158
2339
|
}
|
|
2159
2340
|
|
|
2160
2341
|
// src/conversation/objectql-conversation-service.ts
|
|
2161
|
-
import { randomUUID } from "crypto";
|
|
2342
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2162
2343
|
var CONVERSATIONS_OBJECT = "ai_conversations";
|
|
2163
2344
|
var MESSAGES_OBJECT = "ai_messages";
|
|
2164
2345
|
var CONVERSATION_ORDER = [
|
|
@@ -2175,7 +2356,7 @@ var ObjectQLConversationService = class {
|
|
|
2175
2356
|
}
|
|
2176
2357
|
async create(options = {}) {
|
|
2177
2358
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2178
|
-
const id = `conv_${
|
|
2359
|
+
const id = `conv_${randomUUID2()}`;
|
|
2179
2360
|
const record = {
|
|
2180
2361
|
id,
|
|
2181
2362
|
title: options.title ?? null,
|
|
@@ -2248,7 +2429,7 @@ var ObjectQLConversationService = class {
|
|
|
2248
2429
|
throw new Error(`Conversation "${conversationId}" not found`);
|
|
2249
2430
|
}
|
|
2250
2431
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2251
|
-
const msgId = `msg_${
|
|
2432
|
+
const msgId = `msg_${randomUUID2()}`;
|
|
2252
2433
|
let contentStr;
|
|
2253
2434
|
let toolCallsJson = null;
|
|
2254
2435
|
let toolCallId = null;
|
|
@@ -2493,10 +2674,395 @@ var AiMessageObject = ObjectSchema2.create({
|
|
|
2493
2674
|
}
|
|
2494
2675
|
});
|
|
2495
2676
|
|
|
2677
|
+
// src/objects/ai-trace.object.ts
|
|
2678
|
+
import { ObjectSchema as ObjectSchema3, Field as Field3 } from "@objectstack/spec/data";
|
|
2679
|
+
var AiTraceObject = ObjectSchema3.create({
|
|
2680
|
+
name: "ai_traces",
|
|
2681
|
+
label: "AI Trace",
|
|
2682
|
+
pluralLabel: "AI Traces",
|
|
2683
|
+
icon: "activity",
|
|
2684
|
+
isSystem: true,
|
|
2685
|
+
description: "Per-call LLM invocation trace with token usage and cost",
|
|
2686
|
+
fields: {
|
|
2687
|
+
id: Field3.text({
|
|
2688
|
+
label: "Trace ID",
|
|
2689
|
+
required: true,
|
|
2690
|
+
readonly: true
|
|
2691
|
+
}),
|
|
2692
|
+
conversation_id: Field3.lookup("ai_conversations", {
|
|
2693
|
+
label: "Conversation",
|
|
2694
|
+
required: false,
|
|
2695
|
+
description: "Parent conversation, if any"
|
|
2696
|
+
}),
|
|
2697
|
+
agent_id: Field3.text({
|
|
2698
|
+
label: "Agent",
|
|
2699
|
+
required: false,
|
|
2700
|
+
maxLength: 128,
|
|
2701
|
+
description: "Agent metadata name that originated the call"
|
|
2702
|
+
}),
|
|
2703
|
+
operation: Field3.select({
|
|
2704
|
+
label: "Operation",
|
|
2705
|
+
required: true,
|
|
2706
|
+
options: [
|
|
2707
|
+
{ label: "Chat", value: "chat" },
|
|
2708
|
+
{ label: "Complete", value: "complete" },
|
|
2709
|
+
{ label: "Stream Chat", value: "stream_chat" },
|
|
2710
|
+
{ label: "Chat With Tools", value: "chat_with_tools" },
|
|
2711
|
+
{ label: "Generate Object", value: "generate_object" },
|
|
2712
|
+
{ label: "Embed", value: "embed" }
|
|
2713
|
+
]
|
|
2714
|
+
}),
|
|
2715
|
+
model: Field3.text({
|
|
2716
|
+
label: "Model",
|
|
2717
|
+
required: false,
|
|
2718
|
+
maxLength: 128,
|
|
2719
|
+
description: "Model identifier reported by the adapter"
|
|
2720
|
+
}),
|
|
2721
|
+
adapter: Field3.text({
|
|
2722
|
+
label: "Adapter",
|
|
2723
|
+
required: false,
|
|
2724
|
+
maxLength: 64,
|
|
2725
|
+
description: 'LLM adapter name (e.g. "vercel", "memory")'
|
|
2726
|
+
}),
|
|
2727
|
+
prompt_tokens: Field3.number({
|
|
2728
|
+
label: "Prompt Tokens",
|
|
2729
|
+
required: false,
|
|
2730
|
+
defaultValue: 0
|
|
2731
|
+
}),
|
|
2732
|
+
completion_tokens: Field3.number({
|
|
2733
|
+
label: "Completion Tokens",
|
|
2734
|
+
required: false,
|
|
2735
|
+
defaultValue: 0
|
|
2736
|
+
}),
|
|
2737
|
+
total_tokens: Field3.number({
|
|
2738
|
+
label: "Total Tokens",
|
|
2739
|
+
required: false,
|
|
2740
|
+
defaultValue: 0
|
|
2741
|
+
}),
|
|
2742
|
+
input_cost: Field3.number({
|
|
2743
|
+
label: "Input Cost",
|
|
2744
|
+
required: false,
|
|
2745
|
+
description: "Cost attributable to prompt tokens (currency in `currency` field)"
|
|
2746
|
+
}),
|
|
2747
|
+
output_cost: Field3.number({
|
|
2748
|
+
label: "Output Cost",
|
|
2749
|
+
required: false,
|
|
2750
|
+
description: "Cost attributable to completion tokens"
|
|
2751
|
+
}),
|
|
2752
|
+
total_cost: Field3.number({
|
|
2753
|
+
label: "Total Cost",
|
|
2754
|
+
required: false,
|
|
2755
|
+
description: "input_cost + output_cost"
|
|
2756
|
+
}),
|
|
2757
|
+
currency: Field3.text({
|
|
2758
|
+
label: "Currency",
|
|
2759
|
+
required: false,
|
|
2760
|
+
maxLength: 8,
|
|
2761
|
+
defaultValue: "USD"
|
|
2762
|
+
}),
|
|
2763
|
+
latency_ms: Field3.number({
|
|
2764
|
+
label: "Latency (ms)",
|
|
2765
|
+
required: true,
|
|
2766
|
+
defaultValue: 0,
|
|
2767
|
+
description: "Wall-clock duration of the LLM call"
|
|
2768
|
+
}),
|
|
2769
|
+
status: Field3.select({
|
|
2770
|
+
label: "Status",
|
|
2771
|
+
required: true,
|
|
2772
|
+
options: [
|
|
2773
|
+
{ label: "Success", value: "success" },
|
|
2774
|
+
{ label: "Error", value: "error" }
|
|
2775
|
+
]
|
|
2776
|
+
}),
|
|
2777
|
+
error: Field3.textarea({
|
|
2778
|
+
label: "Error",
|
|
2779
|
+
required: false,
|
|
2780
|
+
description: "Error message when status=error"
|
|
2781
|
+
}),
|
|
2782
|
+
metadata: Field3.textarea({
|
|
2783
|
+
label: "Metadata",
|
|
2784
|
+
required: false,
|
|
2785
|
+
description: "JSON-serialized extra fields (request id, user id, \u2026)"
|
|
2786
|
+
}),
|
|
2787
|
+
created_at: Field3.datetime({
|
|
2788
|
+
label: "Created At",
|
|
2789
|
+
required: true,
|
|
2790
|
+
defaultValue: "NOW()",
|
|
2791
|
+
readonly: true
|
|
2792
|
+
})
|
|
2793
|
+
},
|
|
2794
|
+
indexes: [
|
|
2795
|
+
{ fields: ["conversation_id"] },
|
|
2796
|
+
{ fields: ["agent_id"] },
|
|
2797
|
+
{ fields: ["model"] },
|
|
2798
|
+
{ fields: ["status"] },
|
|
2799
|
+
{ fields: ["created_at"] }
|
|
2800
|
+
],
|
|
2801
|
+
enable: {
|
|
2802
|
+
trackHistory: false,
|
|
2803
|
+
searchable: false,
|
|
2804
|
+
apiEnabled: true,
|
|
2805
|
+
apiMethods: ["get", "list"],
|
|
2806
|
+
trash: false,
|
|
2807
|
+
mru: false
|
|
2808
|
+
}
|
|
2809
|
+
});
|
|
2810
|
+
|
|
2496
2811
|
// src/plugin.ts
|
|
2497
2812
|
init_data_tools();
|
|
2498
2813
|
init_metadata_tools();
|
|
2499
2814
|
|
|
2815
|
+
// src/tools/query-data.tool.ts
|
|
2816
|
+
import { z } from "zod";
|
|
2817
|
+
|
|
2818
|
+
// src/schema-retriever.ts
|
|
2819
|
+
var SchemaRetriever = class {
|
|
2820
|
+
constructor(metadata, options = {}) {
|
|
2821
|
+
this.metadata = metadata;
|
|
2822
|
+
this.options = {
|
|
2823
|
+
limit: options.limit ?? 3,
|
|
2824
|
+
minScore: options.minScore ?? 1,
|
|
2825
|
+
maxFieldsPerObject: options.maxFieldsPerObject ?? 12
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
/**
|
|
2829
|
+
* Find object definitions whose name/label/fields match terms in the query.
|
|
2830
|
+
*
|
|
2831
|
+
* Returns matches sorted by score (descending) capped at `limit`. When
|
|
2832
|
+
* the query yields no matches, returns an empty array — callers may
|
|
2833
|
+
* fall back to a generic "describe what data exists" tool call.
|
|
2834
|
+
*/
|
|
2835
|
+
async retrieve(query) {
|
|
2836
|
+
const terms = tokenise(query);
|
|
2837
|
+
if (terms.length === 0) return [];
|
|
2838
|
+
const objects = await this.metadata.listObjects();
|
|
2839
|
+
const hits = [];
|
|
2840
|
+
for (const raw of objects) {
|
|
2841
|
+
const obj = raw;
|
|
2842
|
+
if (!obj?.name) continue;
|
|
2843
|
+
const score = scoreObject(obj, terms);
|
|
2844
|
+
if (score >= this.options.minScore) {
|
|
2845
|
+
hits.push({ object: obj, score });
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
hits.sort((a, b) => b.score - a.score);
|
|
2849
|
+
return hits.slice(0, this.options.limit);
|
|
2850
|
+
}
|
|
2851
|
+
/**
|
|
2852
|
+
* Render hits as a compact Markdown schema snippet.
|
|
2853
|
+
*
|
|
2854
|
+
* Designed to be appended to the system message — every line carries
|
|
2855
|
+
* exactly the information a model needs to choose object/field names
|
|
2856
|
+
* for query construction.
|
|
2857
|
+
*/
|
|
2858
|
+
static renderSnippet(hits, maxFieldsPerObject = 12) {
|
|
2859
|
+
if (hits.length === 0) return "";
|
|
2860
|
+
const lines = ["## Schema context (auto-injected)"];
|
|
2861
|
+
for (const hit of hits) {
|
|
2862
|
+
const obj = hit.object;
|
|
2863
|
+
const label = obj.label ? ` \u2014 ${obj.label}` : "";
|
|
2864
|
+
lines.push(`### ${obj.name}${label}`);
|
|
2865
|
+
const fields = Object.entries(obj.fields ?? {}).slice(0, maxFieldsPerObject);
|
|
2866
|
+
for (const [name, field] of fields) {
|
|
2867
|
+
lines.push(` - ${name}: ${describeField(field)}`);
|
|
2868
|
+
}
|
|
2869
|
+
const total = Object.keys(obj.fields ?? {}).length;
|
|
2870
|
+
if (total > fields.length) {
|
|
2871
|
+
lines.push(` - \u2026${total - fields.length} more field(s)`);
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
return lines.join("\n");
|
|
2875
|
+
}
|
|
2876
|
+
};
|
|
2877
|
+
function tokenise(query) {
|
|
2878
|
+
const raw = query.toLowerCase().match(/[a-z0-9]+/g) ?? [];
|
|
2879
|
+
return raw.filter((t) => t.length >= 2 && !STOPWORDS.has(t));
|
|
2880
|
+
}
|
|
2881
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
2882
|
+
"the",
|
|
2883
|
+
"and",
|
|
2884
|
+
"for",
|
|
2885
|
+
"with",
|
|
2886
|
+
"from",
|
|
2887
|
+
"are",
|
|
2888
|
+
"has",
|
|
2889
|
+
"have",
|
|
2890
|
+
"had",
|
|
2891
|
+
"was",
|
|
2892
|
+
"were",
|
|
2893
|
+
"this",
|
|
2894
|
+
"that",
|
|
2895
|
+
"these",
|
|
2896
|
+
"those",
|
|
2897
|
+
"all",
|
|
2898
|
+
"any",
|
|
2899
|
+
"how",
|
|
2900
|
+
"what",
|
|
2901
|
+
"when",
|
|
2902
|
+
"where",
|
|
2903
|
+
"who",
|
|
2904
|
+
"why",
|
|
2905
|
+
"which",
|
|
2906
|
+
"show",
|
|
2907
|
+
"list",
|
|
2908
|
+
"find",
|
|
2909
|
+
"get",
|
|
2910
|
+
"count",
|
|
2911
|
+
"of",
|
|
2912
|
+
"in",
|
|
2913
|
+
"on",
|
|
2914
|
+
"at",
|
|
2915
|
+
"to",
|
|
2916
|
+
"as",
|
|
2917
|
+
"by",
|
|
2918
|
+
"is",
|
|
2919
|
+
"it",
|
|
2920
|
+
"an",
|
|
2921
|
+
"or",
|
|
2922
|
+
"be",
|
|
2923
|
+
"me"
|
|
2924
|
+
]);
|
|
2925
|
+
function scoreObject(obj, terms) {
|
|
2926
|
+
let score = 0;
|
|
2927
|
+
const nameTokens = splitSnake(obj.name);
|
|
2928
|
+
const labelTokens = obj.label ? tokenise(obj.label) : [];
|
|
2929
|
+
const pluralTokens = obj.pluralLabel ? tokenise(obj.pluralLabel) : [];
|
|
2930
|
+
const descTokens = obj.description ? tokenise(obj.description) : [];
|
|
2931
|
+
for (const term of terms) {
|
|
2932
|
+
if (nameTokens.includes(term)) score += 3;
|
|
2933
|
+
else if (labelTokens.includes(term) || pluralTokens.includes(term)) score += 2;
|
|
2934
|
+
else if (descTokens.includes(term)) score += 1;
|
|
2935
|
+
}
|
|
2936
|
+
for (const [fieldName, field] of Object.entries(obj.fields ?? {})) {
|
|
2937
|
+
const fnTokens = splitSnake(fieldName);
|
|
2938
|
+
const flTokens = field.label ? tokenise(field.label) : [];
|
|
2939
|
+
for (const term of terms) {
|
|
2940
|
+
if (fnTokens.includes(term)) score += 2;
|
|
2941
|
+
else if (flTokens.includes(term)) score += 1;
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
return score;
|
|
2945
|
+
}
|
|
2946
|
+
function splitSnake(name) {
|
|
2947
|
+
return name.toLowerCase().split("_").filter(Boolean);
|
|
2948
|
+
}
|
|
2949
|
+
function describeField(field) {
|
|
2950
|
+
const t = field.type ?? "unknown";
|
|
2951
|
+
if (t === "lookup" && field.reference) return `lookup \u2192 ${field.reference}`;
|
|
2952
|
+
if (t === "select" && Array.isArray(field.options)) {
|
|
2953
|
+
const values = field.options.map(
|
|
2954
|
+
(o) => typeof o === "string" ? o : o.value
|
|
2955
|
+
).filter(Boolean).slice(0, 6);
|
|
2956
|
+
return `select(${values.join("|")})`;
|
|
2957
|
+
}
|
|
2958
|
+
return t;
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
// src/tools/query-data.tool.ts
|
|
2962
|
+
var QueryPlanSchema = z.object({
|
|
2963
|
+
objectName: z.string().min(1).describe('The snake_case object name to query (e.g. "task", "account").'),
|
|
2964
|
+
where: z.record(z.string(), z.unknown()).optional().describe(
|
|
2965
|
+
'Filter conditions as key-value pairs. Use MongoDB-style operators for ranges, e.g. {"amount": {"$gt": 100}}.'
|
|
2966
|
+
),
|
|
2967
|
+
fields: z.array(z.string()).optional().describe("Field names to return. Omit to return all fields."),
|
|
2968
|
+
orderBy: z.array(
|
|
2969
|
+
z.object({
|
|
2970
|
+
field: z.string(),
|
|
2971
|
+
order: z.enum(["asc", "desc"])
|
|
2972
|
+
})
|
|
2973
|
+
).optional().describe("Sort order. First entry is primary sort key."),
|
|
2974
|
+
limit: z.number().int().min(1).max(200).optional().describe("Maximum number of records (default 20, max 200).")
|
|
2975
|
+
});
|
|
2976
|
+
var QUERY_DATA_TOOL = {
|
|
2977
|
+
name: "query_data",
|
|
2978
|
+
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.",
|
|
2979
|
+
parameters: {
|
|
2980
|
+
type: "object",
|
|
2981
|
+
properties: {
|
|
2982
|
+
request: {
|
|
2983
|
+
type: "string",
|
|
2984
|
+
description: "The natural-language question to answer (paraphrase the user's request if needed for clarity)."
|
|
2985
|
+
},
|
|
2986
|
+
model: {
|
|
2987
|
+
type: "string",
|
|
2988
|
+
description: "Optional model id to use for query planning. Defaults to the AI service's default model."
|
|
2989
|
+
}
|
|
2990
|
+
},
|
|
2991
|
+
required: ["request"],
|
|
2992
|
+
additionalProperties: false
|
|
2993
|
+
}
|
|
2994
|
+
};
|
|
2995
|
+
function createQueryDataHandler(ctx) {
|
|
2996
|
+
const retriever = new SchemaRetriever(ctx.metadata);
|
|
2997
|
+
const maxLimit = ctx.maxLimit ?? 100;
|
|
2998
|
+
return async (args) => {
|
|
2999
|
+
const { request, model } = args;
|
|
3000
|
+
if (!request || typeof request !== "string") {
|
|
3001
|
+
return JSON.stringify({ error: "query_data: `request` is required" });
|
|
3002
|
+
}
|
|
3003
|
+
if (!ctx.ai.generateObject) {
|
|
3004
|
+
return JSON.stringify({
|
|
3005
|
+
error: "query_data requires structured-output support. Configure a Vercel-AI-SDK-backed adapter (OpenAI, Anthropic, Google)."
|
|
3006
|
+
});
|
|
3007
|
+
}
|
|
3008
|
+
const hits = await retriever.retrieve(request);
|
|
3009
|
+
if (hits.length === 0) {
|
|
3010
|
+
return JSON.stringify({
|
|
3011
|
+
error: "No matching objects in metadata. Ask the user which object(s) to query, or list available objects via list_objects."
|
|
3012
|
+
});
|
|
3013
|
+
}
|
|
3014
|
+
const snippet = SchemaRetriever.renderSnippet(hits);
|
|
3015
|
+
const planMessages = [
|
|
3016
|
+
{
|
|
3017
|
+
role: "system",
|
|
3018
|
+
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
|
|
3019
|
+
},
|
|
3020
|
+
{ role: "user", content: request }
|
|
3021
|
+
];
|
|
3022
|
+
let plan;
|
|
3023
|
+
try {
|
|
3024
|
+
const generated = await ctx.ai.generateObject(planMessages, QueryPlanSchema, {
|
|
3025
|
+
model,
|
|
3026
|
+
schemaName: "ObjectQLQueryPlan",
|
|
3027
|
+
schemaDescription: "A single ObjectQL find() query to answer the user request."
|
|
3028
|
+
});
|
|
3029
|
+
plan = generated.object;
|
|
3030
|
+
} catch (err) {
|
|
3031
|
+
return JSON.stringify({
|
|
3032
|
+
error: `Failed to plan query: ${err instanceof Error ? err.message : String(err)}`
|
|
3033
|
+
});
|
|
3034
|
+
}
|
|
3035
|
+
const matchedObject = hits.find((h) => h.object.name === plan.objectName)?.object ?? hits[0].object;
|
|
3036
|
+
if (matchedObject.name !== plan.objectName) {
|
|
3037
|
+
return JSON.stringify({
|
|
3038
|
+
error: `Planned object "${plan.objectName}" is not in the retrieved schema. Available: ${hits.map((h) => h.object.name).join(", ")}`
|
|
3039
|
+
});
|
|
3040
|
+
}
|
|
3041
|
+
const limit = Math.min(plan.limit ?? 20, maxLimit);
|
|
3042
|
+
try {
|
|
3043
|
+
const records = await ctx.dataEngine.find(plan.objectName, {
|
|
3044
|
+
where: plan.where,
|
|
3045
|
+
fields: plan.fields,
|
|
3046
|
+
orderBy: plan.orderBy,
|
|
3047
|
+
limit
|
|
3048
|
+
});
|
|
3049
|
+
return JSON.stringify({
|
|
3050
|
+
plan,
|
|
3051
|
+
count: records.length,
|
|
3052
|
+
records
|
|
3053
|
+
});
|
|
3054
|
+
} catch (err) {
|
|
3055
|
+
return JSON.stringify({
|
|
3056
|
+
plan,
|
|
3057
|
+
error: `Query execution failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
3060
|
+
};
|
|
3061
|
+
}
|
|
3062
|
+
function registerQueryDataTool(registry, context) {
|
|
3063
|
+
registry.register(QUERY_DATA_TOOL, createQueryDataHandler(context));
|
|
3064
|
+
}
|
|
3065
|
+
|
|
2500
3066
|
// src/agent-runtime.ts
|
|
2501
3067
|
import { AgentSchema } from "@objectstack/spec/ai";
|
|
2502
3068
|
var AgentRuntime = class {
|
|
@@ -3012,7 +3578,7 @@ Guidelines:
|
|
|
3012
3578
|
};
|
|
3013
3579
|
|
|
3014
3580
|
// src/adapters/vercel-adapter.ts
|
|
3015
|
-
import { generateText, streamText, tool as vercelTool, jsonSchema } from "ai";
|
|
3581
|
+
import { generateText, streamText, generateObject, tool as vercelTool, jsonSchema } from "ai";
|
|
3016
3582
|
function buildVercelOptions(options) {
|
|
3017
3583
|
if (!options) return {};
|
|
3018
3584
|
const opts = {};
|
|
@@ -3094,11 +3660,102 @@ var VercelLLMAdapter = class {
|
|
|
3094
3660
|
"[VercelLLMAdapter] Embeddings require a dedicated EmbeddingModel. Configure an embedding adapter instead."
|
|
3095
3661
|
);
|
|
3096
3662
|
}
|
|
3663
|
+
async generateObject(messages, schema, options) {
|
|
3664
|
+
const { schemaName, schemaDescription, ...rest } = options ?? {};
|
|
3665
|
+
const result = await generateObject({
|
|
3666
|
+
model: this.model,
|
|
3667
|
+
messages,
|
|
3668
|
+
schema,
|
|
3669
|
+
schemaName,
|
|
3670
|
+
schemaDescription,
|
|
3671
|
+
...buildVercelOptions(rest)
|
|
3672
|
+
});
|
|
3673
|
+
return {
|
|
3674
|
+
object: result.object,
|
|
3675
|
+
model: result.response?.modelId,
|
|
3676
|
+
usage: result.usage ? {
|
|
3677
|
+
promptTokens: result.usage.inputTokens ?? 0,
|
|
3678
|
+
completionTokens: result.usage.outputTokens ?? 0,
|
|
3679
|
+
totalTokens: result.usage.totalTokens ?? 0
|
|
3680
|
+
} : void 0
|
|
3681
|
+
};
|
|
3682
|
+
}
|
|
3097
3683
|
async listModels() {
|
|
3098
3684
|
return [];
|
|
3099
3685
|
}
|
|
3100
3686
|
};
|
|
3101
3687
|
|
|
3688
|
+
// src/model-registry.ts
|
|
3689
|
+
var ModelRegistry = class {
|
|
3690
|
+
constructor(config = {}) {
|
|
3691
|
+
this.models = /* @__PURE__ */ new Map();
|
|
3692
|
+
for (const model of config.models ?? []) {
|
|
3693
|
+
this.models.set(model.id, model);
|
|
3694
|
+
}
|
|
3695
|
+
this.defaultModelId = config.defaultModelId;
|
|
3696
|
+
}
|
|
3697
|
+
/** Register or replace a model. */
|
|
3698
|
+
register(model) {
|
|
3699
|
+
this.models.set(model.id, model);
|
|
3700
|
+
}
|
|
3701
|
+
/** Look up a model by id. */
|
|
3702
|
+
get(id) {
|
|
3703
|
+
return this.models.get(id);
|
|
3704
|
+
}
|
|
3705
|
+
/** Look up a model by id, throwing if missing. */
|
|
3706
|
+
getOrThrow(id) {
|
|
3707
|
+
const model = this.models.get(id);
|
|
3708
|
+
if (!model) {
|
|
3709
|
+
throw new Error(
|
|
3710
|
+
`[ModelRegistry] Unknown model "${id}". Registered: ${[...this.models.keys()].join(", ") || "(none)"}`
|
|
3711
|
+
);
|
|
3712
|
+
}
|
|
3713
|
+
return model;
|
|
3714
|
+
}
|
|
3715
|
+
/** Resolve the default model (explicit > first registered > undefined). */
|
|
3716
|
+
getDefault() {
|
|
3717
|
+
if (this.defaultModelId) {
|
|
3718
|
+
return this.models.get(this.defaultModelId);
|
|
3719
|
+
}
|
|
3720
|
+
return this.models.values().next().value;
|
|
3721
|
+
}
|
|
3722
|
+
/** Set the default model id (must already be registered). */
|
|
3723
|
+
setDefault(id) {
|
|
3724
|
+
this.getOrThrow(id);
|
|
3725
|
+
this.defaultModelId = id;
|
|
3726
|
+
}
|
|
3727
|
+
/** All registered models. */
|
|
3728
|
+
list() {
|
|
3729
|
+
return [...this.models.values()];
|
|
3730
|
+
}
|
|
3731
|
+
/** Number of registered models. */
|
|
3732
|
+
get size() {
|
|
3733
|
+
return this.models.size;
|
|
3734
|
+
}
|
|
3735
|
+
/**
|
|
3736
|
+
* Estimate cost in the model's currency (defaults to USD).
|
|
3737
|
+
*
|
|
3738
|
+
* Returns `undefined` when the model is unknown or has no pricing data.
|
|
3739
|
+
* Costs are computed as `(tokens / 1000) * pricePer1kTokens` for input and
|
|
3740
|
+
* output independently, then summed.
|
|
3741
|
+
*/
|
|
3742
|
+
estimateCost(modelId, usage) {
|
|
3743
|
+
const model = this.models.get(modelId);
|
|
3744
|
+
if (!model?.pricing) return void 0;
|
|
3745
|
+
return computeCost(model.pricing, usage);
|
|
3746
|
+
}
|
|
3747
|
+
};
|
|
3748
|
+
function computeCost(pricing, usage) {
|
|
3749
|
+
const inputCost = pricing.inputCostPer1kTokens != null ? usage.promptTokens / 1e3 * pricing.inputCostPer1kTokens : 0;
|
|
3750
|
+
const outputCost = pricing.outputCostPer1kTokens != null ? usage.completionTokens / 1e3 * pricing.outputCostPer1kTokens : 0;
|
|
3751
|
+
return {
|
|
3752
|
+
inputCost,
|
|
3753
|
+
outputCost,
|
|
3754
|
+
totalCost: inputCost + outputCost,
|
|
3755
|
+
currency: pricing.currency ?? "USD"
|
|
3756
|
+
};
|
|
3757
|
+
}
|
|
3758
|
+
|
|
3102
3759
|
// src/plugin.ts
|
|
3103
3760
|
var AIServicePlugin = class {
|
|
3104
3761
|
constructor(options = {}) {
|
|
@@ -3220,10 +3877,34 @@ var AIServicePlugin = class {
|
|
|
3220
3877
|
adapterDescription = detected.description;
|
|
3221
3878
|
}
|
|
3222
3879
|
ctx.logger.info(`[AI] Using LLM adapter: ${adapterDescription}`);
|
|
3880
|
+
const modelRegistry = new ModelRegistry({
|
|
3881
|
+
models: this.options.models,
|
|
3882
|
+
defaultModelId: this.options.defaultModelId
|
|
3883
|
+
});
|
|
3884
|
+
if (modelRegistry.size > 0) {
|
|
3885
|
+
ctx.logger.info(`[AI] ModelRegistry initialised with ${modelRegistry.size} model(s)`);
|
|
3886
|
+
}
|
|
3887
|
+
let traceRecorder;
|
|
3888
|
+
if (this.options.traceRecorder === null) {
|
|
3889
|
+
ctx.logger.debug("[AI] Tracing disabled (traceRecorder=null)");
|
|
3890
|
+
} else if (this.options.traceRecorder) {
|
|
3891
|
+
traceRecorder = this.options.traceRecorder;
|
|
3892
|
+
} else {
|
|
3893
|
+
try {
|
|
3894
|
+
const engine = ctx.getService("data");
|
|
3895
|
+
if (engine && typeof engine.insert === "function") {
|
|
3896
|
+
traceRecorder = new ObjectQLTraceRecorder(engine, { logger: ctx.logger });
|
|
3897
|
+
ctx.logger.info("[AI] Using ObjectQLTraceRecorder (IDataEngine detected)");
|
|
3898
|
+
}
|
|
3899
|
+
} catch {
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3223
3902
|
const config = {
|
|
3224
3903
|
adapter,
|
|
3225
3904
|
logger: ctx.logger,
|
|
3226
|
-
conversationService
|
|
3905
|
+
conversationService,
|
|
3906
|
+
modelRegistry,
|
|
3907
|
+
traceRecorder
|
|
3227
3908
|
};
|
|
3228
3909
|
this.service = new AIService(config);
|
|
3229
3910
|
if (hasExisting) {
|
|
@@ -3238,7 +3919,7 @@ var AIServicePlugin = class {
|
|
|
3238
3919
|
type: "plugin",
|
|
3239
3920
|
scope: "project",
|
|
3240
3921
|
namespace: "ai",
|
|
3241
|
-
objects: [AiConversationObject, AiMessageObject]
|
|
3922
|
+
objects: [AiConversationObject, AiMessageObject, AiTraceObject]
|
|
3242
3923
|
});
|
|
3243
3924
|
if (this.options.debug) {
|
|
3244
3925
|
ctx.hook("ai:beforeChat", async (messages) => {
|
|
@@ -3270,6 +3951,14 @@ var AIServicePlugin = class {
|
|
|
3270
3951
|
if (dataEngine) {
|
|
3271
3952
|
registerDataTools(this.service.toolRegistry, { dataEngine });
|
|
3272
3953
|
ctx.logger.info("[AI] Built-in data tools registered");
|
|
3954
|
+
if (metadataService) {
|
|
3955
|
+
registerQueryDataTool(this.service.toolRegistry, {
|
|
3956
|
+
ai: this.service,
|
|
3957
|
+
metadata: metadataService,
|
|
3958
|
+
dataEngine
|
|
3959
|
+
});
|
|
3960
|
+
ctx.logger.info("[AI] query_data tool registered");
|
|
3961
|
+
}
|
|
3273
3962
|
if (metadataService) {
|
|
3274
3963
|
const { DATA_TOOL_DEFINITIONS: DATA_TOOL_DEFINITIONS2 } = await Promise.resolve().then(() => (init_data_tools(), data_tools_exports));
|
|
3275
3964
|
for (const toolDef of DATA_TOOL_DEFINITIONS2) {
|
|
@@ -3784,14 +4473,20 @@ export {
|
|
|
3784
4473
|
AgentRuntime,
|
|
3785
4474
|
AiConversationObject,
|
|
3786
4475
|
AiMessageObject,
|
|
4476
|
+
AiTraceObject,
|
|
3787
4477
|
DATA_CHAT_AGENT,
|
|
3788
4478
|
DATA_TOOL_DEFINITIONS,
|
|
3789
4479
|
InMemoryConversationService,
|
|
3790
4480
|
METADATA_ASSISTANT_AGENT,
|
|
3791
4481
|
METADATA_TOOL_DEFINITIONS,
|
|
3792
4482
|
MemoryLLMAdapter,
|
|
4483
|
+
ModelRegistry,
|
|
4484
|
+
NullTraceRecorder,
|
|
3793
4485
|
ObjectQLConversationService,
|
|
4486
|
+
ObjectQLTraceRecorder,
|
|
3794
4487
|
PACKAGE_TOOL_DEFINITIONS,
|
|
4488
|
+
QUERY_DATA_TOOL,
|
|
4489
|
+
SchemaRetriever,
|
|
3795
4490
|
SkillRegistry,
|
|
3796
4491
|
ToolRegistry,
|
|
3797
4492
|
VercelLLMAdapter,
|
|
@@ -3800,8 +4495,11 @@ export {
|
|
|
3800
4495
|
buildAgentRoutes,
|
|
3801
4496
|
buildAssistantRoutes,
|
|
3802
4497
|
buildToolRoutes,
|
|
4498
|
+
buildTraceEvent,
|
|
4499
|
+
computeCost,
|
|
3803
4500
|
createObjectTool,
|
|
3804
4501
|
createPackageTool,
|
|
4502
|
+
createQueryDataHandler,
|
|
3805
4503
|
deleteFieldTool,
|
|
3806
4504
|
describeObjectTool,
|
|
3807
4505
|
encodeStreamPart,
|
|
@@ -3814,6 +4512,7 @@ export {
|
|
|
3814
4512
|
registerDataTools,
|
|
3815
4513
|
registerMetadataTools,
|
|
3816
4514
|
registerPackageTools,
|
|
4515
|
+
registerQueryDataTool,
|
|
3817
4516
|
setActivePackageTool
|
|
3818
4517
|
};
|
|
3819
4518
|
//# sourceMappingURL=index.js.map
|