Compare commits

..

10 Commits

Author SHA1 Message Date
ton 390b9d71b6 update 2026-09-08 06:04:02 +07:00
ton b3ae0a9c50 update 2026-09-03 22:09:22 +07:00
ton 1f3526b996 update 2026-09-03 22:06:28 +07:00
ton 471da0c5ec pass eventSink to MCP 2026-09-03 20:12:37 +07:00
ton c355748e6f update 2026-09-03 11:38:29 +07:00
ton f6895984d6 update 2026-09-03 11:33:17 +07:00
ton 4ac5b97d53 update 2026-09-03 09:49:52 +07:00
ton 6db50b6a52 update 2026-09-02 11:17:41 +07:00
ton 096baad09d update 2026-09-01 10:36:27 +07:00
ton c1120d811a Merge pull request 'update' (#45) from v0.8.0-add_wine_tools-fix_100_cpu into v0.8.0-add_wine_tools
Reviewed-on: #45
2026-08-24 08:22:41 +00:00
9 changed files with 761 additions and 2085 deletions
+128 -96
View File
@@ -1,8 +1,20 @@
# This file is machine-generated - editing it directly is not advised
julia_version = "1.12.6"
julia_version = "1.12.7"
manifest_format = "2.0"
project_hash = "3ff1783eadf40ccb51801954aa0a8df935689752"
project_hash = "43252cf86e6030e836ede74c563b58ab2f0f0548"
[[deps.AWS]]
deps = ["Base64", "Compat", "Dates", "Downloads", "GitHub", "HTTP", "IniFile", "JSON", "MD5", "Mocking", "OrderedCollections", "Random", "SHA", "ScopedValues", "Sockets", "URIs", "UUIDs", "XMLDict"]
git-tree-sha1 = "842075b12a7f763c5f815a04037a99b6ac346df9"
uuid = "fbe9abb3-538b-5e4e-ba9e-bc94f4f92ebc"
version = "1.99.0"
[[deps.AWSS3]]
deps = ["AWS", "ArrowTypes", "Base64", "Compat", "Dates", "EzXML", "FilePathsBase", "HTTP", "MbedTLS", "Mocking", "OrderedCollections", "Retry", "SymDict", "URIs", "UUIDs", "XMLDict"]
git-tree-sha1 = "af99cea71364c8c4483405a6b03697d197ab4d3b"
uuid = "1c724243-ef5b-51ab-93f4-b0a88ac62a95"
version = "0.11.6"
[[deps.Accessors]]
deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"]
@@ -86,9 +98,9 @@ version = "1.11.0"
[[deps.CSV]]
deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "PrecompileTools", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings", "WorkerUtilities"]
git-tree-sha1 = "8d8e0b0f350b8e1c91420b5e64e5de774c2f0f4d"
git-tree-sha1 = "abed1e735dd4152f48c90cf0767e1790e25f332f"
uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
version = "0.10.16"
version = "0.10.17"
[[deps.CodeTracking]]
deps = ["InteractiveUtils", "REPL", "UUIDs"]
@@ -110,9 +122,9 @@ version = "0.4.6"
[[deps.CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9"
git-tree-sha1 = "970758a3d591a2a5c2a907c53f2e2f8c1b1d3537"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.8"
version = "0.7.9"
[[deps.CodecZstd]]
deps = ["TranscodingStreams", "Zstd_jll"]
@@ -121,9 +133,10 @@ uuid = "6b39b394-51ab-5f42-8807-6242bab2b4c2"
version = "0.8.7"
[[deps.CommonSolve]]
git-tree-sha1 = "cf963add2340ad9960e5eb22844e61ad8f931fe1"
deps = ["PrecompileTools"]
git-tree-sha1 = "6c389fa857f6ca5a95474b52a52023fd77f24cb7"
uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
version = "0.2.13"
version = "0.2.14"
[[deps.Compat]]
deps = ["TOML", "UUIDs"]
@@ -143,7 +156,7 @@ version = "0.1.1"
[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
version = "1.3.0+1"
version = "1.3.1+2"
[[deps.CompositionsBase]]
git-tree-sha1 = "802bb88cd69dfd1509f6670416bd4434015693ad"
@@ -181,9 +194,9 @@ uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.2.0"
[[deps.DBInterface]]
git-tree-sha1 = "a444404b3f94deaa43ca2a58e18153a82695282b"
git-tree-sha1 = "265d7f87f329c063cbde41920f9d00713a43cedd"
uuid = "a10d1c49-ce27-4219-8d33-6db1a4562965"
version = "2.6.1"
version = "2.7.0"
[[deps.DataAPI]]
git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe"
@@ -238,9 +251,9 @@ version = "1.11.0"
[[deps.Distributions]]
deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "Roots", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"]
git-tree-sha1 = "d2facc77c08c1c2bfb1a77c148edd05b3db5410b"
git-tree-sha1 = "a958ab3a40c755563f5e1405c0846cb0446bf19d"
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
version = "0.25.130"
version = "0.25.131"
[deps.Distributions.extensions]
DistributionsChainRulesCoreExt = "ChainRulesCore"
@@ -274,15 +287,11 @@ git-tree-sha1 = "d2e49e7efd29719d6f28b891b0e0e159daa9d2b4"
uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
version = "0.1.11"
[[deps.FileIO]]
deps = ["Pkg", "Requires", "UUIDs"]
git-tree-sha1 = "6621fef488e496356c9c9625d0562c12a6070819"
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
version = "1.20.0"
weakdeps = ["HTTP"]
[deps.FileIO.extensions]
HTTPExt = "HTTP"
[[deps.EzXML]]
deps = ["Printf", "XML2_jll"]
git-tree-sha1 = "7ea1aa5869e2626ccae84480e4f37185bc6f41d3"
uuid = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615"
version = "1.2.3"
[[deps.FilePathsBase]]
deps = ["Compat", "Dates"]
@@ -318,17 +327,24 @@ uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"
version = "1.11.0"
[[deps.Gamma]]
git-tree-sha1 = "86f86b6168a016ed88e4ae4e64577b98c3b59e8e"
deps = ["LogExpFunctions"]
git-tree-sha1 = "becc397f7cfb06e343496ae6ffb04818a851da51"
uuid = "a0844989-3bd2-4988-8bea-c9407ab0941b"
version = "1.1.0"
version = "1.2.0"
[[deps.GeneralUtils]]
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "Graphs", "HTTP", "JSON", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "SHA", "StringDistances", "UUIDs"]
git-tree-sha1 = "129b8fa1bf3bf6d8c0a080f39db09b9b986ef8de"
deps = ["AWS", "AWSS3", "CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "Graphs", "HTTP", "JSON", "LibPQ", "NATS", "OrderedCollections", "PrettyPrinting", "Random", "Revise", "SHA", "StringDistances", "URIs", "UUIDs"]
git-tree-sha1 = "4d7ff3b02a9cbd6d4f65fea43551b0bca6a83a22"
repo-rev = "main"
repo-url = "https://git.yiem.cc/ton/GeneralUtils"
uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe"
version = "0.5.11"
version = "0.6.9"
[[deps.GitHub]]
deps = ["Base64", "Dates", "HTTP", "JSON", "MbedTLS", "Sockets", "SodiumSeal", "URIs"]
git-tree-sha1 = "33de1bc5665494f0a9669fd6027e28180709d874"
uuid = "bc5e4493-9b4d-5f90-b8aa-2b2bcaad7a26"
version = "5.13.0"
[[deps.Graphs]]
deps = ["ArnoldiMethod", "DataStructures", "Inflate", "LinearAlgebra", "Random", "SimpleTraits", "SparseArrays", "Statistics"]
@@ -345,9 +361,9 @@ version = "1.14.0"
[[deps.HTTP]]
deps = ["Base64", "CodecZlib", "Dates", "EnumX", "PrecompileTools", "Random", "Reseau", "SHA", "URIs", "UUIDs", "Zlib_jll"]
git-tree-sha1 = "0a58fbbdee93d132a2fb1159b7f5e1b5c2465e71"
git-tree-sha1 = "49a90cd540681d5b79bf5c8ca13c92677e37005e"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "2.6.4"
version = "2.6.6"
[[deps.HashArrayMappedTries]]
git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae"
@@ -377,6 +393,11 @@ git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d"
uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
version = "0.1.5"
[[deps.IniFile]]
git-tree-sha1 = "f550e6e32074c939295eb5ea6de31849ac2c9625"
uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
version = "0.5.1"
[[deps.InlineStrings]]
git-tree-sha1 = "8f3d257792a522b4601c24a577954b0a8cd7334d"
uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48"
@@ -436,9 +457,9 @@ version = "1.8.0"
[[deps.JSON]]
deps = ["Dates", "Logging", "Parsers", "PrecompileTools", "StructUtils", "UUIDs", "Unicode"]
git-tree-sha1 = "65979512c25a0727f050e6e4be40f0fd9ec893f7"
git-tree-sha1 = "c7345ab1a7ca4dc8a02c9f6510da0d9857bbe513"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "1.7.0"
version = "1.7.1"
weakdeps = ["ArrowTypes"]
[deps.JSON.extensions]
@@ -471,18 +492,10 @@ git-tree-sha1 = "0f2899fdadaab4b8f57db558ba21bdb4fb52f1f0"
uuid = "b39eb1a6-c29a-53d7-8c32-632cd16f18da"
version = "1.21.3+0"
[[deps.LLMMCTS]]
deps = ["GeneralUtils", "JSON", "PrettyPrinting"]
git-tree-sha1 = "3dff98131dfa79be8c9bd84fc51cb0ba1832c472"
repo-rev = "main"
repo-url = "https://git.yiem.cc/ton/LLMMCTS"
uuid = "d76c5a4d-449e-4835-8cc4-dd86ec44f241"
version = "0.1.5"
[[deps.LaTeXStrings]]
git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c"
git-tree-sha1 = "f88f3ccef05a6a72a0cf0ed417c8fd68530f4ab2"
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
version = "1.4.0"
version = "1.4.1"
[[deps.LayerDicts]]
git-tree-sha1 = "6087ad3521d6278ebe5c27ae55e7bbb15ca312cb"
@@ -530,6 +543,12 @@ version = "1.11.3+1"
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
version = "1.11.0"
[[deps.Libiconv_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl"]
git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809"
uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
version = "1.18.0+0"
[[deps.LinearAlgebra]]
deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
@@ -567,6 +586,12 @@ git-tree-sha1 = "191686b1ac1ea9c89fc52e996ad15d1d241d1e33"
uuid = "5ced341a-0733-55b8-9ab6-a4889d929147"
version = "1.10.1+0"
[[deps.MD5]]
deps = ["Random", "SHA"]
git-tree-sha1 = "1576f756617d31eb397a4a517b68562fd28dc2b4"
uuid = "6ac74813-4b46-53a4-afec-0b5dc9d7885c"
version = "0.2.3"
[[deps.MacroTools]]
git-tree-sha1 = "1e0228a030642014fe5cfe68c2c0a818f9e3f522"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
@@ -655,7 +680,7 @@ version = "0.8.7+0"
[[deps.OpenSSL_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
version = "3.5.4+0"
version = "3.5.6+0"
[[deps.OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"]
@@ -684,15 +709,6 @@ git-tree-sha1 = "3de8f5e6e90ebfa8d6d1f86997d6cdcd6a912ff3"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.8.7"
[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
version = "1.12.1"
weakdeps = ["REPL"]
[deps.Pkg.extensions]
REPLExt = "REPL"
[[deps.PooledArrays]]
deps = ["DataAPI", "Future"]
git-tree-sha1 = "36d8b4b899628fb92c2749eb488d884a926614d3"
@@ -718,9 +734,9 @@ version = "0.4.3"
[[deps.PrettyTables]]
deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "Tables"]
git-tree-sha1 = "4ac881f5432bd93463a41767a814a45245be22b6"
git-tree-sha1 = "1b8aa19f229b1cea7fc93874a52e49db6a854450"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "3.4.6"
version = "3.4.8"
[deps.PrettyTables.extensions]
PrettyTablesExcelExt = "XLSX"
@@ -781,15 +797,20 @@ version = "1.3.1"
[[deps.Reseau]]
deps = ["NetworkOptions", "OpenSSL_jll", "PrecompileTools", "Random", "SHA"]
git-tree-sha1 = "701ef63506992668c0069a4d1ac1540b77258e6c"
git-tree-sha1 = "aaee74fab545e9af4b21d10c2203c93c636c4ae0"
uuid = "802f3686-a58f-41ce-bb0c-3c43c75bba36"
version = "1.3.6"
version = "1.4.1"
[[deps.Retry]]
git-tree-sha1 = "41ac127cd281bb33e42aba46a9d3b25cd35fc6d5"
uuid = "20febd7b-183b-5ae2-ac4a-720e7ce64774"
version = "0.4.1"
[[deps.Revise]]
deps = ["CRC32c", "CodeTracking", "FileWatching", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "Preferences", "REPL", "UUIDs"]
git-tree-sha1 = "ec46aed6a3a8cc6b67839ca361e7b4aa32eaeee1"
git-tree-sha1 = "ab0f5630e37968bc49b8c4cedd3279a7d30b9486"
uuid = "295af30f-e4ad-537b-8983-00126c2a3abe"
version = "3.16.3"
version = "3.17.0"
weakdeps = ["Distributed"]
[deps.Revise.extensions]
@@ -809,9 +830,9 @@ version = "0.5.2+0"
[[deps.Roots]]
deps = ["Accessors", "CommonSolve", "Printf"]
git-tree-sha1 = "7fb25a964849d90a0446366cdefca822e0e84900"
git-tree-sha1 = "13a9e0164267bb9ebc55c1c5d72fceea8f09555b"
uuid = "f2b01f46-fcfa-551c-844a-d8ac1e96c665"
version = "3.0.6"
version = "3.0.7"
[deps.Roots.extensions]
RootsChainRulesCoreExt = "ChainRulesCore"
@@ -833,14 +854,6 @@ version = "3.0.6"
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
version = "0.7.0"
[[deps.SQLLLM]]
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "FileIO", "GeneralUtils", "HTTP", "JSON", "LLMMCTS", "LibPQ", "PrettyPrinting", "Random", "Revise", "StatsBase", "Tables", "URIs", "UUIDs"]
git-tree-sha1 = "bae2fd2e2b087753fbb3415896be41df1ae0eb90"
repo-rev = "main"
repo-url = "https://git.yiem.cc/ton/SQLLLM"
uuid = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3"
version = "0.2.8"
[[deps.SQLStrings]]
git-tree-sha1 = "55de0530689832b1d3d43491ee6b67bd54d3323c"
uuid = "af517c2e-c243-48fa-aab8-efac3db270f5"
@@ -884,6 +897,12 @@ git-tree-sha1 = "907703e0d50846f300650d7225bdcab145b7bca9"
uuid = "4f5b5e99-b0ad-42cd-b47a-334e172ec8bd"
version = "1.1.2"
[[deps.SodiumSeal]]
deps = ["Base64", "Libdl", "libsodium_jll"]
git-tree-sha1 = "80cef67d2953e33935b41c6ab0a178b9987b1c99"
uuid = "2133526b-2bfb-4018-ac12-889fb3908a75"
version = "0.1.1"
[[deps.SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "13cd91cc9be159e3f4d95b857fa2aa383b53772a"
@@ -897,9 +916,9 @@ version = "1.12.0"
[[deps.SpecialFunctions]]
deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
git-tree-sha1 = "c3ac026e735264e9bdc6a9bcbd1b1e781b36e3bc"
git-tree-sha1 = "429071b23f4c9a13fb6582f807cc2ef454082408"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "2.8.3"
version = "2.9.0"
[deps.SpecialFunctions.extensions]
SpecialFunctionsChainRulesCoreExt = "ChainRulesCore"
@@ -909,9 +928,9 @@ version = "2.8.3"
[[deps.StaticArrays]]
deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"]
git-tree-sha1 = "246a8bb2e6667f832eea063c3a56aef96429a3db"
git-tree-sha1 = "fac51faf3bb96e8bc0bf6f9f39ca4955652776bb"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.9.18"
version = "1.9.19"
[deps.StaticArrays.extensions]
StaticArraysChainRulesCoreExt = "ChainRulesCore"
@@ -928,9 +947,9 @@ version = "1.4.4"
[[deps.Statistics]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0"
git-tree-sha1 = "389592b8592e5fb48f498ff60dc6acb4a0e62953"
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
version = "1.11.1"
version = "1.11.4"
weakdeps = ["SparseArrays"]
[deps.Statistics.extensions]
@@ -944,9 +963,9 @@ version = "1.8.0"
[[deps.StatsBase]]
deps = ["AliasTables", "DataAPI", "DataStructures", "IrrationalConstants", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "e4d7a1a0edc20af42689ea6f4f3587a2175d50ee"
git-tree-sha1 = "adb9da019510162e67a4493fc235c23203d8b09e"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.34.12"
version = "0.34.13"
[[deps.StatsFuns]]
deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
@@ -964,15 +983,15 @@ version = "2.2.1"
[[deps.StringDistances]]
deps = ["Distances", "StatsAPI"]
git-tree-sha1 = "cd83a04baf746e3b43b83c61b7de77ab0409b80a"
git-tree-sha1 = "456f343c7a6c8f52d8b501c0da2a05538ab1b907"
uuid = "88034a9c-02f8-509d-84a9-84ec65e18404"
version = "1.0.0"
version = "1.0.1"
[[deps.StringManipulation]]
deps = ["PrecompileTools"]
git-tree-sha1 = "8a90c1d77c3277a5d43b83927b3cbe2c70a37484"
git-tree-sha1 = "773065c6e0e903924a9d838259be74338422aef2"
uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e"
version = "0.4.7"
version = "0.5.0"
[[deps.StringViews]]
git-tree-sha1 = "f2dcb92855b31ad92fe8f079d4f75ac57c93e4b8"
@@ -987,9 +1006,9 @@ version = "1.11.0"
[[deps.StructUtils]]
deps = ["Dates", "UUIDs"]
git-tree-sha1 = "c65ae4aa47e543c278aea0a3468786d33021a3ff"
git-tree-sha1 = "2d0fc55c61321ba245c47be599570d11bac50303"
uuid = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42"
version = "2.8.4"
version = "2.8.5"
[deps.StructUtils.extensions]
StructUtilsMeasurementsExt = ["Measurements"]
@@ -1014,6 +1033,12 @@ deps = ["Artifacts", "Libdl", "libblastrampoline_jll"]
uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c"
version = "7.8.3+2"
[[deps.SymDict]]
deps = ["Test"]
git-tree-sha1 = "0108ccdaea3ef69d9680eeafc8d5ad198b896ec8"
uuid = "2da68c74-98d7-5633-99d6-8493888d7b1e"
version = "0.3.0"
[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
@@ -1021,9 +1046,9 @@ version = "1.0.3"
[[deps.TZJData]]
deps = ["Artifacts"]
git-tree-sha1 = "72df96b3a595b7aab1e101eb07d2a435963a97e2"
git-tree-sha1 = "d3b30a9f29a898e21fb4bdf280101b7a12e1f39c"
uuid = "dc5dba14-91b3-4cab-a142-028a31da12f7"
version = "1.5.0+2025b"
version = "1.5.1+2025b"
[[deps.TableTraits]]
deps = ["IteratorInterfaceExtensions"]
@@ -1033,14 +1058,9 @@ version = "1.0.1"
[[deps.Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"]
git-tree-sha1 = "0f38a06c83f0007bbab3cf911262841c9a0f07e0"
git-tree-sha1 = "a94d9bdda1b7bed0046cea645639ab3f62196fac"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.13.0"
[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
version = "1.10.0"
version = "1.14.0"
[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
@@ -1063,9 +1083,9 @@ uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.11.3"
[[deps.URIs]]
git-tree-sha1 = "3b0738bd7c5645641845da25cbd99800b8718689"
git-tree-sha1 = "908fec9df6c5de98548ead82a468c95ccf6cd263"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.6.2"
version = "1.7.0"
[[deps.UTCDateTimes]]
deps = ["Dates", "TimeZones"]
@@ -1093,8 +1113,20 @@ git-tree-sha1 = "cd1659ba0d57b71a464a29e64dbc67cfe83d54e7"
uuid = "76eceee3-57b5-4d4a-8e66-0e911cebbf60"
version = "1.6.1"
[[deps.XML2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"]
git-tree-sha1 = "3f3315d89fc954a28f5b471bce698ed6e27481be"
uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
version = "2.15.3+0"
[[deps.XMLDict]]
deps = ["EzXML", "IterTools", "OrderedCollections"]
git-tree-sha1 = "8e8cdc9c93e1f442eba471b56db6f86ffeb1a33a"
uuid = "228000da-037f-5747-90a9-8195ccbf91a5"
version = "0.4.2"
[[deps.YiemAgent]]
deps = ["Base64", "CSV", "DataFrames", "DataStructures", "Dates", "GeneralUtils", "HTTP", "JSON", "LLMMCTS", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "SQLLLM", "Serialization", "URIs", "UUIDs"]
deps = ["Base64", "CSV", "DataFrames", "DataStructures", "Dates", "HTTP", "JSON", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "Serialization", "URIs", "UUIDs", "msghandler"]
path = "."
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
version = "0.8.0"
@@ -1122,12 +1154,12 @@ uuid = "a9144af2-ca23-56d9-984f-0d03f7b5ccf8"
version = "1.0.21+0"
[[deps.msghandler]]
deps = ["Arrow", "Base64", "DataFrames", "Dates", "GeneralUtils", "HTTP", "JSON", "NATS", "PrettyPrinting", "Revise", "UUIDs"]
git-tree-sha1 = "e82a79cf6602541ea25409aded57b2ace4a7c29f"
deps = ["Arrow", "Base64", "DataFrames", "Dates", "GeneralUtils", "HTTP", "JSON", "NATS", "PrettyPrinting", "UUIDs"]
git-tree-sha1 = "e7889a120327db84cdf1b1acf4a2c7caba6e8ef7"
repo-rev = "main"
repo-url = "https://git.yiem.cc/ton/msghandler"
uuid = "f2724d33-f338-4a57-b9f8-1be882570d10"
version = "1.2.1"
version = "1.4.0"
[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
+2 -2
View File
@@ -26,8 +26,8 @@ msghandler = "f2724d33-f338-4a57-b9f8-1be882570d10"
Base64 = "1.11.0"
CSV = "0.10.15"
DataFrames = "1.7.0"
GeneralUtils = "0.5.10"
GeneralUtils = "0.6.9"
HTTP = "2.4.0"
JSON = "1.6.1"
NATS = "0.1.0"
msghandler = "1.2.1"
msghandler = "1.4.0"
+298 -126
View File
@@ -2,138 +2,310 @@
Julia framework for building agents with tool use and MCP (Model Context Protocol) support.
## Getting Started
## Quick Start
1. Install dependencies: `]add JSON, DataStructures, UUIDs, Dates, NATS, DataFrames`
2. Create a callable `mcpServer` struct that communicates with an MCP server via NATS
3. Create a `yiemAgent` with an LLM callable and MCP server:
```julia
agent = YiemAgent.yiemAgent(llmCall; mcpServer=mcpServer, eventSink=yourSink)
```
4. Call `promptAgent(agent, message)` then `takeResponse(agent)`
```julia
using YiemAgent
# 1. Create an LLM callable (see "Custom LLM Callable" below)
llmCall = ...
# 2. Create a callable MCP server (see "MCP Server Integration" below)
mcpServer = ...
# 3. Create the agent
agent = yiemAgent(llmCall; mcpServer=mcpServer, eventSink=println)
# 4. Interact
promptAgent(agent, "What tools do you have?")
response = takeResponse(agent)
# 5. Stop when done
stopAgent(agent)
```
## Agent API
| Function | Description |
|----------|-------------|
| `promptAgent(agent, text)` | Send plain text to the agent's input channel |
| `promptAgent(agent, msg)` | Send a structured message (OpenAI format dict) to the input channel |
| `takeResponse(agent)` | Block and take the agent's response from output channel |
| `followUp(agent, text)` | Send plain text follow-up while agent is still processing |
| `followUp(agent, msg)` | Send a structured follow-up (OpenAI format dict) while agent is processing |
| `stopAgent(agent)` | Gracefully stop the agent and close channels |
## Creating a yiemAgent
```julia
yiemAgent(llmCall;
systemPrompt::String = "You are a helpful assistant.",
model = nothing,
messages::Vector{agentMessage} = agentMessage[],
prepareContext::Function = prepareContext,
formatMsgForLLM::Function = formatMsgForLLM,
beforeToolCall::Function = beforeToolCall,
afterToolCall::Function = afterToolCall,
sessionId::Union{String, Nothing} = nothing,
maxRetryDelayMs::Union{Int64, Nothing} = nothing,
parallelToolExecute::Bool = false,
eventSink::Function = eventSink,
mcpServer = nothing
)
```
| Parameter | Description |
|-----------|-------------|
| `llmCall` | **(required)** Function that receives formatted messages and returns an LLM response |
| `systemPrompt` | System prompt for the agent |
| `model` | LLM model configuration (`llmModel` struct) |
| `messages` | Initial conversation messages |
| `prepareContext` | Hook to transform messages/context before each LLM call |
| `formatMsgForLLM` | Hook to convert agent context to LLM API format |
| `beforeToolCall` | Hook to block/allow tool execution |
| `afterToolCall` | Hook to post-process tool results |
| `sessionId` | Optional session identifier for MCP server |
| `maxRetryDelayMs` | Maximum retry delay in milliseconds |
| `parallelToolExecute` | Run multiple tool calls in parallel |
| `eventSink` | Callback for agent events/debug messages |
| `mcpServer` | Callable struct for MCP server communication |
## Usage Examples
### Text Messages
```julia
# Send plain text
promptAgent(agent, "What tools do you have?")
response = takeResponse(agent)
# Follow-up while agent is processing
promptAgent(agent, "Tell me about wine")
followUp(agent, "Also include prices")
response = takeResponse(agent)
```
### Structured Messages (OpenAI Format)
```julia
promptAgent(agent, Dict("role" => "user", "content" => "Hello"))
response = takeResponse(agent)
```
### Output Format
`takeResponse()` returns a `Dict{String, Any}` in OpenAI message format:
```julia
Dict(
"role" => "assistant",
"content" => [
Dict("type" => "text", "text" => "The weather in Tokyo is Sunny, 22°C."),
# ... more content blocks
]
)
```
Content block types:
| Block Type | Fields | Description |
|------------|--------|-------------|
| `text` | `"type" => "text"`, `"text" => "..."` | Plain text response from the LLM |
| `image_url` | `"type" => "image_url"`, `"image_url" => Dict("url" => "...")` | Image content (rare, used by multimodal models) |
Example — extracting the text response:
```julia
response = takeResponse(agent)
text_response = first(c["text"] for c in response["content"] if c["type"] == "text")
```
### Multimodal Messages (Text + Image)
```julia
# Encode images to data URIs
image_path = "test/image.png"
image_bytes = read(image_path)
base64_string = base64encode(image_bytes)
data_uri = "data:image/png;base64,$(base64_string)"
# Build message with text and image content blocks
message = Dict(
"role" => "user",
"content" => [
Dict("type" => "text", "text" => "Do you have Brunello di Montalcino?"),
Dict("type" => "image_url", "image_url" => Dict("url" => data_uri))
]
)
promptAgent(agent, message)
response = takeResponse(agent)
```
### Custom Hooks
```julia
# beforeToolCall — request user approval for destructive tools
function my_before_tool_call(context::beforeToolCallContext, signal::abortSignal)::beforeToolCallResult
if context.toolCall.name == "deleteFile"
return beforeToolCallResult(true, "User must approve file deletion")
end
return beforeToolCallResult(false, "N/A")
end
# afterToolCall — mask sensitive data from results
function my_after_tool_call(context::afterToolCallContext, signal::abortSignal)::Union{agentToolResult, Nothing}
if context.isError
return agentToolResult(
[textContent("The tool encountered an error. Please try again.")],
context.result.details, context.result.usage, context.result.terminate
)
end
return nothing
end
agent = yiemAgent(llmCall;
mcpServer=mcpServer,
beforeToolCall=my_before_tool_call,
afterToolCall=my_after_tool_call,
eventSink=println
)
```
### Custom LLM Callable
```julia
function my_llm_call(messages_dict::Dict{String, Any})
# messages_dict contains "messages" key with OpenAI-format message array,
# plus optional "tools", "temperature", etc.
# Call your LLM provider (OpenAI, Anthropic, local model, etc.)
raw_response = call_my_llm_provider(messages_dict)
# Return a dict with these fields:
return Dict(
"message" => Dict("role" => "assistant", "content" => raw_response["content"]),
"finish_reason" => raw_response["finish_reason"],
"api" => "my_provider",
"provider" => "my_provider",
"model" => "my-model-name",
"usage" => llmUsage(100, 50)
)
end
```
### Custom Context Preparation
```julia
function my_prepare_context(state::agentState, eventSink)::agentContext
sysPrompt = state.systemPrompt * "\nCurrent time: $(now())"
msgs = deepcopy(state.messages)
# Prune old messages to fit context window
max_messages = 20
if length(msgs) > max_messages
msgs = msgs[end-max_messages+1:end]
end
return agentContext(sysPrompt, msgs, state.tools)
end
agent = yiemAgent(llmCall; prepareContext=my_prepare_context, ...)
```
## MCP Server Integration
YiemAgent uses JSON-RPC 2.0 for MCP communication. The `mcpServer` callable struct must implement `tools/list` and `tools/call` methods.
### Protocol
```julia
# tools/list — returns tool definitions
mcpServer("tools/list")
# → Dict("jsonrpc"=>"2.0", "id"=>1, "result"=>Dict("tools"=>[...], "nextCursor"=>...))
# tools/call — executes a tool
mcpServer("tools/call", toolName, arguments)
# → Dict("jsonrpc"=>"2.0", "id"=>2, "result"=>Dict("content"=>[...], "isError"=>...))
# Protocol error responses include "error" instead of "result":
Dict("jsonrpc"=>"2.0", "id"=>2, "error"=>Dict("code"=>-32602, "message"=>"..."))
```
### Example: NATS-based MCP Server Client
```julia
struct mcpServer
natsConn::NATS.Connection
topic::String
senderID::String
fileserver_url::String
end
# tools/list implementation
function (c::mcpServer)(method::String)
payload = Dict("jsonrpc" => "2.0", "id" => 1, "method" => method, "params" => Dict{String, Any}())
payloads = [("payload", payload, "dictionary")]
_, msg_envelope = msghandler.smartpack(c.topic, payloads;
sender_id=c.senderID, msg_purpose="mcp_tools_list", fileserver_url=c.fileserver_url)
reply = NATS.request(c.natsConn, c.topic, msg_envelope, timeout=180)
incoming = msghandler.smartunpack(String(reply.payload))
return incoming["payloads"][1][2]
end
# tools/call implementation
function (c::mcpServer)(method::String, toolName::String, arguments::Dict{String, Any})
payload = Dict("jsonrpc" => "2.0", "id" => 2, "method" => method,
"params" => Dict("name" => toolName, "arguments" => arguments))
payloads = [("payload", payload, "dictionary")]
_, msg_envelope = msghandler.smartpack(c.topic, payloads;
sender_id=c.senderID, msg_purpose="mcp_tool_call", fileserver_url=c.fileserver_url)
reply = NATS.request(c.natsConn, c.topic, msg_envelope, timeout=180)
incoming = msghandler.smartunpack(String(reply.payload))
return incoming["payloads"][1][2]
end
```
## Tool Discovery
Tools are discovered dynamically via the MCP server:
1. `listTools` is the only pre-registered tool at agent creation
2. When the LLM calls `listTools()`, tools are discovered from the MCP server and registered at runtime
3. Supports pagination via `nextCursor` for large tool sets
4. New tools become immediately available without restarting the agent
## Hooks
Hooks allow customization of the agent's behavior at key points in the execution pipeline.
| Hook | Signature | Purpose |
|------|-----------|---------|
| `prepareContext` | `(state::agentState, eventSink) -> agentContext` | Transform messages/context before LLM call (filter tools, inject context, prune messages) |
| `formatMsgForLLM` | `(ctx::agentContext, eventSink) -> Dict` | Convert agent context to LLM API message format |
| `beforeToolCall` | `(context::beforeToolCallContext, signal::abortSignal) -> beforeToolCallResult` | Block/allow tool execution; request user approval |
| `afterToolCall` | `(context::afterToolCallContext, signal::abortSignal) -> Union{agentToolResult, Nothing}` | Post-process tool results; mask sensitive data; set `terminate` flag |
| `eventSink` | `(msg) -> nothing` | Callback for agent events/debug messages throughout the pipeline |
## Agent Lifecycle
1. **Creation** — `yiemAgent()` spawns a background task (`_agentLoop`) listening on `inputChannel` and `followUpChannel`
2. **Input** — User messages enter via `promptAgent()` (immediate) or `followUp()` (queued while agent is busy)
3. **Processing** — `_processMessage()` handles LLM calls, tool execution, and conversation history in a loop
4. **Tool Execution Pipeline** — each tool call follows three phases:
- `prepareToolCall` — resolve tool, validate arguments, run `beforeToolCall` hook
- `executePreparedToolCall` — invoke `tool.execute()`
- `finalizeExecutedToolCall` — run `afterToolCall` hook, construct `toolResultMessage`
5. **Termination** — tool `execute` functions can set `terminate=true` to stop the agent loop
6. **Shutdown** — `stopAgent()` sends a `:shutdown` signal, drains channels, and closes the agent
## Architecture
```
src/
├── YiemAgent.jl # Module entry point
├── type.jl # Core types (messages, tools, agent state)
├── type.jl # Core types (messages, tools, agent state, events)
├── utils.jl # Message formatting, context preparation, validation
├── agentCore.jl # Agent loop, tool execution pipeline
├── api.jl # Public API (promptAgent, takeResponse, followUp, stopAgent)
└── toolRegistry.jl # Tool store, MCP discovery, listTools registration
├── toolRegistry.jl # Tool store, MCP discovery, listTools registration
├── agentCore.jl # Agent loop, tool execution pipeline, yiemAgent struct
└── api.jl # Public API (promptAgent, takeResponse, followUp, stopAgent)
```
## MCP Protocol
YiemAgent uses JSON-RPC 2.0 for MCP communication. The `mcpServer` callable struct must implement:
```julia
# tools/list — returns tool definitions
mcpServer("tools/list") # → Dict("jsonrpc"=>"2.0", "id"=>1, "result"=>Dict("tools"=>[...], "nextCursor"=>...))
# tools/call — executes a tool
mcpServer("tools/call", toolName, arguments) # → Dict("jsonrpc"=>"2.0", "id"=>2, "result"=>Dict("content"=>[...], "isError"=>...))
```
Protocol error responses include `"error"` instead of `"result"`:
```julia
Dict("jsonrpc"=>"2.0", "id"=>2, "error"=>Dict("code"=>-32602, "message"=>"..."))
```
### Implementing the MCP Server Client
You must provide a callable struct that communicates with your MCP server. Example using NATS:
```julia
struct mcpServer
natsConn::NATS.Connection
topic::String
senderID::String
fileserver_url::String
end
# tools/list implementation
function (c::mcpServer)(method::String)
if method != "tools/list"
error("mcpServer: unexpected method '$method' (expected 'tools/list')")
end
payload = Dict("jsonrpc" => "2.0", "id" => 1, "method" => method, "params" => Dict{String, Any}())
payloads = [("payload", payload, "dictionary")]
_, msg_envelope_json_str = msghandler.smartpack(
c.topic, payloads;
sender_id=c.senderID,
msg_purpose="mcp_tools_list",
fileserver_url=c.fileserver_url)
reply = NATS.request(c.natsConn, c.topic, msg_envelope_json_str, timeout=180)
incoming_env = msghandler.smartunpack(String(reply.payload))
return incoming_env["payloads"][1][2]
end
# tools/call implementation
function (c::mcpServer)(method::String, toolName::String, arguments::Dict{String, Any})
if method != "tools/call"
error("mcpServer: unexpected method '$method' (expected 'tools/call')")
end
payload = Dict(
"jsonrpc" => "2.0",
"id" => 2,
"method" => method,
"params" => Dict(
"name" => toolName,
"arguments" => arguments
)
)
payloads = [("payload", payload, "dictionary")]
_, msg_envelope_json_str = msghandler.smartpack(
c.topic, payloads;
sender_id=c.senderID,
msg_purpose="mcp_tool_call",
fileserver_url=c.fileserver_url)
reply = NATS.request(c.natsConn, c.topic, msg_envelope_json_str, timeout=180)
incoming_env = msghandler.smartunpack(String(reply.payload))
return incoming_env["payloads"][1][2]
end
```
See `test/runtest.jl` for the complete working example.
## Tool Discovery
Tools are discovered dynamically via MCP server:
1. `listTool` is the only pre-registered tool
2. When the LLM calls `listTools()`, tools are discovered from the MCP server and registered at runtime
3. Supports pagination via `nextCursor` for large tool sets
## Agent API
| Function | Description |
|----------|-------------|
| `promptAgent(agent, msg)` | Send a message to the agent's input channel |
| `takeResponse(agent)` | Block and take the agent's response from output channel |
| `followUp(agent, msg)` | Send a follow-up message while agent is still processing |
| `stopAgent(agent)` | Gracefully stop the agent and close channels |
## Agent Lifecycle
1. `yiemAgent()` spawns a background task (`_agentLoop`) listening on `inputChannel` and `followUpChannel`
2. User messages enter via `promptAgent()` or `followUp()`
3. `_processMessage()` handles LLM calls, tool execution, and conversation history
4. Tool execution follows three phases: `prepareToolCall` → `executePreparedToolCall` → `finalizeExecutedToolCall`
5. `beforeToolCall`/`afterToolCall` hooks allow pre/post-processing of tool calls
6. Tool `execute` functions can set `terminate=true` to stop the agent loop
## Hooks
| Hook | Signature | Purpose |
|------|-----------|---------|
| `prepareContext` | `(state, sink, llmCall) -> ctx` | Transform messages/context before LLM call |
| `formatMsgForLLM` | `(ctx, sink) -> dict` | Convert agent context to LLM API format |
| `beforeToolCall` | `(context, signal) -> result` | Block/allow tool execution |
| `afterToolCall` | `(context, signal) -> result` | Post-process tool results |
| `eventSink` | `(msg) -> nothing` | Callback for agent events/debug messages |
-1599
View File
File diff suppressed because it is too large Load Diff
+94
View File
@@ -0,0 +1,94 @@
can you make sure yiemAgent use correct format when contacting MCP server. you can check MCP server doc at /home/ton/docker-apps/sommpanion/mcp/docs/specification.md?
smartpack() and smartunpack() doc is at /home/ton/docker-apps/sommpanion/msghandler/README.md
user code that use yiemAgent is here /home/ton/docker-apps/sommpanion/agent-backend/test/runtest.jl
this is log folder that logs when I ran yiemAgent. you can check /home/ton/docker-apps/sommpanion/agent-backend/log/
+153 -175
View File
@@ -19,14 +19,46 @@ mutable struct yiemAgent <: agent # High-level agent wrapper
# user sends prompt message to agent. if agent is idle, it process user message right away.
# if agent is running, it process user message after the current tool call finished.
# Expect each message in OpenAI chat format:
# image1_path = "test/large_image.png"
# image1_bytes = read(image1_path)
# image1_base64_string = base64encode(image1_bytes)
# mime_type = "image/png"
# data1_uri = "data:$(mime_type);base64,$(image1_base64_string)"
# # 1. Read local file and encode to base64 string
# image2_path = "test/small_image.png"
# image2_bytes = read(image2_path)
# image2_base64_string = base64encode(image2_bytes)
# mime_type = "image/png"
# data2_uri = "data:$(mime_type);base64,$(image2_base64_string)"
# # 3. Construct payload with the Data URI
# message = Dict(
# "role" => "user",
# "content" => [
# Dict("type" => "text", "text" =>
# "
# Do you have Brunello di Montalcino from Tenuta CastelGiocon?
# "),
# Dict(
# "type" => "image_url",
# "image_url" => Dict("url" => data1_uri)
# )
# ]
# )
inputChannel::Channel
# Buffers messages the user sends while the agent is busy. Processed after all inputChannel
# messages are handled and the agent is idle (not using a tool call).
# Buffers follow-up messages the user sends while the agent is busy. Uses the same
# OpenAI chat format as inputChannel. Processed after all inputChannel messages are
# handled and the agent is idle (not in the middle of a tool call).
followUpChannel::Channel
# agent sends response message to user after processing all user messages in inputChannel
# and all followUp messages.
# agent sends response message to user after processing all user messages in inputChannel
# and all followUp messages. Response is in OpenAI message format:
# Dict("role" => "assistant", "content" => [
# Dict("type" => "text", "text" => "Response text here")
# ])
outputChannel::Channel
_agentLoop::Union{Task, Nothing} # agent loop running in the background
@@ -44,81 +76,8 @@ mutable struct yiemAgent <: agent # High-level agent wrapper
# Each block has a type — "text", "thinking", or "toolCall".
# The code filters for type === "toolCall" blocks, then passes them to executeToolCalls().
llmCall
# Callable struct for MCP server communication.
# Called as: mcpServer("tools/list") → returns JSON-RPC 2.0 parsed response
# mcpServer("tools/call", toolName, arguments) → returns JSON-RPC 2.0 parsed response
#
# # Example (weather tool)
# # User provides a callable struct
# struct MyMCPClient
# natsConn::NATS.Connection
# topic::String
# senderID::String
# fileserver_url::String
# end
#
# function (c::MyMCPClient)(method::String)
# payload = Dict("jsonrpc" => "2.0", "id" => 1, "method" => method, "params" => Dict{String, Any}())
# payloads = [("payload", payload, "dictionary")]
# _, msg_envelope_json_str = msghandler.smartpack(
# c.topic, payloads; sender_id=c.senderID,
# msg_purpose="mcp_tools_list", fileserver_url=c.fileserver_url)
# reply = NATS.request(c.natsConn, c.topic, msg_envelope_json_str, timeout=180)
# incoming_env = msghandler.smartunpack(String(reply.payload))
# return incoming_env["payloads"][1][2]
# end
#
# function (c::MyMCPClient)(method::String, toolName::String, arguments::AbstractDict{String, Any})
# payload = Dict("jsonrpc" => "2.0", "id" => 2, "method" => method,
# "params" => Dict("name" => toolName, "arguments" => arguments))
# payloads = [("payload", payload, "dictionary"),]
# _, msg_envelope_json_str = msghandler.smartpack(
# c.topic, payloads; sender_id=c.senderID,
# msg_purpose="mcp_tool_call", fileserver_url=c.fileserver_url)
# reply = NATS.request(c.natsConn, c.topic, msg_envelope_json_str, timeout=180)
# incoming_env = msghandler.smartunpack(String(reply.payload))
# return incoming_env["payloads"][1][2]
# end
#
# # "tools/list" input:
# mcpServer("tools/list")
# # sending out payload before smart packed by msghandler:
# Dict("jsonrpc" => "2.0", "id" => 1, "method" => "tools/list", "params" => Dict{String, Any}())
# # expected return after smart unpacked by msghandler:
# Dict(
# "jsonrpc" => "2.0", "id" => 1,
# "result" => Dict(
# "tools" => [
# Dict(
# "name" => "getWeather",
# "description" => "Fetch current weather for a city.",
# "inputSchema" => Dict("type"=>"object",
# "properties" => Dict("city"=>Dict("type"=>"string", "description"=>"City name"),
# "units"=>Dict("type"=>"string", "enum"=>["celsius","fahrenheit"], "default"=>"celsius")),
# "required" => ["city"])
# )
# ],
# "nextCursor" => nothing
# )
# )
#
# # "tools/call" input:
# mcpServer("tools/call", "getWeather", Dict("city"=>"Tokyo", "units"=>"celsius"))
# # sending out payload before smart packed by msghandler:
# Dict("jsonrpc" => "2.0", "id" => 2, "method" => "tools/call",
# "params" => Dict("name" => "getWeather", "arguments" => Dict("city"=>"Tokyo", "units"=>"celsius")))
# # expected return after smart unpacked by msghandler (success):
# Dict("jsonrpc" => "2.0", "id" => 2,
# "result" => Dict("content" => [{"type" => "text", "text" => "Weather in Tokyo: Sunny, 22°C"}],
# "isError" => false))
# # expected return after smart unpacked by msghandler (tool error):
# Dict("jsonrpc" => "2.0", "id" => 2,
# "result" => Dict("content" => [{"type" => "text", "text" => "Error: City 'Atlantis' not found."}],
# "isError" => true))
# # expected return after smart unpacked by msghandler (protocol error):
# Dict("jsonrpc" => "2.0", "id" => 2,
# "error" => Dict("code" => -32602, "message" => "Missing required argument 'city'"))
# check input and output format at MCP server doc
mcpServer
# Callback invoked before executing a tool call (ask for user permission/confirmation/abort, etc..)
@@ -187,6 +146,27 @@ function yiemAgent(
followUp = Channel(32)
outputChannel = Channel(16)
if mcpServer === nothing
error(""" please implement the following MCP server interface
struct mcpServer
natsConn::NATS.Connection
topic::String
senderID::String
end
# MCP protocol: tools/list — returns tool definitions (JSON-RPC 2.0)
# check input and output format from MCP server doc
function (mcp::mcpServer)(method::String, sessionId::String; eventSink=nothing)::AbstractDict{String, <:Any}
error("please implement MCP server tools/list interface")
end
# MCP protocol: tools/call — executes a tool (JSON-RPC 2.0)
function (mcp::mcpServer)(method::String, toolName::String, arguments::Dict{String, Any},
sessionId::String; eventSink=nothing)::AbstractDict{String, <:Any}
error("please implement MCP server interface")
end
""")
end
tools = OrderedDict{String, agentTool}()
registerAllTools(tools, mcpServer; eventSink=eventSink, sessionId=sessionId)
@@ -284,18 +264,18 @@ function _agentLoop(agent::yiemAgent)
while true
while newUserMsg === nothing
if isready(agent.inputChannel)
agent.eventSink("_agentLoop 1 agent._state.messages length $(length(agent._state.messages))")
agent.eventSink("_agentLoop 1 agent._state.messages length $(length(agent._state.messages))"; publish=false, log=true)
# agent process new user msg immediately after the current tool call finished.
newUserMsg = take!(agent.inputChannel)
agent.eventSink("new user msg")
# agent.eventSink("new user msg\n$newUserMsg"; publish=false, log=true)
else
# check followUp message after _processMessage() is done
if typeof(processingTask) == Task && istaskdone(processingTask) == true
agent.eventSink("_agentLoop 2 agent._state.messages length $(length(agent._state.messages))")
agent.eventSink("_agentLoop 2 agent._state.messages length $(length(agent._state.messages))"; publish=false, log=true)
# if agent runs is done but followUpChannel has messages,
# put new message in inputChannel instead
if isready(agent.followUpChannel)
agent.eventSink("_agentLoop 3 agent._state.messages length $(length(agent._state.messages))")
agent.eventSink("_agentLoop 3 agent._state.messages length $(length(agent._state.messages))"; publish=false, log=true)
while isready(agent.followUpChannel)
followUpMsg = take!(agent.followUpChannel)
put!(agent.inputChannel, followUpMsg)
@@ -303,18 +283,18 @@ function _agentLoop(agent::yiemAgent)
processingTask = nothing # reset
result = nothing # reset
else # _processMessage() done and no followUp message.
agent.eventSink("_agentLoop 4 agent._state.messages length $(length(agent._state.messages))")
agent.eventSink("_agentLoop 4 agent._state.messages length $(length(agent._state.messages))"; publish=false, log=true)
result = deepcopy(agent._state.messages[end])
agent.eventSink("_agentLoop 4-1 ")
agent.eventSink("_agentLoop 4-1 "; publish=false, log=true)
# filter out reasoningContent in-place
filter!(c -> !(c isa reasoningContent), result.content)
agent.eventSink("_agentLoop 5 ")
agent.eventSink("_agentLoop 5 "; publish=false, log=true)
# format output
respondToUI = _assistantMessageToOpenAI(result)
agent.eventSink("_agentLoop 6 ")
agent.eventSink("_agentLoop 6 "; publish=false, log=true)
put!(agent.outputChannel, respondToUI)
if !isempty(result.content) && result.content[1] isa textContent
agent.eventSink(result.content[1].text)
agent.eventSink(result.content[1].text; publish=false, log=true)
end
processingTask = nothing # reset
result = nothing # reset
@@ -345,7 +325,7 @@ function _agentLoop(agent::yiemAgent)
else
# spawn new _processMessage() if it is not already running.
if processingTask === nothing
agent.eventSink("_agentLoop 5 agent._state.messages length $(length(agent._state.messages))")
agent.eventSink("_agentLoop 5 agent._state.messages length $(length(agent._state.messages))"; publish=false, log=true)
# discard all messages in followUpChannel
while isready(agent.followUpChannel)
_ = take!(agent.followUpChannel)
@@ -376,7 +356,7 @@ function _agentLoop(agent::yiemAgent)
showerror(io, e, bt)
println(io)
end
eventSink(errMsg)
eventSink(errMsg; publish=false, log=true)
# On any error, send error response and exit the loop
@error "Agent loop failed" error=e
@@ -424,7 +404,7 @@ function _processMessage(
afterToolCall::Union{Function, Nothing},
parallelToolExecute::Bool,
)::Nothing
eventSink("_processMessage 1 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 1 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
# loop until llmCall() response didn't use tool calls
final_response = nothing
@@ -445,43 +425,43 @@ function _processMessage(
try
# Drain inputChannel and convert OpenAI-format messages to userMessage type
while isready(inputChannel)
eventSink("_processMessage 2 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 2 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
newUserMsg_openai = take!(inputChannel)
eventSink("_processMessage 3 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 3 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
if newUserMsg_openai === :shutdown
eventSink("_processMessage 4 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 4 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
# Re-emit shutdown signal for the loop to handle
put!(inputChannel, :shutdown)
break
end
eventSink("_processMessage 5 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 5 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
newUserMsg = OpenAiToUserMessage(newUserMsg_openai)
push!(agentMsgHistory, newUserMsg)
eventSink("_processMessage 6 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 6 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
end
eventSink("_processMessage 7 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 7 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
# call prepareContext()
state = agentState(systemPrompt, nothing, tools, agentMsgHistory)
eventSink("_processMessage 8 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 8 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
preparedContext = prepareContext(state, eventSink)
eventSink("_processMessage 9 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 9 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
# Call formatMessagesForLLM() to format for LLM
formattedMessages = formatMessagesForLLM(preparedContext, eventSink)
eventSink("_processMessage 10 formattedMessages $formattedMessages")
# eventSink("_processMessage 10 formattedMessages $formattedMessages"; publish=false, log=true)
""" response example
response = JSON.Object{String, Any}("finish_reason" => "tool_calls", "index" => 0, "message" => JSON.Object{String, Any}("role" => "assistant", "content" => "", "reasoning_content" => "Here's a thinking process:\n\n1. **Identify User Request**: The user is asking for the weather in Bangkok.\n2. **Locate Relevant Tool**: I have a `getWeather` function available.\n3. **Check Function Parameters**:\n - `city` (required): City and country, e.g., 'San Francisco, CA' or 'Tokyo, Japan'\n - `units` (optional, default \"celsius\"): Temperature scale (\"celsius\" or \"fahrenheit\")\n4. **Prepare Parameters**:\n - `city`: \"Bangkok, Thailand\" (adding country for clarity, though just \"Bangkok\" might work, following the example format is safer)\n - `units`: Not specified, so I'll use the default (\"celsius\")\n5. **Execute Tool Call**: Call `getWeather` with `city: \"Bangkok, Thailand\"`\n6. **Anticipate Response**: The function will return current weather and forecast data for Bangkok. I'll then format it nicely for the user.\n - *Self-Correction/Verification during thought*: The prompt says \"city: City and country, e.g., 'San Francisco, CA' or 'Tokyo, Japan'\". I'll use \"Bangkok, Thailand\". The `units` parameter is optional, so I'll omit it to use the default.\n - Proceed with tool call.✅\n", "tool_calls" => Any[JSON.Object{String, Any}("type" => "function", "function" => JSON.Object{String, Any}("name" => "getWeather", "arguments" => "{\"city\":\"Bangkok, Thailand\"}"), "id" => "6fOilR5QPcdppbAAHluhkRyUDu3oWMAL")]))
"""
response = llmCall(formattedMessages)
eventSink("LLM response " * string(response))
response = llmCall(formattedMessages, eventSink=eventSink)
# eventSink("LLM response " * string(response); publish=false, log=true)
eventSink("_processMessage 11 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 11 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
# Extract tool calls from LLM response content blocks
hasToolCalls, toolCallList, assistant_msg = _extractToolCalls(response)
eventSink("hasToolCalls: $hasToolCalls\ntoolCallList: $toolCallList _state.messages length $(length(agentMsgHistory))")
eventSink("assistant_msg " * string(assistant_msg))
eventSink("_processMessage 11-1")
eventSink("hasToolCalls: $hasToolCalls\ntoolCallList: $toolCallList _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
eventSink("assistant_msg " * string(assistant_msg); publish=false, log=true)
eventSink("_processMessage 11-1"; publish=false, log=true)
# Add assistant message (tool calls or text) to history for next LLM turn
push!(agentMsgHistory, assistant_msg)
@@ -496,21 +476,21 @@ function _processMessage(
)
signal = abortSignal(false)
eventSink("_processMessage 12 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 12 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
# call executeToolCalls()
toolResultBatch = executeToolCalls(preparedContext, assistant_msg, toolCallList, config,
signal, eventSink)
eventSink("_processMessage 13 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 13 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
# save toolResults to messages
for toolResult in toolResultBatch.messages
eventSink("toolResult " * string(toolResult))
# eventSink("toolResult " * string(toolResult); publish=false, log=true)
push!(agentMsgHistory, toolResult)
end
eventSink("_processMessage 14 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 14 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
if toolResultBatch.terminate
eventSink("_processMessage 15 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 15 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
# If toolResultBatch requested termination, build a final response
final_content = [textContent("Tool execution completed.")]
for toolResult in toolResultBatch.messages
@@ -524,7 +504,7 @@ function _processMessage(
end
end
end
eventSink("_processMessage 16 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 16 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
final_response = assistantMessage(
role="assistant",
content=final_content,
@@ -543,7 +523,7 @@ function _processMessage(
break
end
else
eventSink("_processMessage 17 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 17 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
# LLM did not use tool calls —
break
end
@@ -554,12 +534,12 @@ function _processMessage(
println(io)
end
eventSink(err_msg)
eventSink(err_msg; publish=false, log=true)
end
end
eventSink("_processMessage 18 _state.messages length $(length(agentMsgHistory))")
eventSink("_processMessage 18 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
return nothing
end
@@ -590,7 +570,7 @@ agentToolResult([textContent("text", "Tool not found")], Dict{Any,Any}())
```
"""
function createErrorToolResult(msg::String)::agentToolResult
return agentToolResult([textContent("text", msg)], dict{any,any}())
return agentToolResult([textContent("text", msg)], Dict{Any,Any}())
end
"""
@@ -882,10 +862,10 @@ stopping the agent when other tools in the batch did not intend to terminate.
julia> shouldTerminate(finalizedOutcome[])
false
julia> shouldTerminate([finalizedOutcome(tc, agentToolResult([], dict{any,any}(), dict{any,any}(), false), false) for _ in 1:2])
julia> shouldTerminate([finalizedOutcome(tc, agentToolResult([], Dict{Any,Any}(), Dict{Any,Any}(), false), false) for _ in 1:2])
false
julia> shouldTerminate([finalizedOutcome(tc, agentToolResult([], dict{any,any}(), dict{any,any}(), true), false) for _ in 1:2])
julia> shouldTerminate([finalizedOutcome(tc, agentToolResult([], Dict{Any,Any}(), Dict{Any,Any}(), true), false) for _ in 1:2])
true
```
"""
@@ -999,46 +979,47 @@ function prepareToolCall(
signal::abortSignal,
eventSink
)::Union{preparedToolCall,immediateOutcome}
eventSink("prepareToolCall 1")
eventSink("prepareToolCall 1"; publish=false, log=true)
# pick a called tool from tool store
tool = get(context.tools, toolCall.name, nothing)
if tool === nothing
eventSink("prepareToolCall 2")
eventSink("prepareToolCall 2"; publish=false, log=true)
return immediateOutcome(createErrorToolResult("Tool $toolCall.name not found"), true)
end
try
eventSink("prepareToolCall 3")
eventSink("prepareToolCall 3"; publish=false, log=true)
# 1. prepare arguments (tool-specific transform)
prepared = prepareToolCallArguments(tool, toolCall)
eventSink("prepared " * string(prepared.arguments))
eventSink("prepareToolCall 4")
eventSink("prepared " * string(prepared.arguments); publish=false, log=true)
eventSink("prepareToolCall 4"; publish=false, log=true)
validatedArgs = validateToolArguments(tool, prepared)
eventSink("validatedArgs " * string(validatedArgs))
eventSink("prepareToolCall 5")
eventSink("validatedArgs " * string(validatedArgs); publish=false, log=true)
eventSink("prepareToolCall 5"; publish=false, log=true)
# 2. beforeToolCall hook — can block
if config.beforeToolCall !== nothing
eventSink("prepareToolCall 6")
eventSink("prepareToolCall 6"; publish=false, log=true)
before = config.beforeToolCall(
beforeToolCallContext(assistantMsg, toolCall, validatedArgs, context),
signal
signal,
eventSink
)
eventSink("prepareToolCall 7")
eventSink("prepareToolCall 7"; publish=false, log=true)
if signal.aborted
eventSink("prepareToolCall 8")
eventSink("prepareToolCall 8"; publish=false, log=true)
return immediateOutcome(createErrorToolResult("Operation aborted"), true)
end
if before !== nothing && before.block
eventSink("prepareToolCall 9")
eventSink("prepareToolCall 9"; publish=false, log=true)
return immediateOutcome(
createErrorToolResult(get(before, :reason, "Tool execution was blocked")), true)
end
end
eventSink("prepareToolCall 10")
eventSink("prepareToolCall 10"; publish=false, log=true)
return preparedToolCall(tool, toolCall, validatedArgs)
catch e
bt = catch_backtrace()
@@ -1047,7 +1028,7 @@ function prepareToolCall(
println(io)
end
eventSink(errMsg)
eventSink(errMsg; publish=false, log=true)
return immediateOutcome(createErrorToolResult(sprint(showerror, e)), true)
end
@@ -1086,7 +1067,7 @@ emitted after the result has already been captured.
```julia
# Successful execution
executePreparedToolCall(prep, nothing, emit)
# => executedOutcome(agentToolResult([textContent("text", "Found 3 wines")], dict{any,any}(), dict{any,any}()), false)
# => executedOutcome(agentToolResult([textContent("text", "Found 3 wines")], Dict{Any,Any}(), Dict{Any,Any}()), false)
# Execution error
executePreparedToolCall(prep, nothing, emit)
@@ -1099,13 +1080,16 @@ function executePreparedToolCall(
signal::Union{Nothing,abortSignal},
eventSink,
)::executedOutcome
eventSink("executePreparedToolCall 1")
eventSink("executePreparedToolCall 1"; publish=false, log=true)
try
try #WORKING
result = prep.tool.execute(prep.toolCall.id, prep.args, signal, eventSink)
eventSink("executePreparedToolCall 2")
eventSink("tool execute raw result: " * sprint(show, result))
eventSink("executePreparedToolCall 3")
eventSink("executePreparedToolCall 2"; publish=false, log=true)
eventSink("tool execute raw result: " * sprint(show, result); publish=false, log=true)
eventSink("executePreparedToolCall 3"; publish=false, log=true)
return executedOutcome(result, false)
catch e
bt = catch_backtrace()
@@ -1113,10 +1097,11 @@ function executePreparedToolCall(
showerror(io, e, bt)
println(io)
end
eventSink(errMsg)
eventSink(errMsg; publish=false, log=true)
return executedOutcome(createErrorToolResult(sprint(showerror, e)), true)
end
end
@@ -1185,23 +1170,21 @@ function finalizeExecutedToolCall(
signal::Union{Nothing,abortSignal},
eventSink
)::finalizedOutcome
eventSink("finalizeExecutedToolCall 1")
eventSink("finalizeExecutedToolCall 1"; publish=false, log=true)
result = executed.result
isError = executed.isError
eventSink("finalizeExecutedToolCall 2")
eventSink("finalizeExecutedToolCall result before afterToolCall\n$result"; publish=false, log=true)
if config.afterToolCall !== nothing
try
after = config.afterToolCall(
afterToolCallContext(assistantMsg, prep.toolCall, prep.args, result, isError, context),
signal
signal,
eventSink
)
eventSink("finalizeExecutedToolCall 3")
eventSink("finalizeExecutedToolCall 3"; publish=false, log=true)
if after !== nothing
result = merge(result, dict(:content=>get(after,:content,result.content),
:details=>get(after,:details,result.details),
:usage=>get(after,:usage,result.usage),
:terminate=>get(after,:terminate,result.terminate)))
isError = get(after, :isError, isError)
result = after
isError = false
end
catch e
bt = catch_backtrace()
@@ -1209,13 +1192,13 @@ function finalizeExecutedToolCall(
showerror(io, e, bt)
println(io)
end
eventSink(errMsg)
eventSink(errMsg; publish=false, log=true)
result = createErrorToolResult(sprint(showerror, e))
isError = true
end
end
eventSink("finalizeExecutedToolCall 4")
eventSink("finalizeExecutedToolCall 4"; publish=false, log=true)
return finalizedOutcome(prep.toolCall, result, isError)
end
@@ -1281,39 +1264,39 @@ function executeToolCallsSequential(
signal::abortSignal,
eventSink,
)::agentToolCallBatch
eventSink("executeToolCallsSequential 1")
eventSink("executeToolCallsSequential 1"; publish=false, log=true)
finalizedCalls = finalizedOutcome[]
messages = toolResultMessage[]
for tc in toolCalls
eventSink("start tool execute $(tc.id), $(tc.name), $(tc.arguments)")
eventSink("start tool execute $(tc.id), $(tc.name), $(tc.arguments)"; publish=false, log=true)
prep = prepareToolCall(context, assistantMsg, tc, config, signal, eventSink)
eventSink("executeToolCallsSequential " * string(prep.args))
eventSink("executeToolCallsSequential " * string(prep.args); publish=false, log=true)
if prep isa immediateOutcome
eventSink("executeToolCallsSequential 2-1")
eventSink("executeToolCallsSequential 2-1"; publish=false, log=true)
finalized = finalizedOutcome(tc, prep.result, prep.isError)
eventSink("executeToolCallsSequential 2-2")
eventSink("executeToolCallsSequential 2-2"; publish=false, log=true)
else
eventSink("executeToolCallsSequential 3")
eventSink("executeToolCallsSequential 3"; publish=false, log=true)
executed = executePreparedToolCall(prep, signal, eventSink)
eventSink("executeToolCallsSequential 3-1")
eventSink("executeToolCallsSequential 3-1"; publish=false, log=true)
finalized = finalizeExecutedToolCall(context, assistantMsg, prep, executed, config,
signal, eventSink)
eventSink("executeToolCallsSequential 3-2")
eventSink("executeToolCallsSequential 3-2"; publish=false, log=true)
end
eventSink("executeToolCallsSequential 4")
eventSink("finalized $(finalized.toolCall.id), $(finalized.toolCall.name),
$(finalized.result), $(finalized.isError)")
eventSink("executeToolCallsSequential 4"; publish=false, log=true)
# eventSink("finalized $(finalized.toolCall.id), $(finalized.toolCall.name),
# $(finalized.result), $(finalized.isError)"; publish=false, log=true)
push!(messages, createToolResultMessage(finalized))
push!(finalizedCalls, finalized)
eventSink("executeToolCallsSequential 5")
eventSink("executeToolCallsSequential 5"; publish=false, log=true)
if signal !== nothing && signal.aborted
break
end
end
eventSink("executeToolCallsSequential 6")
eventSink("executeToolCallsSequential 6"; publish=false, log=true)
return agentToolCallBatch(messages, shouldTerminate(finalizedCalls))
end
@@ -1386,7 +1369,7 @@ function executeToolCallsParallel(
entries = Union{finalizedOutcome,Task}[]
for tc in toolCalls
eventSink(toolExecStartEvent(tc.id, tc.name, tc.arguments))
eventSink(toolExecStartEvent(tc.id, tc.name, tc.arguments); publish=false, log=true)
prep = prepareToolCall(context, assistantMsg, tc, config, signal, eventSink)
@@ -1485,7 +1468,7 @@ function executeToolCalls(
eventSink,
)::agentToolCallBatch
eventSink("_executeToolCalls 1")
eventSink("_executeToolCalls 1"; publish=false, log=true)
hasSequential = false
for tc in toolCalls
t = get(context.tools, tc.name, nothing)
@@ -1494,13 +1477,13 @@ function executeToolCalls(
break
end
end
eventSink("_executeToolCalls 2")
eventSink("_executeToolCalls 2"; publish=false, log=true)
if config.toolExecution == "sequential" || hasSequential
eventSink("_executeToolCalls 3")
eventSink("_executeToolCalls 3"; publish=false, log=true)
return executeToolCallsSequential(context, assistantMsg, toolCalls, config, signal,
eventSink)
else
eventSink("_executeToolCalls 4")
eventSink("_executeToolCalls 4"; publish=false, log=true)
return executeToolCallsParallel(context, assistantMsg, toolCalls, config, signal,
eventSink)
end
@@ -1541,13 +1524,13 @@ OpenAiToUserMessage(msg)
# => userMessage("user", [textContent("Hello")], DateTime(...))
```
"""
function OpenAiToUserMessage(msg::Dict)::userMessage
function OpenAiToUserMessage(msg::AbstractDict)::userMessage
content_blocks = Vector{messageContent}()
raw_content = get(msg, "content", Any[])
if raw_content isa Vector
for block in raw_content
if block isa Dict
if typeof(block) <: AbstractDict
block_type = get(block, "type", "")
if block_type == "text"
text = get(block, "text", "")
@@ -1636,11 +1619,6 @@ end
+10
View File
@@ -38,6 +38,11 @@ function promptAgent(agent::yiemAgent, msg::AbstractDict{String, Any})
return agent
end
function promptAgent(agent::yiemAgent, text::String)
put!(agent.inputChannel, Dict("role" => "user", "content" => [Dict("type" => "text", "text" => text)]))
return agent
end
"""
Take a response from the agent's output channel.
@@ -91,6 +96,11 @@ function followUp(agent::yiemAgent, msg::AbstractDict{String, Any})
return agent
end
function followUp(agent::yiemAgent, text::String)
put!(agent.followUpChannel, Dict("role" => "user", "content" => [Dict("type" => "text", "text" => text)]))
return agent
end
"""
Gracefully stop the agent.
+63 -63
View File
@@ -60,48 +60,48 @@ function _wrap_mcp_tool(mcpserver, tool_def::AbstractDict{String, Any}; eventSin
end
return agentTool(
name=name,
label=title,
description=desc,
inputSchema=params,
execute=(toolCallId::String, args::AbstractDict{String, Any},
signal::Union{Nothing,abortSignal},
eventSink) -> begin
try
response = mcpserver("tools/call", name, args, sessionId)
name=name,
label=title,
description=desc,
inputSchema=params,
execute=(toolCallId::String, args::AbstractDict{String, Any},
signal::Union{Nothing,abortSignal},
eventSink) -> begin
try
response = mcpserver("tools/call", name, args, sessionId; eventSink=eventSink)
# Parse JSON-RPC 2.0 response envelope
if haskey(response, "error")
rpc_error = response["error"]
err_msg = get(rpc_error, "message", "Unknown MCP error")
return agentToolResult(
[textContent("MCP error: $err_msg")],
Dict{Any,Any}("error" => err_msg),
nothing, false
)
end
# Parse JSON-RPC 2.0 response envelope
if haskey(response, "error")
rpc_error = response["error"]
err_msg = get(rpc_error, "message", "Unknown MCP error")
return agentToolResult(
[textContent("MCP error: $err_msg")],
Dict{Any,Any}("error" => err_msg),
nothing, false
)
end
result_data = get(response, "result", response)
content_text = _extract_text_content(result_data)
is_error = get(result_data, "isError", false)
result_data = get(response, "result", response)
content_text = _extract_text_content(result_data)
is_error = get(result_data, "isError", false)
return agentToolResult(
[textContent(content_text)],
Dict{Any,Any}("isError" => is_error),
nothing, false
)
catch e
errMsg = sprint(showerror, e)
return agentToolResult(
[textContent("MCP call error: $errMsg")],
Dict{Any,Any}("error" => errMsg),
nothing, false
)
end
end,
prepareArguments=nothing,
validateRequiredArgs=nothing,
parallelToolExecute=false,
return agentToolResult(
[textContent(content_text)],
Dict{Any,Any}("isError" => is_error),
nothing, false
)
catch e
errMsg = sprint(showerror, e)
return agentToolResult(
[textContent("MCP call error: $errMsg")],
Dict{Any,Any}("error" => errMsg),
nothing, false
)
end
end,
prepareArguments=nothing,
validateRequiredArgs=nothing,
parallelToolExecute=false,
)
end
@@ -122,7 +122,7 @@ function register_mcp_tools(tools::OrderedDict{String, agentTool}, mcpserver; ev
new_count = 0
try
eventSink("register_mcp_tools 1")
response = mcpserver("tools/list", sessionId)
response = mcpserver("tools/list", sessionId; eventSink=eventSink)
eventSink("register_mcp_tools 2")
# Parse JSON-RPC 2.0 response envelope
if haskey(response, "error")
@@ -139,41 +139,41 @@ function register_mcp_tools(tools::OrderedDict{String, agentTool}, mcpserver; ev
cursor = get(response, "nextCursor", nothing)
for tool_def in tools_array
name = tool_def["name"]
if haskey(tools, name)
continue
end
name = tool_def["name"]
if haskey(tools, name)
continue
end
wrapped = _wrap_mcp_tool(mcpserver, tool_def, eventSink=eventSink, sessionId=sessionId)
tools[name] = wrapped
new_count += 1
wrapped = _wrap_mcp_tool(mcpserver, tool_def, eventSink=eventSink, sessionId=sessionId)
tools[name] = wrapped
new_count += 1
end
# Paginate: fetch remaining tools if nextCursor is present
while cursor !== nothing && cursor !== ""
response = sessionId !== nothing ? mcpserver("tools/list", sessionId) : mcpserver("tools/list")
if haskey(response, "result")
response = response["result"]
end
tools_array = get(response, "tools", Any[])
cursor = get(response, "nextCursor", nothing)
for tool_def in tools_array
name = tool_def["name"]
if haskey(tools, name)
continue
end
wrapped = _wrap_mcp_tool(mcpserver, tool_def, eventSink=eventSink, sessionId=sessionId)
tools[name] = wrapped
new_count += 1
response = sessionId !== nothing ? mcpserver("tools/list", sessionId; eventSink=eventSink) : mcpserver("tools/list"; eventSink=eventSink)
if haskey(response, "result")
response = response["result"]
end
tools_array = get(response, "tools", Any[])
cursor = get(response, "nextCursor", nothing)
for tool_def in tools_array
name = tool_def["name"]
if haskey(tools, name)
continue
end
wrapped = _wrap_mcp_tool(mcpserver, tool_def, eventSink=eventSink, sessionId=sessionId)
tools[name] = wrapped
new_count += 1
end
end
println("[toolRegistry] Discovered $new_count MCP tools. Total: $(length(tools))")
catch e
bt = catch_backtrace()
err_msg = sprint() do io
showerror(io, e, bt)
println(io)
showerror(io, e, bt)
println(io)
end
eventSink(err_msg)
+13 -24
View File
@@ -2,8 +2,8 @@ module utils
export clearhistory, availableWineToText, prepareContext, formatMsgForLLM, validateRequiredArgs,
validateToolArguments, _userMessageToOpenAI,
_assistantMessageToOpenAI, _toolResultMessageToOpenAI, _messageContentToBlocks, _toolsToOpenAI,
beforeToolCall, afterToolCall, eventSink
_assistantMessageToOpenAI, _toolResultMessageToOpenAI, _messageContentToBlocks,
_toolsToOpenAI, beforeToolCall, afterToolCall
using UUIDs, Dates, DataStructures, HTTP, JSON, NATS
using GeneralUtils
@@ -96,7 +96,7 @@ or pruning and reordering messages before formatting and calling the LLM.
messages, and tools to be sent to the LLM
# Examples
```julia
```jldoctest
# Default: returns an agentContext with deep copies of system prompt, messages, and tools
prepareContext(state).messages == deepcopy(state.messages)
@@ -146,7 +146,7 @@ LLM message formats for different APIs/providers.
an array of OpenAI-format message dicts
# Examples
```julia
```jldoctest
# Default output:
formatMsgForLLm(ctx) == Dict("messages" => [
Dict("role" => "system", "content" => [...]),
@@ -265,7 +265,7 @@ Common use cases:
- `nothing` is treated as allow (equivalent to `beforeToolCallResult(false, "N/A")`).
# Example
```julia
```jldoctest
function beforeToolCall(context::beforeToolCallContext, signal::abortSignal)
if context.toolCall.name == "deleteFile"
# Block file deletion unless explicitly approved
@@ -275,14 +275,10 @@ function beforeToolCall(context::beforeToolCallContext, signal::abortSignal)
end
```
"""
function beforeToolCall(context::beforeToolCallContext, signal::abortSignal
function beforeToolCall(context::beforeToolCallContext, signal::abortSignal, eventSink
)::beforeToolCallResult
# final context check
# seek user approval via UI
# other check
eventSink("beforeToolCall: $(context.toolCall.name) args=$(context.args)")
return beforeToolCallResult(false, "N/A")
end
@@ -310,22 +306,15 @@ Common use cases:
- `agentToolResult(...)` to return a modified result (content, details, usage,
terminate flag can all be overridden).
"""
function afterToolCall(context::afterToolCallContext, signal::abortSignal
""" #WORKING arrange result from searchWine tool
function afterToolCall(context::afterToolCallContext, signal::abortSignal, eventSink
)::Union{agentToolResult, Nothing}
# modify context.result if needed and return agentToolResult
eventSink("afterToolCall: $(context.toolCall.name) result=$(context.result) error=$(context.isError)")
return nothing
end
#TODO
function eventSink(x)
end
"""
Convert a userMessage to OpenAI message format.
"""
@@ -432,7 +421,7 @@ Convert an OrderedDict of agentTool definitions into OpenAI function tool format
Returns an empty vector when `tools` is `nothing` or empty.
# Examples
```julia
```jldoctest
_toolsToOpenAI(nothing) # => Dict{String, Any}[]
_toolsToOpenAI(tools) # => [Dict("type" => "function", "function" => Dict("name" => "getWeather", ...))]
```
@@ -478,7 +467,7 @@ coercion, format validation, cross-field constraints).
- `String` error message listing missing fields otherwise
# Examples
```julia
```jldoctest
schema = Dict("required" => ["city"])
args = Dict{String,Any}()
validateRequiredArgs(args, schema) # => "Missing required arguments: city"
@@ -533,7 +522,7 @@ false `toolExecutionStart` events.
and converted to an `immediateOutcome`
# Examples
```julia
```jldoctest
# With validateRequiredArgs hook set on the tool
validateToolArguments(toolWithHook, tc) # => validated args or throws