@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.cjs
CHANGED
|
@@ -29,8 +29,21 @@ __export(data_tools_exports, {
|
|
|
29
29
|
QUERY_RECORDS_TOOL: () => QUERY_RECORDS_TOOL,
|
|
30
30
|
registerDataTools: () => registerDataTools
|
|
31
31
|
});
|
|
32
|
+
function buildEngineContext(ctx) {
|
|
33
|
+
if (ctx?.actor) {
|
|
34
|
+
return {
|
|
35
|
+
userId: ctx.actor.id,
|
|
36
|
+
roles: ctx.actor.roles ?? [],
|
|
37
|
+
permissions: ctx.actor.permissions ?? [],
|
|
38
|
+
isSystem: false,
|
|
39
|
+
...ctx.environmentId ? { tenantId: ctx.environmentId } : {},
|
|
40
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { roles: [], permissions: [], isSystem: true };
|
|
44
|
+
}
|
|
32
45
|
function createQueryRecordsHandler(ctx) {
|
|
33
|
-
return async (args) => {
|
|
46
|
+
return async (args, execCtx) => {
|
|
34
47
|
const {
|
|
35
48
|
objectName,
|
|
36
49
|
where,
|
|
@@ -47,17 +60,19 @@ function createQueryRecordsHandler(ctx) {
|
|
|
47
60
|
fields,
|
|
48
61
|
orderBy,
|
|
49
62
|
limit: safeLimit,
|
|
50
|
-
offset: safeOffset
|
|
63
|
+
offset: safeOffset,
|
|
64
|
+
context: buildEngineContext(execCtx)
|
|
51
65
|
});
|
|
52
66
|
return JSON.stringify({ count: records.length, records });
|
|
53
67
|
};
|
|
54
68
|
}
|
|
55
69
|
function createGetRecordHandler(ctx) {
|
|
56
|
-
return async (args) => {
|
|
70
|
+
return async (args, execCtx) => {
|
|
57
71
|
const { objectName, recordId, fields } = args;
|
|
58
72
|
const record = await ctx.dataEngine.findOne(objectName, {
|
|
59
73
|
where: { id: recordId },
|
|
60
|
-
fields
|
|
74
|
+
fields,
|
|
75
|
+
context: buildEngineContext(execCtx)
|
|
61
76
|
});
|
|
62
77
|
if (!record) {
|
|
63
78
|
return JSON.stringify({ error: `Record "${recordId}" not found in "${objectName}"` });
|
|
@@ -66,7 +81,7 @@ function createGetRecordHandler(ctx) {
|
|
|
66
81
|
};
|
|
67
82
|
}
|
|
68
83
|
function createAggregateDataHandler(ctx) {
|
|
69
|
-
return async (args) => {
|
|
84
|
+
return async (args, execCtx) => {
|
|
70
85
|
const { objectName, aggregations, groupBy, where } = args;
|
|
71
86
|
for (const a of aggregations) {
|
|
72
87
|
if (!VALID_AGG_FUNCTIONS.has(a.function)) {
|
|
@@ -82,7 +97,8 @@ function createAggregateDataHandler(ctx) {
|
|
|
82
97
|
function: a.function,
|
|
83
98
|
field: a.field,
|
|
84
99
|
alias: a.alias
|
|
85
|
-
}))
|
|
100
|
+
})),
|
|
101
|
+
context: buildEngineContext(execCtx)
|
|
86
102
|
});
|
|
87
103
|
return JSON.stringify(result);
|
|
88
104
|
};
|
|
@@ -870,6 +886,7 @@ __export(index_exports, {
|
|
|
870
886
|
ObjectQLTraceRecorder: () => ObjectQLTraceRecorder,
|
|
871
887
|
PACKAGE_TOOL_DEFINITIONS: () => PACKAGE_TOOL_DEFINITIONS,
|
|
872
888
|
QUERY_DATA_TOOL: () => QUERY_DATA_TOOL,
|
|
889
|
+
SEARCH_KNOWLEDGE_TOOL: () => SEARCH_KNOWLEDGE_TOOL,
|
|
873
890
|
SchemaRetriever: () => SchemaRetriever,
|
|
874
891
|
SkillRegistry: () => SkillRegistry,
|
|
875
892
|
ToolRegistry: () => ToolRegistry,
|
|
@@ -898,6 +915,7 @@ __export(index_exports, {
|
|
|
898
915
|
modifyFieldTool: () => modifyFieldTool,
|
|
899
916
|
registerActionsAsTools: () => registerActionsAsTools,
|
|
900
917
|
registerDataTools: () => registerDataTools,
|
|
918
|
+
registerKnowledgeTools: () => registerKnowledgeTools,
|
|
901
919
|
registerMetadataTools: () => registerMetadataTools,
|
|
902
920
|
registerPackageTools: () => registerPackageTools,
|
|
903
921
|
registerQueryDataTool: () => registerQueryDataTool,
|
|
@@ -1277,8 +1295,15 @@ var ToolRegistry = class {
|
|
|
1277
1295
|
}
|
|
1278
1296
|
/**
|
|
1279
1297
|
* Execute a tool call and return the result.
|
|
1298
|
+
*
|
|
1299
|
+
* @param toolCall The decoded tool-call part from the model response.
|
|
1300
|
+
* @param ctx Optional per-call execution context (actor, conversation,
|
|
1301
|
+
* environment). Handlers may use this to enforce RLS,
|
|
1302
|
+
* attribute audit entries, or correlate traces. When
|
|
1303
|
+
* omitted, handlers should fall back to system-level
|
|
1304
|
+
* behaviour for backward compatibility.
|
|
1280
1305
|
*/
|
|
1281
|
-
async execute(toolCall) {
|
|
1306
|
+
async execute(toolCall, ctx) {
|
|
1282
1307
|
const handler = this.handlers.get(toolCall.toolName);
|
|
1283
1308
|
if (!handler) {
|
|
1284
1309
|
return {
|
|
@@ -1291,7 +1316,7 @@ var ToolRegistry = class {
|
|
|
1291
1316
|
}
|
|
1292
1317
|
try {
|
|
1293
1318
|
const args = typeof toolCall.input === "string" ? JSON.parse(toolCall.input) : toolCall.input ?? {};
|
|
1294
|
-
const content = await handler(args);
|
|
1319
|
+
const content = await handler(args, ctx);
|
|
1295
1320
|
return {
|
|
1296
1321
|
type: "tool-result",
|
|
1297
1322
|
toolCallId: toolCall.toolCallId,
|
|
@@ -1310,10 +1335,11 @@ var ToolRegistry = class {
|
|
|
1310
1335
|
}
|
|
1311
1336
|
}
|
|
1312
1337
|
/**
|
|
1313
|
-
* Execute multiple tool calls in parallel
|
|
1338
|
+
* Execute multiple tool calls in parallel, threading the same
|
|
1339
|
+
* execution context to each handler.
|
|
1314
1340
|
*/
|
|
1315
|
-
async executeAll(toolCalls) {
|
|
1316
|
-
return Promise.all(toolCalls.map((tc) => this.execute(tc)));
|
|
1341
|
+
async executeAll(toolCalls, ctx) {
|
|
1342
|
+
return Promise.all(toolCalls.map((tc) => this.execute(tc, ctx)));
|
|
1317
1343
|
}
|
|
1318
1344
|
/**
|
|
1319
1345
|
* Clear all registered tools.
|
|
@@ -1491,6 +1517,23 @@ var _AIService = class _AIService {
|
|
|
1491
1517
|
get adapterName() {
|
|
1492
1518
|
return this.adapter.name;
|
|
1493
1519
|
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Best-effort persistence of a single chat message to the conversation
|
|
1522
|
+
* store. Failures are logged at warn level and swallowed — chat requests
|
|
1523
|
+
* must never fail because the history write failed. Mirrors the
|
|
1524
|
+
* precedent set by `ObjectQLTraceRecorder.record`.
|
|
1525
|
+
*/
|
|
1526
|
+
async persistMessage(conversationId, message) {
|
|
1527
|
+
try {
|
|
1528
|
+
await this.conversationService.addMessage(conversationId, message);
|
|
1529
|
+
} catch (err) {
|
|
1530
|
+
this.logger.warn("[AI] persist message failed", {
|
|
1531
|
+
conversationId,
|
|
1532
|
+
role: message.role,
|
|
1533
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1494
1537
|
/**
|
|
1495
1538
|
* Run an adapter call and emit a trace event.
|
|
1496
1539
|
*
|
|
@@ -1604,9 +1647,15 @@ var _AIService = class _AIService {
|
|
|
1604
1647
|
);
|
|
1605
1648
|
}
|
|
1606
1649
|
async chatWithToolsImpl(messages, options) {
|
|
1607
|
-
const {
|
|
1650
|
+
const {
|
|
1651
|
+
maxIterations: maxIter,
|
|
1652
|
+
onToolError,
|
|
1653
|
+
toolExecutionContext,
|
|
1654
|
+
...restOptions
|
|
1655
|
+
} = options ?? {};
|
|
1608
1656
|
const maxIterations = maxIter ?? _AIService.DEFAULT_MAX_ITERATIONS;
|
|
1609
1657
|
const registeredTools = this.toolRegistry.getAll();
|
|
1658
|
+
const conversationId = toolExecutionContext?.conversationId;
|
|
1610
1659
|
const mergedTools = [
|
|
1611
1660
|
...registeredTools,
|
|
1612
1661
|
...restOptions.tools ?? []
|
|
@@ -1617,6 +1666,12 @@ var _AIService = class _AIService {
|
|
|
1617
1666
|
toolChoice: mergedTools.length > 0 ? restOptions.toolChoice ?? "auto" : void 0
|
|
1618
1667
|
};
|
|
1619
1668
|
const conversation = [...messages];
|
|
1669
|
+
if (conversationId && messages.length > 0) {
|
|
1670
|
+
const last = messages[messages.length - 1];
|
|
1671
|
+
if (last && last.role === "user") {
|
|
1672
|
+
await this.persistMessage(conversationId, last);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1620
1675
|
const toolErrors = [];
|
|
1621
1676
|
this.logger.debug("[AI] chatWithTools start", {
|
|
1622
1677
|
messageCount: conversation.length,
|
|
@@ -1628,6 +1683,12 @@ var _AIService = class _AIService {
|
|
|
1628
1683
|
const result = await this.adapter.chat(conversation, chatOptions);
|
|
1629
1684
|
if (!result.toolCalls || result.toolCalls.length === 0) {
|
|
1630
1685
|
this.logger.debug("[AI] chatWithTools finished", { iteration, content: result.content.slice(0, 80) });
|
|
1686
|
+
if (conversationId) {
|
|
1687
|
+
await this.persistMessage(conversationId, {
|
|
1688
|
+
role: "assistant",
|
|
1689
|
+
content: result.content
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1631
1692
|
return result;
|
|
1632
1693
|
}
|
|
1633
1694
|
this.logger.debug("[AI] chatWithTools tool calls", {
|
|
@@ -1637,11 +1698,18 @@ var _AIService = class _AIService {
|
|
|
1637
1698
|
const assistantContent = [];
|
|
1638
1699
|
if (result.content) assistantContent.push({ type: "text", text: result.content });
|
|
1639
1700
|
assistantContent.push(...result.toolCalls);
|
|
1640
|
-
|
|
1701
|
+
const assistantTurn = {
|
|
1641
1702
|
role: "assistant",
|
|
1642
1703
|
content: assistantContent
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1704
|
+
};
|
|
1705
|
+
conversation.push(assistantTurn);
|
|
1706
|
+
if (conversationId) {
|
|
1707
|
+
await this.persistMessage(conversationId, assistantTurn);
|
|
1708
|
+
}
|
|
1709
|
+
const toolResults = await this.toolRegistry.executeAll(
|
|
1710
|
+
result.toolCalls,
|
|
1711
|
+
toolExecutionContext
|
|
1712
|
+
);
|
|
1645
1713
|
for (const tr of toolResults) {
|
|
1646
1714
|
if (tr.isError) {
|
|
1647
1715
|
const matchedCall = result.toolCalls.find((tc) => tc.toolCallId === tr.toolCallId);
|
|
@@ -1657,10 +1725,14 @@ var _AIService = class _AIService {
|
|
|
1657
1725
|
}
|
|
1658
1726
|
}
|
|
1659
1727
|
}
|
|
1660
|
-
|
|
1728
|
+
const toolTurn = {
|
|
1661
1729
|
role: "tool",
|
|
1662
1730
|
content: [tr]
|
|
1663
|
-
}
|
|
1731
|
+
};
|
|
1732
|
+
conversation.push(toolTurn);
|
|
1733
|
+
if (conversationId) {
|
|
1734
|
+
await this.persistMessage(conversationId, toolTurn);
|
|
1735
|
+
}
|
|
1664
1736
|
}
|
|
1665
1737
|
if (abortedByCallback) {
|
|
1666
1738
|
break;
|
|
@@ -1678,6 +1750,12 @@ var _AIService = class _AIService {
|
|
|
1678
1750
|
tools: void 0,
|
|
1679
1751
|
toolChoice: void 0
|
|
1680
1752
|
});
|
|
1753
|
+
if (conversationId) {
|
|
1754
|
+
await this.persistMessage(conversationId, {
|
|
1755
|
+
role: "assistant",
|
|
1756
|
+
content: finalResult.content
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1681
1759
|
return finalResult;
|
|
1682
1760
|
}
|
|
1683
1761
|
/**
|
|
@@ -1688,9 +1766,15 @@ var _AIService = class _AIService {
|
|
|
1688
1766
|
* fed back until a final text stream is produced.
|
|
1689
1767
|
*/
|
|
1690
1768
|
async *streamChatWithTools(messages, options) {
|
|
1691
|
-
const {
|
|
1769
|
+
const {
|
|
1770
|
+
maxIterations: maxIter,
|
|
1771
|
+
onToolError,
|
|
1772
|
+
toolExecutionContext,
|
|
1773
|
+
...restOptions
|
|
1774
|
+
} = options ?? {};
|
|
1692
1775
|
const maxIterations = maxIter ?? _AIService.DEFAULT_MAX_ITERATIONS;
|
|
1693
1776
|
const registeredTools = this.toolRegistry.getAll();
|
|
1777
|
+
const conversationId = toolExecutionContext?.conversationId;
|
|
1694
1778
|
const mergedTools = [
|
|
1695
1779
|
...registeredTools,
|
|
1696
1780
|
...restOptions.tools ?? []
|
|
@@ -1702,9 +1786,21 @@ var _AIService = class _AIService {
|
|
|
1702
1786
|
};
|
|
1703
1787
|
const conversation = [...messages];
|
|
1704
1788
|
let abortedByCallback = false;
|
|
1789
|
+
if (conversationId && messages.length > 0) {
|
|
1790
|
+
const last = messages[messages.length - 1];
|
|
1791
|
+
if (last && last.role === "user") {
|
|
1792
|
+
await this.persistMessage(conversationId, last);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1705
1795
|
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
1706
1796
|
const result2 = await this.adapter.chat(conversation, chatOptions);
|
|
1707
1797
|
if (!result2.toolCalls || result2.toolCalls.length === 0) {
|
|
1798
|
+
if (conversationId) {
|
|
1799
|
+
await this.persistMessage(conversationId, {
|
|
1800
|
+
role: "assistant",
|
|
1801
|
+
content: result2.content
|
|
1802
|
+
});
|
|
1803
|
+
}
|
|
1708
1804
|
yield textDeltaPart("stream", result2.content);
|
|
1709
1805
|
yield finishPart(result2);
|
|
1710
1806
|
return;
|
|
@@ -1715,11 +1811,18 @@ var _AIService = class _AIService {
|
|
|
1715
1811
|
const assistantContent = [];
|
|
1716
1812
|
if (result2.content) assistantContent.push({ type: "text", text: result2.content });
|
|
1717
1813
|
assistantContent.push(...result2.toolCalls);
|
|
1718
|
-
|
|
1814
|
+
const assistantTurn = {
|
|
1719
1815
|
role: "assistant",
|
|
1720
1816
|
content: assistantContent
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1817
|
+
};
|
|
1818
|
+
conversation.push(assistantTurn);
|
|
1819
|
+
if (conversationId) {
|
|
1820
|
+
await this.persistMessage(conversationId, assistantTurn);
|
|
1821
|
+
}
|
|
1822
|
+
const toolResults = await this.toolRegistry.executeAll(
|
|
1823
|
+
result2.toolCalls,
|
|
1824
|
+
toolExecutionContext
|
|
1825
|
+
);
|
|
1723
1826
|
for (const tr of toolResults) {
|
|
1724
1827
|
if (tr.isError && onToolError) {
|
|
1725
1828
|
const matchedCall = result2.toolCalls.find((tc) => tc.toolCallId === tr.toolCallId);
|
|
@@ -1737,10 +1840,14 @@ var _AIService = class _AIService {
|
|
|
1737
1840
|
toolName: tr.toolName,
|
|
1738
1841
|
output: tr.output
|
|
1739
1842
|
};
|
|
1740
|
-
|
|
1843
|
+
const toolTurn = {
|
|
1741
1844
|
role: "tool",
|
|
1742
1845
|
content: [tr]
|
|
1743
|
-
}
|
|
1846
|
+
};
|
|
1847
|
+
conversation.push(toolTurn);
|
|
1848
|
+
if (conversationId) {
|
|
1849
|
+
await this.persistMessage(conversationId, toolTurn);
|
|
1850
|
+
}
|
|
1744
1851
|
}
|
|
1745
1852
|
if (abortedByCallback) {
|
|
1746
1853
|
break;
|
|
@@ -1753,6 +1860,12 @@ var _AIService = class _AIService {
|
|
|
1753
1860
|
}
|
|
1754
1861
|
const finalOptions = { ...chatOptions, tools: void 0, toolChoice: void 0 };
|
|
1755
1862
|
const result = await this.adapter.chat(conversation, finalOptions);
|
|
1863
|
+
if (conversationId) {
|
|
1864
|
+
await this.persistMessage(conversationId, {
|
|
1865
|
+
role: "assistant",
|
|
1866
|
+
content: result.content
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1756
1869
|
yield textDeltaPart("stream", result.content);
|
|
1757
1870
|
yield finishPart(result);
|
|
1758
1871
|
}
|
|
@@ -2257,6 +2370,32 @@ function buildAIRoutes(aiService, conversationService, logger) {
|
|
|
2257
2370
|
}
|
|
2258
2371
|
}
|
|
2259
2372
|
},
|
|
2373
|
+
{
|
|
2374
|
+
method: "GET",
|
|
2375
|
+
path: "/api/v1/ai/conversations/:id",
|
|
2376
|
+
description: "Get a conversation with its full message history",
|
|
2377
|
+
auth: true,
|
|
2378
|
+
permissions: ["ai:conversations"],
|
|
2379
|
+
handler: async (req) => {
|
|
2380
|
+
const id = req.params?.id;
|
|
2381
|
+
if (!id) {
|
|
2382
|
+
return { status: 400, body: { error: "conversation id is required" } };
|
|
2383
|
+
}
|
|
2384
|
+
try {
|
|
2385
|
+
const conversation = await conversationService.get(id);
|
|
2386
|
+
if (!conversation) {
|
|
2387
|
+
return { status: 404, body: { error: `Conversation "${id}" not found` } };
|
|
2388
|
+
}
|
|
2389
|
+
if (req.user?.userId && conversation.userId && conversation.userId !== req.user.userId) {
|
|
2390
|
+
return { status: 403, body: { error: "You do not have access to this conversation" } };
|
|
2391
|
+
}
|
|
2392
|
+
return { status: 200, body: conversation };
|
|
2393
|
+
} catch (err) {
|
|
2394
|
+
logger.error("[AI Route] GET /conversations/:id error", err instanceof Error ? err : void 0);
|
|
2395
|
+
return { status: 500, body: { error: "Internal AI service error" } };
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
},
|
|
2260
2399
|
{
|
|
2261
2400
|
method: "POST",
|
|
2262
2401
|
path: "/api/v1/ai/conversations/:id/messages",
|
|
@@ -2423,7 +2562,19 @@ function buildAgentRoutes(aiService, agentRuntime, logger) {
|
|
|
2423
2562
|
];
|
|
2424
2563
|
const chatWithToolsOptions = {
|
|
2425
2564
|
...mergedOptions,
|
|
2426
|
-
maxIterations: agent.planning?.maxIterations
|
|
2565
|
+
maxIterations: agent.planning?.maxIterations,
|
|
2566
|
+
// Forward authenticated actor → built-in data tools enforce
|
|
2567
|
+
// ObjectQL RLS, action tools attribute audit to the user.
|
|
2568
|
+
toolExecutionContext: req.user ? {
|
|
2569
|
+
actor: {
|
|
2570
|
+
id: req.user.userId,
|
|
2571
|
+
name: req.user.displayName,
|
|
2572
|
+
roles: req.user.roles,
|
|
2573
|
+
permissions: req.user.permissions
|
|
2574
|
+
},
|
|
2575
|
+
conversationId: typeof body.conversationId === "string" ? body.conversationId : void 0,
|
|
2576
|
+
environmentId: typeof chatContext?.environmentId === "string" ? chatContext.environmentId : void 0
|
|
2577
|
+
} : void 0
|
|
2427
2578
|
};
|
|
2428
2579
|
const wantStream = body.stream !== false;
|
|
2429
2580
|
if (wantStream) {
|
|
@@ -2612,7 +2763,21 @@ function buildAssistantRoutes(aiService, agentRuntime, skillRegistry, logger) {
|
|
|
2612
2763
|
];
|
|
2613
2764
|
const chatWithToolsOptions = {
|
|
2614
2765
|
...mergedOptions,
|
|
2615
|
-
maxIterations: agent.planning?.maxIterations
|
|
2766
|
+
maxIterations: agent.planning?.maxIterations,
|
|
2767
|
+
// Forward the authenticated actor into every tool the loop
|
|
2768
|
+
// invokes — built-in data tools promote this into the
|
|
2769
|
+
// ObjectQL execution context so row-level security scopes
|
|
2770
|
+
// the LLM to whatever the human user can already see/do.
|
|
2771
|
+
toolExecutionContext: req.user ? {
|
|
2772
|
+
actor: {
|
|
2773
|
+
id: req.user.userId,
|
|
2774
|
+
name: req.user.displayName,
|
|
2775
|
+
roles: req.user.roles,
|
|
2776
|
+
permissions: req.user.permissions
|
|
2777
|
+
},
|
|
2778
|
+
conversationId: typeof body.conversationId === "string" ? body.conversationId : void 0,
|
|
2779
|
+
environmentId: typeof context.environmentId === "string" ? context.environmentId : void 0
|
|
2780
|
+
} : void 0
|
|
2616
2781
|
};
|
|
2617
2782
|
const wantStream = body.stream !== false;
|
|
2618
2783
|
if (wantStream) {
|
|
@@ -3960,6 +4125,19 @@ function describeField(field) {
|
|
|
3960
4125
|
}
|
|
3961
4126
|
|
|
3962
4127
|
// src/tools/query-data.tool.ts
|
|
4128
|
+
function buildAiEngineContext(ctx) {
|
|
4129
|
+
if (ctx?.actor) {
|
|
4130
|
+
return {
|
|
4131
|
+
userId: ctx.actor.id,
|
|
4132
|
+
roles: ctx.actor.roles ?? [],
|
|
4133
|
+
permissions: ctx.actor.permissions ?? [],
|
|
4134
|
+
isSystem: false,
|
|
4135
|
+
...ctx.environmentId ? { tenantId: ctx.environmentId } : {},
|
|
4136
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {}
|
|
4137
|
+
};
|
|
4138
|
+
}
|
|
4139
|
+
return { roles: [], permissions: [], isSystem: true };
|
|
4140
|
+
}
|
|
3963
4141
|
var QueryPlanSchema = import_zod.z.object({
|
|
3964
4142
|
objectName: import_zod.z.string().min(1).describe('The snake_case object name to query (e.g. "task", "account").'),
|
|
3965
4143
|
whereJson: import_zod.z.string().nullable().describe(
|
|
@@ -3992,7 +4170,7 @@ var QUERY_DATA_TOOL = {
|
|
|
3992
4170
|
function createQueryDataHandler(ctx) {
|
|
3993
4171
|
const retriever = new SchemaRetriever(ctx.metadata);
|
|
3994
4172
|
const maxLimit = ctx.maxLimit ?? 100;
|
|
3995
|
-
return async (args) => {
|
|
4173
|
+
return async (args, execCtx) => {
|
|
3996
4174
|
const { request } = args;
|
|
3997
4175
|
if (!request || typeof request !== "string") {
|
|
3998
4176
|
return JSON.stringify({ error: "query_data: `request` is required" });
|
|
@@ -4059,7 +4237,8 @@ function createQueryDataHandler(ctx) {
|
|
|
4059
4237
|
where,
|
|
4060
4238
|
fields: plan.fields ?? void 0,
|
|
4061
4239
|
orderBy: plan.orderBy ?? void 0,
|
|
4062
|
-
limit
|
|
4240
|
+
limit,
|
|
4241
|
+
context: buildAiEngineContext(execCtx)
|
|
4063
4242
|
});
|
|
4064
4243
|
return JSON.stringify({
|
|
4065
4244
|
plan: { ...plan, where },
|
|
@@ -4239,11 +4418,13 @@ function buildHandlerEngineAdapter(engine) {
|
|
|
4239
4418
|
};
|
|
4240
4419
|
}
|
|
4241
4420
|
function createActionToolHandler(action, ctx) {
|
|
4242
|
-
const
|
|
4421
|
+
const fallbackPrincipal = ctx.principal ?? { id: "ai_agent", name: "AI Assistant" };
|
|
4243
4422
|
const requiresRecord = Array.isArray(action.locations) && action.locations.some(
|
|
4244
4423
|
(l) => l === "list_item" || l === "record_header" || l === "record_more" || l === "record_related"
|
|
4245
4424
|
);
|
|
4246
|
-
return async (args) => {
|
|
4425
|
+
return async (args, execCtx) => {
|
|
4426
|
+
const principal = execCtx?.actor ? { id: execCtx.actor.id, name: execCtx.actor.name } : fallbackPrincipal;
|
|
4427
|
+
const engineCtx = buildActionEngineContext(execCtx);
|
|
4247
4428
|
const objectName = action.objectName;
|
|
4248
4429
|
const target = action.target;
|
|
4249
4430
|
const result = {
|
|
@@ -4269,7 +4450,8 @@ function createActionToolHandler(action, ctx) {
|
|
|
4269
4450
|
try {
|
|
4270
4451
|
const found = await ctx.dataEngine.find(objectName, {
|
|
4271
4452
|
where: { id: recordId },
|
|
4272
|
-
limit: 1
|
|
4453
|
+
limit: 1,
|
|
4454
|
+
context: engineCtx
|
|
4273
4455
|
});
|
|
4274
4456
|
record = found[0];
|
|
4275
4457
|
if (!record) {
|
|
@@ -4291,6 +4473,8 @@ function createActionToolHandler(action, ctx) {
|
|
|
4291
4473
|
actionName: action.name,
|
|
4292
4474
|
toolName,
|
|
4293
4475
|
toolInput: args,
|
|
4476
|
+
conversationId: execCtx?.conversationId,
|
|
4477
|
+
messageId: execCtx?.messageId,
|
|
4294
4478
|
proposedBy: principal.id
|
|
4295
4479
|
});
|
|
4296
4480
|
const pending = {
|
|
@@ -4328,6 +4512,19 @@ function createActionToolHandler(action, ctx) {
|
|
|
4328
4512
|
}
|
|
4329
4513
|
};
|
|
4330
4514
|
}
|
|
4515
|
+
function buildActionEngineContext(ctx) {
|
|
4516
|
+
if (ctx?.actor) {
|
|
4517
|
+
return {
|
|
4518
|
+
userId: ctx.actor.id,
|
|
4519
|
+
roles: ctx.actor.roles ?? [],
|
|
4520
|
+
permissions: ctx.actor.permissions ?? [],
|
|
4521
|
+
isSystem: false,
|
|
4522
|
+
...ctx.environmentId ? { tenantId: ctx.environmentId } : {},
|
|
4523
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {}
|
|
4524
|
+
};
|
|
4525
|
+
}
|
|
4526
|
+
return { roles: [], permissions: [], isSystem: true };
|
|
4527
|
+
}
|
|
4331
4528
|
async function dispatchScriptAction(action, ctx, params, record, principal) {
|
|
4332
4529
|
const engineAdapter = buildHandlerEngineAdapter(ctx.dataEngine);
|
|
4333
4530
|
const handlerCtx = { record, user: principal, engine: engineAdapter, params };
|
|
@@ -5654,6 +5851,94 @@ var AIServicePlugin = class {
|
|
|
5654
5851
|
// src/index.ts
|
|
5655
5852
|
init_data_tools();
|
|
5656
5853
|
init_metadata_tools();
|
|
5854
|
+
|
|
5855
|
+
// src/tools/knowledge-tools.ts
|
|
5856
|
+
var DEFAULT_TOP_K = 5;
|
|
5857
|
+
var MAX_TOP_K = 20;
|
|
5858
|
+
function buildEngineContext2(ctx) {
|
|
5859
|
+
if (ctx?.actor) {
|
|
5860
|
+
return {
|
|
5861
|
+
userId: ctx.actor.id,
|
|
5862
|
+
roles: ctx.actor.roles ?? [],
|
|
5863
|
+
permissions: ctx.actor.permissions ?? [],
|
|
5864
|
+
isSystem: false,
|
|
5865
|
+
...ctx.environmentId ? { tenantId: ctx.environmentId } : {},
|
|
5866
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {}
|
|
5867
|
+
};
|
|
5868
|
+
}
|
|
5869
|
+
return { roles: [], permissions: [], isSystem: true };
|
|
5870
|
+
}
|
|
5871
|
+
var SEARCH_KNOWLEDGE_TOOL = {
|
|
5872
|
+
name: "search_knowledge",
|
|
5873
|
+
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.",
|
|
5874
|
+
parameters: {
|
|
5875
|
+
type: "object",
|
|
5876
|
+
properties: {
|
|
5877
|
+
query: {
|
|
5878
|
+
type: "string",
|
|
5879
|
+
description: "Free-text question or keywords to search for."
|
|
5880
|
+
},
|
|
5881
|
+
sourceIds: {
|
|
5882
|
+
type: "array",
|
|
5883
|
+
items: { type: "string" },
|
|
5884
|
+
description: "Optional list of source ids to restrict the search to. When omitted, every source the caller can see is queried."
|
|
5885
|
+
},
|
|
5886
|
+
topK: {
|
|
5887
|
+
type: "number",
|
|
5888
|
+
description: `Maximum hits to return (default ${DEFAULT_TOP_K}, max ${MAX_TOP_K}).`
|
|
5889
|
+
},
|
|
5890
|
+
filter: {
|
|
5891
|
+
type: "object",
|
|
5892
|
+
description: 'Optional adapter-specific metadata filter (e.g. {"topic":"refunds"}).'
|
|
5893
|
+
}
|
|
5894
|
+
},
|
|
5895
|
+
required: ["query"]
|
|
5896
|
+
}
|
|
5897
|
+
};
|
|
5898
|
+
function createSearchKnowledgeHandler(context) {
|
|
5899
|
+
return async (args, ctx) => {
|
|
5900
|
+
const query = typeof args.query === "string" ? args.query.trim() : "";
|
|
5901
|
+
if (!query) {
|
|
5902
|
+
return JSON.stringify({ error: "search_knowledge: `query` is required." });
|
|
5903
|
+
}
|
|
5904
|
+
const sourceIds = Array.isArray(args.sourceIds) ? args.sourceIds.filter((x) => typeof x === "string") : void 0;
|
|
5905
|
+
const topKRaw = typeof args.topK === "number" ? args.topK : DEFAULT_TOP_K;
|
|
5906
|
+
const topK = Math.max(1, Math.min(MAX_TOP_K, Math.floor(topKRaw)));
|
|
5907
|
+
const filter = args.filter && typeof args.filter === "object" && !Array.isArray(args.filter) ? args.filter : void 0;
|
|
5908
|
+
const opts = {
|
|
5909
|
+
topK,
|
|
5910
|
+
executionContext: buildEngineContext2(ctx)
|
|
5911
|
+
};
|
|
5912
|
+
if (sourceIds && sourceIds.length > 0) opts.sourceIds = sourceIds;
|
|
5913
|
+
if (filter) opts.filter = filter;
|
|
5914
|
+
try {
|
|
5915
|
+
const hits = await context.knowledgeService.search(query, opts);
|
|
5916
|
+
return JSON.stringify({
|
|
5917
|
+
query,
|
|
5918
|
+
count: hits.length,
|
|
5919
|
+
hits: hits.map((h) => ({
|
|
5920
|
+
documentId: h.documentId,
|
|
5921
|
+
chunkId: h.chunkId,
|
|
5922
|
+
sourceId: h.sourceId,
|
|
5923
|
+
sourceRecordId: h.sourceRecordId,
|
|
5924
|
+
score: Number(h.score?.toFixed?.(4) ?? h.score ?? 0),
|
|
5925
|
+
title: h.title,
|
|
5926
|
+
snippet: h.snippet,
|
|
5927
|
+
metadata: h.metadata
|
|
5928
|
+
}))
|
|
5929
|
+
});
|
|
5930
|
+
} catch (err) {
|
|
5931
|
+
return JSON.stringify({
|
|
5932
|
+
error: `search_knowledge failed: ${err instanceof Error ? err.message : String(err)}`
|
|
5933
|
+
});
|
|
5934
|
+
}
|
|
5935
|
+
};
|
|
5936
|
+
}
|
|
5937
|
+
function registerKnowledgeTools(registry, context) {
|
|
5938
|
+
registry.register(SEARCH_KNOWLEDGE_TOOL, createSearchKnowledgeHandler(context));
|
|
5939
|
+
}
|
|
5940
|
+
|
|
5941
|
+
// src/index.ts
|
|
5657
5942
|
init_metadata_tools();
|
|
5658
5943
|
|
|
5659
5944
|
// src/tools/list-packages.tool.ts
|
|
@@ -6010,6 +6295,7 @@ function registerPackageTools(registry, context) {
|
|
|
6010
6295
|
ObjectQLTraceRecorder,
|
|
6011
6296
|
PACKAGE_TOOL_DEFINITIONS,
|
|
6012
6297
|
QUERY_DATA_TOOL,
|
|
6298
|
+
SEARCH_KNOWLEDGE_TOOL,
|
|
6013
6299
|
SchemaRetriever,
|
|
6014
6300
|
SkillRegistry,
|
|
6015
6301
|
ToolRegistry,
|
|
@@ -6038,6 +6324,7 @@ function registerPackageTools(registry, context) {
|
|
|
6038
6324
|
modifyFieldTool,
|
|
6039
6325
|
registerActionsAsTools,
|
|
6040
6326
|
registerDataTools,
|
|
6327
|
+
registerKnowledgeTools,
|
|
6041
6328
|
registerMetadataTools,
|
|
6042
6329
|
registerPackageTools,
|
|
6043
6330
|
registerQueryDataTool,
|