@objectstack/service-ai 6.2.0 → 6.4.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 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 { maxIterations: maxIter, onToolError, ...restOptions } = options ?? {};
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
- conversation.push({
1701
+ const assistantTurn = {
1641
1702
  role: "assistant",
1642
1703
  content: assistantContent
1643
- });
1644
- const toolResults = await this.toolRegistry.executeAll(result.toolCalls);
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
- conversation.push({
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 { maxIterations: maxIter, onToolError, ...restOptions } = options ?? {};
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
- conversation.push({
1814
+ const assistantTurn = {
1719
1815
  role: "assistant",
1720
1816
  content: assistantContent
1721
- });
1722
- const toolResults = await this.toolRegistry.executeAll(result2.toolCalls);
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
- conversation.push({
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 principal = ctx.principal ?? { id: "ai_agent", name: "AI Assistant" };
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,