keyring-agent-core 0.1.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/README.md +169 -0
- package/dist/agent/AgentCore.d.ts +220 -0
- package/dist/agent/AgentCore.d.ts.map +1 -0
- package/dist/agent/AgentCore.js +950 -0
- package/dist/agent/AgentCore.js.map +1 -0
- package/dist/agent/QueryRewriter.d.ts +67 -0
- package/dist/agent/QueryRewriter.d.ts.map +1 -0
- package/dist/agent/QueryRewriter.js +262 -0
- package/dist/agent/QueryRewriter.js.map +1 -0
- package/dist/agent/Router.d.ts +33 -0
- package/dist/agent/Router.d.ts.map +1 -0
- package/dist/agent/Router.js +191 -0
- package/dist/agent/Router.js.map +1 -0
- package/dist/agent/Subagent.d.ts +63 -0
- package/dist/agent/Subagent.d.ts.map +1 -0
- package/dist/agent/Subagent.js +240 -0
- package/dist/agent/Subagent.js.map +1 -0
- package/dist/agent/Synthesizer.d.ts +11 -0
- package/dist/agent/Synthesizer.d.ts.map +1 -0
- package/dist/agent/Synthesizer.js +86 -0
- package/dist/agent/Synthesizer.js.map +1 -0
- package/dist/agent/index.d.ts +10 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +24 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/subagents/AiAgent.d.ts +6 -0
- package/dist/agent/subagents/AiAgent.d.ts.map +1 -0
- package/dist/agent/subagents/AiAgent.js +48 -0
- package/dist/agent/subagents/AiAgent.js.map +1 -0
- package/dist/agent/subagents/NftAgent.d.ts +6 -0
- package/dist/agent/subagents/NftAgent.d.ts.map +1 -0
- package/dist/agent/subagents/NftAgent.js +69 -0
- package/dist/agent/subagents/NftAgent.js.map +1 -0
- package/dist/agent/subagents/PoolAgent.d.ts +6 -0
- package/dist/agent/subagents/PoolAgent.d.ts.map +1 -0
- package/dist/agent/subagents/PoolAgent.js +184 -0
- package/dist/agent/subagents/PoolAgent.js.map +1 -0
- package/dist/agent/subagents/TokenAgent.d.ts +6 -0
- package/dist/agent/subagents/TokenAgent.d.ts.map +1 -0
- package/dist/agent/subagents/TokenAgent.js +75 -0
- package/dist/agent/subagents/TokenAgent.js.map +1 -0
- package/dist/agent/subagents/WalletAgent.d.ts +6 -0
- package/dist/agent/subagents/WalletAgent.d.ts.map +1 -0
- package/dist/agent/subagents/WalletAgent.js +92 -0
- package/dist/agent/subagents/WalletAgent.js.map +1 -0
- package/dist/agent/subagents/index.d.ts +14 -0
- package/dist/agent/subagents/index.d.ts.map +1 -0
- package/dist/agent/subagents/index.js +38 -0
- package/dist/agent/subagents/index.js.map +1 -0
- package/dist/constants/abi.d.ts +520 -0
- package/dist/constants/abi.d.ts.map +1 -0
- package/dist/constants/abi.js +667 -0
- package/dist/constants/abi.js.map +1 -0
- package/dist/functions/pool.d.ts +25 -0
- package/dist/functions/pool.d.ts.map +1 -0
- package/dist/functions/pool.js +151 -0
- package/dist/functions/pool.js.map +1 -0
- package/dist/functions/web3.d.ts +32 -0
- package/dist/functions/web3.d.ts.map +1 -0
- package/dist/functions/web3.js +277 -0
- package/dist/functions/web3.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/GeminiProvider.d.ts +16 -0
- package/dist/llm/GeminiProvider.d.ts.map +1 -0
- package/dist/llm/GeminiProvider.js +248 -0
- package/dist/llm/GeminiProvider.js.map +1 -0
- package/dist/llm/index.d.ts +2 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +6 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/memory/ChatHistory.d.ts +83 -0
- package/dist/memory/ChatHistory.d.ts.map +1 -0
- package/dist/memory/ChatHistory.js +143 -0
- package/dist/memory/ChatHistory.js.map +1 -0
- package/dist/memory/KnowledgeBase.d.ts +38 -0
- package/dist/memory/KnowledgeBase.d.ts.map +1 -0
- package/dist/memory/KnowledgeBase.js +139 -0
- package/dist/memory/KnowledgeBase.js.map +1 -0
- package/dist/memory/Summarizer.d.ts +12 -0
- package/dist/memory/Summarizer.d.ts.map +1 -0
- package/dist/memory/Summarizer.js +39 -0
- package/dist/memory/Summarizer.js.map +1 -0
- package/dist/memory/UpstashKnowledgeBase.d.ts +124 -0
- package/dist/memory/UpstashKnowledgeBase.d.ts.map +1 -0
- package/dist/memory/UpstashKnowledgeBase.js +152 -0
- package/dist/memory/UpstashKnowledgeBase.js.map +1 -0
- package/dist/memory/historyUtils.d.ts +58 -0
- package/dist/memory/historyUtils.d.ts.map +1 -0
- package/dist/memory/historyUtils.js +190 -0
- package/dist/memory/historyUtils.js.map +1 -0
- package/dist/memory/index.d.ts +7 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +12 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/ingestKnowledgeBase.d.ts +17 -0
- package/dist/memory/ingestKnowledgeBase.d.ts.map +1 -0
- package/dist/memory/ingestKnowledgeBase.js +35 -0
- package/dist/memory/ingestKnowledgeBase.js.map +1 -0
- package/dist/services/MoralisService.d.ts +1350 -0
- package/dist/services/MoralisService.d.ts.map +1 -0
- package/dist/services/MoralisService.js +1364 -0
- package/dist/services/MoralisService.js.map +1 -0
- package/dist/services/PantographService.d.ts +38 -0
- package/dist/services/PantographService.d.ts.map +1 -0
- package/dist/services/PantographService.js +199 -0
- package/dist/services/PantographService.js.map +1 -0
- package/dist/services/PoolService.d.ts +241 -0
- package/dist/services/PoolService.d.ts.map +1 -0
- package/dist/services/PoolService.js +507 -0
- package/dist/services/PoolService.js.map +1 -0
- package/dist/services/UniswapService.d.ts +289 -0
- package/dist/services/UniswapService.d.ts.map +1 -0
- package/dist/services/UniswapService.js +585 -0
- package/dist/services/UniswapService.js.map +1 -0
- package/dist/services/swap/BaseSwapService.d.ts +17 -0
- package/dist/services/swap/BaseSwapService.d.ts.map +1 -0
- package/dist/services/swap/BaseSwapService.js +19 -0
- package/dist/services/swap/BaseSwapService.js.map +1 -0
- package/dist/services/swap/DebridgeAdapter.d.ts +20 -0
- package/dist/services/swap/DebridgeAdapter.d.ts.map +1 -0
- package/dist/services/swap/DebridgeAdapter.js +175 -0
- package/dist/services/swap/DebridgeAdapter.js.map +1 -0
- package/dist/services/swap/RelayAdapter.d.ts +19 -0
- package/dist/services/swap/RelayAdapter.d.ts.map +1 -0
- package/dist/services/swap/RelayAdapter.js +189 -0
- package/dist/services/swap/RelayAdapter.js.map +1 -0
- package/dist/services/swap/SwapServiceFactory.d.ts +24 -0
- package/dist/services/swap/SwapServiceFactory.d.ts.map +1 -0
- package/dist/services/swap/SwapServiceFactory.js +74 -0
- package/dist/services/swap/SwapServiceFactory.js.map +1 -0
- package/dist/services/swap/addresses.d.ts +3 -0
- package/dist/services/swap/addresses.d.ts.map +1 -0
- package/dist/services/swap/addresses.js +68 -0
- package/dist/services/swap/addresses.js.map +1 -0
- package/dist/services/swap/affiliate.d.ts +17 -0
- package/dist/services/swap/affiliate.d.ts.map +1 -0
- package/dist/services/swap/affiliate.js +126 -0
- package/dist/services/swap/affiliate.js.map +1 -0
- package/dist/services/swap/config.d.ts +9 -0
- package/dist/services/swap/config.d.ts.map +1 -0
- package/dist/services/swap/config.js +19 -0
- package/dist/services/swap/config.js.map +1 -0
- package/dist/services/swap/index.d.ts +10 -0
- package/dist/services/swap/index.d.ts.map +1 -0
- package/dist/services/swap/index.js +22 -0
- package/dist/services/swap/index.js.map +1 -0
- package/dist/services/swap/types.d.ts +89 -0
- package/dist/services/swap/types.d.ts.map +1 -0
- package/dist/services/swap/types.js +6 -0
- package/dist/services/swap/types.js.map +1 -0
- package/dist/tools/BaseTool.d.ts +15 -0
- package/dist/tools/BaseTool.d.ts.map +1 -0
- package/dist/tools/BaseTool.js +35 -0
- package/dist/tools/BaseTool.js.map +1 -0
- package/dist/tools/ToolRegistry.d.ts +27 -0
- package/dist/tools/ToolRegistry.d.ts.map +1 -0
- package/dist/tools/ToolRegistry.js +80 -0
- package/dist/tools/ToolRegistry.js.map +1 -0
- package/dist/tools/builtin/ai/MoralisCortexTool.d.ts +33 -0
- package/dist/tools/builtin/ai/MoralisCortexTool.d.ts.map +1 -0
- package/dist/tools/builtin/ai/MoralisCortexTool.js +76 -0
- package/dist/tools/builtin/ai/MoralisCortexTool.js.map +1 -0
- package/dist/tools/builtin/ai/SolanaCortexTool.d.ts +22 -0
- package/dist/tools/builtin/ai/SolanaCortexTool.d.ts.map +1 -0
- package/dist/tools/builtin/ai/SolanaCortexTool.js +80 -0
- package/dist/tools/builtin/ai/SolanaCortexTool.js.map +1 -0
- package/dist/tools/builtin/ai/index.d.ts +5 -0
- package/dist/tools/builtin/ai/index.d.ts.map +1 -0
- package/dist/tools/builtin/ai/index.js +8 -0
- package/dist/tools/builtin/ai/index.js.map +1 -0
- package/dist/tools/builtin/index.d.ts +6 -0
- package/dist/tools/builtin/index.d.ts.map +1 -0
- package/dist/tools/builtin/index.js +22 -0
- package/dist/tools/builtin/index.js.map +1 -0
- package/dist/tools/builtin/nft/NFTContractInfoTool.d.ts +21 -0
- package/dist/tools/builtin/nft/NFTContractInfoTool.d.ts.map +1 -0
- package/dist/tools/builtin/nft/NFTContractInfoTool.js +54 -0
- package/dist/tools/builtin/nft/NFTContractInfoTool.js.map +1 -0
- package/dist/tools/builtin/nft/NFTMetadataTool.d.ts +121 -0
- package/dist/tools/builtin/nft/NFTMetadataTool.d.ts.map +1 -0
- package/dist/tools/builtin/nft/NFTMetadataTool.js +159 -0
- package/dist/tools/builtin/nft/NFTMetadataTool.js.map +1 -0
- package/dist/tools/builtin/nft/WalletNFTsTool.d.ts +82 -0
- package/dist/tools/builtin/nft/WalletNFTsTool.d.ts.map +1 -0
- package/dist/tools/builtin/nft/WalletNFTsTool.js +172 -0
- package/dist/tools/builtin/nft/WalletNFTsTool.js.map +1 -0
- package/dist/tools/builtin/nft/index.d.ts +7 -0
- package/dist/tools/builtin/nft/index.d.ts.map +1 -0
- package/dist/tools/builtin/nft/index.js +10 -0
- package/dist/tools/builtin/nft/index.js.map +1 -0
- package/dist/tools/builtin/pool/EstimatePoolYieldTool.d.ts +124 -0
- package/dist/tools/builtin/pool/EstimatePoolYieldTool.d.ts.map +1 -0
- package/dist/tools/builtin/pool/EstimatePoolYieldTool.js +235 -0
- package/dist/tools/builtin/pool/EstimatePoolYieldTool.js.map +1 -0
- package/dist/tools/builtin/pool/OpenAddLiquidityFormTool.d.ts +127 -0
- package/dist/tools/builtin/pool/OpenAddLiquidityFormTool.d.ts.map +1 -0
- package/dist/tools/builtin/pool/OpenAddLiquidityFormTool.js +517 -0
- package/dist/tools/builtin/pool/OpenAddLiquidityFormTool.js.map +1 -0
- package/dist/tools/builtin/pool/PoolByAddressTool.d.ts +127 -0
- package/dist/tools/builtin/pool/PoolByAddressTool.d.ts.map +1 -0
- package/dist/tools/builtin/pool/PoolByAddressTool.js +237 -0
- package/dist/tools/builtin/pool/PoolByAddressTool.js.map +1 -0
- package/dist/tools/builtin/pool/PoolDetailTool.d.ts +127 -0
- package/dist/tools/builtin/pool/PoolDetailTool.d.ts.map +1 -0
- package/dist/tools/builtin/pool/PoolDetailTool.js +272 -0
- package/dist/tools/builtin/pool/PoolDetailTool.js.map +1 -0
- package/dist/tools/builtin/pool/PoolSearchTool.d.ts +50 -0
- package/dist/tools/builtin/pool/PoolSearchTool.d.ts.map +1 -0
- package/dist/tools/builtin/pool/PoolSearchTool.js +159 -0
- package/dist/tools/builtin/pool/PoolSearchTool.js.map +1 -0
- package/dist/tools/builtin/pool/PreviewAddLiquidityTool.d.ts +123 -0
- package/dist/tools/builtin/pool/PreviewAddLiquidityTool.d.ts.map +1 -0
- package/dist/tools/builtin/pool/PreviewAddLiquidityTool.js +380 -0
- package/dist/tools/builtin/pool/PreviewAddLiquidityTool.js.map +1 -0
- package/dist/tools/builtin/pool/TopPoolsTool.d.ts +68 -0
- package/dist/tools/builtin/pool/TopPoolsTool.d.ts.map +1 -0
- package/dist/tools/builtin/pool/TopPoolsTool.js +158 -0
- package/dist/tools/builtin/pool/TopPoolsTool.js.map +1 -0
- package/dist/tools/builtin/pool/index.d.ts +15 -0
- package/dist/tools/builtin/pool/index.d.ts.map +1 -0
- package/dist/tools/builtin/pool/index.js +18 -0
- package/dist/tools/builtin/pool/index.js.map +1 -0
- package/dist/tools/builtin/token/TokenAnalyticsTool.d.ts +71 -0
- package/dist/tools/builtin/token/TokenAnalyticsTool.d.ts.map +1 -0
- package/dist/tools/builtin/token/TokenAnalyticsTool.js +146 -0
- package/dist/tools/builtin/token/TokenAnalyticsTool.js.map +1 -0
- package/dist/tools/builtin/token/TokenHoldersTool.d.ts +81 -0
- package/dist/tools/builtin/token/TokenHoldersTool.d.ts.map +1 -0
- package/dist/tools/builtin/token/TokenHoldersTool.js +138 -0
- package/dist/tools/builtin/token/TokenHoldersTool.js.map +1 -0
- package/dist/tools/builtin/token/TokenInfoTool.d.ts +36 -0
- package/dist/tools/builtin/token/TokenInfoTool.d.ts.map +1 -0
- package/dist/tools/builtin/token/TokenInfoTool.js +163 -0
- package/dist/tools/builtin/token/TokenInfoTool.js.map +1 -0
- package/dist/tools/builtin/token/TokenScoreTool.d.ts +63 -0
- package/dist/tools/builtin/token/TokenScoreTool.d.ts.map +1 -0
- package/dist/tools/builtin/token/TokenScoreTool.js +147 -0
- package/dist/tools/builtin/token/TokenScoreTool.js.map +1 -0
- package/dist/tools/builtin/token/TopGainersTool.d.ts +46 -0
- package/dist/tools/builtin/token/TopGainersTool.d.ts.map +1 -0
- package/dist/tools/builtin/token/TopGainersTool.js +110 -0
- package/dist/tools/builtin/token/TopGainersTool.js.map +1 -0
- package/dist/tools/builtin/token/TopLosersTool.d.ts +66 -0
- package/dist/tools/builtin/token/TopLosersTool.d.ts.map +1 -0
- package/dist/tools/builtin/token/TopLosersTool.js +128 -0
- package/dist/tools/builtin/token/TopLosersTool.js.map +1 -0
- package/dist/tools/builtin/token/TrendingTokensTool.d.ts +39 -0
- package/dist/tools/builtin/token/TrendingTokensTool.d.ts.map +1 -0
- package/dist/tools/builtin/token/TrendingTokensTool.js +75 -0
- package/dist/tools/builtin/token/TrendingTokensTool.js.map +1 -0
- package/dist/tools/builtin/token/index.d.ts +15 -0
- package/dist/tools/builtin/token/index.d.ts.map +1 -0
- package/dist/tools/builtin/token/index.js +18 -0
- package/dist/tools/builtin/token/index.js.map +1 -0
- package/dist/tools/builtin/wallet/TransactionByHashTool.d.ts +70 -0
- package/dist/tools/builtin/wallet/TransactionByHashTool.d.ts.map +1 -0
- package/dist/tools/builtin/wallet/TransactionByHashTool.js +110 -0
- package/dist/tools/builtin/wallet/TransactionByHashTool.js.map +1 -0
- package/dist/tools/builtin/wallet/WalletApprovalsTool.d.ts +107 -0
- package/dist/tools/builtin/wallet/WalletApprovalsTool.d.ts.map +1 -0
- package/dist/tools/builtin/wallet/WalletApprovalsTool.js +163 -0
- package/dist/tools/builtin/wallet/WalletApprovalsTool.js.map +1 -0
- package/dist/tools/builtin/wallet/WalletDefiPositionsTool.d.ts +61 -0
- package/dist/tools/builtin/wallet/WalletDefiPositionsTool.d.ts.map +1 -0
- package/dist/tools/builtin/wallet/WalletDefiPositionsTool.js +99 -0
- package/dist/tools/builtin/wallet/WalletDefiPositionsTool.js.map +1 -0
- package/dist/tools/builtin/wallet/WalletDefiProtocolPositionsTool.d.ts +68 -0
- package/dist/tools/builtin/wallet/WalletDefiProtocolPositionsTool.d.ts.map +1 -0
- package/dist/tools/builtin/wallet/WalletDefiProtocolPositionsTool.js +114 -0
- package/dist/tools/builtin/wallet/WalletDefiProtocolPositionsTool.js.map +1 -0
- package/dist/tools/builtin/wallet/WalletDefiSummaryTool.d.ts +50 -0
- package/dist/tools/builtin/wallet/WalletDefiSummaryTool.d.ts.map +1 -0
- package/dist/tools/builtin/wallet/WalletDefiSummaryTool.js +79 -0
- package/dist/tools/builtin/wallet/WalletDefiSummaryTool.js.map +1 -0
- package/dist/tools/builtin/wallet/WalletHistoryTool.d.ts +31 -0
- package/dist/tools/builtin/wallet/WalletHistoryTool.d.ts.map +1 -0
- package/dist/tools/builtin/wallet/WalletHistoryTool.js +153 -0
- package/dist/tools/builtin/wallet/WalletHistoryTool.js.map +1 -0
- package/dist/tools/builtin/wallet/WalletNetWorthTool.d.ts +44 -0
- package/dist/tools/builtin/wallet/WalletNetWorthTool.d.ts.map +1 -0
- package/dist/tools/builtin/wallet/WalletNetWorthTool.js +122 -0
- package/dist/tools/builtin/wallet/WalletNetWorthTool.js.map +1 -0
- package/dist/tools/builtin/wallet/WalletNftTransfersTool.d.ts +86 -0
- package/dist/tools/builtin/wallet/WalletNftTransfersTool.d.ts.map +1 -0
- package/dist/tools/builtin/wallet/WalletNftTransfersTool.js +264 -0
- package/dist/tools/builtin/wallet/WalletNftTransfersTool.js.map +1 -0
- package/dist/tools/builtin/wallet/WalletPnlSummaryTool.d.ts +43 -0
- package/dist/tools/builtin/wallet/WalletPnlSummaryTool.d.ts.map +1 -0
- package/dist/tools/builtin/wallet/WalletPnlSummaryTool.js +88 -0
- package/dist/tools/builtin/wallet/WalletPnlSummaryTool.js.map +1 -0
- package/dist/tools/builtin/wallet/WalletPnlTool.d.ts +43 -0
- package/dist/tools/builtin/wallet/WalletPnlTool.d.ts.map +1 -0
- package/dist/tools/builtin/wallet/WalletPnlTool.js +174 -0
- package/dist/tools/builtin/wallet/WalletPnlTool.js.map +1 -0
- package/dist/tools/builtin/wallet/WalletTokenBalancesTool.d.ts +35 -0
- package/dist/tools/builtin/wallet/WalletTokenBalancesTool.d.ts.map +1 -0
- package/dist/tools/builtin/wallet/WalletTokenBalancesTool.js +67 -0
- package/dist/tools/builtin/wallet/WalletTokenBalancesTool.js.map +1 -0
- package/dist/tools/builtin/wallet/WalletTokenTransfersTool.d.ts +109 -0
- package/dist/tools/builtin/wallet/WalletTokenTransfersTool.d.ts.map +1 -0
- package/dist/tools/builtin/wallet/WalletTokenTransfersTool.js +353 -0
- package/dist/tools/builtin/wallet/WalletTokenTransfersTool.js.map +1 -0
- package/dist/tools/builtin/wallet/index.d.ts +25 -0
- package/dist/tools/builtin/wallet/index.d.ts.map +1 -0
- package/dist/tools/builtin/wallet/index.js +28 -0
- package/dist/tools/builtin/wallet/index.js.map +1 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +23 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types/index.d.ts +566 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,950 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AgentCore = void 0;
|
|
4
|
+
const GeminiProvider_1 = require("../llm/GeminiProvider");
|
|
5
|
+
const ToolRegistry_1 = require("../tools/ToolRegistry");
|
|
6
|
+
const builtin_1 = require("../tools/builtin");
|
|
7
|
+
const ChatHistory_1 = require("../memory/ChatHistory");
|
|
8
|
+
const Summarizer_1 = require("../memory/Summarizer");
|
|
9
|
+
const KnowledgeBase_1 = require("../memory/KnowledgeBase");
|
|
10
|
+
const UpstashKnowledgeBase_1 = require("../memory/UpstashKnowledgeBase");
|
|
11
|
+
const historyUtils_1 = require("../memory/historyUtils");
|
|
12
|
+
const Router_1 = require("./Router");
|
|
13
|
+
const subagents_1 = require("./subagents");
|
|
14
|
+
const Synthesizer_1 = require("./Synthesizer");
|
|
15
|
+
const QueryRewriter_1 = require("./QueryRewriter");
|
|
16
|
+
/** Generate a short stable id for one response turn (8 hex chars). */
|
|
17
|
+
function generateMessageId() {
|
|
18
|
+
const ts = Date.now().toString(16);
|
|
19
|
+
const rand = Math.floor(Math.random() * 0xffff)
|
|
20
|
+
.toString(16)
|
|
21
|
+
.padStart(4, '0');
|
|
22
|
+
return (ts + rand).slice(-8);
|
|
23
|
+
}
|
|
24
|
+
const DEFAULT_SYSTEM_PROMPT = 'You are a helpful AI assistant. Answer the user accurately and concisely. ' +
|
|
25
|
+
'Respond in the same language as the user.';
|
|
26
|
+
/**
|
|
27
|
+
* Top-level orchestrator. Two-layer architecture:
|
|
28
|
+
*
|
|
29
|
+
* User query
|
|
30
|
+
* → Router (picks 0..N domain subagents)
|
|
31
|
+
* → Subagent(s) (each runs its own ReAct loop over a narrow tool set, in parallel)
|
|
32
|
+
* → Synthesizer (merges subagent answers into the final reply)
|
|
33
|
+
*
|
|
34
|
+
* Compared to a flat planner that sees every tool, this keeps per-call prompt
|
|
35
|
+
* size bounded regardless of how many tools are registered.
|
|
36
|
+
*/
|
|
37
|
+
class AgentCore {
|
|
38
|
+
llm;
|
|
39
|
+
registry;
|
|
40
|
+
history;
|
|
41
|
+
summarizer;
|
|
42
|
+
router;
|
|
43
|
+
rewriter;
|
|
44
|
+
subagents;
|
|
45
|
+
synthesizer;
|
|
46
|
+
systemPrompt;
|
|
47
|
+
debug;
|
|
48
|
+
userContext = {};
|
|
49
|
+
storage;
|
|
50
|
+
storageKey;
|
|
51
|
+
persistHistoryEnabled;
|
|
52
|
+
/** Active KB used at search time. May be the legacy LLM matcher or a vector backend. */
|
|
53
|
+
kb;
|
|
54
|
+
/** Always-present in-memory KB that holds the original entries (used for autoIngest + as fallback). */
|
|
55
|
+
kbEntries;
|
|
56
|
+
vectorKB = null;
|
|
57
|
+
vectorKBAutoIngested = false;
|
|
58
|
+
vectorKBAutoIngestPromise = null;
|
|
59
|
+
autoIngestEnabled = false;
|
|
60
|
+
kbAnswerThreshold;
|
|
61
|
+
suggestionsEnabled;
|
|
62
|
+
historyLoaded = false;
|
|
63
|
+
historyLoadingPromise = null;
|
|
64
|
+
/**
|
|
65
|
+
* Resolves once the initial persisted-history load attempt has finished
|
|
66
|
+
* (success or failure). Only meaningful when `persistHistory: true` and a
|
|
67
|
+
* `storage` provider is configured — in that case consumers rendering a
|
|
68
|
+
* chat list on mount should `await agent.historyReady` before calling
|
|
69
|
+
* {@link getHistory} / {@link getDisplayHistory}, otherwise the first read
|
|
70
|
+
* happens before storage has been hydrated and returns an empty array.
|
|
71
|
+
* With persistence disabled this still resolves (immediately), so it is
|
|
72
|
+
* always safe to await.
|
|
73
|
+
*/
|
|
74
|
+
historyReady;
|
|
75
|
+
constructor(config) {
|
|
76
|
+
this.llm = new GeminiProvider_1.GeminiProvider(config.llm);
|
|
77
|
+
this.registry = new ToolRegistry_1.ToolRegistry();
|
|
78
|
+
this.history = new ChatHistory_1.ChatHistory(config.maxHistoryMessages ?? 50);
|
|
79
|
+
this.summarizer = new Summarizer_1.Summarizer(this.llm);
|
|
80
|
+
this.synthesizer = new Synthesizer_1.Synthesizer(this.llm);
|
|
81
|
+
this.systemPrompt = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
82
|
+
this.debug = config.debug ?? false;
|
|
83
|
+
this.suggestionsEnabled = config.generateSuggestions ?? true;
|
|
84
|
+
this.storage = config.storage ?? null;
|
|
85
|
+
this.storageKey = config.storageKey ?? 'keyring-agent-history';
|
|
86
|
+
this.persistHistoryEnabled = config.persistHistory ?? false;
|
|
87
|
+
// Always build the in-memory KB so callers can add/replace entries at
|
|
88
|
+
// runtime even when vector mode is enabled (we re-ingest from it).
|
|
89
|
+
this.kbEntries = new KnowledgeBase_1.KnowledgeBase(config.knowledgeBase);
|
|
90
|
+
this.kbEntries.setLLM(this.llm);
|
|
91
|
+
// Resolve which backend handles search: vector (Upstash / custom) or legacy.
|
|
92
|
+
const vec = config.vectorKB;
|
|
93
|
+
if (vec?.enabled) {
|
|
94
|
+
if (vec.provider) {
|
|
95
|
+
this.kb = vec.provider;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
this.vectorKB = new UpstashKnowledgeBase_1.UpstashKnowledgeBase({
|
|
99
|
+
url: vec.url,
|
|
100
|
+
token: vec.token,
|
|
101
|
+
namespace: vec.namespace,
|
|
102
|
+
minScore: vec.minScore,
|
|
103
|
+
defaultTopK: vec.defaultTopK,
|
|
104
|
+
debug: this.debug,
|
|
105
|
+
});
|
|
106
|
+
this.kb = this.vectorKB;
|
|
107
|
+
this.autoIngestEnabled = vec.autoIngest === true;
|
|
108
|
+
}
|
|
109
|
+
if (this.debug) {
|
|
110
|
+
console.log(`[AgentCore] vector KB enabled (${vec.provider ? 'custom provider' : 'Upstash'}` +
|
|
111
|
+
(vec.namespace ? `, ns=${vec.namespace}` : '') +
|
|
112
|
+
')');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
this.kb = this.kbEntries;
|
|
117
|
+
}
|
|
118
|
+
// kbAnswerThreshold default differs by backend:
|
|
119
|
+
// - LLM matcher: scores are 0.6–1.0, the "high-confidence" cut-off is 0.8.
|
|
120
|
+
// - Upstash vector backend (DBSF fusion, hybrid index): scores are
|
|
121
|
+
// distribution-fused and unbounded — strong matches sit above ~1.3,
|
|
122
|
+
// decent ones around 0.6–1.0, noise near 0. We deliberately keep a
|
|
123
|
+
// HIGH cut-off here (1.3) to avoid answer-from-KB false positives when
|
|
124
|
+
// sparse keyword overlap pushes a non-matching entry above 1.0 even
|
|
125
|
+
// though the dense semantic match is weak. Lower-scoring hits still
|
|
126
|
+
// flow to the router/synthesiser as reference context — they are just
|
|
127
|
+
// not allowed to bypass the router on their own.
|
|
128
|
+
const explicitThreshold = config.kbAnswerThreshold;
|
|
129
|
+
if (typeof explicitThreshold === 'number') {
|
|
130
|
+
this.kbAnswerThreshold = explicitThreshold;
|
|
131
|
+
}
|
|
132
|
+
else if (vec?.enabled && !vec.provider) {
|
|
133
|
+
this.kbAnswerThreshold = vec.minScore ?? 1.3;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
this.kbAnswerThreshold = 0.8;
|
|
137
|
+
}
|
|
138
|
+
if (this.debug) {
|
|
139
|
+
console.log(`[AgentCore] kbAnswerThreshold = ${this.kbAnswerThreshold}`);
|
|
140
|
+
}
|
|
141
|
+
// Register built-in Moralis tools
|
|
142
|
+
if (config.moralis !== false) {
|
|
143
|
+
const moralisConfig = typeof config.moralis === 'object' ? config.moralis : undefined;
|
|
144
|
+
this.registry.register(new builtin_1.WalletTokenBalancesTool(moralisConfig));
|
|
145
|
+
this.registry.register(new builtin_1.TokenInfoTool(moralisConfig));
|
|
146
|
+
this.registry.register(new builtin_1.WalletNFTsTool(moralisConfig));
|
|
147
|
+
this.registry.register(new builtin_1.NFTContractInfoTool(moralisConfig));
|
|
148
|
+
this.registry.register(new builtin_1.NFTMetadataTool(moralisConfig));
|
|
149
|
+
this.registry.register(new builtin_1.WalletHistoryTool(moralisConfig));
|
|
150
|
+
this.registry.register(new builtin_1.WalletTokenTransfersTool(moralisConfig));
|
|
151
|
+
this.registry.register(new builtin_1.WalletNftTransfersTool(moralisConfig));
|
|
152
|
+
this.registry.register(new builtin_1.TokenHoldersTool(moralisConfig));
|
|
153
|
+
this.registry.register(new builtin_1.WalletPnlSummaryTool(moralisConfig));
|
|
154
|
+
this.registry.register(new builtin_1.WalletPnlTool(moralisConfig));
|
|
155
|
+
this.registry.register(new builtin_1.TransactionByHashTool(moralisConfig));
|
|
156
|
+
this.registry.register(new builtin_1.SolanaCortexTool(moralisConfig));
|
|
157
|
+
this.registry.register(new builtin_1.WalletDefiSummaryTool(moralisConfig));
|
|
158
|
+
this.registry.register(new builtin_1.WalletDefiPositionsTool(moralisConfig));
|
|
159
|
+
this.registry.register(new builtin_1.WalletDefiProtocolPositionsTool(moralisConfig));
|
|
160
|
+
this.registry.register(new builtin_1.WalletApprovalsTool(moralisConfig));
|
|
161
|
+
this.registry.register(new builtin_1.TrendingTokensTool(moralisConfig));
|
|
162
|
+
// this.registry.register(new TopGainersTool(moralisConfig)); //Deprecated
|
|
163
|
+
// this.registry.register(new TopLosersTool(moralisConfig)); //Deprecated
|
|
164
|
+
this.registry.register(new builtin_1.TokenAnalyticsTool(moralisConfig));
|
|
165
|
+
this.registry.register(new builtin_1.TokenScoreTool(moralisConfig));
|
|
166
|
+
this.registry.register(new builtin_1.MoralisCortexTool(moralisConfig));
|
|
167
|
+
}
|
|
168
|
+
// Register built-in Uniswap (DEX pool) tools
|
|
169
|
+
if (config.uniswap !== false) {
|
|
170
|
+
const uniswapConfig = typeof config.uniswap === 'object' ? config.uniswap : undefined;
|
|
171
|
+
this.registry.register(new builtin_1.TopPoolsTool(uniswapConfig));
|
|
172
|
+
this.registry.register(new builtin_1.PoolDetailTool(uniswapConfig));
|
|
173
|
+
this.registry.register(new builtin_1.PoolSearchTool(uniswapConfig));
|
|
174
|
+
this.registry.register(new builtin_1.PoolByAddressTool(uniswapConfig));
|
|
175
|
+
this.registry.register(new builtin_1.EstimatePoolYieldTool(uniswapConfig));
|
|
176
|
+
// Add-liquidity action tools (need on-chain RPC + gateway addresses).
|
|
177
|
+
const poolServiceConfig = uniswapConfig
|
|
178
|
+
? {
|
|
179
|
+
rpcUrls: uniswapConfig.rpcUrls,
|
|
180
|
+
isProduction: uniswapConfig.isProduction,
|
|
181
|
+
}
|
|
182
|
+
: undefined;
|
|
183
|
+
this.registry.register(new builtin_1.OpenAddLiquidityFormTool({
|
|
184
|
+
baseUrl: uniswapConfig?.baseUrl,
|
|
185
|
+
pool: poolServiceConfig,
|
|
186
|
+
minProvideUsd: uniswapConfig?.minProvideUsd,
|
|
187
|
+
}));
|
|
188
|
+
this.registry.register(new builtin_1.PreviewAddLiquidityTool({ pool: poolServiceConfig }));
|
|
189
|
+
}
|
|
190
|
+
// Wire router + subagents. Subagents share the same registry, so any custom
|
|
191
|
+
// tools registered later via `registerTool()` become available as long as
|
|
192
|
+
// their name matches one of the subagent's declared tool names.
|
|
193
|
+
this.router = new Router_1.Router(this.llm, { debug: this.debug });
|
|
194
|
+
this.rewriter = new QueryRewriter_1.QueryRewriter(this.llm, { debug: this.debug });
|
|
195
|
+
const subagentList = (0, subagents_1.createDefaultSubagents)(this.llm, this.registry, {
|
|
196
|
+
maxToolCalls: config.maxIterations ?? 5,
|
|
197
|
+
debug: this.debug,
|
|
198
|
+
});
|
|
199
|
+
this.subagents = new Map(subagentList.map((s) => [s.name, s]));
|
|
200
|
+
// Kick off the initial history load eagerly so consumers that read
|
|
201
|
+
// history synchronously right after `new AgentCore(...)` (e.g. a React
|
|
202
|
+
// component reading `getHistory()` during its first render) can await
|
|
203
|
+
// `agent.historyReady` to ensure persisted storage has been hydrated.
|
|
204
|
+
this.historyReady = this.loadHistory();
|
|
205
|
+
}
|
|
206
|
+
// ---- Tool management ----
|
|
207
|
+
/** Register a tool so the agent can use it. */
|
|
208
|
+
registerTool(tool) {
|
|
209
|
+
this.registry.register(tool);
|
|
210
|
+
}
|
|
211
|
+
/** Register multiple tools at once. */
|
|
212
|
+
registerTools(tools) {
|
|
213
|
+
for (const tool of tools) {
|
|
214
|
+
this.registry.register(tool);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/** Unregister a tool by name. */
|
|
218
|
+
unregisterTool(name) {
|
|
219
|
+
return this.registry.unregister(name);
|
|
220
|
+
}
|
|
221
|
+
/** List registered tool names. */
|
|
222
|
+
listTools() {
|
|
223
|
+
return this.registry.listNames();
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Directly invoke a tool with structured args, bypassing the LLM/router.
|
|
227
|
+
*
|
|
228
|
+
* Use this from the FE for **deterministic actions** where the user has
|
|
229
|
+
* already supplied structured input via a UI form (e.g. confirming an
|
|
230
|
+
* AddLiquidityForm with a chosen strategy + amount). It is faster, cheaper,
|
|
231
|
+
* and removes the risk of the LLM mis-parsing numeric values from form data.
|
|
232
|
+
*
|
|
233
|
+
* The result is returned with the same `AgentResponse` shape as `chat()` —
|
|
234
|
+
* including `uiActions` collected from the tool's UI payload — so the FE
|
|
235
|
+
* renders it uniformly. The interaction is also recorded in chat history so
|
|
236
|
+
* subsequent conversational turns retain context.
|
|
237
|
+
*
|
|
238
|
+
* Use `chat()` (the LLM-driven path) for free-form natural-language input.
|
|
239
|
+
*/
|
|
240
|
+
async invokeTool(name, args, options) {
|
|
241
|
+
await this.loadHistory();
|
|
242
|
+
const tool = this.registry.get(name);
|
|
243
|
+
if (!tool) {
|
|
244
|
+
const answer = `Unknown tool: "${name}".`;
|
|
245
|
+
return { answer, messageId: generateMessageId() };
|
|
246
|
+
}
|
|
247
|
+
if (this.debug) {
|
|
248
|
+
console.log(`[AgentCore] invokeTool ${name} ${JSON.stringify(args)}`);
|
|
249
|
+
}
|
|
250
|
+
// Record the user's intent in history so the next conversational turn
|
|
251
|
+
// can reference what the user just confirmed.
|
|
252
|
+
const userMsg = options?.userMessage ?? `[invoke ${name}]`;
|
|
253
|
+
this.history.add({ role: 'user', content: userMsg, timestamp: Date.now() });
|
|
254
|
+
const result = await this.registry.execute(name, args, this.userContext);
|
|
255
|
+
// Build a synthetic assistant message so chat history stays well-formed
|
|
256
|
+
// (one assistant reply per user turn). For UI/action tools this is
|
|
257
|
+
// typically just the tool's own `summary` field.
|
|
258
|
+
const data = result.data;
|
|
259
|
+
const answer = result.success ? (data?.summary ?? `Done.`) : `Action failed: ${result.error ?? 'unknown error'}`;
|
|
260
|
+
const messageId = generateMessageId();
|
|
261
|
+
// invokeTool() is FE-driven (form confirmation, not free-form chat) — no
|
|
262
|
+
// chat-message language detection runs here. Stamp 'en' as a safe default
|
|
263
|
+
// so FE never sees `undefined`; FE may override with its own app locale.
|
|
264
|
+
const uiActions = result.success && result.ui ? [this.stampLanguage(result.ui, null)] : [];
|
|
265
|
+
const assistantMsg = {
|
|
266
|
+
role: 'assistant',
|
|
267
|
+
content: answer,
|
|
268
|
+
timestamp: Date.now(),
|
|
269
|
+
subagents: ['direct-invoke'],
|
|
270
|
+
messageId,
|
|
271
|
+
};
|
|
272
|
+
if (uiActions.length > 0)
|
|
273
|
+
assistantMsg.uiActions = uiActions;
|
|
274
|
+
this.history.add(assistantMsg);
|
|
275
|
+
await this.persistHistory();
|
|
276
|
+
return {
|
|
277
|
+
answer,
|
|
278
|
+
messageId,
|
|
279
|
+
...(uiActions.length > 0 ? { uiActions } : {}),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
// ---- Subagent management ----
|
|
283
|
+
/**
|
|
284
|
+
* Register a custom subagent. Replaces any existing subagent with the same name.
|
|
285
|
+
* Advanced: most consumers should rely on the default wallet/token/nft/ai agents.
|
|
286
|
+
*/
|
|
287
|
+
registerSubagent(agent) {
|
|
288
|
+
this.subagents.set(agent.name, agent);
|
|
289
|
+
}
|
|
290
|
+
/** Unregister a subagent by name. */
|
|
291
|
+
unregisterSubagent(name) {
|
|
292
|
+
return this.subagents.delete(name);
|
|
293
|
+
}
|
|
294
|
+
/** List registered subagent names. */
|
|
295
|
+
listSubagents() {
|
|
296
|
+
return Array.from(this.subagents.keys());
|
|
297
|
+
}
|
|
298
|
+
// ---- Knowledge Base ----
|
|
299
|
+
/**
|
|
300
|
+
* Replace all in-memory KB entries. When vector mode is enabled, the new
|
|
301
|
+
* entries become the source of truth for the next `ingestKnowledgeBase()`
|
|
302
|
+
* call but are NOT auto-pushed to the remote index — call that explicitly.
|
|
303
|
+
*/
|
|
304
|
+
setKnowledgeBase(entries) {
|
|
305
|
+
this.kbEntries.setEntries(entries);
|
|
306
|
+
this.vectorKBAutoIngested = false;
|
|
307
|
+
}
|
|
308
|
+
addKnowledgeBase(entries) {
|
|
309
|
+
this.kbEntries.addEntries(entries);
|
|
310
|
+
this.vectorKBAutoIngested = false;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Push the current in-memory KB entries to the configured vector backend.
|
|
314
|
+
* Idempotent. No-op when vector mode is disabled or there are no entries.
|
|
315
|
+
*/
|
|
316
|
+
async ingestKnowledgeBase() {
|
|
317
|
+
if (!this.vectorKB)
|
|
318
|
+
return { count: 0 };
|
|
319
|
+
const entries = this.kbEntries.getEntries();
|
|
320
|
+
if (entries.length === 0)
|
|
321
|
+
return { count: 0 };
|
|
322
|
+
const res = await this.vectorKB.upsert(entries);
|
|
323
|
+
this.vectorKBAutoIngested = true;
|
|
324
|
+
return res;
|
|
325
|
+
}
|
|
326
|
+
async maybeAutoIngest(autoIngest) {
|
|
327
|
+
if (!autoIngest || !this.vectorKB || this.vectorKBAutoIngested)
|
|
328
|
+
return;
|
|
329
|
+
if (this.vectorKBAutoIngestPromise)
|
|
330
|
+
return this.vectorKBAutoIngestPromise;
|
|
331
|
+
this.vectorKBAutoIngestPromise = (async () => {
|
|
332
|
+
try {
|
|
333
|
+
const { count } = await this.ingestKnowledgeBase();
|
|
334
|
+
if (this.debug && count > 0) {
|
|
335
|
+
console.log(`[AgentCore] auto-ingested ${count} KB entr${count === 1 ? 'y' : 'ies'} to vector store`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
if (this.debug)
|
|
340
|
+
console.warn('[AgentCore] auto-ingest failed:', err);
|
|
341
|
+
}
|
|
342
|
+
finally {
|
|
343
|
+
this.vectorKBAutoIngestPromise = null;
|
|
344
|
+
}
|
|
345
|
+
})();
|
|
346
|
+
await this.vectorKBAutoIngestPromise;
|
|
347
|
+
}
|
|
348
|
+
// ---- User context (wallet / chain from UI) ----
|
|
349
|
+
setUserContext(ctx) {
|
|
350
|
+
this.userContext = { ...ctx };
|
|
351
|
+
}
|
|
352
|
+
getUserContext() {
|
|
353
|
+
return { ...this.userContext };
|
|
354
|
+
}
|
|
355
|
+
// ---- Chat ----
|
|
356
|
+
async loadHistory() {
|
|
357
|
+
// If already loaded or loading, return the same promise so concurrent
|
|
358
|
+
// callers (e.g. chat() racing with the consumer's useEffect) don't
|
|
359
|
+
// trigger duplicate loads.
|
|
360
|
+
if (this.historyLoaded)
|
|
361
|
+
return;
|
|
362
|
+
if (this.historyLoadingPromise)
|
|
363
|
+
return this.historyLoadingPromise;
|
|
364
|
+
this.historyLoadingPromise = this._doLoadHistory();
|
|
365
|
+
await this.historyLoadingPromise;
|
|
366
|
+
}
|
|
367
|
+
async _doLoadHistory() {
|
|
368
|
+
if (!this.storage) {
|
|
369
|
+
if (this.debug)
|
|
370
|
+
console.log(`[AgentCore] loadHistory: no storage configured`);
|
|
371
|
+
this.historyLoaded = true;
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (!this.persistHistoryEnabled) {
|
|
375
|
+
if (this.debug)
|
|
376
|
+
console.log(`[AgentCore] loadHistory: persistHistory disabled — starting fresh session`);
|
|
377
|
+
// Best-effort wipe any leftover data from a previous run that had
|
|
378
|
+
// persistence enabled, so a consumer reading storage directly cannot
|
|
379
|
+
// accidentally rehydrate the old session.
|
|
380
|
+
try {
|
|
381
|
+
await this.storage.removeItem(this.storageKey);
|
|
382
|
+
}
|
|
383
|
+
catch (err) {
|
|
384
|
+
if (this.debug)
|
|
385
|
+
console.warn('[AgentCore] failed to clear stale history:', err);
|
|
386
|
+
}
|
|
387
|
+
this.historyLoaded = true;
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
const raw = await this.storage.getItem(this.storageKey);
|
|
392
|
+
if (this.debug) {
|
|
393
|
+
console.log(`[AgentCore] loadHistory: storage key "${this.storageKey}" ${raw ? `has ${raw.length} chars` : 'is empty'}`);
|
|
394
|
+
}
|
|
395
|
+
if (raw) {
|
|
396
|
+
const data = JSON.parse(raw);
|
|
397
|
+
this.history.restore(data);
|
|
398
|
+
if (this.debug) {
|
|
399
|
+
console.log(`[AgentCore] Restored ${this.history.length} messages from storage`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
catch (err) {
|
|
404
|
+
if (this.debug) {
|
|
405
|
+
console.warn('[AgentCore] Failed to load history from storage:', err);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
finally {
|
|
409
|
+
this.historyLoaded = true;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
async persistHistory() {
|
|
413
|
+
if (!this.storage)
|
|
414
|
+
return;
|
|
415
|
+
if (!this.persistHistoryEnabled)
|
|
416
|
+
return;
|
|
417
|
+
try {
|
|
418
|
+
const serialised = JSON.stringify(this.history.serialise());
|
|
419
|
+
await this.storage.setItem(this.storageKey, serialised);
|
|
420
|
+
}
|
|
421
|
+
catch (err) {
|
|
422
|
+
if (this.debug) {
|
|
423
|
+
console.warn('[AgentCore] Failed to persist history to storage:', err);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Process a user message and return an answer. Main entry point.
|
|
429
|
+
*/
|
|
430
|
+
async chat(userMessage, options) {
|
|
431
|
+
const doSuggest = options?.generateSuggestions ?? this.suggestionsEnabled;
|
|
432
|
+
// Ensure history is loaded from storage before processing the first message.
|
|
433
|
+
// This is a no-op if already loaded, and deduplicates concurrent calls so
|
|
434
|
+
// a racing useEffect loadHistory() and this call don't double-load.
|
|
435
|
+
await this.loadHistory();
|
|
436
|
+
// First-call auto-ingest for vector KB (if configured). Idempotent.
|
|
437
|
+
await this.maybeAutoIngest(this.autoIngestEnabled);
|
|
438
|
+
this.addUserMessage(userMessage);
|
|
439
|
+
if (this.history.needsSummary()) {
|
|
440
|
+
await this.compactHistory();
|
|
441
|
+
}
|
|
442
|
+
// Drop the trailing user message itself so Router/Subagent prompts don't
|
|
443
|
+
// duplicate it (we pass the query separately as "User query: …"). Keeping
|
|
444
|
+
// it would cause Gemini to focus on the last (standalone) user message
|
|
445
|
+
// and lose the prior context needed to resolve follow-ups.
|
|
446
|
+
const rawHistory = this.history.getConversation();
|
|
447
|
+
const conversationMessages = rawHistory.length > 0 && rawHistory[rawHistory.length - 1].role === 'user' ? rawHistory.slice(0, -1) : rawHistory;
|
|
448
|
+
const turnStart = Date.now();
|
|
449
|
+
if (this.debug) {
|
|
450
|
+
console.log(`\n${'─'.repeat(60)}`);
|
|
451
|
+
console.log(`[AgentCore] query: "${userMessage}"`);
|
|
452
|
+
}
|
|
453
|
+
// Look up which subagent handled the previous assistant turn. This is a
|
|
454
|
+
// strong hint for short follow-ups ("more", "tiếp", "yes") — they almost
|
|
455
|
+
// always continue the same domain.
|
|
456
|
+
const lastSubagents = (0, historyUtils_1.getLastAssistantSubagents)(conversationMessages);
|
|
457
|
+
if (this.debug && lastSubagents.length > 0) {
|
|
458
|
+
console.log(`[AgentCore] previous turn handled by: ${lastSubagents.join(', ')}`);
|
|
459
|
+
}
|
|
460
|
+
// Stage 1 — Contextualize. Resolve coreferences / ellipsis / follow-ups
|
|
461
|
+
// BEFORE the Router so every downstream component (KB, Router, Subagent)
|
|
462
|
+
// operates on a fully-specified standalone question.
|
|
463
|
+
// See Alhena "Contextualizer" (2025) + "LLMs Get Lost in Multi-Turn" (ICLR 2025).
|
|
464
|
+
const rewrite = await this.rewriter.rewrite(userMessage, conversationMessages, lastSubagents);
|
|
465
|
+
if (this.debug) {
|
|
466
|
+
console.log('🚀 ~ AgentCore ~ chat ~ rewrite:', rewrite);
|
|
467
|
+
}
|
|
468
|
+
const effectiveQuery = rewrite.rewrittenQuery;
|
|
469
|
+
// Stage 2 — KB search.
|
|
470
|
+
// - For follow-ups ("yes" / "tiếp" / "thế còn..."), search with the
|
|
471
|
+
// rewritten query so we have enough signal to match anything.
|
|
472
|
+
// - For self-contained turns, search with the ORIGINAL message: it is
|
|
473
|
+
// what the user actually typed and is therefore the closest possible
|
|
474
|
+
// input to what the embedding model saw at upsert time. The rewriter
|
|
475
|
+
// is an LLM and may paraphrase even when told not to — which silently
|
|
476
|
+
// degrades retrieval (e.g. "CoinPool an toàn không?" gets reworded
|
|
477
|
+
// into something that no longer scores high against the indexed
|
|
478
|
+
// phrasing).
|
|
479
|
+
const kbQuery = rewrite.isFollowUp ? effectiveQuery : userMessage;
|
|
480
|
+
if (this.debug && kbQuery !== effectiveQuery) {
|
|
481
|
+
console.log(`[AgentCore] KB search using original message (not rewritten): "${kbQuery}"`);
|
|
482
|
+
}
|
|
483
|
+
const kbHits = await this.searchKB(kbQuery);
|
|
484
|
+
const kbContext = this.formatKBContext(kbHits);
|
|
485
|
+
// High-confidence KB match → answer via KB, then rollback the user message
|
|
486
|
+
// from the working window so this exchange never enters the LLM context.
|
|
487
|
+
// Both messages are written to fullMessages only (UI log).
|
|
488
|
+
const messageId = generateMessageId();
|
|
489
|
+
const kbAnswer = await this.tryAnswerFromKB(userMessage, kbHits, kbContext, messageId);
|
|
490
|
+
if (kbAnswer) {
|
|
491
|
+
return { ...kbAnswer, messageId, rewrite };
|
|
492
|
+
}
|
|
493
|
+
const cards = Array.from(this.subagents.values()).map((s) => s.getCard());
|
|
494
|
+
const decision = await this.router.route(effectiveQuery, conversationMessages, cards, this.userContext, lastSubagents);
|
|
495
|
+
// No subagent needed → answer directly via LLM
|
|
496
|
+
if (decision.assignments.length === 0) {
|
|
497
|
+
const direct = await this.answerDirectly(effectiveQuery, conversationMessages, kbContext, doSuggest, messageId);
|
|
498
|
+
if (this.debug)
|
|
499
|
+
console.log(`[AgentCore] turn done in ${Date.now() - turnStart}ms (direct LLM)\n`);
|
|
500
|
+
return { ...direct, messageId, routerDecision: decision, rewrite };
|
|
501
|
+
}
|
|
502
|
+
// Dispatch subagents in parallel
|
|
503
|
+
const subResults = await this.router.dispatch(decision, this.subagents, conversationMessages, this.userContext);
|
|
504
|
+
// Persist tool call/result messages BEFORE synthesising so the synthesiser
|
|
505
|
+
// (and all future turns) can see this turn's tool data via history.
|
|
506
|
+
this.persistToolMessages(subResults);
|
|
507
|
+
if (this.debug) {
|
|
508
|
+
const totalToolMsgs = subResults.reduce((n, r) => n + r.toolMessages.length, 0);
|
|
509
|
+
const totalToolCalls = subResults.reduce((n, r) => n + r.toolResults.length, 0);
|
|
510
|
+
const perSubagent = subResults
|
|
511
|
+
.map((r) => {
|
|
512
|
+
const tools = r.toolResults.map((t) => t.toolName);
|
|
513
|
+
const counts = tools.reduce((acc, name) => {
|
|
514
|
+
acc[name] = (acc[name] ?? 0) + 1;
|
|
515
|
+
return acc;
|
|
516
|
+
}, {});
|
|
517
|
+
const toolStr = Object.keys(counts).length === 0
|
|
518
|
+
? 'no tools'
|
|
519
|
+
: Object.entries(counts)
|
|
520
|
+
.map(([name, c]) => (c > 1 ? `${name}×${c}` : name))
|
|
521
|
+
.join(', ');
|
|
522
|
+
return `${r.subagentName} (${r.toolResults.length} tool call${r.toolResults.length === 1 ? '' : 's'}: ${toolStr})`;
|
|
523
|
+
})
|
|
524
|
+
.join(' | ');
|
|
525
|
+
console.log(`\n[AgentCore] turn summary — subagents: ${subResults.length}, total tool calls: ${totalToolCalls}\n ${perSubagent}`);
|
|
526
|
+
console.log(`[AgentCore] synthesising ${subResults.length} subagent result(s), stored ${totalToolMsgs} tool message(s) in history…`);
|
|
527
|
+
}
|
|
528
|
+
// Synthesiser runs WITHOUT tools — strip tool messages from the history
|
|
529
|
+
// slice to avoid sending orphan functionResponse parts to Gemini. Also
|
|
530
|
+
// drop the trailing user message (Synthesiser already receives userQuery
|
|
531
|
+
// as a separate argument; keeping it here would duplicate it).
|
|
532
|
+
const synthHistoryRaw = this.history.getConversation();
|
|
533
|
+
const synthHistoryTrimmed = synthHistoryRaw.length > 0 && synthHistoryRaw[synthHistoryRaw.length - 1].role === 'user'
|
|
534
|
+
? synthHistoryRaw.slice(0, -1)
|
|
535
|
+
: synthHistoryRaw;
|
|
536
|
+
const synthesiserHistory = (0, historyUtils_1.stripToolMessages)(synthHistoryTrimmed);
|
|
537
|
+
const trace = this.mergeTrace(subResults);
|
|
538
|
+
// Pass the ORIGINAL user message to the synthesiser so the final answer
|
|
539
|
+
// addresses what the user actually typed (the rewritten query was only
|
|
540
|
+
// for routing/tool-selection purposes, not user-facing phrasing).
|
|
541
|
+
// Note: kbContext is intentionally NOT forwarded here. When the router
|
|
542
|
+
// chose to dispatch subagents it judged the query needs live tool data —
|
|
543
|
+
// injecting (potentially loose-match) KB material into the synthesiser
|
|
544
|
+
// would let an unrelated reference override correct tool results. To
|
|
545
|
+
// make more KB hits answer from KB, lower `kbAnswerThreshold` instead.
|
|
546
|
+
const synthesised = await this.synthesizer.synthesise(userMessage, trace, synthesiserHistory, rewrite.language);
|
|
547
|
+
// Tag the assistant message with the subagents that handled it — used by
|
|
548
|
+
// QueryRewriter and Router on the NEXT turn to resolve "more" / "tiếp" / etc.
|
|
549
|
+
const handledBy = subResults.map((r) => r.subagentName);
|
|
550
|
+
const uiActions = this.collectUiActions(subResults, rewrite.language || null);
|
|
551
|
+
const response = await this.finaliseAnswer({ userMessage, answer: synthesised, subagents: handledBy, uiActions, messageId }, { generateSuggestions: doSuggest });
|
|
552
|
+
if (this.debug) {
|
|
553
|
+
console.log(`[AgentCore] turn done in ${Date.now() - turnStart}ms total`);
|
|
554
|
+
console.log(`${'─'.repeat(60)}\n`);
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
...response,
|
|
558
|
+
messageId,
|
|
559
|
+
trace,
|
|
560
|
+
routerDecision: decision,
|
|
561
|
+
subagentResults: subResults,
|
|
562
|
+
rewrite,
|
|
563
|
+
...(uiActions.length > 0 ? { uiActions } : {}),
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Aggregate UI payloads from this turn's tool calls. Tools with kind='ui' or
|
|
568
|
+
* 'action' attach a `ui` field to their ToolResult; the FE renders one
|
|
569
|
+
* component per entry in the order they were produced.
|
|
570
|
+
*/
|
|
571
|
+
collectUiActions(subResults, language) {
|
|
572
|
+
const out = [];
|
|
573
|
+
for (const r of subResults) {
|
|
574
|
+
for (const tr of r.toolResults) {
|
|
575
|
+
if (tr.success && tr.ui && typeof tr.ui === 'object' && typeof tr.ui.component === 'string') {
|
|
576
|
+
out.push(this.stampLanguage(tr.ui, language));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (this.debug && out.length > 0) {
|
|
581
|
+
const stamped = out.map((u) => `${u.component}=${u.language}`).join(', ');
|
|
582
|
+
console.log(`[AgentCore] uiActions stamped (rewrite.language="${language ?? ''}"): ${stamped}`);
|
|
583
|
+
}
|
|
584
|
+
return out;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Stamp the rewriter-detected user-message language onto a UIPayload so FE
|
|
588
|
+
* renders the component in whichever language the user just typed in. Tools
|
|
589
|
+
* never set this themselves — it is a transport-level concern owned by
|
|
590
|
+
* AgentCore. No-op when the rewriter did not produce a language (FE may
|
|
591
|
+
* fall back to its own locale in that rare case).
|
|
592
|
+
*/
|
|
593
|
+
stampLanguage(ui, language) {
|
|
594
|
+
if (!language || ui.language)
|
|
595
|
+
return ui;
|
|
596
|
+
return { ...ui, language };
|
|
597
|
+
}
|
|
598
|
+
// ---- Chat sub-steps ----
|
|
599
|
+
addUserMessage(userMessage) {
|
|
600
|
+
this.history.add({ role: 'user', content: userMessage, timestamp: Date.now() });
|
|
601
|
+
}
|
|
602
|
+
/** Single KB lookup — callers reuse the result for both context + short-circuit. */
|
|
603
|
+
async searchKB(query) {
|
|
604
|
+
const kbResults = await this.kb.search(query);
|
|
605
|
+
if (this.debug) {
|
|
606
|
+
if (kbResults.length === 0) {
|
|
607
|
+
console.log(`[AgentCore] KB no hits for "${query}"`);
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
const summary = kbResults
|
|
611
|
+
.slice(0, 3)
|
|
612
|
+
.map((r) => `${r.score.toFixed(3)} ${this.snippet(r.entry.question, 60)}`)
|
|
613
|
+
.join(' | ');
|
|
614
|
+
console.log(`[AgentCore] KB hits (${kbResults.length}): ${summary}`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return kbResults;
|
|
618
|
+
}
|
|
619
|
+
snippet(text, max) {
|
|
620
|
+
return text.length <= max ? text : text.slice(0, max - 1) + '…';
|
|
621
|
+
}
|
|
622
|
+
/** Format KB hits as a reference-block string for LLM consumption. */
|
|
623
|
+
formatKBContext(hits) {
|
|
624
|
+
if (hits.length === 0)
|
|
625
|
+
return '';
|
|
626
|
+
return hits.map((r, i) => `[Reference ${i + 1}]\nQ: ${r.entry.question}\nA: ${r.entry.answer}`).join('\n\n');
|
|
627
|
+
}
|
|
628
|
+
async tryAnswerFromKB(userMessage, kbResults, kbContext, messageId) {
|
|
629
|
+
if (!kbContext || kbResults.length === 0) {
|
|
630
|
+
if (this.debug)
|
|
631
|
+
console.log('[AgentCore] KB skip: no hits → router pipeline');
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
const top = kbResults[0].score;
|
|
635
|
+
if (top < this.kbAnswerThreshold) {
|
|
636
|
+
if (this.debug) {
|
|
637
|
+
console.log(`[AgentCore] KB skip: top score ${top.toFixed(3)} < threshold ${this.kbAnswerThreshold} → router pipeline`);
|
|
638
|
+
}
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
if (this.debug) {
|
|
642
|
+
console.log(`[AgentCore] KB answer: top score ${top.toFixed(3)} ≥ threshold ${this.kbAnswerThreshold} → verifying intent before KB answer`);
|
|
643
|
+
}
|
|
644
|
+
// Intent guard: verify the user message is actually asking about the FAQ
|
|
645
|
+
// topic before bypassing the router. High embedding similarity alone is not
|
|
646
|
+
// sufficient — a question like "what is the price of ETH?" can score high
|
|
647
|
+
// against a KB entry about wallets simply due to shared vocabulary, but it
|
|
648
|
+
// should be handled by a subagent, not the KB.
|
|
649
|
+
const topQuestion = kbResults[0].entry.question;
|
|
650
|
+
const intentCheckMessages = [
|
|
651
|
+
{
|
|
652
|
+
role: 'system',
|
|
653
|
+
content: 'You are a strict intent classifier. Respond with ONLY "yes" or "no" — no other text.' +
|
|
654
|
+
'\n\nAnswer "yes" if the user message is genuinely asking about the same topic as the FAQ question.' +
|
|
655
|
+
'\nAnswer "no" if the user message is asking for real-time data, live lookups, prices, balances, transactions, or anything that requires fetching external data — even if the FAQ question looks similar.',
|
|
656
|
+
timestamp: 0,
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
role: 'user',
|
|
660
|
+
content: `FAQ question: "${topQuestion}"\nUser message: "${userMessage}"\n\nIs the user message genuinely asking about the same topic as the FAQ question?`,
|
|
661
|
+
timestamp: Date.now(),
|
|
662
|
+
},
|
|
663
|
+
];
|
|
664
|
+
const intentResponse = await this.llm.chat(intentCheckMessages);
|
|
665
|
+
const intentVerdict = intentResponse.text.trim().toLowerCase();
|
|
666
|
+
if (!intentVerdict.startsWith('yes')) {
|
|
667
|
+
if (this.debug) {
|
|
668
|
+
console.log(`[AgentCore] KB intent guard: verdict="${intentVerdict}" → skipping KB, routing to subagent pipeline`);
|
|
669
|
+
}
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
if (this.debug) {
|
|
673
|
+
console.log(`[AgentCore] KB intent guard: verdict="yes" → answering from KB`);
|
|
674
|
+
}
|
|
675
|
+
// Strip tool messages (no tools declared here) and send only the system
|
|
676
|
+
// prompt + KB material + the current user message. Omitting old history
|
|
677
|
+
// prevents older turns in a different language from overriding language
|
|
678
|
+
// detection — the LLM must reply in the language of THIS message only.
|
|
679
|
+
const kbMessages = [
|
|
680
|
+
{
|
|
681
|
+
role: 'system',
|
|
682
|
+
content: this.systemPrompt +
|
|
683
|
+
"\n\nYou have reference material below. Use it ONLY if it directly addresses the user's question. " +
|
|
684
|
+
'Rephrase and adapt the content naturally — do NOT copy it verbatim. ' +
|
|
685
|
+
'Do NOT supplement with general knowledge or speculate beyond what the reference provides. ' +
|
|
686
|
+
'If the reference does not fully answer the question, say so honestly.' +
|
|
687
|
+
'\n\n---\nREFERENCE MATERIAL:\n' +
|
|
688
|
+
kbContext +
|
|
689
|
+
'\n---',
|
|
690
|
+
timestamp: 0,
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
role: 'user',
|
|
694
|
+
content: `${userMessage}\n\n(You MUST reply in the same language as the question above, regardless of the reference material language.)`,
|
|
695
|
+
timestamp: Date.now(),
|
|
696
|
+
},
|
|
697
|
+
];
|
|
698
|
+
const kbResponse = await this.llm.chat(kbMessages);
|
|
699
|
+
const answer = kbResponse.text;
|
|
700
|
+
// KB turns are persisted to the working window so future turns can reference them.
|
|
701
|
+
const kbMsg = { role: 'assistant', content: answer, timestamp: Date.now() };
|
|
702
|
+
if (messageId)
|
|
703
|
+
kbMsg.messageId = messageId;
|
|
704
|
+
this.history.add(kbMsg);
|
|
705
|
+
await this.persistHistory();
|
|
706
|
+
return { answer, suggestedPrompts: [] };
|
|
707
|
+
}
|
|
708
|
+
async answerDirectly(userMessage, conversationMessages, kbContext, generateSuggestions = true, messageId) {
|
|
709
|
+
if (this.debug)
|
|
710
|
+
console.log('[AgentCore] Router returned no assignments, answering directly via LLM…');
|
|
711
|
+
const systemContent = kbContext
|
|
712
|
+
? this.systemPrompt +
|
|
713
|
+
'\n\nYou may use the following reference material if relevant:\n\n' +
|
|
714
|
+
'---\nREFERENCE MATERIAL:\n' +
|
|
715
|
+
kbContext +
|
|
716
|
+
'\n---'
|
|
717
|
+
: this.systemPrompt;
|
|
718
|
+
// Direct LLM call has NO tools declared → strip tool messages to avoid
|
|
719
|
+
// orphan functionResponse parts (Gemini 400). Also append the current
|
|
720
|
+
// user message (caller dropped it from the history slice).
|
|
721
|
+
const cleanHistory = (0, historyUtils_1.stripToolMessages)(conversationMessages);
|
|
722
|
+
const directMessages = [
|
|
723
|
+
{ role: 'system', content: systemContent, timestamp: 0 },
|
|
724
|
+
...cleanHistory,
|
|
725
|
+
{ role: 'user', content: userMessage, timestamp: Date.now() },
|
|
726
|
+
];
|
|
727
|
+
const directResponse = await this.llm.chat(directMessages, undefined, { googleSearch: true });
|
|
728
|
+
return await this.finaliseAnswer({ userMessage, answer: directResponse.text, messageId }, { generateSuggestions });
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Flatten the per-subagent ReAct steps into a single trace that the
|
|
732
|
+
* Synthesizer can consume. The `finalAnswer` here is a compact
|
|
733
|
+
* concatenation of each subagent's own answer, labelled by name.
|
|
734
|
+
*/
|
|
735
|
+
mergeTrace(results) {
|
|
736
|
+
const steps = [];
|
|
737
|
+
for (const r of results) {
|
|
738
|
+
steps.push({
|
|
739
|
+
phase: 'think',
|
|
740
|
+
content: `[${r.subagentName}] begin`,
|
|
741
|
+
timestamp: Date.now(),
|
|
742
|
+
});
|
|
743
|
+
steps.push(...r.steps);
|
|
744
|
+
steps.push({
|
|
745
|
+
phase: 'observe',
|
|
746
|
+
content: `[${r.subagentName}] answer: ${r.finalAnswer}`,
|
|
747
|
+
timestamp: Date.now(),
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
const finalAnswer = results.map((r) => `[${r.subagentName}] ${r.finalAnswer}`).join('\n\n');
|
|
751
|
+
return { steps, finalAnswer };
|
|
752
|
+
}
|
|
753
|
+
async finaliseAnswer(params, options) {
|
|
754
|
+
const { userMessage, answer, subagents, uiActions, messageId } = params ?? {};
|
|
755
|
+
const { generateSuggestions = true } = options ?? {};
|
|
756
|
+
const suggestedPrompts = generateSuggestions
|
|
757
|
+
? await this.generateSuggestions(userMessage, answer, subagents ?? [])
|
|
758
|
+
: [];
|
|
759
|
+
const msg = { role: 'assistant', content: answer, timestamp: Date.now() };
|
|
760
|
+
if (subagents && subagents.length > 0)
|
|
761
|
+
msg.subagents = subagents;
|
|
762
|
+
if (suggestedPrompts.length > 0)
|
|
763
|
+
msg.suggestedPrompts = suggestedPrompts;
|
|
764
|
+
if (uiActions && uiActions.length > 0)
|
|
765
|
+
msg.uiActions = uiActions;
|
|
766
|
+
if (messageId)
|
|
767
|
+
msg.messageId = messageId;
|
|
768
|
+
this.history.add(msg);
|
|
769
|
+
await this.persistHistory();
|
|
770
|
+
return { answer, suggestedPrompts };
|
|
771
|
+
}
|
|
772
|
+
// ---- Suggested prompts ----
|
|
773
|
+
async generateSuggestions(userMessage, answer, subagents) {
|
|
774
|
+
// The only domain we explicitly funnel is `pool`: once the user is looking
|
|
775
|
+
// at a pool, the most valuable next step is almost always to add liquidity
|
|
776
|
+
// or estimate yield on THAT pool, so we hard-code that bias. For every
|
|
777
|
+
// other domain we let the model derive suggestions from the message + answer.
|
|
778
|
+
const poolFunnel = subagents.includes('pool')
|
|
779
|
+
? '\nPOOL FUNNEL: This turn was handled by the pool subagent. If the answer references a SPECIFIC pool ' +
|
|
780
|
+
'(token pair + fee tier, or a pool address), the FIRST suggestion MUST propose adding liquidity to ' +
|
|
781
|
+
'THAT exact pool (e.g. "Add liquidity to USDC/WETH 0.05%"). A second suggestion should propose ' +
|
|
782
|
+
'estimating yield for a deposit into the same pool. Skip this funnel only if no specific pool is ' +
|
|
783
|
+
'identifiable in the answer.\n'
|
|
784
|
+
: '';
|
|
785
|
+
const handledBy = subagents.length > 0 ? `\nHandled by subagent(s): ${subagents.join(', ')}` : '';
|
|
786
|
+
try {
|
|
787
|
+
const response = await this.llm.chat([
|
|
788
|
+
{
|
|
789
|
+
role: 'user',
|
|
790
|
+
content: 'Analyze this conversation and propose follow-up prompts the user is likely to want NEXT.\n\n' +
|
|
791
|
+
`User: ${userMessage}\nAssistant: ${answer}${handledBy}\n` +
|
|
792
|
+
poolFunnel +
|
|
793
|
+
'\nRULES:\n' +
|
|
794
|
+
'- Derive suggestions from the user message + the assistant answer. Reference concrete entities that appear there (pool name, token symbol, chain, address, NFT, wallet).\n' +
|
|
795
|
+
'- Prefer ACTION-ORIENTED suggestions over informational ones whenever the answer makes one possible. Example after showing a pool: "Add liquidity to USDC/WETH 0.05%", not "what is a liquidity pool?".\n' +
|
|
796
|
+
'- Reference concrete entities from the answer (pool name, token symbol, chain, address) — never generic placeholders.\n' +
|
|
797
|
+
'- If the topic is shallow, closed-ended, or fully resolved (greetings, simple yes/no, trivial facts), return an empty array: []\n' +
|
|
798
|
+
'- Return a JSON array of 0 to 3 strings, no other text.\n' +
|
|
799
|
+
'- Each suggestion must be concise (under 60 chars) and phrased as something the user would TYPE.\n' +
|
|
800
|
+
'- Suggestions must be diverse — do not repeat the same intent in different words.\n' +
|
|
801
|
+
'- Use the same language as the user.\n' +
|
|
802
|
+
'- Good example (pool): ["Add liquidity to USDC/WETH 0.05%", "Estimate yield for $1000 here", "Compare with 0.3% fee tier"]\n' +
|
|
803
|
+
'- Bad example: ["What is a pool?", "How do pools work?", "Tell me more"] (generic, no action, no specifics)',
|
|
804
|
+
timestamp: 0,
|
|
805
|
+
},
|
|
806
|
+
]);
|
|
807
|
+
const match = response.text.match(/\[[\s\S]*\]/);
|
|
808
|
+
if (!match)
|
|
809
|
+
return [];
|
|
810
|
+
const suggestions = JSON.parse(match[0]);
|
|
811
|
+
return suggestions.filter((s) => typeof s === 'string' && s.length > 0).slice(0, 3);
|
|
812
|
+
}
|
|
813
|
+
catch {
|
|
814
|
+
return [];
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
// ---- History management ----
|
|
818
|
+
getHistory() {
|
|
819
|
+
return this.history.getAllFull();
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* History filtered down to user-facing messages only — the user's prompts
|
|
823
|
+
* and each turn's final assistant reply. Internal records (tool calls,
|
|
824
|
+
* tool results, system summaries, and `_intermediate` bridging assistant
|
|
825
|
+
* messages) are dropped, as is any assistant message with empty content
|
|
826
|
+
* (a pure tool-call turn). Use this to seed a chat UI on mount.
|
|
827
|
+
*/
|
|
828
|
+
getDisplayHistory() {
|
|
829
|
+
return this.history.getAllFull().filter((m) => {
|
|
830
|
+
if (m.role !== 'user' && m.role !== 'assistant')
|
|
831
|
+
return false;
|
|
832
|
+
if (m._intermediate)
|
|
833
|
+
return false;
|
|
834
|
+
if (m.role === 'assistant' && !m.content?.trim())
|
|
835
|
+
return false;
|
|
836
|
+
return true;
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Suggested follow-up prompts attached to the most recent final assistant
|
|
841
|
+
* turn, or `[]` if none. Used to re-seed suggestion chips after restoring
|
|
842
|
+
* a persisted session — the chips travel with the assistant message in
|
|
843
|
+
* storage, so this just locates the last one.
|
|
844
|
+
*/
|
|
845
|
+
getLastSuggestions() {
|
|
846
|
+
const display = this.getDisplayHistory();
|
|
847
|
+
for (let i = display.length - 1; i >= 0; i--) {
|
|
848
|
+
const m = display[i];
|
|
849
|
+
if (m.role === 'assistant')
|
|
850
|
+
return m.suggestedPrompts ?? [];
|
|
851
|
+
}
|
|
852
|
+
return [];
|
|
853
|
+
}
|
|
854
|
+
async clearHistory() {
|
|
855
|
+
this.history.clear();
|
|
856
|
+
this.historyLoaded = true; // cleared intentionally, no need to re-load
|
|
857
|
+
this.historyLoadingPromise = null;
|
|
858
|
+
await this.persistHistory();
|
|
859
|
+
}
|
|
860
|
+
restoreHistory(data) {
|
|
861
|
+
this.history.restore(data);
|
|
862
|
+
}
|
|
863
|
+
serialiseHistory() {
|
|
864
|
+
return this.history.serialise();
|
|
865
|
+
}
|
|
866
|
+
// ---- Internals ----
|
|
867
|
+
async compactHistory() {
|
|
868
|
+
if (this.debug) {
|
|
869
|
+
console.log('[AgentCore] Summarizing conversation history…');
|
|
870
|
+
}
|
|
871
|
+
const all = this.history.getAll();
|
|
872
|
+
// Keep the last 6 user turns (plus any tool messages belonging to those
|
|
873
|
+
// turns) so the working window always has meaningful recent context.
|
|
874
|
+
// We count backward by user turns rather than raw message count because
|
|
875
|
+
// tool exchanges inflate the raw count without adding user-visible turns.
|
|
876
|
+
const USER_TURNS_TO_KEEP = 6;
|
|
877
|
+
let userTurnsSeen = 0;
|
|
878
|
+
let keepStart = all.length;
|
|
879
|
+
for (let i = all.length - 1; i >= 0; i--) {
|
|
880
|
+
if (all[i].role === 'user') {
|
|
881
|
+
userTurnsSeen++;
|
|
882
|
+
if (userTurnsSeen >= USER_TURNS_TO_KEEP) {
|
|
883
|
+
keepStart = i;
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
// Never split a tool-call group at the boundary.
|
|
889
|
+
const rawTail = all.slice(keepStart);
|
|
890
|
+
const keepTail = (0, historyUtils_1.trimPreservingToolPairs)(rawTail, rawTail.length);
|
|
891
|
+
const tailStart = all.length - keepTail.length;
|
|
892
|
+
const toSummarize = (0, historyUtils_1.stripToolMessages)(all.slice(0, tailStart));
|
|
893
|
+
const existingSummary = this.history.getSummary();
|
|
894
|
+
// Prepend the existing summary as a User turn so Summarizer includes it
|
|
895
|
+
// in the rolling summary (Summarizer filters to user/assistant only).
|
|
896
|
+
const messagesForSummary = [];
|
|
897
|
+
if (existingSummary) {
|
|
898
|
+
messagesForSummary.push({
|
|
899
|
+
role: 'user',
|
|
900
|
+
content: `[Previous context summary]: ${existingSummary}`,
|
|
901
|
+
timestamp: 0,
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
messagesForSummary.push(...toSummarize);
|
|
905
|
+
const summary = await this.summarizer.summarize(messagesForSummary);
|
|
906
|
+
this.history.compactWith(summary, keepTail);
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Persist the interleaved assistant+toolCalls / role:'tool' messages from
|
|
910
|
+
* each subagent run into ChatHistory, followed by the subagent's finalAnswer
|
|
911
|
+
* as a plain assistant turn.
|
|
912
|
+
*
|
|
913
|
+
* The full sequence stored per subagent is:
|
|
914
|
+
* model(functionCall) → function(response) → ... → model(text: finalAnswer)
|
|
915
|
+
*
|
|
916
|
+
* This satisfies Gemini's strict turn-order rule: a function-response turn
|
|
917
|
+
* must always be followed by a model turn before the next user message.
|
|
918
|
+
* Without the trailing model(text), the history ends with function(response)
|
|
919
|
+
* and the next user message causes a 400 "function call turn must come
|
|
920
|
+
* immediately after a user turn" error.
|
|
921
|
+
*/
|
|
922
|
+
persistToolMessages(results) {
|
|
923
|
+
for (const r of results) {
|
|
924
|
+
if (r.toolMessages.length > 0) {
|
|
925
|
+
this.history.addMany(r.toolMessages);
|
|
926
|
+
// Append the subagent's final text answer so the history always ends
|
|
927
|
+
// with model(text) after a tool exchange — never with function(response).
|
|
928
|
+
this.history.add({
|
|
929
|
+
role: 'assistant',
|
|
930
|
+
content: r.finalAnswer,
|
|
931
|
+
_intermediate: true,
|
|
932
|
+
timestamp: Date.now(),
|
|
933
|
+
});
|
|
934
|
+
if (this.debug) {
|
|
935
|
+
console.log(`[AgentCore] stored ${r.toolMessages.length} tool message(s) + finalAnswer from ${r.subagentName}`);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
/** Expose the LLM provider for advanced usage. */
|
|
941
|
+
getLLMProvider() {
|
|
942
|
+
return this.llm;
|
|
943
|
+
}
|
|
944
|
+
/** Expose the tool registry for advanced usage. */
|
|
945
|
+
getToolRegistry() {
|
|
946
|
+
return this.registry;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
exports.AgentCore = AgentCore;
|
|
950
|
+
//# sourceMappingURL=AgentCore.js.map
|