enya-agent 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.
Files changed (389) hide show
  1. package/.env.example +20 -0
  2. package/.github/workflows/ci.yml +70 -0
  3. package/.github/workflows/publish.yml +250 -0
  4. package/.gitmodules +3 -0
  5. package/Cargo.lock +3584 -0
  6. package/Cargo.toml +97 -0
  7. package/crates/enact/Cargo.toml +27 -0
  8. package/crates/enact/src/lib.rs +60 -0
  9. package/crates/enact-a2a/Cargo.toml +25 -0
  10. package/crates/enact-a2a/src/lib.rs +411 -0
  11. package/crates/enact-channels/Cargo.toml +64 -0
  12. package/crates/enact-channels/examples/README.md +80 -0
  13. package/crates/enact-channels/examples/channel_bot.rs +169 -0
  14. package/crates/enact-channels/examples/telegram-echo.rs +34 -0
  15. package/crates/enact-channels/examples/whatsapp-echo.rs +142 -0
  16. package/crates/enact-channels/src/config.rs +213 -0
  17. package/crates/enact-channels/src/lib.rs +25 -0
  18. package/crates/enact-channels/src/runtime.rs +237 -0
  19. package/crates/enact-channels/src/security/mod.rs +5 -0
  20. package/crates/enact-channels/src/security/pairing.rs +205 -0
  21. package/crates/enact-channels/src/teams.rs +601 -0
  22. package/crates/enact-channels/src/telegram.rs +2833 -0
  23. package/crates/enact-channels/src/traits.rs +200 -0
  24. package/crates/enact-channels/src/webhook.rs +262 -0
  25. package/crates/enact-channels/src/whatsapp.rs +310 -0
  26. package/crates/enact-cli/Cargo.toml +40 -0
  27. package/crates/enact-cli/src/commands/doctor.rs +62 -0
  28. package/crates/enact-cli/src/commands/mod.rs +3 -0
  29. package/crates/enact-cli/src/commands/run.rs +69 -0
  30. package/crates/enact-cli/src/commands/serve.rs +81 -0
  31. package/crates/enact-cli/src/config.rs +2 -0
  32. package/crates/enact-cli/src/main.rs +79 -0
  33. package/crates/enact-config/Cargo.toml +36 -0
  34. package/crates/enact-config/ENV_VAR_MAPPING.md +135 -0
  35. package/crates/enact-config/QUICK_REFERENCE.md +92 -0
  36. package/crates/enact-config/README.md +107 -0
  37. package/crates/enact-config/TESTING.md +161 -0
  38. package/crates/enact-config/examples/test-env-vars.rs +100 -0
  39. package/crates/enact-config/src/config.rs +399 -0
  40. package/crates/enact-config/src/encrypted_store.rs +211 -0
  41. package/crates/enact-config/src/lib.rs +298 -0
  42. package/crates/enact-config/src/secrets.rs +149 -0
  43. package/crates/enact-config/src/sync.rs +260 -0
  44. package/crates/enact-config/test-env-vars.sh +34 -0
  45. package/crates/enact-config/tests/README.md +99 -0
  46. package/crates/enact-config/tests/config_integration_test.rs +202 -0
  47. package/crates/enact-config/tests/security_test.rs +140 -0
  48. package/crates/enact-context/Cargo.toml +41 -0
  49. package/crates/enact-context/src/budget.rs +314 -0
  50. package/crates/enact-context/src/calibrator.rs +535 -0
  51. package/crates/enact-context/src/compactor.rs +392 -0
  52. package/crates/enact-context/src/condenser.rs +826 -0
  53. package/crates/enact-context/src/lib.rs +94 -0
  54. package/crates/enact-context/src/segment.rs +238 -0
  55. package/crates/enact-context/src/step_context.rs +645 -0
  56. package/crates/enact-context/src/token_counter.rs +148 -0
  57. package/crates/enact-context/src/window.rs +372 -0
  58. package/crates/enact-core/Cargo.toml +42 -0
  59. package/crates/enact-core/README.md +98 -0
  60. package/crates/enact-core/src/background/executor.rs +524 -0
  61. package/crates/enact-core/src/background/mod.rs +48 -0
  62. package/crates/enact-core/src/background/target_binding.rs +390 -0
  63. package/crates/enact-core/src/background/trigger.rs +511 -0
  64. package/crates/enact-core/src/callable/callable.rs +152 -0
  65. package/crates/enact-core/src/callable/composite.rs +817 -0
  66. package/crates/enact-core/src/callable/graph.rs +104 -0
  67. package/crates/enact-core/src/callable/llm.rs +211 -0
  68. package/crates/enact-core/src/callable/mod.rs +64 -0
  69. package/crates/enact-core/src/callable/registry.rs +206 -0
  70. package/crates/enact-core/src/context/execution_context.rs +757 -0
  71. package/crates/enact-core/src/context/invocation.rs +99 -0
  72. package/crates/enact-core/src/context/mod.rs +50 -0
  73. package/crates/enact-core/src/context/tenant.rs +175 -0
  74. package/crates/enact-core/src/context/trace.rs +127 -0
  75. package/crates/enact-core/src/flow/conditional.rs +293 -0
  76. package/crates/enact-core/src/flow/mod.rs +43 -0
  77. package/crates/enact-core/src/flow/parallel.rs +437 -0
  78. package/crates/enact-core/src/flow/repeat.rs +534 -0
  79. package/crates/enact-core/src/flow/sequential.rs +248 -0
  80. package/crates/enact-core/src/graph/checkpoint.rs +79 -0
  81. package/crates/enact-core/src/graph/checkpoint_store.rs +76 -0
  82. package/crates/enact-core/src/graph/compiled.rs +189 -0
  83. package/crates/enact-core/src/graph/edge.rs +59 -0
  84. package/crates/enact-core/src/graph/graph_schema.rs +218 -0
  85. package/crates/enact-core/src/graph/loader.rs +155 -0
  86. package/crates/enact-core/src/graph/mod.rs +18 -0
  87. package/crates/enact-core/src/graph/node/function.rs +49 -0
  88. package/crates/enact-core/src/graph/node/mod.rs +48 -0
  89. package/crates/enact-core/src/graph/schema.rs +62 -0
  90. package/crates/enact-core/src/inbox/message.rs +405 -0
  91. package/crates/enact-core/src/inbox/mod.rs +31 -0
  92. package/crates/enact-core/src/inbox/store.rs +355 -0
  93. package/crates/enact-core/src/kernel/artifact/filesystem.rs +546 -0
  94. package/crates/enact-core/src/kernel/artifact/metadata.rs +283 -0
  95. package/crates/enact-core/src/kernel/artifact/mod.rs +27 -0
  96. package/crates/enact-core/src/kernel/artifact/store.rs +427 -0
  97. package/crates/enact-core/src/kernel/enforcement.rs +1315 -0
  98. package/crates/enact-core/src/kernel/error.rs +1200 -0
  99. package/crates/enact-core/src/kernel/event.rs +1394 -0
  100. package/crates/enact-core/src/kernel/execution_model.rs +831 -0
  101. package/crates/enact-core/src/kernel/execution_state.rs +189 -0
  102. package/crates/enact-core/src/kernel/execution_strategy.rs +117 -0
  103. package/crates/enact-core/src/kernel/ids.rs +2086 -0
  104. package/crates/enact-core/src/kernel/interrupt.rs +125 -0
  105. package/crates/enact-core/src/kernel/kernel.rs +1283 -0
  106. package/crates/enact-core/src/kernel/mod.rs +205 -0
  107. package/crates/enact-core/src/kernel/persistence/event_store.rs +270 -0
  108. package/crates/enact-core/src/kernel/persistence/message_store.rs +908 -0
  109. package/crates/enact-core/src/kernel/persistence/mod.rs +102 -0
  110. package/crates/enact-core/src/kernel/persistence/state_store.rs +228 -0
  111. package/crates/enact-core/src/kernel/persistence/vector_store.rs +299 -0
  112. package/crates/enact-core/src/kernel/reducer.rs +808 -0
  113. package/crates/enact-core/src/kernel/replay.rs +153 -0
  114. package/crates/enact-core/src/lib.rs +413 -0
  115. package/crates/enact-core/src/memory/episodic.rs +0 -0
  116. package/crates/enact-core/src/memory/mod.rs +6 -0
  117. package/crates/enact-core/src/memory/semantic.rs +0 -0
  118. package/crates/enact-core/src/memory/trait.rs +0 -0
  119. package/crates/enact-core/src/memory/vector_db.rs +0 -0
  120. package/crates/enact-core/src/memory/working.rs +0 -0
  121. package/crates/enact-core/src/policy/execution_policy.rs +292 -0
  122. package/crates/enact-core/src/policy/filters.rs +458 -0
  123. package/crates/enact-core/src/policy/input_processor.rs +407 -0
  124. package/crates/enact-core/src/policy/long_running.rs +134 -0
  125. package/crates/enact-core/src/policy/mod.rs +193 -0
  126. package/crates/enact-core/src/policy/pii_input.rs +274 -0
  127. package/crates/enact-core/src/policy/tenant_policy.rs +453 -0
  128. package/crates/enact-core/src/policy/tool_policy.rs +407 -0
  129. package/crates/enact-core/src/providers/mod.rs +63 -0
  130. package/crates/enact-core/src/providers/trait.rs +292 -0
  131. package/crates/enact-core/src/runner/callbacks.rs +6 -0
  132. package/crates/enact-core/src/runner/execution_runner.rs +476 -0
  133. package/crates/enact-core/src/runner/loop.rs +117 -0
  134. package/crates/enact-core/src/runner/mod.rs +58 -0
  135. package/crates/enact-core/src/runner/protected_runner.rs +280 -0
  136. package/crates/enact-core/src/signal/inmemory.rs +231 -0
  137. package/crates/enact-core/src/signal/mod.rs +108 -0
  138. package/crates/enact-core/src/streaming/event_logger.rs +195 -0
  139. package/crates/enact-core/src/streaming/event_stream.rs +1423 -0
  140. package/crates/enact-core/src/streaming/mod.rs +108 -0
  141. package/crates/enact-core/src/streaming/pause_cancel.rs +0 -0
  142. package/crates/enact-core/src/streaming/protected_emitter.rs +173 -0
  143. package/crates/enact-core/src/streaming/protection/context.rs +136 -0
  144. package/crates/enact-core/src/streaming/protection/encryption.rs +289 -0
  145. package/crates/enact-core/src/streaming/protection/mod.rs +43 -0
  146. package/crates/enact-core/src/streaming/protection/pii_protection.rs +243 -0
  147. package/crates/enact-core/src/streaming/protection/processor.rs +166 -0
  148. package/crates/enact-core/src/streaming/sse.rs +0 -0
  149. package/crates/enact-core/src/telemetry/exporter.rs +0 -0
  150. package/crates/enact-core/src/telemetry/init.rs +0 -0
  151. package/crates/enact-core/src/telemetry/mod.rs +49 -0
  152. package/crates/enact-core/src/telemetry/spans.rs +245 -0
  153. package/crates/enact-core/src/tool/agent_tool.rs +177 -0
  154. package/crates/enact-core/src/tool/browser/mod.rs +0 -0
  155. package/crates/enact-core/src/tool/browser/webdriver.rs +0 -0
  156. package/crates/enact-core/src/tool/cost.rs +247 -0
  157. package/crates/enact-core/src/tool/discovery.rs +0 -0
  158. package/crates/enact-core/src/tool/dispatcher.rs +347 -0
  159. package/crates/enact-core/src/tool/filesystem.rs +231 -0
  160. package/crates/enact-core/src/tool/function.rs +99 -0
  161. package/crates/enact-core/src/tool/git.rs +162 -0
  162. package/crates/enact-core/src/tool/http.rs +214 -0
  163. package/crates/enact-core/src/tool/mcp/client.rs +0 -0
  164. package/crates/enact-core/src/tool/mcp/mod.rs +0 -0
  165. package/crates/enact-core/src/tool/mod.rs +51 -0
  166. package/crates/enact-core/src/tool/reasoning/debugging.rs +0 -0
  167. package/crates/enact-core/src/tool/reasoning/mcts.rs +0 -0
  168. package/crates/enact-core/src/tool/reasoning/mod.rs +0 -0
  169. package/crates/enact-core/src/tool/reasoning/sequential.rs +0 -0
  170. package/crates/enact-core/src/tool/sandbox/dagger.rs +0 -0
  171. package/crates/enact-core/src/tool/sandbox/mod.rs +0 -0
  172. package/crates/enact-core/src/tool/shell.rs +147 -0
  173. package/crates/enact-core/src/tool/trait.rs +33 -0
  174. package/crates/enact-core/src/tool/web_search.rs +277 -0
  175. package/crates/enact-core/src/util/config.rs +0 -0
  176. package/crates/enact-core/src/util/errors.rs +0 -0
  177. package/crates/enact-core/src/util/mod.rs +6 -0
  178. package/crates/enact-core/tests/airgapped_e2e_test.rs +291 -0
  179. package/crates/enact-core/tests/e2e_agentic_loop.rs +119 -0
  180. package/crates/enact-core/tests/e2e_test.rs +259 -0
  181. package/crates/enact-core/tests/graph_test.rs +130 -0
  182. package/crates/enact-core/tests/stream_event_id_validation.rs +435 -0
  183. package/crates/enact-cron/Cargo.toml +28 -0
  184. package/crates/enact-cron/src/lib.rs +44 -0
  185. package/crates/enact-cron/src/schedule.rs +156 -0
  186. package/crates/enact-cron/src/store.rs +589 -0
  187. package/crates/enact-cron/src/types.rs +148 -0
  188. package/crates/enact-gateway/Cargo.toml +31 -0
  189. package/crates/enact-gateway/README.md +30 -0
  190. package/crates/enact-gateway/examples/whatsapp-gateway-runner-mock.rs +59 -0
  191. package/crates/enact-gateway/examples/whatsapp-gateway.rs +42 -0
  192. package/crates/enact-gateway/src/lib.rs +582 -0
  193. package/crates/enact-mcp/Cargo.toml +24 -0
  194. package/crates/enact-mcp/src/lib.rs +178 -0
  195. package/crates/enact-memory/Cargo.toml +25 -0
  196. package/crates/enact-memory/src/backend.rs +20 -0
  197. package/crates/enact-memory/src/chunker.rs +230 -0
  198. package/crates/enact-memory/src/embeddings.rs +221 -0
  199. package/crates/enact-memory/src/lib.rs +67 -0
  200. package/crates/enact-memory/src/markdown.rs +127 -0
  201. package/crates/enact-memory/src/none.rs +61 -0
  202. package/crates/enact-memory/src/sqlite.rs +276 -0
  203. package/crates/enact-memory/src/traits.rs +65 -0
  204. package/crates/enact-memory/src/vector.rs +198 -0
  205. package/crates/enact-oauth/Cargo.toml +27 -0
  206. package/crates/enact-oauth/src/lib.rs +584 -0
  207. package/crates/enact-observability/Cargo.toml +22 -0
  208. package/crates/enact-observability/src/lib.rs +197 -0
  209. package/crates/enact-providers/Cargo.toml +33 -0
  210. package/crates/enact-providers/examples/hello-agent.rs +33 -0
  211. package/crates/enact-providers/src/anthropic.rs +182 -0
  212. package/crates/enact-providers/src/azure.rs +96 -0
  213. package/crates/enact-providers/src/bridge.rs +221 -0
  214. package/crates/enact-providers/src/gemini.rs +227 -0
  215. package/crates/enact-providers/src/http.rs +78 -0
  216. package/crates/enact-providers/src/lib.rs +53 -0
  217. package/crates/enact-providers/src/openai_compatible.rs +167 -0
  218. package/crates/enact-providers/src/openrouter.rs +33 -0
  219. package/crates/enact-runner/Cargo.toml +24 -0
  220. package/crates/enact-runner/README.md +76 -0
  221. package/crates/enact-runner/src/compaction.rs +225 -0
  222. package/crates/enact-runner/src/config.rs +118 -0
  223. package/crates/enact-runner/src/lib.rs +63 -0
  224. package/crates/enact-runner/src/loop_driver.rs +414 -0
  225. package/crates/enact-runner/src/parser.rs +421 -0
  226. package/crates/enact-runner/src/retry.rs +262 -0
  227. package/crates/enact-runner/tests/integration.rs +278 -0
  228. package/crates/enact-security/Cargo.toml +22 -0
  229. package/crates/enact-security/src/audit.rs +375 -0
  230. package/crates/enact-security/src/lib.rs +37 -0
  231. package/crates/enact-security/src/policy.rs +406 -0
  232. package/crates/enact-skills/Cargo.toml +25 -0
  233. package/crates/enact-skills/src/lib.rs +506 -0
  234. package/crates/enact-tools/Cargo.toml +22 -0
  235. package/crates/enact-tools/src/file_read.rs +166 -0
  236. package/crates/enact-tools/src/file_write.rs +216 -0
  237. package/crates/enact-tools/src/git_operations.rs +513 -0
  238. package/crates/enact-tools/src/http_request.rs +417 -0
  239. package/crates/enact-tools/src/lib.rs +104 -0
  240. package/crates/enact-tools/src/security.rs +227 -0
  241. package/crates/enact-tools/src/shell.rs +191 -0
  242. package/crates/enact-tools/src/traits.rs +159 -0
  243. package/docs/Makefile +74 -0
  244. package/docs/config.toml +62 -0
  245. package/docs/content/_index.md +174 -0
  246. package/docs/content/a2a/_index.md +431 -0
  247. package/docs/content/api/_index.md +323 -0
  248. package/docs/content/channels/_index.md +160 -0
  249. package/docs/content/channels/teams.md +205 -0
  250. package/docs/content/channels/telegram.md +182 -0
  251. package/docs/content/channels/webhook.md +423 -0
  252. package/docs/content/channels/whatsapp.md +240 -0
  253. package/docs/content/cli/_index.md +261 -0
  254. package/docs/content/concepts/_index.md +273 -0
  255. package/docs/content/configuration/_index.md +241 -0
  256. package/docs/content/cron/_index.md +248 -0
  257. package/docs/content/developers/_index.md +278 -0
  258. package/docs/content/getting-started/_index.md +180 -0
  259. package/docs/content/installation/_index.md +186 -0
  260. package/docs/content/installation/uninstall.md +101 -0
  261. package/docs/content/installation/updating.md +120 -0
  262. package/docs/content/mcp/_index.md +215 -0
  263. package/docs/content/memory/_index.md +163 -0
  264. package/docs/content/oauth/_index.md +515 -0
  265. package/docs/content/providers/_index.md +206 -0
  266. package/docs/content/roadmap/_index.md +199 -0
  267. package/docs/content/security/_index.md +219 -0
  268. package/docs/content/skills/_index.md +228 -0
  269. package/docs/content/tools/_index.md +485 -0
  270. package/docs/content/troubleshooting/_index.md +259 -0
  271. package/docs/content/yaml-schema/_index.md +294 -0
  272. package/docs/static/giallo-dark.css +91 -0
  273. package/docs/static/giallo-light.css +91 -0
  274. package/docs/themes/tanuki/.github/workflows/deploy.yml +44 -0
  275. package/docs/themes/tanuki/LICENSE +21 -0
  276. package/docs/themes/tanuki/README.md +166 -0
  277. package/docs/themes/tanuki/examples/blog/config.toml +58 -0
  278. package/docs/themes/tanuki/examples/blog/content/_index.md +4 -0
  279. package/docs/themes/tanuki/examples/blog/content/about.md +33 -0
  280. package/docs/themes/tanuki/examples/blog/content/blog/_index.md +7 -0
  281. package/docs/themes/tanuki/examples/blog/content/blog/api-design-best-practices.md +245 -0
  282. package/docs/themes/tanuki/examples/blog/content/blog/building-accessible-websites.md +147 -0
  283. package/docs/themes/tanuki/examples/blog/content/blog/css-grid-vs-flexbox.md +165 -0
  284. package/docs/themes/tanuki/examples/blog/content/blog/customizing-catppuccin-colors.md +137 -0
  285. package/docs/themes/tanuki/examples/blog/content/blog/dark-mode-best-practices.md +82 -0
  286. package/docs/themes/tanuki/examples/blog/content/blog/docker-essentials.md +301 -0
  287. package/docs/themes/tanuki/examples/blog/content/blog/getting-started-with-zola.md +129 -0
  288. package/docs/themes/tanuki/examples/blog/content/blog/git-workflow-for-content.md +112 -0
  289. package/docs/themes/tanuki/examples/blog/content/blog/introduction-to-webassembly.md +183 -0
  290. package/docs/themes/tanuki/examples/blog/content/blog/modern-javascript-features.md +234 -0
  291. package/docs/themes/tanuki/examples/blog/content/blog/testing-strategies.md +311 -0
  292. package/docs/themes/tanuki/examples/blog/content/blog/typography-for-developers.md +104 -0
  293. package/docs/themes/tanuki/examples/blog/content/blog/welcome-to-tanuki.md +67 -0
  294. package/docs/themes/tanuki/examples/blog/content/blog/why-static-sites.md +85 -0
  295. package/docs/themes/tanuki/examples/blog/content/projects.md +64 -0
  296. package/docs/themes/tanuki/examples/book/config.toml +17 -0
  297. package/docs/themes/tanuki/examples/book/content/_index.md +12 -0
  298. package/docs/themes/tanuki/examples/book/content/chapter-1.md +90 -0
  299. package/docs/themes/tanuki/examples/book/content/chapter-2.md +143 -0
  300. package/docs/themes/tanuki/examples/book/content/chapter-3.md +217 -0
  301. package/docs/themes/tanuki/examples/book/content/chapter-4.md +224 -0
  302. package/docs/themes/tanuki/examples/book/content/chapter-5.md +297 -0
  303. package/docs/themes/tanuki/examples/book/content/print.md +6 -0
  304. package/docs/themes/tanuki/examples/docs/config.toml +28 -0
  305. package/docs/themes/tanuki/examples/docs/content/_index.md +20 -0
  306. package/docs/themes/tanuki/examples/docs/content/components.md +156 -0
  307. package/docs/themes/tanuki/examples/docs/content/configuration.md +94 -0
  308. package/docs/themes/tanuki/examples/docs/content/customization.md +202 -0
  309. package/docs/themes/tanuki/examples/docs/content/deployment.md +204 -0
  310. package/docs/themes/tanuki/examples/docs/content/installation.md +59 -0
  311. package/docs/themes/tanuki/examples/docs/content/print.md +6 -0
  312. package/docs/themes/tanuki/examples/docs/static/img/tanuki-icon.avif +0 -0
  313. package/docs/themes/tanuki/examples/index.html +2104 -0
  314. package/docs/themes/tanuki/mise.toml +108 -0
  315. package/docs/themes/tanuki/sass/base/_catppuccin.scss +164 -0
  316. package/docs/themes/tanuki/sass/base/_fonts.scss +64 -0
  317. package/docs/themes/tanuki/sass/base/_reset.scss +152 -0
  318. package/docs/themes/tanuki/sass/base/_typography.scss +523 -0
  319. package/docs/themes/tanuki/sass/components/_buttons.scss +209 -0
  320. package/docs/themes/tanuki/sass/components/_code.scss +457 -0
  321. package/docs/themes/tanuki/sass/components/_landing.scss +633 -0
  322. package/docs/themes/tanuki/sass/components/_layout.scss +294 -0
  323. package/docs/themes/tanuki/sass/components/_navigation.scss +1200 -0
  324. package/docs/themes/tanuki/sass/components/_print.scss +237 -0
  325. package/docs/themes/tanuki/sass/components/_search.scss +224 -0
  326. package/docs/themes/tanuki/sass/components/_sidebar.scss +473 -0
  327. package/docs/themes/tanuki/sass/components/_theme-toggle.scss +186 -0
  328. package/docs/themes/tanuki/sass/modes/_blog.scss +366 -0
  329. package/docs/themes/tanuki/sass/modes/_product.scss +875 -0
  330. package/docs/themes/tanuki/sass/modes/_raskell.scss +1696 -0
  331. package/docs/themes/tanuki/sass/patterns/_buttons.scss +183 -0
  332. package/docs/themes/tanuki/sass/patterns/_cards.scss +144 -0
  333. package/docs/themes/tanuki/sass/patterns/_index.scss +9 -0
  334. package/docs/themes/tanuki/sass/patterns/_lists.scss +259 -0
  335. package/docs/themes/tanuki/sass/patterns/_sections.scss +243 -0
  336. package/docs/themes/tanuki/sass/style.scss +47 -0
  337. package/docs/themes/tanuki/sass/tokens/_colors.scss +139 -0
  338. package/docs/themes/tanuki/sass/tokens/_spacing.scss +100 -0
  339. package/docs/themes/tanuki/sass/tokens/_typography.scss +186 -0
  340. package/docs/themes/tanuki/screenshot.png +0 -0
  341. package/docs/themes/tanuki/sentinel.kdl +59 -0
  342. package/docs/themes/tanuki/static/elasticlunr.min.js +10 -0
  343. package/docs/themes/tanuki/static/fonts/GEIST-LICENSE.txt +92 -0
  344. package/docs/themes/tanuki/static/fonts/Geist-Variable.woff2 +0 -0
  345. package/docs/themes/tanuki/static/fonts/GeistMono-Variable.woff2 +0 -0
  346. package/docs/themes/tanuki/static/img/tanuki-icon.avif +0 -0
  347. package/docs/themes/tanuki/static/img/tanuki-icon.png +0 -0
  348. package/docs/themes/tanuki/static/js/anchors.js +18 -0
  349. package/docs/themes/tanuki/static/js/app.js +274 -0
  350. package/docs/themes/tanuki/static/js/code.js +394 -0
  351. package/docs/themes/tanuki/static/js/navigation.js +778 -0
  352. package/docs/themes/tanuki/static/js/scroll-to-top.js +33 -0
  353. package/docs/themes/tanuki/static/js/search-raskell.js +240 -0
  354. package/docs/themes/tanuki/static/js/search.js +215 -0
  355. package/docs/themes/tanuki/static/js/theme.js +169 -0
  356. package/docs/themes/tanuki/static/syntax-dark.css +151 -0
  357. package/docs/themes/tanuki/static/syntax-light.css +151 -0
  358. package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm.js +486 -0
  359. package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm_bg.wasm +0 -0
  360. package/docs/themes/tanuki/templates/404.html +52 -0
  361. package/docs/themes/tanuki/templates/base.html +428 -0
  362. package/docs/themes/tanuki/templates/blog.html +66 -0
  363. package/docs/themes/tanuki/templates/home.html +108 -0
  364. package/docs/themes/tanuki/templates/index.html +178 -0
  365. package/docs/themes/tanuki/templates/landing.html +168 -0
  366. package/docs/themes/tanuki/templates/macros/nav.html +128 -0
  367. package/docs/themes/tanuki/templates/macros/posts.html +101 -0
  368. package/docs/themes/tanuki/templates/macros/ui.html +159 -0
  369. package/docs/themes/tanuki/templates/page.html +135 -0
  370. package/docs/themes/tanuki/templates/partials/footer.html +38 -0
  371. package/docs/themes/tanuki/templates/partials/header.html +366 -0
  372. package/docs/themes/tanuki/templates/partials/nav-buttons.html +55 -0
  373. package/docs/themes/tanuki/templates/partials/nav-overlay.html +81 -0
  374. package/docs/themes/tanuki/templates/partials/page-toc-panel.html +43 -0
  375. package/docs/themes/tanuki/templates/partials/search.html +52 -0
  376. package/docs/themes/tanuki/templates/partials/sidebar.html +107 -0
  377. package/docs/themes/tanuki/templates/partials/theme-toggle.html +35 -0
  378. package/docs/themes/tanuki/templates/partials/toc-overlay.html +146 -0
  379. package/docs/themes/tanuki/templates/partials/version-picker.html +38 -0
  380. package/docs/themes/tanuki/templates/print.html +244 -0
  381. package/docs/themes/tanuki/templates/section.html +186 -0
  382. package/docs/themes/tanuki/templates/taxonomy_list.html +18 -0
  383. package/docs/themes/tanuki/templates/taxonomy_single.html +31 -0
  384. package/docs/themes/tanuki/theme.toml +58 -0
  385. package/examples/hello-agent.rs +55 -0
  386. package/package.json +36 -0
  387. package/proto/config.proto +60 -0
  388. package/proto/events.proto +0 -0
  389. package/proto/runtime.proto +215 -0
