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,817 @@
1
+ //! Composite Callable - Callables that invoke other callables
2
+ //!
3
+ //! This module provides the infrastructure for:
4
+ //! - Callable-within-callable invocation
5
+ //! - Dynamic callable discovery
6
+ //! - Resource allocation strategies
7
+ //!
8
+ //! @see packages/enact-schemas/src/execution.schemas.ts
9
+
10
+ use serde::{Deserialize, Serialize};
11
+ use std::collections::HashMap;
12
+ use std::time::Instant;
13
+
14
+ use super::{Callable, CallableRegistry, DynCallable};
15
+ use crate::kernel::ids::{CallableType, ExecutionId, SpawnMode};
16
+ use crate::kernel::TokenUsage;
17
+
18
+ /// CostTier - Estimated cost tier for resource planning
19
+ /// @see packages/enact-schemas/src/execution.schemas.ts - callableDescriptorSchema.costTier
20
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
21
+ #[serde(rename_all = "snake_case")]
22
+ pub enum CostTier {
23
+ Free,
24
+ Low,
25
+ #[default]
26
+ Medium,
27
+ High,
28
+ Premium,
29
+ }
30
+
31
+ /// CallableDescriptor - Describes a callable for discovery
32
+ /// @see packages/enact-schemas/src/execution.schemas.ts - callableDescriptorSchema
33
+ #[derive(Debug, Clone, Serialize, Deserialize)]
34
+ #[serde(rename_all = "camelCase")]
35
+ pub struct CallableDescriptor {
36
+ /// Callable name (unique identifier)
37
+ pub name: String,
38
+
39
+ /// Human-readable description
40
+ #[serde(skip_serializing_if = "Option::is_none")]
41
+ pub description: Option<String>,
42
+
43
+ /// Callable type
44
+ pub callable_type: CallableType,
45
+
46
+ /// Input schema (JSON Schema)
47
+ #[serde(skip_serializing_if = "Option::is_none")]
48
+ pub input_schema: Option<serde_json::Value>,
49
+
50
+ /// Output schema (JSON Schema)
51
+ #[serde(skip_serializing_if = "Option::is_none")]
52
+ pub output_schema: Option<serde_json::Value>,
53
+
54
+ /// Tags for categorization
55
+ #[serde(default)]
56
+ pub tags: Vec<String>,
57
+
58
+ /// Whether this callable can spawn children
59
+ #[serde(default)]
60
+ pub can_spawn_children: bool,
61
+
62
+ /// Estimated cost tier
63
+ #[serde(default)]
64
+ pub cost_tier: CostTier,
65
+
66
+ /// Average latency in milliseconds
67
+ #[serde(skip_serializing_if = "Option::is_none")]
68
+ pub avg_latency_ms: Option<u64>,
69
+ }
70
+
71
+ impl CallableDescriptor {
72
+ /// Create a new descriptor from a callable
73
+ pub fn from_callable(callable: &dyn Callable, callable_type: CallableType) -> Self {
74
+ Self {
75
+ name: callable.name().to_string(),
76
+ description: callable.description().map(String::from),
77
+ callable_type,
78
+ input_schema: None,
79
+ output_schema: None,
80
+ tags: Vec::new(),
81
+ can_spawn_children: false,
82
+ cost_tier: CostTier::Medium,
83
+ avg_latency_ms: None,
84
+ }
85
+ }
86
+
87
+ /// Add tags
88
+ pub fn with_tags(mut self, tags: Vec<String>) -> Self {
89
+ self.tags = tags;
90
+ self
91
+ }
92
+
93
+ /// Set cost tier
94
+ pub fn with_cost_tier(mut self, tier: CostTier) -> Self {
95
+ self.cost_tier = tier;
96
+ self
97
+ }
98
+
99
+ /// Set can spawn children
100
+ pub fn with_spawn_capability(mut self, can_spawn: bool) -> Self {
101
+ self.can_spawn_children = can_spawn;
102
+ self
103
+ }
104
+ }
105
+
106
+ /// CallableInvocation - Request to invoke a callable from another callable
107
+ /// @see packages/enact-schemas/src/execution.schemas.ts - callableInvocationSchema
108
+ #[derive(Debug, Clone, Serialize, Deserialize)]
109
+ #[serde(rename_all = "camelCase")]
110
+ pub struct CallableInvocation {
111
+ /// Target callable name
112
+ pub callable_name: String,
113
+
114
+ /// Input to pass
115
+ pub input: String,
116
+
117
+ /// Context to pass
118
+ #[serde(skip_serializing_if = "Option::is_none")]
119
+ pub context: Option<HashMap<String, String>>,
120
+
121
+ /// Spawn mode for child execution
122
+ #[serde(default)]
123
+ pub spawn_mode: SpawnMode,
124
+
125
+ /// Priority (higher = more important)
126
+ #[serde(default = "default_priority")]
127
+ pub priority: u8,
128
+
129
+ /// Timeout in milliseconds
130
+ #[serde(skip_serializing_if = "Option::is_none")]
131
+ pub timeout_ms: Option<u64>,
132
+ }
133
+
134
+ fn default_priority() -> u8 {
135
+ 50
136
+ }
137
+
138
+ /// CallableInvocationResult - Result of a callable invocation
139
+ /// @see packages/enact-schemas/src/execution.schemas.ts - callableInvocationResultSchema
140
+ #[derive(Debug, Clone, Serialize, Deserialize)]
141
+ #[serde(rename_all = "camelCase")]
142
+ pub struct CallableInvocationResult {
143
+ /// Whether the invocation succeeded
144
+ pub success: bool,
145
+
146
+ /// Output (if successful)
147
+ #[serde(skip_serializing_if = "Option::is_none")]
148
+ pub output: Option<String>,
149
+
150
+ /// Error message (if failed)
151
+ #[serde(skip_serializing_if = "Option::is_none")]
152
+ pub error: Option<String>,
153
+
154
+ /// Child execution ID (for SpawnMode::Child)
155
+ #[serde(skip_serializing_if = "Option::is_none")]
156
+ pub child_execution_id: Option<ExecutionId>,
157
+
158
+ /// Duration in milliseconds
159
+ pub duration_ms: u64,
160
+
161
+ /// Token usage
162
+ #[serde(skip_serializing_if = "Option::is_none")]
163
+ pub token_usage: Option<TokenUsage>,
164
+ }
165
+
166
+ impl CallableInvocationResult {
167
+ /// Create a success result
168
+ pub fn success(output: String, duration_ms: u64) -> Self {
169
+ Self {
170
+ success: true,
171
+ output: Some(output),
172
+ error: None,
173
+ child_execution_id: None,
174
+ duration_ms,
175
+ token_usage: None,
176
+ }
177
+ }
178
+
179
+ /// Create a failure result
180
+ pub fn failure(error: impl Into<String>, duration_ms: u64) -> Self {
181
+ Self {
182
+ success: false,
183
+ output: None,
184
+ error: Some(error.into()),
185
+ child_execution_id: None,
186
+ duration_ms,
187
+ token_usage: None,
188
+ }
189
+ }
190
+
191
+ /// Create a child spawn result
192
+ pub fn child_spawned(execution_id: ExecutionId, duration_ms: u64) -> Self {
193
+ Self {
194
+ success: true,
195
+ output: None,
196
+ error: None,
197
+ child_execution_id: Some(execution_id),
198
+ duration_ms,
199
+ token_usage: None,
200
+ }
201
+ }
202
+ }
203
+
204
+ /// ResourceAllocationStrategy - How to allocate resources among child callables
205
+ /// @see packages/enact-schemas/src/execution.schemas.ts - resourceAllocationStrategySchema
206
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
207
+ #[serde(rename_all = "snake_case")]
208
+ pub enum ResourceAllocationStrategy {
209
+ /// Divide resources equally among children
210
+ EqualSplit,
211
+ /// Shared pool with first-come-first-served
212
+ #[default]
213
+ SharedPool,
214
+ /// Higher priority gets more resources
215
+ Priority,
216
+ /// Allocate based on estimated cost
217
+ Proportional,
218
+ }
219
+
220
+ /// ResourceBudget - Budget for a callable execution
221
+ /// @see packages/enact-schemas/src/execution.schemas.ts - resourceBudgetSchema
222
+ #[derive(Debug, Clone, Serialize, Deserialize)]
223
+ #[serde(rename_all = "camelCase")]
224
+ pub struct ResourceBudget {
225
+ /// Maximum tokens
226
+ #[serde(skip_serializing_if = "Option::is_none")]
227
+ pub max_tokens: Option<u64>,
228
+
229
+ /// Maximum time in milliseconds
230
+ #[serde(skip_serializing_if = "Option::is_none")]
231
+ pub max_time_ms: Option<u64>,
232
+
233
+ /// Maximum cost in cents
234
+ #[serde(skip_serializing_if = "Option::is_none")]
235
+ pub max_cost_cents: Option<f64>,
236
+
237
+ /// Maximum child spawns
238
+ #[serde(skip_serializing_if = "Option::is_none")]
239
+ pub max_children: Option<u32>,
240
+
241
+ /// Maximum discovery depth
242
+ #[serde(default = "default_max_depth")]
243
+ pub max_discovery_depth: u32,
244
+ }
245
+
246
+ fn default_max_depth() -> u32 {
247
+ 3
248
+ }
249
+
250
+ impl Default for ResourceBudget {
251
+ fn default() -> Self {
252
+ Self {
253
+ max_tokens: None,
254
+ max_time_ms: None,
255
+ max_cost_cents: None,
256
+ max_children: None,
257
+ max_discovery_depth: default_max_depth(),
258
+ }
259
+ }
260
+ }
261
+
262
+ /// ResourceAllocation - Allocated resources for an execution
263
+ /// @see packages/enact-schemas/src/execution.schemas.ts - resourceAllocationSchema
264
+ #[derive(Debug, Clone, Serialize, Deserialize)]
265
+ #[serde(rename_all = "camelCase")]
266
+ pub struct ResourceAllocation {
267
+ /// Allocation strategy used
268
+ pub strategy: ResourceAllocationStrategy,
269
+
270
+ /// Budget for this allocation
271
+ pub budget: ResourceBudget,
272
+
273
+ /// Currently used tokens
274
+ #[serde(default)]
275
+ pub used_tokens: u64,
276
+
277
+ /// Currently used time in milliseconds
278
+ #[serde(default)]
279
+ pub used_time_ms: u64,
280
+
281
+ /// Currently used cost in cents
282
+ #[serde(default)]
283
+ pub used_cost_cents: f64,
284
+
285
+ /// Number of children spawned
286
+ #[serde(default)]
287
+ pub children_spawned: u32,
288
+
289
+ /// Current discovery depth
290
+ #[serde(default)]
291
+ pub current_depth: u32,
292
+ }
293
+
294
+ impl ResourceAllocation {
295
+ /// Create a new allocation with a budget
296
+ pub fn new(strategy: ResourceAllocationStrategy, budget: ResourceBudget) -> Self {
297
+ Self {
298
+ strategy,
299
+ budget,
300
+ used_tokens: 0,
301
+ used_time_ms: 0,
302
+ used_cost_cents: 0.0,
303
+ children_spawned: 0,
304
+ current_depth: 0,
305
+ }
306
+ }
307
+
308
+ /// Check if we can spawn another child
309
+ pub fn can_spawn_child(&self) -> bool {
310
+ match self.budget.max_children {
311
+ Some(max) => self.children_spawned < max,
312
+ None => true,
313
+ }
314
+ }
315
+
316
+ /// Check if we can go deeper in discovery
317
+ pub fn can_discover_deeper(&self) -> bool {
318
+ self.current_depth < self.budget.max_discovery_depth
319
+ }
320
+
321
+ /// Check if we have token budget remaining
322
+ pub fn has_token_budget(&self, tokens: u64) -> bool {
323
+ match self.budget.max_tokens {
324
+ Some(max) => self.used_tokens + tokens <= max,
325
+ None => true,
326
+ }
327
+ }
328
+
329
+ /// Check if we have time budget remaining
330
+ pub fn has_time_budget(&self, time_ms: u64) -> bool {
331
+ match self.budget.max_time_ms {
332
+ Some(max) => self.used_time_ms + time_ms <= max,
333
+ None => true,
334
+ }
335
+ }
336
+
337
+ /// Record token usage
338
+ pub fn record_tokens(&mut self, tokens: u64) {
339
+ self.used_tokens += tokens;
340
+ }
341
+
342
+ /// Record time usage
343
+ pub fn record_time(&mut self, time_ms: u64) {
344
+ self.used_time_ms += time_ms;
345
+ }
346
+
347
+ /// Record child spawn
348
+ pub fn record_child_spawn(&mut self) {
349
+ self.children_spawned += 1;
350
+ }
351
+
352
+ /// Increment depth
353
+ pub fn increment_depth(&mut self) {
354
+ self.current_depth += 1;
355
+ }
356
+
357
+ /// Create a child allocation (for nested invocations)
358
+ pub fn child_allocation(&self) -> Self {
359
+ let mut child = self.clone();
360
+ child.increment_depth();
361
+
362
+ // Adjust budget based on strategy
363
+ match self.strategy {
364
+ ResourceAllocationStrategy::EqualSplit => {
365
+ // Split remaining budget
366
+ if let Some(max) = child.budget.max_tokens {
367
+ let remaining = max.saturating_sub(self.used_tokens);
368
+ child.budget.max_tokens = Some(remaining / 2);
369
+ }
370
+ if let Some(max) = child.budget.max_time_ms {
371
+ let remaining = max.saturating_sub(self.used_time_ms);
372
+ child.budget.max_time_ms = Some(remaining / 2);
373
+ }
374
+ }
375
+ ResourceAllocationStrategy::SharedPool => {
376
+ // Share the same budget (just track separately)
377
+ }
378
+ ResourceAllocationStrategy::Priority => {
379
+ // High priority children get more (80%)
380
+ if let Some(max) = child.budget.max_tokens {
381
+ let remaining = max.saturating_sub(self.used_tokens);
382
+ child.budget.max_tokens = Some((remaining * 80) / 100);
383
+ }
384
+ }
385
+ ResourceAllocationStrategy::Proportional => {
386
+ // Based on cost tier - would need callable info
387
+ // For now, same as equal split
388
+ if let Some(max) = child.budget.max_tokens {
389
+ let remaining = max.saturating_sub(self.used_tokens);
390
+ child.budget.max_tokens = Some(remaining / 2);
391
+ }
392
+ }
393
+ }
394
+
395
+ child
396
+ }
397
+ }
398
+
399
+ /// DiscoveryQuery - Query for discovering callables
400
+ /// @see packages/enact-schemas/src/execution.schemas.ts - discoveryQuerySchema
401
+ #[derive(Debug, Clone, Serialize, Deserialize)]
402
+ #[serde(rename_all = "camelCase")]
403
+ pub struct DiscoveryQuery {
404
+ /// Filter by callable type
405
+ #[serde(skip_serializing_if = "Option::is_none")]
406
+ pub callable_type: Option<CallableType>,
407
+
408
+ /// Filter by tags (any match)
409
+ #[serde(skip_serializing_if = "Option::is_none")]
410
+ pub tags: Option<Vec<String>>,
411
+
412
+ /// Filter by name pattern (glob-like)
413
+ #[serde(skip_serializing_if = "Option::is_none")]
414
+ pub name_pattern: Option<String>,
415
+
416
+ /// Filter by maximum cost tier
417
+ #[serde(skip_serializing_if = "Option::is_none")]
418
+ pub max_cost_tier: Option<CostTier>,
419
+
420
+ /// Maximum results
421
+ #[serde(default = "default_limit")]
422
+ pub limit: usize,
423
+ }
424
+
425
+ fn default_limit() -> usize {
426
+ 10
427
+ }
428
+
429
+ impl Default for DiscoveryQuery {
430
+ fn default() -> Self {
431
+ Self {
432
+ callable_type: None,
433
+ tags: None,
434
+ name_pattern: None,
435
+ max_cost_tier: None,
436
+ limit: default_limit(),
437
+ }
438
+ }
439
+ }
440
+
441
+ /// DiscoveryResult - Result of a callable discovery query
442
+ /// @see packages/enact-schemas/src/execution.schemas.ts - discoveryResultSchema
443
+ #[derive(Debug, Clone, Serialize, Deserialize)]
444
+ #[serde(rename_all = "camelCase")]
445
+ pub struct DiscoveryResult {
446
+ /// Matching callables
447
+ pub callables: Vec<CallableDescriptor>,
448
+
449
+ /// Total count (before limit)
450
+ pub total_count: usize,
451
+
452
+ /// Query that was executed
453
+ pub query: DiscoveryQuery,
454
+ }
455
+
456
+ /// CallableInvoker - Invokes callables from a registry
457
+ ///
458
+ /// This is the core mechanism for callable-within-callable invocation.
459
+ pub struct CallableInvoker {
460
+ registry: CallableRegistry,
461
+ descriptors: HashMap<String, CallableDescriptor>,
462
+ }
463
+
464
+ impl CallableInvoker {
465
+ /// Create a new invoker with a registry
466
+ pub fn new(registry: CallableRegistry) -> Self {
467
+ Self {
468
+ registry,
469
+ descriptors: HashMap::new(),
470
+ }
471
+ }
472
+
473
+ /// Register a descriptor for a callable
474
+ pub fn register_descriptor(&mut self, descriptor: CallableDescriptor) {
475
+ self.descriptors.insert(descriptor.name.clone(), descriptor);
476
+ }
477
+
478
+ /// Get a callable from the registry
479
+ pub fn get(&self, name: &str) -> Option<DynCallable> {
480
+ self.registry.get(name)
481
+ }
482
+
483
+ /// Invoke a callable by name
484
+ pub async fn invoke(&self, invocation: CallableInvocation) -> CallableInvocationResult {
485
+ let start = Instant::now();
486
+
487
+ let callable = match self.registry.get(&invocation.callable_name) {
488
+ Some(c) => c,
489
+ None => {
490
+ return CallableInvocationResult::failure(
491
+ format!("Callable '{}' not found", invocation.callable_name),
492
+ start.elapsed().as_millis() as u64,
493
+ );
494
+ }
495
+ };
496
+
497
+ match invocation.spawn_mode {
498
+ SpawnMode::Inline => {
499
+ // Inline execution - run directly
500
+ match callable.run(&invocation.input).await {
501
+ Ok(output) => {
502
+ CallableInvocationResult::success(output, start.elapsed().as_millis() as u64)
503
+ }
504
+ Err(e) => CallableInvocationResult::failure(
505
+ e.to_string(),
506
+ start.elapsed().as_millis() as u64,
507
+ ),
508
+ }
509
+ }
510
+ SpawnMode::Child { background, .. } => {
511
+ if background {
512
+ // Background execution - spawn and return immediately
513
+ let execution_id = ExecutionId::new();
514
+ // In a real implementation, this would spawn a background task
515
+ CallableInvocationResult::child_spawned(
516
+ execution_id,
517
+ start.elapsed().as_millis() as u64,
518
+ )
519
+ } else {
520
+ // Child execution - run and wait
521
+ match callable.run(&invocation.input).await {
522
+ Ok(output) => CallableInvocationResult::success(
523
+ output,
524
+ start.elapsed().as_millis() as u64,
525
+ ),
526
+ Err(e) => CallableInvocationResult::failure(
527
+ e.to_string(),
528
+ start.elapsed().as_millis() as u64,
529
+ ),
530
+ }
531
+ }
532
+ }
533
+ }
534
+ }
535
+
536
+ /// Discover callables matching a query
537
+ pub fn discover(&self, query: DiscoveryQuery) -> DiscoveryResult {
538
+ let mut matches: Vec<CallableDescriptor> = self
539
+ .descriptors
540
+ .values()
541
+ .filter(|desc| {
542
+ // Filter by type
543
+ if let Some(ref t) = query.callable_type {
544
+ if &desc.callable_type != t {
545
+ return false;
546
+ }
547
+ }
548
+
549
+ // Filter by tags
550
+ if let Some(ref tags) = query.tags {
551
+ if !tags.iter().any(|t| desc.tags.contains(t)) {
552
+ return false;
553
+ }
554
+ }
555
+
556
+ // Filter by name pattern (simple glob)
557
+ if let Some(ref pattern) = query.name_pattern {
558
+ if !matches_glob(&desc.name, pattern) {
559
+ return false;
560
+ }
561
+ }
562
+
563
+ // Filter by cost tier
564
+ if let Some(ref max_tier) = query.max_cost_tier {
565
+ if !is_cost_tier_within(&desc.cost_tier, max_tier) {
566
+ return false;
567
+ }
568
+ }
569
+
570
+ true
571
+ })
572
+ .cloned()
573
+ .collect();
574
+
575
+ let total_count = matches.len();
576
+ matches.truncate(query.limit);
577
+
578
+ DiscoveryResult {
579
+ callables: matches,
580
+ total_count,
581
+ query,
582
+ }
583
+ }
584
+
585
+ /// List all registered callable names
586
+ pub fn list(&self) -> Vec<String> {
587
+ self.registry.list()
588
+ }
589
+ }
590
+
591
+ /// Simple glob matching (supports * and ?)
592
+ fn matches_glob(name: &str, pattern: &str) -> bool {
593
+ // Simple glob matching without regex
594
+ let mut name_chars = name.chars().peekable();
595
+ let mut pattern_chars = pattern.chars().peekable();
596
+
597
+ while let Some(p) = pattern_chars.next() {
598
+ match p {
599
+ '*' => {
600
+ // * matches zero or more characters
601
+ if pattern_chars.peek().is_none() {
602
+ return true; // Trailing * matches everything
603
+ }
604
+ // Try matching zero characters first, then more
605
+ let remaining_pattern: String = pattern_chars.collect();
606
+ let mut remaining_name = String::new();
607
+ loop {
608
+ if matches_glob(&remaining_name, &remaining_pattern) {
609
+ return true;
610
+ }
611
+ match name_chars.next() {
612
+ Some(c) => remaining_name.push(c),
613
+ None => return matches_glob("", &remaining_pattern),
614
+ }
615
+ }
616
+ }
617
+ '?' => {
618
+ // ? matches exactly one character
619
+ if name_chars.next().is_none() {
620
+ return false;
621
+ }
622
+ }
623
+ c => {
624
+ // Literal character must match
625
+ match name_chars.next() {
626
+ Some(nc) if nc == c => continue,
627
+ _ => return false,
628
+ }
629
+ }
630
+ }
631
+ }
632
+
633
+ // Pattern exhausted - name should be too
634
+ name_chars.next().is_none()
635
+ }
636
+
637
+ /// Check if cost tier is within limit
638
+ fn is_cost_tier_within(tier: &CostTier, max_tier: &CostTier) -> bool {
639
+ let tier_value = match tier {
640
+ CostTier::Free => 0,
641
+ CostTier::Low => 1,
642
+ CostTier::Medium => 2,
643
+ CostTier::High => 3,
644
+ CostTier::Premium => 4,
645
+ };
646
+ let max_value = match max_tier {
647
+ CostTier::Free => 0,
648
+ CostTier::Low => 1,
649
+ CostTier::Medium => 2,
650
+ CostTier::High => 3,
651
+ CostTier::Premium => 4,
652
+ };
653
+ tier_value <= max_value
654
+ }
655
+
656
+ #[cfg(test)]
657
+ mod tests {
658
+ use super::*;
659
+ use async_trait::async_trait;
660
+ use std::sync::Arc;
661
+
662
+ struct TestCallable {
663
+ name: String,
664
+ output: String,
665
+ }
666
+
667
+ #[async_trait]
668
+ impl Callable for TestCallable {
669
+ fn name(&self) -> &str {
670
+ &self.name
671
+ }
672
+
673
+ async fn run(&self, _input: &str) -> anyhow::Result<String> {
674
+ Ok(self.output.clone())
675
+ }
676
+ }
677
+
678
+ #[test]
679
+ fn test_callable_descriptor() {
680
+ let callable = TestCallable {
681
+ name: "test".to_string(),
682
+ output: "output".to_string(),
683
+ };
684
+
685
+ let desc = CallableDescriptor::from_callable(&callable, CallableType::Agent)
686
+ .with_tags(vec!["research".to_string(), "analysis".to_string()])
687
+ .with_cost_tier(CostTier::High)
688
+ .with_spawn_capability(true);
689
+
690
+ assert_eq!(desc.name, "test");
691
+ assert_eq!(desc.callable_type, CallableType::Agent);
692
+ assert_eq!(desc.tags.len(), 2);
693
+ assert_eq!(desc.cost_tier, CostTier::High);
694
+ assert!(desc.can_spawn_children);
695
+ }
696
+
697
+ #[test]
698
+ fn test_resource_allocation() {
699
+ let budget = ResourceBudget {
700
+ max_tokens: Some(1000),
701
+ max_time_ms: Some(5000),
702
+ max_children: Some(3),
703
+ ..Default::default()
704
+ };
705
+
706
+ let mut allocation = ResourceAllocation::new(ResourceAllocationStrategy::EqualSplit, budget);
707
+
708
+ assert!(allocation.can_spawn_child());
709
+ assert!(allocation.has_token_budget(500));
710
+
711
+ allocation.record_tokens(400);
712
+ allocation.record_child_spawn();
713
+
714
+ assert!(allocation.has_token_budget(500));
715
+ assert!(!allocation.has_token_budget(700));
716
+ assert!(allocation.can_spawn_child());
717
+
718
+ allocation.record_child_spawn();
719
+ allocation.record_child_spawn();
720
+ assert!(!allocation.can_spawn_child());
721
+ }
722
+
723
+ #[test]
724
+ fn test_child_allocation() {
725
+ let budget = ResourceBudget {
726
+ max_tokens: Some(1000),
727
+ ..Default::default()
728
+ };
729
+
730
+ let allocation = ResourceAllocation::new(ResourceAllocationStrategy::EqualSplit, budget);
731
+ let child = allocation.child_allocation();
732
+
733
+ assert_eq!(child.current_depth, 1);
734
+ assert_eq!(child.budget.max_tokens, Some(500)); // Half of parent
735
+ }
736
+
737
+ #[tokio::test]
738
+ async fn test_callable_invoker() {
739
+ let registry = CallableRegistry::new();
740
+ let callable = Arc::new(TestCallable {
741
+ name: "test".to_string(),
742
+ output: "test output".to_string(),
743
+ });
744
+ registry.register("test".to_string(), callable);
745
+
746
+ let invoker = CallableInvoker::new(registry);
747
+
748
+ let invocation = CallableInvocation {
749
+ callable_name: "test".to_string(),
750
+ input: "input".to_string(),
751
+ context: None,
752
+ spawn_mode: SpawnMode::Inline,
753
+ priority: 50,
754
+ timeout_ms: None,
755
+ };
756
+
757
+ let result = invoker.invoke(invocation).await;
758
+ assert!(result.success);
759
+ assert_eq!(result.output, Some("test output".to_string()));
760
+ }
761
+
762
+ #[test]
763
+ fn test_discovery() {
764
+ let registry = CallableRegistry::new();
765
+ let mut invoker = CallableInvoker::new(registry);
766
+
767
+ invoker.register_descriptor(
768
+ CallableDescriptor::from_callable(
769
+ &TestCallable {
770
+ name: "research-agent".to_string(),
771
+ output: "".to_string(),
772
+ },
773
+ CallableType::Agent,
774
+ )
775
+ .with_tags(vec!["research".to_string()])
776
+ .with_cost_tier(CostTier::Medium),
777
+ );
778
+
779
+ invoker.register_descriptor(
780
+ CallableDescriptor::from_callable(
781
+ &TestCallable {
782
+ name: "analysis-agent".to_string(),
783
+ output: "".to_string(),
784
+ },
785
+ CallableType::Agent,
786
+ )
787
+ .with_tags(vec!["analysis".to_string()])
788
+ .with_cost_tier(CostTier::High),
789
+ );
790
+
791
+ // Query by tag
792
+ let result = invoker.discover(DiscoveryQuery {
793
+ tags: Some(vec!["research".to_string()]),
794
+ ..Default::default()
795
+ });
796
+ assert_eq!(result.callables.len(), 1);
797
+ assert_eq!(result.callables[0].name, "research-agent");
798
+
799
+ // Query by cost tier
800
+ let result = invoker.discover(DiscoveryQuery {
801
+ max_cost_tier: Some(CostTier::Medium),
802
+ ..Default::default()
803
+ });
804
+ assert_eq!(result.callables.len(), 1);
805
+
806
+ // Query all
807
+ let result = invoker.discover(DiscoveryQuery::default());
808
+ assert_eq!(result.total_count, 2);
809
+ }
810
+
811
+ #[test]
812
+ fn test_cost_tier_comparison() {
813
+ assert!(is_cost_tier_within(&CostTier::Free, &CostTier::High));
814
+ assert!(is_cost_tier_within(&CostTier::Medium, &CostTier::Medium));
815
+ assert!(!is_cost_tier_within(&CostTier::High, &CostTier::Low));
816
+ }
817
+ }