@distri/core 0.3.2 → 0.3.4

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.js CHANGED
@@ -55,8 +55,14 @@ module.exports = __toCommonJS(index_exports);
55
55
  function isArrayParts(result) {
56
56
  return Array.isArray(result) && result[0].part_type;
57
57
  }
58
- function createSuccessfulToolResult(toolCallId, toolName, result) {
59
- const parts = isArrayParts(result) ? result : [{
58
+ function createSuccessfulToolResult(toolCallId, toolName, result, explicitPartsMetadata) {
59
+ console.log("[createSuccessfulToolResult] toolName:", toolName);
60
+ console.log("[createSuccessfulToolResult] isArrayParts:", isArrayParts(result));
61
+ console.log("[createSuccessfulToolResult] result type:", typeof result, Array.isArray(result) ? `array[${result.length}]` : "");
62
+ if (isArrayParts(result)) {
63
+ console.log("[createSuccessfulToolResult] parts:", result.map((p) => ({ part_type: p.part_type, hasMetadata: !!p.__metadata })));
64
+ }
65
+ const rawParts = isArrayParts(result) ? result : [{
60
66
  part_type: "data",
61
67
  data: {
62
68
  result,
@@ -64,10 +70,22 @@ function createSuccessfulToolResult(toolCallId, toolName, result) {
64
70
  error: void 0
65
71
  }
66
72
  }];
73
+ const parts_metadata = { ...explicitPartsMetadata };
74
+ const parts = rawParts.map((part, index) => {
75
+ if ("__metadata" in part && part.__metadata) {
76
+ parts_metadata[index] = { ...parts_metadata[index], ...part.__metadata };
77
+ }
78
+ if (part.part_type === "image" && !parts_metadata[index]) {
79
+ parts_metadata[index] = { save: false };
80
+ }
81
+ const { __metadata, ...cleanPart } = part;
82
+ return cleanPart;
83
+ });
67
84
  return {
68
85
  tool_call_id: toolCallId,
69
86
  tool_name: toolName,
70
- parts
87
+ parts,
88
+ parts_metadata: Object.keys(parts_metadata).length > 0 ? parts_metadata : void 0
71
89
  };
72
90
  }
73
91
  function createFailedToolResult(toolCallId, toolName, error, result) {
@@ -543,11 +561,22 @@ var A2AClient = class {
543
561
  // src/encoder.ts
544
562
  function convertA2AMessageToDistri(a2aMessage) {
545
563
  const role = a2aMessage.role === "agent" ? "assistant" : "user";
564
+ let agent_id;
565
+ let agent_name;
566
+ if (a2aMessage.metadata) {
567
+ const metadata = a2aMessage.metadata;
568
+ if (metadata.agent) {
569
+ agent_id = metadata.agent.agent_id;
570
+ agent_name = metadata.agent.agent_name;
571
+ }
572
+ }
546
573
  return {
547
574
  id: a2aMessage.messageId,
548
575
  role,
549
576
  parts: a2aMessage.parts.map(convertA2APartToDistri),
550
- created_at: a2aMessage.createdAt
577
+ created_at: a2aMessage.createdAt,
578
+ agent_id,
579
+ agent_name
551
580
  };
552
581
  }
553
582
  function convertA2AStatusUpdateToDistri(statusUpdate) {
@@ -570,8 +599,8 @@ function convertA2AStatusUpdateToDistri(statusUpdate) {
570
599
  const runErrorResult = {
571
600
  type: "run_error",
572
601
  data: {
573
- message: statusUpdate.error,
574
- code: statusUpdate.code
602
+ message: metadata.message || statusUpdate.status?.message || "Unknown error",
603
+ code: metadata.code
575
604
  }
576
605
  };
577
606
  return runErrorResult;
@@ -741,6 +770,19 @@ function convertA2AStatusUpdateToDistri(statusUpdate) {
741
770
  };
742
771
  return browserSessionStarted;
743
772
  }
773
+ case "todos_updated": {
774
+ const todos = parseTodosFromFormatted(metadata.formatted_todos || "");
775
+ const todosUpdated = {
776
+ type: "todos_updated",
777
+ data: {
778
+ formatted_todos: metadata.formatted_todos || "",
779
+ action: metadata.action || "write_todos",
780
+ todo_count: metadata.todo_count || 0,
781
+ todos
782
+ }
783
+ };
784
+ return todosUpdated;
785
+ }
744
786
  default: {
745
787
  console.warn(`Unhandled status update metadata type: ${metadata.type}`, metadata);
746
788
  const defaultResult = {
@@ -792,7 +834,7 @@ function convertA2APartToDistri(a2aPart) {
792
834
  const fileUrl = { type: "url", mime_type: a2aPart.file.mimeType || "application/octet-stream", url: a2aPart.file.uri || "" };
793
835
  return { part_type: "image", data: fileUrl };
794
836
  } else {
795
- const fileBytes = { type: "bytes", mime_type: a2aPart.file.mimeType || "application/octet-stream", data: a2aPart.file.bytes || "" };
837
+ const fileBytes = { type: "bytes", mime_type: a2aPart.file.mimeType || "application/octet-stream", bytes: a2aPart.file.bytes || "" };
796
838
  return { part_type: "image", data: fileBytes };
797
839
  }
798
840
  case "data":
@@ -819,6 +861,7 @@ function convertDistriMessageToA2A(distriMessage, context) {
819
861
  break;
820
862
  case "system":
821
863
  case "tool":
864
+ case "developer":
822
865
  role = "user";
823
866
  break;
824
867
  default:
@@ -830,7 +873,8 @@ function convertDistriMessageToA2A(distriMessage, context) {
830
873
  parts: distriMessage.parts.map(convertDistriPartToA2A),
831
874
  kind: "message",
832
875
  contextId: context.thread_id,
833
- taskId: context.task_id || context.run_id || void 0
876
+ taskId: context.task_id || context.run_id || void 0,
877
+ metadata: distriMessage.metadata
834
878
  };
835
879
  }
836
880
  function convertDistriPartToA2A(distriPart) {
@@ -844,7 +888,7 @@ function convertDistriPartToA2A(distriPart) {
844
888
  const fileUri = { mimeType: distriPart.data.mime_type, uri: distriPart.data.url };
845
889
  result = { kind: "file", file: fileUri };
846
890
  } else {
847
- const fileBytes = { mimeType: distriPart.data.mime_type, bytes: distriPart.data.data };
891
+ const fileBytes = { mimeType: distriPart.data.mime_type, bytes: distriPart.data.bytes };
848
892
  result = { kind: "file", file: fileBytes };
849
893
  }
850
894
  break;
@@ -909,12 +953,41 @@ function extractToolCallsFromDistriMessage(message) {
909
953
  function extractToolResultsFromDistriMessage(message) {
910
954
  return message.parts.filter((part) => part.part_type === "tool_result").map((part) => part.data);
911
955
  }
956
+ function parseTodosFromFormatted(formatted) {
957
+ if (!formatted || formatted === "\u25A1 No todos") {
958
+ return [];
959
+ }
960
+ const lines = formatted.split("\n").filter((line) => line.trim());
961
+ return lines.map((line, index) => {
962
+ const trimmed = line.trim();
963
+ let status = "open";
964
+ let content = trimmed;
965
+ if (trimmed.startsWith("\u25A0")) {
966
+ status = "done";
967
+ content = trimmed.slice(1).trim();
968
+ } else if (trimmed.startsWith("\u25D0")) {
969
+ status = "in_progress";
970
+ content = trimmed.slice(1).trim();
971
+ } else if (trimmed.startsWith("\u25A1")) {
972
+ status = "open";
973
+ content = trimmed.slice(1).trim();
974
+ }
975
+ return {
976
+ id: `todo_${index}`,
977
+ content,
978
+ status
979
+ };
980
+ });
981
+ }
912
982
 
913
983
  // src/distri-client.ts
914
984
  var _DistriClient = class _DistriClient {
915
985
  constructor(config) {
916
986
  this.agentClients = /* @__PURE__ */ new Map();
917
987
  const headers = { ...config.headers };
988
+ if (config.workspaceId) {
989
+ headers["X-Workspace-Id"] = config.workspaceId;
990
+ }
918
991
  this.accessToken = config.accessToken;
919
992
  this.refreshToken = config.refreshToken;
920
993
  this.tokenRefreshSkewMs = config.tokenRefreshSkewMs ?? 6e4;
@@ -929,7 +1002,8 @@ var _DistriClient = class _DistriClient {
929
1002
  headers,
930
1003
  interceptor: config.interceptor ?? (async (init) => Promise.resolve(init)),
931
1004
  onTokenRefresh: config.onTokenRefresh,
932
- clientId: config.clientId
1005
+ clientId: config.clientId,
1006
+ workspaceId: config.workspaceId
933
1007
  };
934
1008
  }
935
1009
  /**
@@ -944,6 +1018,24 @@ var _DistriClient = class _DistriClient {
944
1018
  set clientId(value) {
945
1019
  this.config.clientId = value;
946
1020
  }
1021
+ /**
1022
+ * Get the configured workspace ID.
1023
+ */
1024
+ get workspaceId() {
1025
+ return this.config.workspaceId;
1026
+ }
1027
+ /**
1028
+ * Set the workspace ID for multi-tenant support.
1029
+ * Updates the X-Workspace-Id header for all subsequent requests.
1030
+ */
1031
+ set workspaceId(value) {
1032
+ this.config.workspaceId = value;
1033
+ if (value) {
1034
+ this.config.headers["X-Workspace-Id"] = value;
1035
+ } else {
1036
+ delete this.config.headers["X-Workspace-Id"];
1037
+ }
1038
+ }
947
1039
  /**
948
1040
  * Create a client with default cloud configuration.
949
1041
  *
@@ -1396,8 +1488,51 @@ var _DistriClient = class _DistriClient {
1396
1488
  yield* await client.sendMessageStream(params);
1397
1489
  } catch (error) {
1398
1490
  console.error(error);
1399
- throw new DistriError(`Failed to stream message to agent ${agentId}`, "STREAM_MESSAGE_ERROR", error);
1491
+ const errorMessage = this.extractErrorMessage(error);
1492
+ throw new DistriError(errorMessage, "STREAM_MESSAGE_ERROR", error);
1493
+ }
1494
+ }
1495
+ /**
1496
+ * Extract a user-friendly error message from potentially nested errors
1497
+ */
1498
+ extractErrorMessage(error) {
1499
+ if (!error) return "Unknown error occurred";
1500
+ if (typeof error === "object" && error !== null) {
1501
+ const err = error;
1502
+ if (err.error && typeof err.error === "object") {
1503
+ const jsonRpcError = err.error;
1504
+ if (typeof jsonRpcError.message === "string") {
1505
+ return jsonRpcError.message;
1506
+ }
1507
+ }
1508
+ if (err.message && typeof err.message === "string") {
1509
+ return err.message;
1510
+ }
1511
+ if (err.details && typeof err.details === "object") {
1512
+ const details = err.details;
1513
+ if (details.message && typeof details.message === "string") {
1514
+ return details.message;
1515
+ }
1516
+ if (details.error && typeof details.error === "object") {
1517
+ const nestedError = details.error;
1518
+ if (typeof nestedError.message === "string") {
1519
+ return nestedError.message;
1520
+ }
1521
+ }
1522
+ }
1523
+ if (err.cause && typeof err.cause === "object") {
1524
+ return this.extractErrorMessage(err.cause);
1525
+ }
1400
1526
  }
1527
+ if (error instanceof Error) {
1528
+ const msg = error.message;
1529
+ const sseMatch = msg.match(/SSE event contained an error:\s*(.+?)\s*\(Code:/);
1530
+ if (sseMatch) return sseMatch[1];
1531
+ const rpcMatch = msg.match(/RPC Error:\s*(.+?)\s*\(Code:/);
1532
+ if (rpcMatch) return rpcMatch[1];
1533
+ return msg;
1534
+ }
1535
+ return String(error);
1401
1536
  }
1402
1537
  /**
1403
1538
  * Get task details
@@ -1459,11 +1594,19 @@ var _DistriClient = class _DistriClient {
1459
1594
  }
1460
1595
  }
1461
1596
  /**
1462
- * Get agents sorted by thread count (most active first)
1597
+ * Get agents sorted by thread count (most active first).
1598
+ * Includes all registered agents, even those with 0 threads.
1599
+ * Optionally filter by name with search parameter.
1463
1600
  */
1464
- async getAgentsByUsage() {
1601
+ async getAgentsByUsage(options) {
1465
1602
  try {
1466
- const response = await this.fetch("/threads/agents");
1603
+ const params = new URLSearchParams();
1604
+ if (options?.search) {
1605
+ params.set("search", options.search);
1606
+ }
1607
+ const query = params.toString();
1608
+ const url = query ? `/threads/agents?${query}` : "/threads/agents";
1609
+ const response = await this.fetch(url);
1467
1610
  if (!response.ok) {
1468
1611
  throw new ApiError(`Failed to fetch agents by usage: ${response.statusText}`, response.status);
1469
1612
  }
@@ -1528,6 +1671,138 @@ var _DistriClient = class _DistriClient {
1528
1671
  const messages = await this.getThreadMessages(threadId);
1529
1672
  return messages.map(convertA2AMessageToDistri);
1530
1673
  }
1674
+ // ========== Message Read Status Methods ==========
1675
+ /**
1676
+ * Mark a message as read
1677
+ */
1678
+ async markMessageRead(threadId, messageId) {
1679
+ try {
1680
+ const response = await this.fetch(
1681
+ `/threads/${encodeURIComponent(threadId)}/messages/${encodeURIComponent(messageId)}/read`,
1682
+ { method: "POST" }
1683
+ );
1684
+ if (!response.ok) {
1685
+ throw new ApiError(`Failed to mark message as read: ${response.statusText}`, response.status);
1686
+ }
1687
+ return await response.json();
1688
+ } catch (error) {
1689
+ if (error instanceof ApiError) throw error;
1690
+ throw new DistriError(`Failed to mark message ${messageId} as read`, "MARK_READ_ERROR", error);
1691
+ }
1692
+ }
1693
+ /**
1694
+ * Get read status for a specific message
1695
+ */
1696
+ async getMessageReadStatus(threadId, messageId) {
1697
+ try {
1698
+ const response = await this.fetch(
1699
+ `/threads/${encodeURIComponent(threadId)}/messages/${encodeURIComponent(messageId)}/read`
1700
+ );
1701
+ if (response.status === 404) {
1702
+ return null;
1703
+ }
1704
+ if (!response.ok) {
1705
+ throw new ApiError(`Failed to get message read status: ${response.statusText}`, response.status);
1706
+ }
1707
+ return await response.json();
1708
+ } catch (error) {
1709
+ if (error instanceof ApiError) throw error;
1710
+ throw new DistriError(`Failed to get read status for message ${messageId}`, "FETCH_ERROR", error);
1711
+ }
1712
+ }
1713
+ /**
1714
+ * Get read status for all messages in a thread
1715
+ */
1716
+ async getThreadReadStatus(threadId) {
1717
+ try {
1718
+ const response = await this.fetch(
1719
+ `/threads/${encodeURIComponent(threadId)}/read-status`
1720
+ );
1721
+ if (!response.ok) {
1722
+ throw new ApiError(`Failed to get thread read status: ${response.statusText}`, response.status);
1723
+ }
1724
+ return await response.json();
1725
+ } catch (error) {
1726
+ if (error instanceof ApiError) throw error;
1727
+ throw new DistriError(`Failed to get read status for thread ${threadId}`, "FETCH_ERROR", error);
1728
+ }
1729
+ }
1730
+ // ========== Message Voting Methods ==========
1731
+ /**
1732
+ * Vote on a message (upvote or downvote)
1733
+ * Downvotes require a comment explaining the issue
1734
+ */
1735
+ async voteMessage(threadId, messageId, request) {
1736
+ try {
1737
+ const response = await this.fetch(
1738
+ `/threads/${encodeURIComponent(threadId)}/messages/${encodeURIComponent(messageId)}/vote`,
1739
+ {
1740
+ method: "POST",
1741
+ headers: { "Content-Type": "application/json" },
1742
+ body: JSON.stringify(request)
1743
+ }
1744
+ );
1745
+ if (!response.ok) {
1746
+ const errorData = await response.json().catch(() => ({}));
1747
+ throw new ApiError(errorData.error || `Failed to vote on message: ${response.statusText}`, response.status);
1748
+ }
1749
+ return await response.json();
1750
+ } catch (error) {
1751
+ if (error instanceof ApiError) throw error;
1752
+ throw new DistriError(`Failed to vote on message ${messageId}`, "VOTE_ERROR", error);
1753
+ }
1754
+ }
1755
+ /**
1756
+ * Remove vote from a message
1757
+ */
1758
+ async removeVote(threadId, messageId) {
1759
+ try {
1760
+ const response = await this.fetch(
1761
+ `/threads/${encodeURIComponent(threadId)}/messages/${encodeURIComponent(messageId)}/vote`,
1762
+ { method: "DELETE" }
1763
+ );
1764
+ if (!response.ok && response.status !== 204) {
1765
+ throw new ApiError(`Failed to remove vote: ${response.statusText}`, response.status);
1766
+ }
1767
+ } catch (error) {
1768
+ if (error instanceof ApiError) throw error;
1769
+ throw new DistriError(`Failed to remove vote from message ${messageId}`, "VOTE_ERROR", error);
1770
+ }
1771
+ }
1772
+ /**
1773
+ * Get vote summary for a message (counts + current user's vote)
1774
+ */
1775
+ async getMessageVoteSummary(threadId, messageId) {
1776
+ try {
1777
+ const response = await this.fetch(
1778
+ `/threads/${encodeURIComponent(threadId)}/messages/${encodeURIComponent(messageId)}/vote`
1779
+ );
1780
+ if (!response.ok) {
1781
+ throw new ApiError(`Failed to get vote summary: ${response.statusText}`, response.status);
1782
+ }
1783
+ return await response.json();
1784
+ } catch (error) {
1785
+ if (error instanceof ApiError) throw error;
1786
+ throw new DistriError(`Failed to get vote summary for message ${messageId}`, "FETCH_ERROR", error);
1787
+ }
1788
+ }
1789
+ /**
1790
+ * Get all votes for a message (admin/analytics use)
1791
+ */
1792
+ async getMessageVotes(threadId, messageId) {
1793
+ try {
1794
+ const response = await this.fetch(
1795
+ `/threads/${encodeURIComponent(threadId)}/messages/${encodeURIComponent(messageId)}/votes`
1796
+ );
1797
+ if (!response.ok) {
1798
+ throw new ApiError(`Failed to get message votes: ${response.statusText}`, response.status);
1799
+ }
1800
+ return await response.json();
1801
+ } catch (error) {
1802
+ if (error instanceof ApiError) throw error;
1803
+ throw new DistriError(`Failed to get votes for message ${messageId}`, "FETCH_ERROR", error);
1804
+ }
1805
+ }
1531
1806
  /**
1532
1807
  * Send a DistriMessage to a thread
1533
1808
  */
@@ -1553,7 +1828,12 @@ var _DistriClient = class _DistriClient {
1553
1828
  },
1554
1829
  body: JSON.stringify({
1555
1830
  tool_call_id: result.tool_call_id,
1556
- tool_response: result
1831
+ tool_response: {
1832
+ tool_call_id: result.tool_call_id,
1833
+ tool_name: result.tool_name,
1834
+ parts: result.parts,
1835
+ parts_metadata: result.parts_metadata
1836
+ }
1557
1837
  })
1558
1838
  });
1559
1839
  if (!response.ok) {
@@ -1817,9 +2097,17 @@ var _DistriClient = class _DistriClient {
1817
2097
  };
1818
2098
  }
1819
2099
  /**
1820
- * Helper method to create message send parameters
1821
- */
1822
- static initMessageParams(message, configuration, metadata) {
2100
+ * Helper method to create message send parameters.
2101
+ *
2102
+ * Pass `dynamicMetadata` to inject `dynamic_sections` and/or `dynamic_values`
2103
+ * into the metadata so the server can apply them to prompt templates.
2104
+ */
2105
+ static initMessageParams(message, configuration, metadata, dynamicMetadata) {
2106
+ const mergedMetadata = {
2107
+ ...metadata,
2108
+ ...dynamicMetadata?.dynamic_sections ? { dynamic_sections: dynamicMetadata.dynamic_sections } : {},
2109
+ ...dynamicMetadata?.dynamic_values ? { dynamic_values: dynamicMetadata.dynamic_values } : {}
2110
+ };
1823
2111
  return {
1824
2112
  message,
1825
2113
  configuration: {
@@ -1828,18 +2116,26 @@ var _DistriClient = class _DistriClient {
1828
2116
  // Default to non-blocking for streaming
1829
2117
  ...configuration
1830
2118
  },
1831
- metadata
2119
+ metadata: Object.keys(mergedMetadata).length > 0 ? mergedMetadata : metadata
1832
2120
  };
1833
2121
  }
1834
2122
  /**
1835
- * Create MessageSendParams from a DistriMessage using InvokeContext
2123
+ * Create MessageSendParams from a DistriMessage using InvokeContext.
2124
+ *
2125
+ * Pass `dynamicMetadata` to inject `dynamic_sections` and/or `dynamic_values`
2126
+ * into the metadata so the server can apply them to prompt templates.
1836
2127
  */
1837
- static initDistriMessageParams(message, context) {
2128
+ static initDistriMessageParams(message, context, dynamicMetadata) {
1838
2129
  const a2aMessage = convertDistriMessageToA2A(message, context);
1839
2130
  const contextMetadata = context.getMetadata?.() || {};
2131
+ const mergedMetadata = {
2132
+ ...contextMetadata,
2133
+ ...dynamicMetadata?.dynamic_sections ? { dynamic_sections: dynamicMetadata.dynamic_sections } : {},
2134
+ ...dynamicMetadata?.dynamic_values ? { dynamic_values: dynamicMetadata.dynamic_values } : {}
2135
+ };
1840
2136
  return {
1841
2137
  message: a2aMessage,
1842
- metadata: contextMetadata
2138
+ metadata: Object.keys(mergedMetadata).length > 0 ? mergedMetadata : contextMetadata
1843
2139
  };
1844
2140
  }
1845
2141
  };
@@ -1945,28 +2241,40 @@ var Agent = class _Agent {
1945
2241
  const enhancedParams = this.enhanceParamsWithTools(params, tools);
1946
2242
  const a2aStream = this.client.sendMessageStream(this.agentDefinition.id, enhancedParams);
1947
2243
  const self = this;
1948
- return async function* () {
1949
- for await (const event of a2aStream) {
1950
- const converted = decodeA2AStreamEvent(event);
1951
- if (converted && converted.type === "inline_hook_requested") {
1952
- const hookReq = converted.data;
1953
- const handler = self.hookHandlers.get(hookReq.hook) || self.defaultHookHandler;
1954
- if (handler) {
1955
- try {
1956
- const mutation = await handler(hookReq);
1957
- await self.client.completeInlineHook(hookReq.hook_id, mutation);
1958
- } catch (err) {
2244
+ return (async function* () {
2245
+ try {
2246
+ for await (const event of a2aStream) {
2247
+ const converted = decodeA2AStreamEvent(event);
2248
+ if (converted && converted.type === "inline_hook_requested") {
2249
+ const hookReq = converted.data;
2250
+ const handler = self.hookHandlers.get(hookReq.hook) || self.defaultHookHandler;
2251
+ if (handler) {
2252
+ try {
2253
+ const mutation = await handler(hookReq);
2254
+ await self.client.completeInlineHook(hookReq.hook_id, mutation);
2255
+ } catch (err) {
2256
+ await self.client.completeInlineHook(hookReq.hook_id, { dynamic_values: {} });
2257
+ }
2258
+ } else {
1959
2259
  await self.client.completeInlineHook(hookReq.hook_id, { dynamic_values: {} });
1960
2260
  }
1961
- } else {
1962
- await self.client.completeInlineHook(hookReq.hook_id, { dynamic_values: {} });
2261
+ yield converted;
2262
+ } else if (converted) {
2263
+ yield converted;
1963
2264
  }
1964
- yield converted;
1965
- } else if (converted) {
1966
- yield converted;
1967
2265
  }
2266
+ } catch (error) {
2267
+ const message = error instanceof Error ? error.message : String(error);
2268
+ const runError = {
2269
+ type: "run_error",
2270
+ data: {
2271
+ message,
2272
+ code: "STREAM_ERROR"
2273
+ }
2274
+ };
2275
+ yield runError;
1968
2276
  }
1969
- }();
2277
+ })();
1970
2278
  }
1971
2279
  /**
1972
2280
  * Validate that required external tools are registered before invoking.
@@ -1994,12 +2302,16 @@ var Agent = class _Agent {
1994
2302
  };
1995
2303
  }
1996
2304
  /**
1997
- * Enhance message params with tool definitions
2305
+ * Enhance message params with tool definitions and dynamic metadata.
2306
+ *
2307
+ * When `dynamic_sections` or `dynamic_values` are present in `params.metadata`,
2308
+ * they are forwarded so the server injects them into the prompt template.
1998
2309
  */
1999
2310
  enhanceParamsWithTools(params, tools) {
2000
2311
  this.assertExternalTools(tools);
2312
+ const existingMeta = params.metadata ?? {};
2001
2313
  const metadata = {
2002
- ...params.metadata,
2314
+ ...existingMeta,
2003
2315
  external_tools: tools?.map((tool) => ({
2004
2316
  name: tool.name,
2005
2317
  description: tool.description,