@@ -0,0 +1,347 @@
1
+ //! Tool Executor - Policy-aware tool execution
2
+ //!
3
+ //! This module ensures that EVERY tool invocation passes through policy
4
+ //! evaluation before execution. This is a critical security boundary.
5
+ //!
6
+ //! ## Usage
7
+ //! ```ignore
8
+ //! let policy = ToolPolicy::new();
9
+ //! let executor = ToolExecutor::new(policy);
10
+ //!
11
+ //! // This will evaluate policy before executing
12
+ //! let result = executor.execute(&tool, args, &ctx).await?;
13
+ //! ```
14
+
15
+ use crate::context::TenantContext;
16
+ use crate::kernel::{ExecutionId, StepId};
17
+ use crate::policy::{PolicyAction, PolicyContext, PolicyDecision, PolicyEvaluator, ToolPolicy};
18
+ use crate::streaming::{EventEmitter, StreamEvent};
19
+ use super::Tool;
20
+ use serde_json::Value;
21
+ use std::sync::Arc;
22
+
23
+ /// Error returned when tool execution is denied by policy
24
+ #[derive(Debug, thiserror::Error)]
25
+ pub enum ToolExecutionError {
26
+ #[error("Tool execution denied: {reason}")]
27
+ PolicyDenied { reason: String },
28
+
29
+ #[error("Tool execution error: {0}")]
30
+ ExecutionFailed(#[from] anyhow::Error),
31
+ }
32
+
33
+ /// Context for tool execution
34
+ #[derive(Debug, Clone)]
35
+ pub struct ToolExecutionContext {
36
+ /// Execution ID
37
+ pub execution_id: ExecutionId,
38
+ /// Step ID
39
+ pub step_id: Option<StepId>,
40
+ /// Tenant context (REQUIRED)
41
+ pub tenant: TenantContext,
42
+ /// Additional metadata
43
+ pub metadata: std::collections::HashMap<String, String>,
44
+ }
45
+
46
+ impl ToolExecutionContext {
47
+ /// Create a new tool execution context
48
+ pub fn new(execution_id: ExecutionId, tenant: TenantContext) -> Self {
49
+ Self {
50
+ execution_id,
51
+ step_id: None,
52
+ tenant,
53
+ metadata: std::collections::HashMap::new(),
54
+ }
55
+ }
56
+
57
+ /// Set step ID
58
+ pub fn with_step(mut self, step_id: StepId) -> Self {
59
+ self.step_id = Some(step_id);
60
+ self
61
+ }
62
+ }
63
+
64
+ /// Policy-aware tool executor
65
+ ///
66
+ /// This executor ensures that every tool invocation passes through
67
+ /// ToolPolicy::evaluate() before execution. Policy decisions are emitted
68
+ /// as events for audit trail when an emitter is configured.
69
+ pub struct ToolExecutor {
70
+ policy: Arc<ToolPolicy>,
71
+ /// Optional event emitter for policy decision audit trail
72
+ emitter: Option<EventEmitter>,
73
+ }
74
+
75
+ impl ToolExecutor {
76
+ /// Create a new tool executor with the given policy
77
+ pub fn new(policy: ToolPolicy) -> Self {
78
+ Self {
79
+ policy: Arc::new(policy),
80
+ emitter: None,
81
+ }
82
+ }
83
+
84
+ /// Create with a shared policy
85
+ pub fn with_shared_policy(policy: Arc<ToolPolicy>) -> Self {
86
+ Self {
87
+ policy,
88
+ emitter: None,
89
+ }
90
+ }
91
+
92
+ /// Configure an event emitter for policy decision audit trail
93
+ pub fn with_emitter(mut self, emitter: EventEmitter) -> Self {
94
+ self.emitter = Some(emitter);
95
+ self
96
+ }
97
+
98
+ /// Set the event emitter for policy decision audit trail
99
+ pub fn set_emitter(&mut self, emitter: EventEmitter) {
100
+ self.emitter = Some(emitter);
101
+ }
102
+
103
+ /// Execute a tool with policy enforcement
104
+ ///
105
+ /// This is the ONLY way to execute tools - it ensures policy is always checked.
106
+ /// Policy decisions are emitted as events for audit trail when an emitter is configured.
107
+ pub async fn execute(
108
+ &self,
109
+ tool: &dyn Tool,
110
+ args: Value,
111
+ ctx: &ToolExecutionContext,
112
+ ) -> Result<Value, ToolExecutionError> {
113
+ // Create policy context
114
+ let policy_ctx = PolicyContext {
115
+ tenant_id: Some(ctx.tenant.tenant_id().as_str().to_string()),
116
+ user_id: ctx.tenant.user_id().map(|u| u.as_str().to_string()),
117
+ action: PolicyAction::InvokeTool {
118
+ tool_name: tool.name().to_string(),
119
+ },
120
+ metadata: ctx.metadata.clone(),
121
+ };
122
+
123
+ let tool_name = tool.name().to_string();
124
+
125
+ // Evaluate policy and emit decision event for audit trail
126
+ match self.policy.evaluate(&policy_ctx) {
127
+ PolicyDecision::Allow => {
128
+ // Emit allow decision event
129
+ if let Some(emitter) = &self.emitter {
130
+ emitter.emit(StreamEvent::policy_decision_allow(
131
+ &ctx.execution_id,
132
+ ctx.step_id.as_ref(),
133
+ &tool_name,
134
+ ));
135
+ }
136
+ // Policy allows execution
137
+ tool.execute(args).await.map_err(ToolExecutionError::from)
138
+ }
139
+ PolicyDecision::Deny { reason } => {
140
+ // Emit deny decision event
141
+ if let Some(emitter) = &self.emitter {
142
+ emitter.emit(StreamEvent::policy_decision_deny(
143
+ &ctx.execution_id,
144
+ ctx.step_id.as_ref(),
145
+ &tool_name,
146
+ &reason,
147
+ ));
148
+ }
149
+ Err(ToolExecutionError::PolicyDenied { reason })
150
+ }
151
+ PolicyDecision::Warn { message } => {
152
+ // Emit warn decision event
153
+ if let Some(emitter) = &self.emitter {
154
+ emitter.emit(StreamEvent::policy_decision_warn(
155
+ &ctx.execution_id,
156
+ ctx.step_id.as_ref(),
157
+ &tool_name,
158
+ &message,
159
+ ));
160
+ }
161
+ // Policy warns but allows - log and continue
162
+ tracing::warn!(tool = tool.name(), message = %message, "Tool policy warning");
163
+ tool.execute(args).await.map_err(ToolExecutionError::from)
164
+ }
165
+ }
166
+ }
167
+
168
+ /// Execute multiple tools in sequence, stopping on first policy denial
169
+ pub async fn execute_sequence(
170
+ &self,
171
+ tools: &[(Arc<dyn Tool>, Value)],
172
+ ctx: &ToolExecutionContext,
173
+ ) -> Result<Vec<Value>, ToolExecutionError> {
174
+ let mut results = Vec::new();
175
+ for (tool, args) in tools {
176
+ let result = self.execute(tool.as_ref(), args.clone(), ctx).await?;
177
+ results.push(result);
178
+ }
179
+ Ok(results)
180
+ }
181
+
182
+ /// Check if a tool would be allowed by policy (without executing)
183
+ pub fn is_allowed(&self, tool_name: &str, ctx: &ToolExecutionContext) -> bool {
184
+ let policy_ctx = PolicyContext {
185
+ tenant_id: Some(ctx.tenant.tenant_id().as_str().to_string()),
186
+ user_id: ctx.tenant.user_id().map(|u| u.as_str().to_string()),
187
+ action: PolicyAction::InvokeTool {
188
+ tool_name: tool_name.to_string(),
189
+ },
190
+ metadata: std::collections::HashMap::new(),
191
+ };
192
+
193
+ matches!(
194
+ self.policy.evaluate(&policy_ctx),
195
+ PolicyDecision::Allow | PolicyDecision::Warn { .. }
196
+ )
197
+ }
198
+
199
+ /// Get the permissions for a tool
200
+ pub fn get_permissions(&self, tool_name: &str) -> &crate::policy::ToolPermissions {
201
+ self.policy.get_permissions(tool_name)
202
+ }
203
+
204
+ /// Get the policy reference
205
+ pub fn policy(&self) -> &ToolPolicy {
206
+ &self.policy
207
+ }
208
+ }
209
+
210
+ impl Default for ToolExecutor {
211
+ fn default() -> Self {
212
+ Self::new(ToolPolicy::default())
213
+ }
214
+ }
215
+
216
+ #[cfg(test)]
217
+ mod tests {
218
+ use super::*;
219
+ use crate::kernel::TenantId;
220
+ use async_trait::async_trait;
221
+
222
+ struct MockTool {
223
+ name: String,
224
+ }
225
+
226
+ #[async_trait]
227
+ impl Tool for MockTool {
228
+ fn name(&self) -> &str {
229
+ &self.name
230
+ }
231
+
232
+ fn description(&self) -> &str {
233
+ "Mock tool for testing"
234
+ }
235
+
236
+ async fn execute(&self, args: Value) -> anyhow::Result<Value> {
237
+ Ok(args)
238
+ }
239
+ }
240
+
241
+ #[tokio::test]
242
+ async fn test_tool_execution_allowed() {
243
+ let policy = ToolPolicy::new();
244
+ let executor = ToolExecutor::new(policy);
245
+
246
+ let tool = MockTool {
247
+ name: "test_tool".to_string(),
248
+ };
249
+ let ctx = ToolExecutionContext::new(
250
+ ExecutionId::new(),
251
+ TenantContext::new(TenantId::from("tenant_123")),
252
+ );
253
+
254
+ let result = executor.execute(&tool, Value::String("test".into()), &ctx).await;
255
+ assert!(result.is_ok());
256
+ }
257
+
258
+ #[tokio::test]
259
+ async fn test_tool_execution_blocked() {
260
+ let policy = ToolPolicy::new().block_tool("blocked_tool");
261
+ let executor = ToolExecutor::new(policy);
262
+
263
+ let tool = MockTool {
264
+ name: "blocked_tool".to_string(),
265
+ };
266
+ let ctx = ToolExecutionContext::new(
267
+ ExecutionId::new(),
268
+ TenantContext::new(TenantId::from("tenant_123")),
269
+ );
270
+
271
+ let result = executor.execute(&tool, Value::Null, &ctx).await;
272
+ assert!(matches!(result, Err(ToolExecutionError::PolicyDenied { .. })));
273
+ }
274
+
275
+ #[tokio::test]
276
+ async fn test_is_allowed() {
277
+ let policy = ToolPolicy::new().block_tool("blocked_tool");
278
+ let executor = ToolExecutor::new(policy);
279
+
280
+ let ctx = ToolExecutionContext::new(
281
+ ExecutionId::new(),
282
+ TenantContext::new(TenantId::from("tenant_123")),
283
+ );
284
+
285
+ assert!(executor.is_allowed("allowed_tool", &ctx));
286
+ assert!(!executor.is_allowed("blocked_tool", &ctx));
287
+ }
288
+
289
+ #[tokio::test]
290
+ async fn test_policy_decision_event_emission_allowed() {
291
+ let policy = ToolPolicy::new();
292
+ let emitter = EventEmitter::new();
293
+ let executor = ToolExecutor::new(policy).with_emitter(emitter.clone());
294
+
295
+ let tool = MockTool {
296
+ name: "test_tool".to_string(),
297
+ };
298
+ let ctx = ToolExecutionContext::new(
299
+ ExecutionId::new(),
300
+ TenantContext::new(TenantId::from("tenant_123")),
301
+ );
302
+
303
+ let result = executor.execute(&tool, Value::Null, &ctx).await;
304
+ assert!(result.is_ok());
305
+
306
+ // Check that allow event was emitted
307
+ let events = emitter.drain();
308
+ assert_eq!(events.len(), 1);
309
+ match &events[0] {
310
+ StreamEvent::PolicyDecision { decision, tool_name, .. } => {
311
+ assert_eq!(decision, "allow");
312
+ assert_eq!(tool_name, "test_tool");
313
+ }
314
+ _ => panic!("Expected PolicyDecision event"),
315
+ }
316
+ }
317
+
318
+ #[tokio::test]
319
+ async fn test_policy_decision_event_emission_denied() {
320
+ let policy = ToolPolicy::new().block_tool("blocked_tool");
321
+ let emitter = EventEmitter::new();
322
+ let executor = ToolExecutor::new(policy).with_emitter(emitter.clone());
323
+
324
+ let tool = MockTool {
325
+ name: "blocked_tool".to_string(),
326
+ };
327
+ let ctx = ToolExecutionContext::new(
328
+ ExecutionId::new(),
329
+ TenantContext::new(TenantId::from("tenant_123")),
330
+ );
331
+
332
+ let result = executor.execute(&tool, Value::Null, &ctx).await;
333
+ assert!(matches!(result, Err(ToolExecutionError::PolicyDenied { .. })));
334
+
335
+ // Check that deny event was emitted
336
+ let events = emitter.drain();
337
+ assert_eq!(events.len(), 1);
338
+ match &events[0] {
339
+ StreamEvent::PolicyDecision { decision, tool_name, reason, .. } => {
340
+ assert_eq!(decision, "deny");
341
+ assert_eq!(tool_name, "blocked_tool");
342
+ assert!(reason.is_some());
343
+ }
344
+ _ => panic!("Expected PolicyDecision event"),
345
+ }
346
+ }
347
+ }
@@ -0,0 +1,231 @@
1
+ //! File system tools for reading and writing files
2
+
3
+ use crate::tool::Tool;
4
+ use async_trait::async_trait;
5
+ use serde_json::json;
6
+ use std::path::Path;
7
+
8
+ const MAX_FILE_SIZE_BYTES: u64 = 10 * 1024 * 1024; // 10MB
9
+
10
+ /// Read file contents from workspace
11
+ pub struct FileReadTool;
12
+
13
+ impl FileReadTool {
14
+ pub fn new() -> Self {
15
+ Self
16
+ }
17
+ }
18
+
19
+ impl Default for FileReadTool {
20
+ fn default() -> Self {
21
+ Self::new()
22
+ }
23
+ }
24
+
25
+ #[async_trait]
26
+ impl Tool for FileReadTool {
27
+ fn name(&self) -> &str {
28
+ "file_read"
29
+ }
30
+
31
+ fn description(&self) -> &str {
32
+ "Read the contents of a file in the workspace"
33
+ }
34
+
35
+ fn parameters_schema(&self) -> serde_json::Value {
36
+ json!({
37
+ "type": "object",
38
+ "properties": {
39
+ "path": {
40
+ "type": "string",
41
+ "description": "Relative path to the file within the workspace"
42
+ }
43
+ },
44
+ "required": ["path"]
45
+ })
46
+ }
47
+
48
+ fn requires_network(&self) -> bool {
49
+ false
50
+ }
51
+
52
+ async fn execute(&self, args: serde_json::Value) -> anyhow::Result<serde_json::Value> {
53
+ let path = args
54
+ .get("path")
55
+ .and_then(|v| v.as_str())
56
+ .ok_or_else(|| anyhow::anyhow!("Missing 'path' parameter"))?;
57
+
58
+ let path = Path::new(path);
59
+
60
+ // Security: Prevent directory traversal
61
+ if path.components().any(|c| matches!(c, std::path::Component::ParentDir)) {
62
+ anyhow::bail!("Path cannot contain '..' (directory traversal not allowed)");
63
+ }
64
+
65
+ // Check file exists
66
+ if !path.exists() {
67
+ anyhow::bail!("File not found: {}", path.display());
68
+ }
69
+
70
+ // Check it's a file
71
+ if !path.is_file() {
72
+ anyhow::bail!("Path is not a file: {}", path.display());
73
+ }
74
+
75
+ // Check file size
76
+ let metadata = tokio::fs::metadata(path).await?;
77
+ if metadata.len() > MAX_FILE_SIZE_BYTES {
78
+ anyhow::bail!(
79
+ "File too large: {} bytes (max: {} bytes)",
80
+ metadata.len(),
81
+ MAX_FILE_SIZE_BYTES
82
+ );
83
+ }
84
+
85
+ let content = tokio::fs::read_to_string(path).await?;
86
+
87
+ Ok(json!({
88
+ "success": true,
89
+ "content": content,
90
+ "path": path.to_string_lossy().to_string(),
91
+ "size": metadata.len()
92
+ }))
93
+ }
94
+ }
95
+
96
+ /// Write file contents to workspace
97
+ pub struct FileWriteTool;
98
+
99
+ impl FileWriteTool {
100
+ pub fn new() -> Self {
101
+ Self
102
+ }
103
+ }
104
+
105
+ impl Default for FileWriteTool {
106
+ fn default() -> Self {
107
+ Self::new()
108
+ }
109
+ }
110
+
111
+ #[async_trait]
112
+ impl Tool for FileWriteTool {
113
+ fn name(&self) -> &str {
114
+ "file_write"
115
+ }
116
+
117
+ fn description(&self) -> &str {
118
+ "Write content to a file in the workspace (creates or overwrites)"
119
+ }
120
+
121
+ fn parameters_schema(&self) -> serde_json::Value {
122
+ json!({
123
+ "type": "object",
124
+ "properties": {
125
+ "path": {
126
+ "type": "string",
127
+ "description": "Relative path to the file within the workspace"
128
+ },
129
+ "content": {
130
+ "type": "string",
131
+ "description": "Content to write to the file"
132
+ }
133
+ },
134
+ "required": ["path", "content"]
135
+ })
136
+ }
137
+
138
+ fn requires_network(&self) -> bool {
139
+ false
140
+ }
141
+
142
+ async fn execute(&self, args: serde_json::Value) -> anyhow::Result<serde_json::Value> {
143
+ let path = args
144
+ .get("path")
145
+ .and_then(|v| v.as_str())
146
+ .ok_or_else(|| anyhow::anyhow!("Missing 'path' parameter"))?;
147
+
148
+ let content = args
149
+ .get("content")
150
+ .and_then(|v| v.as_str())
151
+ .ok_or_else(|| anyhow::anyhow!("Missing 'content' parameter"))?;
152
+
153
+ let path = Path::new(path);
154
+
155
+ // Security: Prevent directory traversal
156
+ if path.components().any(|c| matches!(c, std::path::Component::ParentDir)) {
157
+ anyhow::bail!("Path cannot contain '..' (directory traversal not allowed)");
158
+ }
159
+
160
+ // Create parent directories if needed
161
+ if let Some(parent) = path.parent() {
162
+ tokio::fs::create_dir_all(parent).await?;
163
+ }
164
+
165
+ tokio::fs::write(path, content).await?;
166
+
167
+ let metadata = tokio::fs::metadata(path).await?;
168
+
169
+ Ok(json!({
170
+ "success": true,
171
+ "path": path.to_string_lossy().to_string(),
172
+ "size": metadata.len(),
173
+ "message": "File written successfully"
174
+ }))
175
+ }
176
+ }
177
+
178
+ #[cfg(test)]
179
+ mod tests {
180
+ use super::*;
181
+
182
+ #[tokio::test]
183
+ async fn test_file_read_success() {
184
+ let tool = FileReadTool::new();
185
+
186
+ // Create a test file
187
+ let test_content = "Hello, World!";
188
+ tokio::fs::write("/tmp/test_read.txt", test_content).await.unwrap();
189
+
190
+ let result = tool.execute(json!({"path": "/tmp/test_read.txt"})).await.unwrap();
191
+ assert_eq!(result["success"], true);
192
+ assert_eq!(result["content"], test_content);
193
+
194
+ // Cleanup
195
+ tokio::fs::remove_file("/tmp/test_read.txt").await.ok();
196
+ }
197
+
198
+ #[tokio::test]
199
+ async fn test_file_write_success() {
200
+ let tool = FileWriteTool::new();
201
+
202
+ let result = tool.execute(json!({
203
+ "path": "/tmp/test_write.txt",
204
+ "content": "Test content"
205
+ })).await.unwrap();
206
+
207
+ assert_eq!(result["success"], true);
208
+
209
+ // Verify file was written
210
+ let content = tokio::fs::read_to_string("/tmp/test_write.txt").await.unwrap();
211
+ assert_eq!(content, "Test content");
212
+
213
+ // Cleanup
214
+ tokio::fs::remove_file("/tmp/test_write.txt").await.ok();
215
+ }
216
+
217
+ #[tokio::test]
218
+ async fn test_file_read_not_found() {
219
+ let tool = FileReadTool::new();
220
+ let result = tool.execute(json!({"path": "/tmp/nonexistent_file_xyz.txt"})).await;
221
+ assert!(result.is_err());
222
+ }
223
+
224
+ #[tokio::test]
225
+ async fn test_file_read_traversal_prevention() {
226
+ let tool = FileReadTool::new();
227
+ let result = tool.execute(json!({"path": "../etc/passwd"})).await;
228
+ assert!(result.is_err());
229
+ assert!(result.unwrap_err().to_string().contains("directory traversal"));
230
+ }
231
+ }
@@ -0,0 +1,99 @@
1
+ //! Function tool - wrap any async function as a tool
2
+
3
+ use super::Tool;
4
+ use async_trait::async_trait;
5
+ use serde_json::Value;
6
+ use std::future::Future;
7
+ use std::pin::Pin;
8
+ use std::sync::Arc;
9
+
10
+ /// Type alias for async tool functions
11
+ pub type ToolFn = Arc<
12
+ dyn Fn(Value) -> Pin<Box<dyn Future<Output = anyhow::Result<Value>> + Send>>
13
+ + Send
14
+ + Sync,
15
+ >;
16
+
17
+ /// FunctionTool - wraps an async closure as a tool
18
+ pub struct FunctionTool {
19
+ name: String,
20
+ description: String,
21
+ parameters: Value,
22
+ func: ToolFn,
23
+ }
24
+
25
+ impl FunctionTool {
26
+ /// Create a new function tool
27
+ pub fn new<F, Fut>(
28
+ name: impl Into<String>,
29
+ description: impl Into<String>,
30
+ parameters: Value,
31
+ func: F,
32
+ ) -> Self
33
+ where
34
+ F: Fn(Value) -> Fut + Send + Sync + 'static,
35
+ Fut: Future<Output = anyhow::Result<Value>> + Send + 'static,
36
+ {
37
+ let func = Arc::new(move |args: Value| {
38
+ let fut = func(args);
39
+ Box::pin(fut) as Pin<Box<dyn Future<Output = anyhow::Result<Value>> + Send>>
40
+ });
41
+
42
+ Self {
43
+ name: name.into(),
44
+ description: description.into(),
45
+ parameters,
46
+ func,
47
+ }
48
+ }
49
+
50
+ /// Create a simple tool with no parameters
51
+ pub fn simple<F, Fut>(name: impl Into<String>, description: impl Into<String>, func: F) -> Self
52
+ where
53
+ F: Fn(Value) -> Fut + Send + Sync + 'static,
54
+ Fut: Future<Output = anyhow::Result<Value>> + Send + 'static,
55
+ {
56
+ Self::new(
57
+ name,
58
+ description,
59
+ serde_json::json!({
60
+ "type": "object",
61
+ "properties": {},
62
+ "required": []
63
+ }),
64
+ func,
65
+ )
66
+ }
67
+ }
68
+
69
+ #[async_trait]
70
+ impl Tool for FunctionTool {
71
+ fn name(&self) -> &str {
72
+ &self.name
73
+ }
74
+
75
+ fn description(&self) -> &str {
76
+ &self.description
77
+ }
78
+
79
+ fn parameters_schema(&self) -> Value {
80
+ self.parameters.clone()
81
+ }
82
+
83
+ async fn execute(&self, args: Value) -> anyhow::Result<Value> {
84
+ (self.func)(args).await
85
+ }
86
+ }
87
+
88
+ /// Macro to create a FunctionTool more easily
89
+ #[macro_export]
90
+ macro_rules! tool {
91
+ ($name:expr, $desc:expr, |$args:ident| $body:expr) => {
92
+ FunctionTool::new(
93
+ $name,
94
+ $desc,
95
+ serde_json::json!({"type": "object", "properties": {}}),
96
+ |$args| async move { $body },
97
+ )
98
+ };
99
+ }