@objectstack/service-ai 6.3.0 → 6.5.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 +317 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -7
- package/dist/index.d.ts +46 -7
- package/dist/index.js +315 -30
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -17,8 +17,21 @@ __export(data_tools_exports, {
|
|
|
17
17
|
QUERY_RECORDS_TOOL: () => QUERY_RECORDS_TOOL,
|
|
18
18
|
registerDataTools: () => registerDataTools
|
|
19
19
|
});
|
|
20
|
+
function buildEngineContext(ctx) {
|
|
21
|
+
if (ctx?.actor) {
|
|
22
|
+
return {
|
|
23
|
+
userId: ctx.actor.id,
|
|
24
|
+
roles: ctx.actor.roles ?? [],
|
|
25
|
+
permissions: ctx.actor.permissions ?? [],
|
|
26
|
+
isSystem: false,
|
|
27
|
+
...ctx.environmentId ? { tenantId: ctx.environmentId } : {},
|
|
28
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return { roles: [], permissions: [], isSystem: true };
|
|
32
|
+
}
|
|
20
33
|
function createQueryRecordsHandler(ctx) {
|
|
21
|
-
return async (args) => {
|
|
34
|
+
return async (args, execCtx) => {
|
|
22
35
|
const {
|
|
23
36
|
objectName,
|
|
24
37
|
where,
|
|
@@ -35,17 +48,19 @@ function createQueryRecordsHandler(ctx) {
|
|
|
35
48
|
fields,
|
|
36
49
|
orderBy,
|
|
37
50
|
limit: safeLimit,
|
|
38
|
-
offset: safeOffset
|
|
51
|
+
offset: safeOffset,
|
|
52
|
+
context: buildEngineContext(execCtx)
|
|
39
53
|
});
|
|
40
54
|
return JSON.stringify({ count: records.length, records });
|
|
41
55
|
};
|
|
42
56
|
}
|
|
43
57
|
function createGetRecordHandler(ctx) {
|
|
44
|
-
return async (args) => {
|
|
58
|
+
return async (args, execCtx) => {
|
|
45
59
|
const { objectName, recordId, fields } = args;
|
|
46
60
|
const record = await ctx.dataEngine.findOne(objectName, {
|
|
47
61
|
where: { id: recordId },
|
|
48
|
-
fields
|
|
62
|
+
fields,
|
|
63
|
+
context: buildEngineContext(execCtx)
|
|
49
64
|
});
|
|
50
65
|
if (!record) {
|
|
51
66
|
return JSON.stringify({ error: `Record "${recordId}" not found in "${objectName}"` });
|
|
@@ -54,7 +69,7 @@ function createGetRecordHandler(ctx) {
|
|
|
54
69
|
};
|
|
55
70
|
}
|
|
56
71
|
function createAggregateDataHandler(ctx) {
|
|
57
|
-
return async (args) => {
|
|
72
|
+
return async (args, execCtx) => {
|
|
58
73
|
const { objectName, aggregations, groupBy, where } = args;
|
|
59
74
|
for (const a of aggregations) {
|
|
60
75
|
if (!VALID_AGG_FUNCTIONS.has(a.function)) {
|
|
@@ -70,7 +85,8 @@ function createAggregateDataHandler(ctx) {
|
|
|
70
85
|
function: a.function,
|
|
71
86
|
field: a.field,
|
|
72
87
|
alias: a.alias
|
|
73
|
-
}))
|
|
88
|
+
})),
|
|
89
|
+
context: buildEngineContext(execCtx)
|
|
74
90
|
});
|
|
75
91
|
return JSON.stringify(result);
|
|
76
92
|
};
|
|
@@ -1205,8 +1221,15 @@ var ToolRegistry = class {
|
|
|
1205
1221
|
}
|
|
1206
1222
|
/**
|
|
1207
1223
|
* Execute a tool call and return the result.
|
|
1224
|
+
*
|
|
1225
|
+
* @param toolCall The decoded tool-call part from the model response.
|
|
1226
|
+
* @param ctx Optional per-call execution context (actor, conversation,
|
|
1227
|
+
* environment). Handlers may use this to enforce RLS,
|
|
1228
|
+
* attribute audit entries, or correlate traces. When
|
|
1229
|
+
* omitted, handlers should fall back to system-level
|
|
1230
|
+
* behaviour for backward compatibility.
|
|
1208
1231
|
*/
|
|
1209
|
-
async execute(toolCall) {
|
|
1232
|
+
async execute(toolCall, ctx) {
|
|
1210
1233
|
const handler = this.handlers.get(toolCall.toolName);
|
|
1211
1234
|
if (!handler) {
|
|
1212
1235
|
return {
|
|
@@ -1219,7 +1242,7 @@ var ToolRegistry = class {
|
|
|
1219
1242
|
}
|
|
1220
1243
|
try {
|
|
1221
1244
|
const args = typeof toolCall.input === "string" ? JSON.parse(toolCall.input) : toolCall.input ?? {};
|
|
1222
|
-
const content = await handler(args);
|
|
1245
|
+
const content = await handler(args, ctx);
|
|
1223
1246
|
return {
|
|
1224
1247
|
type: "tool-result",
|
|
1225
1248
|
toolCallId: toolCall.toolCallId,
|
|
@@ -1238,10 +1261,11 @@ var ToolRegistry = class {
|
|
|
1238
1261
|
}
|
|
1239
1262
|
}
|
|
1240
1263
|
/**
|
|
1241
|
-
* Execute multiple tool calls in parallel
|
|
1264
|
+
* Execute multiple tool calls in parallel, threading the same
|
|
1265
|
+
* execution context to each handler.
|
|
1242
1266
|
*/
|
|
1243
|
-
async executeAll(toolCalls) {
|
|
1244
|
-
return Promise.all(toolCalls.map((tc) => this.execute(tc)));
|
|
1267
|
+
async executeAll(toolCalls, ctx) {
|
|
1268
|
+
return Promise.all(toolCalls.map((tc) => this.execute(tc, ctx)));
|
|
1245
1269
|
}
|
|
1246
1270
|
/**
|
|
1247
1271
|
* Clear all registered tools.
|
|
@@ -1419,6 +1443,23 @@ var _AIService = class _AIService {
|
|
|
1419
1443
|
get adapterName() {
|
|
1420
1444
|
return this.adapter.name;
|
|
1421
1445
|
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Best-effort persistence of a single chat message to the conversation
|
|
1448
|
+
* store. Failures are logged at warn level and swallowed — chat requests
|
|
1449
|
+
* must never fail because the history write failed. Mirrors the
|
|
1450
|
+
* precedent set by `ObjectQLTraceRecorder.record`.
|
|
1451
|
+
*/
|
|
1452
|
+
async persistMessage(conversationId, message) {
|
|
1453
|
+
try {
|
|
1454
|
+
await this.conversationService.addMessage(conversationId, message);
|
|
1455
|
+
} catch (err) {
|
|
1456
|
+
this.logger.warn("[AI] persist message failed", {
|
|
1457
|
+
conversationId,
|
|
1458
|
+
role: message.role,
|
|
1459
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1422
1463
|
/**
|
|
1423
1464
|
* Run an adapter call and emit a trace event.
|
|
1424
1465
|
*
|
|
@@ -1532,9 +1573,15 @@ var _AIService = class _AIService {
|
|
|
1532
1573
|
);
|
|
1533
1574
|
}
|
|
1534
1575
|
async chatWithToolsImpl(messages, options) {
|
|
1535
|
-
const {
|
|
1576
|
+
const {
|
|
1577
|
+
maxIterations: maxIter,
|
|
1578
|
+
onToolError,
|
|
1579
|
+
toolExecutionContext,
|
|
1580
|
+
...restOptions
|
|
1581
|
+
} = options ?? {};
|
|
1536
1582
|
const maxIterations = maxIter ?? _AIService.DEFAULT_MAX_ITERATIONS;
|
|
1537
1583
|
const registeredTools = this.toolRegistry.getAll();
|
|
1584
|
+
const conversationId = toolExecutionContext?.conversationId;
|
|
1538
1585
|
const mergedTools = [
|
|
1539
1586
|
...registeredTools,
|
|
1540
1587
|
...restOptions.tools ?? []
|
|
@@ -1545,6 +1592,12 @@ var _AIService = class _AIService {
|
|
|
1545
1592
|
toolChoice: mergedTools.length > 0 ? restOptions.toolChoice ?? "auto" : void 0
|
|
1546
1593
|
};
|
|
1547
1594
|
const conversation = [...messages];
|
|
1595
|
+
if (conversationId && messages.length > 0) {
|
|
1596
|
+
const last = messages[messages.length - 1];
|
|
1597
|
+
if (last && last.role === "user") {
|
|
1598
|
+
await this.persistMessage(conversationId, last);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1548
1601
|
const toolErrors = [];
|
|
1549
1602
|
this.logger.debug("[AI] chatWithTools start", {
|
|
1550
1603
|
messageCount: conversation.length,
|
|
@@ -1556,6 +1609,12 @@ var _AIService = class _AIService {
|
|
|
1556
1609
|
const result = await this.adapter.chat(conversation, chatOptions);
|
|
1557
1610
|
if (!result.toolCalls || result.toolCalls.length === 0) {
|
|
1558
1611
|
this.logger.debug("[AI] chatWithTools finished", { iteration, content: result.content.slice(0, 80) });
|
|
1612
|
+
if (conversationId) {
|
|
1613
|
+
await this.persistMessage(conversationId, {
|
|
1614
|
+
role: "assistant",
|
|
1615
|
+
content: result.content
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1559
1618
|
return result;
|
|
1560
1619
|
}
|
|
1561
1620
|
this.logger.debug("[AI] chatWithTools tool calls", {
|
|
@@ -1565,11 +1624,18 @@ var _AIService = class _AIService {
|
|
|
1565
1624
|
const assistantContent = [];
|
|
1566
1625
|
if (result.content) assistantContent.push({ type: "text", text: result.content });
|
|
1567
1626
|
assistantContent.push(...result.toolCalls);
|
|
1568
|
-
|
|
1627
|
+
const assistantTurn = {
|
|
1569
1628
|
role: "assistant",
|
|
1570
1629
|
content: assistantContent
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1630
|
+
};
|
|
1631
|
+
conversation.push(assistantTurn);
|
|
1632
|
+
if (conversationId) {
|
|
1633
|
+
await this.persistMessage(conversationId, assistantTurn);
|
|
1634
|
+
}
|
|
1635
|
+
const toolResults = await this.toolRegistry.executeAll(
|
|
1636
|
+
result.toolCalls,
|
|
1637
|
+
toolExecutionContext
|
|
1638
|
+
);
|
|
1573
1639
|
for (const tr of toolResults) {
|
|
1574
1640
|
if (tr.isError) {
|
|
1575
1641
|
const matchedCall = result.toolCalls.find((tc) => tc.toolCallId === tr.toolCallId);
|
|
@@ -1585,10 +1651,14 @@ var _AIService = class _AIService {
|
|
|
1585
1651
|
}
|
|
1586
1652
|
}
|
|
1587
1653
|
}
|
|
1588
|
-
|
|
1654
|
+
const toolTurn = {
|
|
1589
1655
|
role: "tool",
|
|
1590
1656
|
content: [tr]
|
|
1591
|
-
}
|
|
1657
|
+
};
|
|
1658
|
+
conversation.push(toolTurn);
|
|
1659
|
+
if (conversationId) {
|
|
1660
|
+
await this.persistMessage(conversationId, toolTurn);
|
|
1661
|
+
}
|
|
1592
1662
|
}
|
|
1593
1663
|
if (abortedByCallback) {
|
|
1594
1664
|
break;
|
|
@@ -1606,6 +1676,12 @@ var _AIService = class _AIService {
|
|
|
1606
1676
|
tools: void 0,
|
|
1607
1677
|
toolChoice: void 0
|
|
1608
1678
|
});
|
|
1679
|
+
if (conversationId) {
|
|
1680
|
+
await this.persistMessage(conversationId, {
|
|
1681
|
+
role: "assistant",
|
|
1682
|
+
content: finalResult.content
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1609
1685
|
return finalResult;
|
|
1610
1686
|
}
|
|
1611
1687
|
/**
|
|
@@ -1616,9 +1692,15 @@ var _AIService = class _AIService {
|
|
|
1616
1692
|
* fed back until a final text stream is produced.
|
|
1617
1693
|
*/
|
|
1618
1694
|
async *streamChatWithTools(messages, options) {
|
|
1619
|
-
const {
|
|
1695
|
+
const {
|
|
1696
|
+
maxIterations: maxIter,
|
|
1697
|
+
onToolError,
|
|
1698
|
+
toolExecutionContext,
|
|
1699
|
+
...restOptions
|
|
1700
|
+
} = options ?? {};
|
|
1620
1701
|
const maxIterations = maxIter ?? _AIService.DEFAULT_MAX_ITERATIONS;
|
|
1621
1702
|
const registeredTools = this.toolRegistry.getAll();
|
|
1703
|
+
const conversationId = toolExecutionContext?.conversationId;
|
|
1622
1704
|
const mergedTools = [
|
|
1623
1705
|
...registeredTools,
|
|
1624
1706
|
...restOptions.tools ?? []
|
|
@@ -1630,9 +1712,21 @@ var _AIService = class _AIService {
|
|
|
1630
1712
|
};
|
|
1631
1713
|
const conversation = [...messages];
|
|
1632
1714
|
let abortedByCallback = false;
|
|
1715
|
+
if (conversationId && messages.length > 0) {
|
|
1716
|
+
const last = messages[messages.length - 1];
|
|
1717
|
+
if (last && last.role === "user") {
|
|
1718
|
+
await this.persistMessage(conversationId, last);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1633
1721
|
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
1634
1722
|
const result2 = await this.adapter.chat(conversation, chatOptions);
|
|
1635
1723
|
if (!result2.toolCalls || result2.toolCalls.length === 0) {
|
|
1724
|
+
if (conversationId) {
|
|
1725
|
+
await this.persistMessage(conversationId, {
|
|
1726
|
+
role: "assistant",
|
|
1727
|
+
content: result2.content
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1636
1730
|
yield textDeltaPart("stream", result2.content);
|
|
1637
1731
|
yield finishPart(result2);
|
|
1638
1732
|
return;
|
|
@@ -1643,11 +1737,18 @@ var _AIService = class _AIService {
|
|
|
1643
1737
|
const assistantContent = [];
|
|
1644
1738
|
if (result2.content) assistantContent.push({ type: "text", text: result2.content });
|
|
1645
1739
|
assistantContent.push(...result2.toolCalls);
|
|
1646
|
-
|
|
1740
|
+
const assistantTurn = {
|
|
1647
1741
|
role: "assistant",
|
|
1648
1742
|
content: assistantContent
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1743
|
+
};
|
|
1744
|
+
conversation.push(assistantTurn);
|
|
1745
|
+
if (conversationId) {
|
|
1746
|
+
await this.persistMessage(conversationId, assistantTurn);
|
|
1747
|
+
}
|
|
1748
|
+
const toolResults = await this.toolRegistry.executeAll(
|
|
1749
|
+
result2.toolCalls,
|
|
1750
|
+
toolExecutionContext
|
|
1751
|
+
);
|
|
1651
1752
|
for (const tr of toolResults) {
|
|
1652
1753
|
if (tr.isError && onToolError) {
|
|
1653
1754
|
const matchedCall = result2.toolCalls.find((tc) => tc.toolCallId === tr.toolCallId);
|
|
@@ -1665,10 +1766,14 @@ var _AIService = class _AIService {
|
|
|
1665
1766
|
toolName: tr.toolName,
|
|
1666
1767
|
output: tr.output
|
|
1667
1768
|
};
|
|
1668
|
-
|
|
1769
|
+
const toolTurn = {
|
|
1669
1770
|
role: "tool",
|
|
1670
1771
|
content: [tr]
|
|
1671
|
-
}
|
|
1772
|
+
};
|
|
1773
|
+
conversation.push(toolTurn);
|
|
1774
|
+
if (conversationId) {
|
|
1775
|
+
await this.persistMessage(conversationId, toolTurn);
|
|
1776
|
+
}
|
|
1672
1777
|
}
|
|
1673
1778
|
if (abortedByCallback) {
|
|
1674
1779
|
break;
|
|
@@ -1681,6 +1786,12 @@ var _AIService = class _AIService {
|
|
|
1681
1786
|
}
|
|
1682
1787
|
const finalOptions = { ...chatOptions, tools: void 0, toolChoice: void 0 };
|
|
1683
1788
|
const result = await this.adapter.chat(conversation, finalOptions);
|
|
1789
|
+
if (conversationId) {
|
|
1790
|
+
await this.persistMessage(conversationId, {
|
|
1791
|
+
role: "assistant",
|
|
1792
|
+
content: result.content
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1684
1795
|
yield textDeltaPart("stream", result.content);
|
|
1685
1796
|
yield finishPart(result);
|
|
1686
1797
|
}
|
|
@@ -2185,6 +2296,32 @@ function buildAIRoutes(aiService, conversationService, logger) {
|
|
|
2185
2296
|
}
|
|
2186
2297
|
}
|
|
2187
2298
|
},
|
|
2299
|
+
{
|
|
2300
|
+
method: "GET",
|
|
2301
|
+
path: "/api/v1/ai/conversations/:id",
|
|
2302
|
+
description: "Get a conversation with its full message history",
|
|
2303
|
+
auth: true,
|
|
2304
|
+
permissions: ["ai:conversations"],
|
|
2305
|
+
handler: async (req) => {
|
|
2306
|
+
const id = req.params?.id;
|
|
2307
|
+
if (!id) {
|
|
2308
|
+
return { status: 400, body: { error: "conversation id is required" } };
|
|
2309
|
+
}
|
|
2310
|
+
try {
|
|
2311
|
+
const conversation = await conversationService.get(id);
|
|
2312
|
+
if (!conversation) {
|
|
2313
|
+
return { status: 404, body: { error: `Conversation "${id}" not found` } };
|
|
2314
|
+
}
|
|
2315
|
+
if (req.user?.userId && conversation.userId && conversation.userId !== req.user.userId) {
|
|
2316
|
+
return { status: 403, body: { error: "You do not have access to this conversation" } };
|
|
2317
|
+
}
|
|
2318
|
+
return { status: 200, body: conversation };
|
|
2319
|
+
} catch (err) {
|
|
2320
|
+
logger.error("[AI Route] GET /conversations/:id error", err instanceof Error ? err : void 0);
|
|
2321
|
+
return { status: 500, body: { error: "Internal AI service error" } };
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
},
|
|
2188
2325
|
{
|
|
2189
2326
|
method: "POST",
|
|
2190
2327
|
path: "/api/v1/ai/conversations/:id/messages",
|
|
@@ -2351,7 +2488,19 @@ function buildAgentRoutes(aiService, agentRuntime, logger) {
|
|
|
2351
2488
|
];
|
|
2352
2489
|
const chatWithToolsOptions = {
|
|
2353
2490
|
...mergedOptions,
|
|
2354
|
-
maxIterations: agent.planning?.maxIterations
|
|
2491
|
+
maxIterations: agent.planning?.maxIterations,
|
|
2492
|
+
// Forward authenticated actor → built-in data tools enforce
|
|
2493
|
+
// ObjectQL RLS, action tools attribute audit to the user.
|
|
2494
|
+
toolExecutionContext: req.user ? {
|
|
2495
|
+
actor: {
|
|
2496
|
+
id: req.user.userId,
|
|
2497
|
+
name: req.user.displayName,
|
|
2498
|
+
roles: req.user.roles,
|
|
2499
|
+
permissions: req.user.permissions
|
|
2500
|
+
},
|
|
2501
|
+
conversationId: typeof body.conversationId === "string" ? body.conversationId : void 0,
|
|
2502
|
+
environmentId: typeof chatContext?.environmentId === "string" ? chatContext.environmentId : void 0
|
|
2503
|
+
} : void 0
|
|
2355
2504
|
};
|
|
2356
2505
|
const wantStream = body.stream !== false;
|
|
2357
2506
|
if (wantStream) {
|
|
@@ -2540,7 +2689,21 @@ function buildAssistantRoutes(aiService, agentRuntime, skillRegistry, logger) {
|
|
|
2540
2689
|
];
|
|
2541
2690
|
const chatWithToolsOptions = {
|
|
2542
2691
|
...mergedOptions,
|
|
2543
|
-
maxIterations: agent.planning?.maxIterations
|
|
2692
|
+
maxIterations: agent.planning?.maxIterations,
|
|
2693
|
+
// Forward the authenticated actor into every tool the loop
|
|
2694
|
+
// invokes — built-in data tools promote this into the
|
|
2695
|
+
// ObjectQL execution context so row-level security scopes
|
|
2696
|
+
// the LLM to whatever the human user can already see/do.
|
|
2697
|
+
toolExecutionContext: req.user ? {
|
|
2698
|
+
actor: {
|
|
2699
|
+
id: req.user.userId,
|
|
2700
|
+
name: req.user.displayName,
|
|
2701
|
+
roles: req.user.roles,
|
|
2702
|
+
permissions: req.user.permissions
|
|
2703
|
+
},
|
|
2704
|
+
conversationId: typeof body.conversationId === "string" ? body.conversationId : void 0,
|
|
2705
|
+
environmentId: typeof context.environmentId === "string" ? context.environmentId : void 0
|
|
2706
|
+
} : void 0
|
|
2544
2707
|
};
|
|
2545
2708
|
const wantStream = body.stream !== false;
|
|
2546
2709
|
if (wantStream) {
|
|
@@ -3888,6 +4051,19 @@ function describeField(field) {
|
|
|
3888
4051
|
}
|
|
3889
4052
|
|
|
3890
4053
|
// src/tools/query-data.tool.ts
|
|
4054
|
+
function buildAiEngineContext(ctx) {
|
|
4055
|
+
if (ctx?.actor) {
|
|
4056
|
+
return {
|
|
4057
|
+
userId: ctx.actor.id,
|
|
4058
|
+
roles: ctx.actor.roles ?? [],
|
|
4059
|
+
permissions: ctx.actor.permissions ?? [],
|
|
4060
|
+
isSystem: false,
|
|
4061
|
+
...ctx.environmentId ? { tenantId: ctx.environmentId } : {},
|
|
4062
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {}
|
|
4063
|
+
};
|
|
4064
|
+
}
|
|
4065
|
+
return { roles: [], permissions: [], isSystem: true };
|
|
4066
|
+
}
|
|
3891
4067
|
var QueryPlanSchema = z.object({
|
|
3892
4068
|
objectName: z.string().min(1).describe('The snake_case object name to query (e.g. "task", "account").'),
|
|
3893
4069
|
whereJson: z.string().nullable().describe(
|
|
@@ -3920,7 +4096,7 @@ var QUERY_DATA_TOOL = {
|
|
|
3920
4096
|
function createQueryDataHandler(ctx) {
|
|
3921
4097
|
const retriever = new SchemaRetriever(ctx.metadata);
|
|
3922
4098
|
const maxLimit = ctx.maxLimit ?? 100;
|
|
3923
|
-
return async (args) => {
|
|
4099
|
+
return async (args, execCtx) => {
|
|
3924
4100
|
const { request } = args;
|
|
3925
4101
|
if (!request || typeof request !== "string") {
|
|
3926
4102
|
return JSON.stringify({ error: "query_data: `request` is required" });
|
|
@@ -3987,7 +4163,8 @@ function createQueryDataHandler(ctx) {
|
|
|
3987
4163
|
where,
|
|
3988
4164
|
fields: plan.fields ?? void 0,
|
|
3989
4165
|
orderBy: plan.orderBy ?? void 0,
|
|
3990
|
-
limit
|
|
4166
|
+
limit,
|
|
4167
|
+
context: buildAiEngineContext(execCtx)
|
|
3991
4168
|
});
|
|
3992
4169
|
return JSON.stringify({
|
|
3993
4170
|
plan: { ...plan, where },
|
|
@@ -4167,11 +4344,13 @@ function buildHandlerEngineAdapter(engine) {
|
|
|
4167
4344
|
};
|
|
4168
4345
|
}
|
|
4169
4346
|
function createActionToolHandler(action, ctx) {
|
|
4170
|
-
const
|
|
4347
|
+
const fallbackPrincipal = ctx.principal ?? { id: "ai_agent", name: "AI Assistant" };
|
|
4171
4348
|
const requiresRecord = Array.isArray(action.locations) && action.locations.some(
|
|
4172
4349
|
(l) => l === "list_item" || l === "record_header" || l === "record_more" || l === "record_related"
|
|
4173
4350
|
);
|
|
4174
|
-
return async (args) => {
|
|
4351
|
+
return async (args, execCtx) => {
|
|
4352
|
+
const principal = execCtx?.actor ? { id: execCtx.actor.id, name: execCtx.actor.name } : fallbackPrincipal;
|
|
4353
|
+
const engineCtx = buildActionEngineContext(execCtx);
|
|
4175
4354
|
const objectName = action.objectName;
|
|
4176
4355
|
const target = action.target;
|
|
4177
4356
|
const result = {
|
|
@@ -4197,7 +4376,8 @@ function createActionToolHandler(action, ctx) {
|
|
|
4197
4376
|
try {
|
|
4198
4377
|
const found = await ctx.dataEngine.find(objectName, {
|
|
4199
4378
|
where: { id: recordId },
|
|
4200
|
-
limit: 1
|
|
4379
|
+
limit: 1,
|
|
4380
|
+
context: engineCtx
|
|
4201
4381
|
});
|
|
4202
4382
|
record = found[0];
|
|
4203
4383
|
if (!record) {
|
|
@@ -4219,6 +4399,8 @@ function createActionToolHandler(action, ctx) {
|
|
|
4219
4399
|
actionName: action.name,
|
|
4220
4400
|
toolName,
|
|
4221
4401
|
toolInput: args,
|
|
4402
|
+
conversationId: execCtx?.conversationId,
|
|
4403
|
+
messageId: execCtx?.messageId,
|
|
4222
4404
|
proposedBy: principal.id
|
|
4223
4405
|
});
|
|
4224
4406
|
const pending = {
|
|
@@ -4256,6 +4438,19 @@ function createActionToolHandler(action, ctx) {
|
|
|
4256
4438
|
}
|
|
4257
4439
|
};
|
|
4258
4440
|
}
|
|
4441
|
+
function buildActionEngineContext(ctx) {
|
|
4442
|
+
if (ctx?.actor) {
|
|
4443
|
+
return {
|
|
4444
|
+
userId: ctx.actor.id,
|
|
4445
|
+
roles: ctx.actor.roles ?? [],
|
|
4446
|
+
permissions: ctx.actor.permissions ?? [],
|
|
4447
|
+
isSystem: false,
|
|
4448
|
+
...ctx.environmentId ? { tenantId: ctx.environmentId } : {},
|
|
4449
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {}
|
|
4450
|
+
};
|
|
4451
|
+
}
|
|
4452
|
+
return { roles: [], permissions: [], isSystem: true };
|
|
4453
|
+
}
|
|
4259
4454
|
async function dispatchScriptAction(action, ctx, params, record, principal) {
|
|
4260
4455
|
const engineAdapter = buildHandlerEngineAdapter(ctx.dataEngine);
|
|
4261
4456
|
const handlerCtx = { record, user: principal, engine: engineAdapter, params };
|
|
@@ -5582,6 +5777,94 @@ var AIServicePlugin = class {
|
|
|
5582
5777
|
// src/index.ts
|
|
5583
5778
|
init_data_tools();
|
|
5584
5779
|
init_metadata_tools();
|
|
5780
|
+
|
|
5781
|
+
// src/tools/knowledge-tools.ts
|
|
5782
|
+
var DEFAULT_TOP_K = 5;
|
|
5783
|
+
var MAX_TOP_K = 20;
|
|
5784
|
+
function buildEngineContext2(ctx) {
|
|
5785
|
+
if (ctx?.actor) {
|
|
5786
|
+
return {
|
|
5787
|
+
userId: ctx.actor.id,
|
|
5788
|
+
roles: ctx.actor.roles ?? [],
|
|
5789
|
+
permissions: ctx.actor.permissions ?? [],
|
|
5790
|
+
isSystem: false,
|
|
5791
|
+
...ctx.environmentId ? { tenantId: ctx.environmentId } : {},
|
|
5792
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {}
|
|
5793
|
+
};
|
|
5794
|
+
}
|
|
5795
|
+
return { roles: [], permissions: [], isSystem: true };
|
|
5796
|
+
}
|
|
5797
|
+
var SEARCH_KNOWLEDGE_TOOL = {
|
|
5798
|
+
name: "search_knowledge",
|
|
5799
|
+
description: "Search registered knowledge sources (object snapshots, uploaded files, external URLs) and return the most relevant excerpts. Use this when the user asks a question whose answer is in documents, policies, or reference material the LLM does not natively know. Results are already permission-filtered for the current user.",
|
|
5800
|
+
parameters: {
|
|
5801
|
+
type: "object",
|
|
5802
|
+
properties: {
|
|
5803
|
+
query: {
|
|
5804
|
+
type: "string",
|
|
5805
|
+
description: "Free-text question or keywords to search for."
|
|
5806
|
+
},
|
|
5807
|
+
sourceIds: {
|
|
5808
|
+
type: "array",
|
|
5809
|
+
items: { type: "string" },
|
|
5810
|
+
description: "Optional list of source ids to restrict the search to. When omitted, every source the caller can see is queried."
|
|
5811
|
+
},
|
|
5812
|
+
topK: {
|
|
5813
|
+
type: "number",
|
|
5814
|
+
description: `Maximum hits to return (default ${DEFAULT_TOP_K}, max ${MAX_TOP_K}).`
|
|
5815
|
+
},
|
|
5816
|
+
filter: {
|
|
5817
|
+
type: "object",
|
|
5818
|
+
description: 'Optional adapter-specific metadata filter (e.g. {"topic":"refunds"}).'
|
|
5819
|
+
}
|
|
5820
|
+
},
|
|
5821
|
+
required: ["query"]
|
|
5822
|
+
}
|
|
5823
|
+
};
|
|
5824
|
+
function createSearchKnowledgeHandler(context) {
|
|
5825
|
+
return async (args, ctx) => {
|
|
5826
|
+
const query = typeof args.query === "string" ? args.query.trim() : "";
|
|
5827
|
+
if (!query) {
|
|
5828
|
+
return JSON.stringify({ error: "search_knowledge: `query` is required." });
|
|
5829
|
+
}
|
|
5830
|
+
const sourceIds = Array.isArray(args.sourceIds) ? args.sourceIds.filter((x) => typeof x === "string") : void 0;
|
|
5831
|
+
const topKRaw = typeof args.topK === "number" ? args.topK : DEFAULT_TOP_K;
|
|
5832
|
+
const topK = Math.max(1, Math.min(MAX_TOP_K, Math.floor(topKRaw)));
|
|
5833
|
+
const filter = args.filter && typeof args.filter === "object" && !Array.isArray(args.filter) ? args.filter : void 0;
|
|
5834
|
+
const opts = {
|
|
5835
|
+
topK,
|
|
5836
|
+
executionContext: buildEngineContext2(ctx)
|
|
5837
|
+
};
|
|
5838
|
+
if (sourceIds && sourceIds.length > 0) opts.sourceIds = sourceIds;
|
|
5839
|
+
if (filter) opts.filter = filter;
|
|
5840
|
+
try {
|
|
5841
|
+
const hits = await context.knowledgeService.search(query, opts);
|
|
5842
|
+
return JSON.stringify({
|
|
5843
|
+
query,
|
|
5844
|
+
count: hits.length,
|
|
5845
|
+
hits: hits.map((h) => ({
|
|
5846
|
+
documentId: h.documentId,
|
|
5847
|
+
chunkId: h.chunkId,
|
|
5848
|
+
sourceId: h.sourceId,
|
|
5849
|
+
sourceRecordId: h.sourceRecordId,
|
|
5850
|
+
score: Number(h.score?.toFixed?.(4) ?? h.score ?? 0),
|
|
5851
|
+
title: h.title,
|
|
5852
|
+
snippet: h.snippet,
|
|
5853
|
+
metadata: h.metadata
|
|
5854
|
+
}))
|
|
5855
|
+
});
|
|
5856
|
+
} catch (err) {
|
|
5857
|
+
return JSON.stringify({
|
|
5858
|
+
error: `search_knowledge failed: ${err instanceof Error ? err.message : String(err)}`
|
|
5859
|
+
});
|
|
5860
|
+
}
|
|
5861
|
+
};
|
|
5862
|
+
}
|
|
5863
|
+
function registerKnowledgeTools(registry, context) {
|
|
5864
|
+
registry.register(SEARCH_KNOWLEDGE_TOOL, createSearchKnowledgeHandler(context));
|
|
5865
|
+
}
|
|
5866
|
+
|
|
5867
|
+
// src/index.ts
|
|
5585
5868
|
init_metadata_tools();
|
|
5586
5869
|
|
|
5587
5870
|
// src/tools/list-packages.tool.ts
|
|
@@ -5937,6 +6220,7 @@ export {
|
|
|
5937
6220
|
ObjectQLTraceRecorder,
|
|
5938
6221
|
PACKAGE_TOOL_DEFINITIONS,
|
|
5939
6222
|
QUERY_DATA_TOOL,
|
|
6223
|
+
SEARCH_KNOWLEDGE_TOOL,
|
|
5940
6224
|
SchemaRetriever,
|
|
5941
6225
|
SkillRegistry,
|
|
5942
6226
|
ToolRegistry,
|
|
@@ -5965,6 +6249,7 @@ export {
|
|
|
5965
6249
|
modifyFieldTool,
|
|
5966
6250
|
registerActionsAsTools,
|
|
5967
6251
|
registerDataTools,
|
|
6252
|
+
registerKnowledgeTools,
|
|
5968
6253
|
registerMetadataTools,
|
|
5969
6254
|
registerPackageTools,
|
|
5970
6255
|
registerQueryDataTool,
|