legate 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 (317) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +345 -0
  4. data/bin/legate +13 -0
  5. data/examples/00_quickstart.rb +51 -0
  6. data/examples/01_simple_agent.rb +105 -0
  7. data/examples/02_multi_tool_agent.rb +140 -0
  8. data/examples/03_custom_tool.rb +93 -0
  9. data/examples/04_agent_instructions.rb +84 -0
  10. data/examples/05_state_and_sessions.rb +91 -0
  11. data/examples/06_callbacks.rb +186 -0
  12. data/examples/07_async_jobs.rb +112 -0
  13. data/examples/08_loop_agent.rb +197 -0
  14. data/examples/09_sequential_workflow.rb +40 -0
  15. data/examples/10_parallel_workflow.rb +34 -0
  16. data/examples/11_agent_delegation.rb +24 -0
  17. data/examples/12_http_client_tool.rb +156 -0
  18. data/examples/13_authentication.rb +220 -0
  19. data/examples/14_mcp_client.rb +154 -0
  20. data/examples/15_mcp_server.rb +79 -0
  21. data/examples/16_webhooks.rb +91 -0
  22. data/examples/README_sequential_agents.md +164 -0
  23. data/examples/advanced/auth/cookie_auth_tool.rb +146 -0
  24. data/examples/advanced/auth/custom_auth_flows_example.rb +626 -0
  25. data/examples/advanced/auth/excon_middleware.rb +317 -0
  26. data/examples/advanced/auth/excon_middleware_auth.rb +399 -0
  27. data/examples/advanced/auth/fiber_auth_example.rb +281 -0
  28. data/examples/advanced/auth/fiber_oidc_example.rb +403 -0
  29. data/examples/advanced/auth/httpbin_bearer_tool.rb +159 -0
  30. data/examples/advanced/auth/oauth2_auth.rb +419 -0
  31. data/examples/advanced/auth/oidc_auth.rb +514 -0
  32. data/examples/advanced/auth/openweather_api.rb +251 -0
  33. data/examples/advanced/auth/openweather_tool.rb +153 -0
  34. data/examples/advanced/auth/query_param_middleware_test.rb +138 -0
  35. data/examples/advanced/auth/service_account.rb +135 -0
  36. data/examples/advanced/auth/test_with_httpbin.rb +202 -0
  37. data/examples/advanced/auth/token_lifecycle_example.rb +428 -0
  38. data/examples/advanced/callback_monitoring.rb +679 -0
  39. data/examples/advanced/mas/fixed_delegation_example.rb +191 -0
  40. data/examples/advanced/mas/loop_workflow.rb +28 -0
  41. data/examples/advanced/mas/mock_planner.rb +77 -0
  42. data/examples/advanced/mas/proper_delegation_example.rb +276 -0
  43. data/examples/advanced/mcp/legate_mcp_server_resource_example.rb +182 -0
  44. data/examples/advanced/mcp/mcp_resource_server_example.rb +309 -0
  45. data/examples/advanced/mcp/mcp_server_async.rb +76 -0
  46. data/examples/advanced/mcp/mcp_server_async_tools.rb +122 -0
  47. data/examples/advanced/mcp/mcp_server_legate_agent.rb +95 -0
  48. data/examples/advanced/mcp/mcp_server_rack.rb +89 -0
  49. data/examples/advanced/random_calculator.rb +104 -0
  50. data/examples/advanced/sleep_agent.rb +153 -0
  51. data/examples/advanced/webhooks/webhook_e2e_runner.rb +110 -0
  52. data/examples/advanced/webhooks/webhook_receiver_agent.rb +58 -0
  53. data/examples/advanced/workflows/task_refinement_loop_agent.rb +278 -0
  54. data/examples/advanced/workflows/travel_planner_auto_sequential.rb +444 -0
  55. data/examples/advanced/workflows/travel_planner_parallel.rb +656 -0
  56. data/examples/advanced/workflows/travel_planner_sequential.rb +512 -0
  57. data/examples/tools/oauth2_example.rb +136 -0
  58. data/examples/tools/sleepy_tool.rb +42 -0
  59. data/lib/legate/activity_log.rb +71 -0
  60. data/lib/legate/agent.rb +959 -0
  61. data/lib/legate/agent_code_generator.rb +185 -0
  62. data/lib/legate/agent_definition.rb +812 -0
  63. data/lib/legate/agentic/decision.rb +49 -0
  64. data/lib/legate/agentic/loop.rb +134 -0
  65. data/lib/legate/agentic.rb +5 -0
  66. data/lib/legate/agents/loop_agent.rb +248 -0
  67. data/lib/legate/agents/parallel_agent.rb +163 -0
  68. data/lib/legate/agents/sequential_agent.rb +190 -0
  69. data/lib/legate/agents.rb +14 -0
  70. data/lib/legate/auth/config.rb +148 -0
  71. data/lib/legate/auth/coordinator.rb +218 -0
  72. data/lib/legate/auth/coordinators/oauth2_coordinator.rb +99 -0
  73. data/lib/legate/auth/coordinators/oidc_coordinator.rb +68 -0
  74. data/lib/legate/auth/coordinators/service_account_coordinator.rb +122 -0
  75. data/lib/legate/auth/credential.rb +157 -0
  76. data/lib/legate/auth/encryption.rb +108 -0
  77. data/lib/legate/auth/error.rb +94 -0
  78. data/lib/legate/auth/exchanged_credential.rb +180 -0
  79. data/lib/legate/auth/excon_middleware.rb +285 -0
  80. data/lib/legate/auth/http_client_utils.rb +364 -0
  81. data/lib/legate/auth/manager.rb +531 -0
  82. data/lib/legate/auth/manager_store.rb +394 -0
  83. data/lib/legate/auth/middleware_factory.rb +290 -0
  84. data/lib/legate/auth/runner.rb +279 -0
  85. data/lib/legate/auth/scheme.rb +125 -0
  86. data/lib/legate/auth/schemes/api_key.rb +212 -0
  87. data/lib/legate/auth/schemes/google_service_account.rb +108 -0
  88. data/lib/legate/auth/schemes/http_bearer.rb +98 -0
  89. data/lib/legate/auth/schemes/oauth2.rb +396 -0
  90. data/lib/legate/auth/schemes/openid_connect.rb +346 -0
  91. data/lib/legate/auth/schemes/service_account.rb +388 -0
  92. data/lib/legate/auth/schemes.rb +40 -0
  93. data/lib/legate/auth/token_manager.rb +362 -0
  94. data/lib/legate/auth/token_store.rb +86 -0
  95. data/lib/legate/auth/tool_context_extension.rb +97 -0
  96. data/lib/legate/auth/tool_integration.rb +188 -0
  97. data/lib/legate/auth/url_guard.rb +81 -0
  98. data/lib/legate/auth.rb +453 -0
  99. data/lib/legate/callbacks/callback_context.rb +71 -0
  100. data/lib/legate/cli/agent_commands.rb +950 -0
  101. data/lib/legate/cli/auth_commands.rb +520 -0
  102. data/lib/legate/cli/base_command.rb +24 -0
  103. data/lib/legate/cli/deployment_commands.rb +934 -0
  104. data/lib/legate/cli/output_helper.rb +108 -0
  105. data/lib/legate/cli/session_commands.rb +138 -0
  106. data/lib/legate/cli/skaffold_commands.rb +223 -0
  107. data/lib/legate/cli/tool_commands.rb +261 -0
  108. data/lib/legate/cli/web_commands.rb +182 -0
  109. data/lib/legate/cli.rb +40 -0
  110. data/lib/legate/configuration/webhooks.rb +113 -0
  111. data/lib/legate/configuration.rb +39 -0
  112. data/lib/legate/definition_store.rb +23 -0
  113. data/lib/legate/errors.rb +118 -0
  114. data/lib/legate/event.rb +161 -0
  115. data/lib/legate/gemini_ai_beta_patch.rb +39 -0
  116. data/lib/legate/generators/agent_generator.rb +412 -0
  117. data/lib/legate/generators/code_validator.rb +48 -0
  118. data/lib/legate/generators/legate/install_generator.rb +35 -0
  119. data/lib/legate/generators/legate/templates/create_legate_tables.rb.tt +36 -0
  120. data/lib/legate/generators/legate/templates/initializer.rb +18 -0
  121. data/lib/legate/generators/runtime_tool_loader.rb +76 -0
  122. data/lib/legate/generators/tool_generator.rb +408 -0
  123. data/lib/legate/generators.rb +11 -0
  124. data/lib/legate/global_definition_registry.rb +506 -0
  125. data/lib/legate/global_tool_manager.rb +135 -0
  126. data/lib/legate/llm/adapter.rb +69 -0
  127. data/lib/legate/llm/gemini.rb +172 -0
  128. data/lib/legate/llm/ollama.rb +80 -0
  129. data/lib/legate/llm.rb +34 -0
  130. data/lib/legate/mcp/client.rb +320 -0
  131. data/lib/legate/mcp/connection/sse.rb +292 -0
  132. data/lib/legate/mcp/connection/stdio.rb +273 -0
  133. data/lib/legate/mcp/connection_manager.rb +103 -0
  134. data/lib/legate/mcp/server/legate_agent_adapter.rb +170 -0
  135. data/lib/legate/mcp/server/legate_direct_agent_adapter.rb +140 -0
  136. data/lib/legate/mcp/server/legate_tool_adapter.rb +119 -0
  137. data/lib/legate/mcp/tool_wrapper.rb +138 -0
  138. data/lib/legate/mcp/util/schema_converter.rb +134 -0
  139. data/lib/legate/mcp.rb +23 -0
  140. data/lib/legate/plan_executor.rb +375 -0
  141. data/lib/legate/planner.rb +839 -0
  142. data/lib/legate/rails/railtie.rb +43 -0
  143. data/lib/legate/rails.rb +9 -0
  144. data/lib/legate/redaction.rb +32 -0
  145. data/lib/legate/session.rb +299 -0
  146. data/lib/legate/session_service/active_record.rb +300 -0
  147. data/lib/legate/session_service/base.rb +68 -0
  148. data/lib/legate/session_service/event_broadcast.rb +74 -0
  149. data/lib/legate/session_service/in_memory.rb +188 -0
  150. data/lib/legate/tool/metadata_dsl.rb +122 -0
  151. data/lib/legate/tool.rb +276 -0
  152. data/lib/legate/tool_code_generator.rb +103 -0
  153. data/lib/legate/tool_context.rb +350 -0
  154. data/lib/legate/tool_loader.rb +39 -0
  155. data/lib/legate/tool_registry.rb +73 -0
  156. data/lib/legate/tool_result.rb +61 -0
  157. data/lib/legate/tools/agent_tool.rb +187 -0
  158. data/lib/legate/tools/base/http_client.rb +319 -0
  159. data/lib/legate/tools/base/safe_url.rb +56 -0
  160. data/lib/legate/tools/base_async_job_tool.rb +91 -0
  161. data/lib/legate/tools/calculator.rb +89 -0
  162. data/lib/legate/tools/cat_facts.rb +81 -0
  163. data/lib/legate/tools/check_job_status_tool.rb +48 -0
  164. data/lib/legate/tools/current_time_tool.rb +64 -0
  165. data/lib/legate/tools/echo.rb +43 -0
  166. data/lib/legate/tools/http_request_tool.rb +105 -0
  167. data/lib/legate/tools/random_number_tool.rb +64 -0
  168. data/lib/legate/tools/read_webpage_tool.rb +92 -0
  169. data/lib/legate/tools/sleepy_tool.rb +74 -0
  170. data/lib/legate/tools/webhook_tool.rb +146 -0
  171. data/lib/legate/version.rb +5 -0
  172. data/lib/legate/web/app.rb +984 -0
  173. data/lib/legate/web/public/css/main.css +4980 -0
  174. data/lib/legate/web/public/images/favicon-256.png +0 -0
  175. data/lib/legate/web/public/images/favicon-32.png +0 -0
  176. data/lib/legate/web/public/images/legate-logo-dark.png +0 -0
  177. data/lib/legate/web/public/images/legate-logo-light.png +0 -0
  178. data/lib/legate/web/public/js/legate.js +616 -0
  179. data/lib/legate/web/public/styles/main.scss +4402 -0
  180. data/lib/legate/web/routes/agent_authentication_routes.rb +530 -0
  181. data/lib/legate/web/routes/agent_definition_routes.rb +803 -0
  182. data/lib/legate/web/routes/agent_generator_routes.rb +80 -0
  183. data/lib/legate/web/routes/agent_interaction_routes.rb +734 -0
  184. data/lib/legate/web/routes/agent_runtime_routes.rb +323 -0
  185. data/lib/legate/web/routes/api_routes.rb +56 -0
  186. data/lib/legate/web/routes/authentication_routes.rb +1541 -0
  187. data/lib/legate/web/routes/core_routes.rb +111 -0
  188. data/lib/legate/web/routes/documentation_routes.rb +220 -0
  189. data/lib/legate/web/routes/tool_generator_routes.rb +81 -0
  190. data/lib/legate/web/routes/tools_ui_routes.rb +207 -0
  191. data/lib/legate/web/sass_compiler.rb +73 -0
  192. data/lib/legate/web/views/_active_session_info.slim +25 -0
  193. data/lib/legate/web/views/_activity_list.slim +55 -0
  194. data/lib/legate/web/views/_agent_card.slim +56 -0
  195. data/lib/legate/web/views/_agent_generator_modal.slim +382 -0
  196. data/lib/legate/web/views/_agent_status_controls.slim +71 -0
  197. data/lib/legate/web/views/_agent_tool_table.slim +74 -0
  198. data/lib/legate/web/views/_chat_message.slim +95 -0
  199. data/lib/legate/web/views/_display_agent_configuration.slim +26 -0
  200. data/lib/legate/web/views/_display_agent_description.slim +11 -0
  201. data/lib/legate/web/views/_display_agent_fallback.slim +15 -0
  202. data/lib/legate/web/views/_display_agent_hierarchy.slim +93 -0
  203. data/lib/legate/web/views/_display_agent_instruction.slim +17 -0
  204. data/lib/legate/web/views/_display_agent_mcp.slim +13 -0
  205. data/lib/legate/web/views/_display_agent_model.slim +17 -0
  206. data/lib/legate/web/views/_display_agent_name.slim +42 -0
  207. data/lib/legate/web/views/_display_agent_output_key.slim +26 -0
  208. data/lib/legate/web/views/_display_agent_type.slim +65 -0
  209. data/lib/legate/web/views/_edit_agent_configuration.slim +74 -0
  210. data/lib/legate/web/views/_edit_agent_description.slim +16 -0
  211. data/lib/legate/web/views/_edit_agent_fallback.slim +25 -0
  212. data/lib/legate/web/views/_edit_agent_hierarchy.slim +98 -0
  213. data/lib/legate/web/views/_edit_agent_instruction.slim +49 -0
  214. data/lib/legate/web/views/_edit_agent_mcp.slim +33 -0
  215. data/lib/legate/web/views/_edit_agent_model.slim +23 -0
  216. data/lib/legate/web/views/_edit_agent_output_key.slim +36 -0
  217. data/lib/legate/web/views/_edit_agent_tools.slim +40 -0
  218. data/lib/legate/web/views/_edit_agent_type.slim +67 -0
  219. data/lib/legate/web/views/_session_error.slim +4 -0
  220. data/lib/legate/web/views/_skeleton.slim +69 -0
  221. data/lib/legate/web/views/_tool_card.slim +9 -0
  222. data/lib/legate/web/views/_tool_generator_modal.slim +311 -0
  223. data/lib/legate/web/views/agent.slim +436 -0
  224. data/lib/legate/web/views/agent_auth.slim +562 -0
  225. data/lib/legate/web/views/agents.slim +369 -0
  226. data/lib/legate/web/views/auth.slim +112 -0
  227. data/lib/legate/web/views/auth_credential_detail.slim +327 -0
  228. data/lib/legate/web/views/auth_credentials.slim +261 -0
  229. data/lib/legate/web/views/auth_debug.slim +94 -0
  230. data/lib/legate/web/views/auth_mapping_detail.slim +151 -0
  231. data/lib/legate/web/views/auth_mapping_new.slim +123 -0
  232. data/lib/legate/web/views/auth_mappings.slim +120 -0
  233. data/lib/legate/web/views/auth_scheme_detail.slim +274 -0
  234. data/lib/legate/web/views/auth_schemes.slim +259 -0
  235. data/lib/legate/web/views/auth_test.slim +418 -0
  236. data/lib/legate/web/views/chat.slim +192 -0
  237. data/lib/legate/web/views/docs_index.slim +105 -0
  238. data/lib/legate/web/views/docs_show.slim +105 -0
  239. data/lib/legate/web/views/error_404.slim +5 -0
  240. data/lib/legate/web/views/index.slim +148 -0
  241. data/lib/legate/web/views/layout.slim +144 -0
  242. data/lib/legate/web/views/tool_detail.slim +87 -0
  243. data/lib/legate/web/views/tools.slim +50 -0
  244. data/lib/legate/web/webhook_listener.rb +367 -0
  245. data/lib/legate/web.rb +9 -0
  246. data/lib/legate.rb +220 -0
  247. data/public/docs/advanced/callbacks.md +828 -0
  248. data/public/docs/advanced/mcp_schema_conversion.md +59 -0
  249. data/public/docs/authentication/api_reference/config.md +210 -0
  250. data/public/docs/authentication/api_reference/credential.md +246 -0
  251. data/public/docs/authentication/api_reference/encryption.md +218 -0
  252. data/public/docs/authentication/api_reference/exchanged_credential.md +271 -0
  253. data/public/docs/authentication/api_reference/excon_middleware.md +175 -0
  254. data/public/docs/authentication/api_reference/index.md +30 -0
  255. data/public/docs/authentication/api_reference/scheme.md +250 -0
  256. data/public/docs/authentication/api_reference/schemes/api_key.md +175 -0
  257. data/public/docs/authentication/api_reference/schemes/google_service_account.md +221 -0
  258. data/public/docs/authentication/api_reference/schemes/http_bearer.md +169 -0
  259. data/public/docs/authentication/api_reference/schemes/oauth2.md +343 -0
  260. data/public/docs/authentication/api_reference/schemes/oidc.md +73 -0
  261. data/public/docs/authentication/api_reference/schemes/openid_connect.md +311 -0
  262. data/public/docs/authentication/api_reference/schemes/service_account.md +287 -0
  263. data/public/docs/authentication/api_reference/token_manager.md +221 -0
  264. data/public/docs/authentication/api_reference/token_store.md +146 -0
  265. data/public/docs/authentication/api_reference/tool_context_extension.md +166 -0
  266. data/public/docs/authentication/guides/api_key.md +190 -0
  267. data/public/docs/authentication/guides/bearer.md +172 -0
  268. data/public/docs/authentication/guides/configuration.md +255 -0
  269. data/public/docs/authentication/guides/custom_flow.md +523 -0
  270. data/public/docs/authentication/guides/index.md +24 -0
  271. data/public/docs/authentication/guides/migration.md +435 -0
  272. data/public/docs/authentication/guides/oauth2.md +252 -0
  273. data/public/docs/authentication/guides/oidc.md +241 -0
  274. data/public/docs/authentication/guides/overview.md +155 -0
  275. data/public/docs/authentication/guides/secure_storage.md +301 -0
  276. data/public/docs/authentication/guides/service_account.md +228 -0
  277. data/public/docs/authentication/guides/token_lifecycle.md +295 -0
  278. data/public/docs/authentication/guides/web_ui_integration.md +504 -0
  279. data/public/docs/authentication/index.md +58 -0
  280. data/public/docs/authentication/troubleshooting/credential_storage.md +550 -0
  281. data/public/docs/authentication/troubleshooting/environment_variables.md +540 -0
  282. data/public/docs/authentication/troubleshooting/index.md +11 -0
  283. data/public/docs/authentication/troubleshooting/oauth2_issues.md +220 -0
  284. data/public/docs/authentication/troubleshooting/oidc_issues.md +412 -0
  285. data/public/docs/authentication/troubleshooting/token_refresh.md +338 -0
  286. data/public/docs/cli/legate_cli_usage.md +363 -0
  287. data/public/docs/core_concepts/legate_agent_lifecycle.md +124 -0
  288. data/public/docs/core_concepts/legate_architecture_overview.md +110 -0
  289. data/public/docs/core_concepts/legate_configuration.md +116 -0
  290. data/public/docs/core_concepts/legate_definition_store.md +102 -0
  291. data/public/docs/core_concepts/legate_planner.md +94 -0
  292. data/public/docs/core_concepts/legate_session_service.md +104 -0
  293. data/public/docs/error_handling/legate_error_handling.md +122 -0
  294. data/public/docs/examples.md +199 -0
  295. data/public/docs/getting_started.md +111 -0
  296. data/public/docs/guides/agentic_agents.md +137 -0
  297. data/public/docs/guides/ai_code_generators.md +437 -0
  298. data/public/docs/guides/auto_loading.md +326 -0
  299. data/public/docs/guides/configuring_agent_webhooks.md +219 -0
  300. data/public/docs/guides/http_client_usage.md +264 -0
  301. data/public/docs/guides/llm_providers.md +137 -0
  302. data/public/docs/guides/mcp_client_integration.md +232 -0
  303. data/public/docs/guides/mcp_server_exposure.md +206 -0
  304. data/public/docs/guides/rails_integration.md +128 -0
  305. data/public/docs/guides/sending_outbound_webhooks.md +227 -0
  306. data/public/docs/guides/streaming.md +112 -0
  307. data/public/docs/guides/webhooks.md +288 -0
  308. data/public/docs/introduction.md +51 -0
  309. data/public/docs/multi_agent_systems/advanced_features.md +57 -0
  310. data/public/docs/multi_agent_systems/agent_delegation.md +190 -0
  311. data/public/docs/multi_agent_systems/agent_hierarchy.md +49 -0
  312. data/public/docs/multi_agent_systems/state_management.md +47 -0
  313. data/public/docs/multi_agent_systems/workflow_agents.md +72 -0
  314. data/public/docs/tools/legate_built_in_tools.md +332 -0
  315. data/public/docs/tools/legate_tools_and_registry.md +263 -0
  316. data/public/docs/web_ui/legate_web_ui.md +137 -0
  317. metadata +823 -0
@@ -0,0 +1,95 @@
1
+ # File: examples/advanced/mcp/mcp_server_legate_agent.rb
2
+ # frozen_string_literal: true
3
+
4
+ # --- Example: Exposing an Legate::Agent via MCP using fast-mcp ---
5
+ #
6
+ # This script demonstrates how to wrap an existing Legate::Agent definition
7
+ # and expose it as a single tool on an MCP server
8
+ # run over STDIO using the fast-mcp gem and LegateAgentAdapter.
9
+ #
10
+ # Prerequisites:
11
+ # - Run `bundle install` in the legate directory.
12
+ # - Ensure the `fast-mcp` gem is available (e.g., via Gemfile path).
13
+ # - An agent definition registered in GlobalDefinitionRegistry.
14
+ # Replace `MY_AGENT_NAME` below with the actual name.
15
+ #
16
+ # Usage:
17
+ # 1. Replace `MY_AGENT_NAME` with your agent's name.
18
+ # 2. Run this script from the legate root directory:
19
+ # `bundle exec ruby examples/advanced/mcp/mcp_server_legate_agent.rb`
20
+ # 3. The script will start and wait for MCP JSON-RPC messages on STDIN.
21
+ # 4. Use an MCP client connected to this script's STDIO to interact.
22
+ #
23
+ # Example Interaction (using a generic client):
24
+ # -> {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{}}}
25
+ # <- {"jsonrpc":"2.0","id":1,"result":{"capabilities":{},"serverInfo":{"name":"Legate Agent Server","version":"x.y.z"}}}
26
+ # -> {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
27
+ # <- {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"run_agent_MY_AGENT_NAME","description":"Runs the Legate Agent 'MY_AGENT_NAME' with the given prompt.","inputSchema":{"type":"object","properties":{"prompt":{"type":"string","description":"The user input/prompt for the agent"}},"required":["prompt"]}}]}}
28
+ # -> {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"run_agent_MY_AGENT_NAME","arguments":{"prompt":"Hello agent!"}}}
29
+ # <- {"jsonrpc":"2.0","id":3,"result":"Hello from the agent!"} // (or whatever the agent returns)
30
+ #
31
+ # -------------------------------------------------------------
32
+
33
+ $LOAD_PATH.unshift File.expand_path('../../lib', __dir__)
34
+ require 'legate'
35
+ Legate.load_environment # Handle Bundler, Dotenv, etc.
36
+ require 'legate/mcp'
37
+ require 'legate/mcp/server/legate_agent_adapter' # Load the Agent adapter
38
+ require 'legate/session_service/in_memory' # Using in-memory for temporary sessions
39
+ require 'fast_mcp'
40
+
41
+ # --- ** Configuration: Replace with your agent name ** ---
42
+ AGENT_NAME = 'my_agent' # <<< CHANGE THIS
43
+ # ---------------------------------------------------------
44
+
45
+ Legate.configure do |config|
46
+ # config.log_level = :debug
47
+ end
48
+
49
+ # --- 1. Create Session Service Instance ---
50
+ # LegateAgentAdapter needs this to create temporary sessions for each call.
51
+ # Using InMemory for simplicity, but RedisSessionService would also work.
52
+ session_service = Legate::SessionService::InMemory.new
53
+ Legate.logger.info("Using session service: #{session_service.class}")
54
+
55
+ # --- 2. Wrap the Legate Agent Definition ---
56
+ begin
57
+ AdaptedAgent = Legate::Mcp::Server::LegateAgentAdapter.wrap(AGENT_NAME, session_service)
58
+ Legate.logger.info("Successfully wrapped Legate Agent definition '#{AGENT_NAME}' for MCP.")
59
+ rescue Legate::Mcp::Error => e
60
+ Legate.logger.fatal("Failed to wrap Legate Agent: #{e.message}")
61
+ Legate.logger.fatal("Please ensure the agent '#{AGENT_NAME}' is defined.")
62
+ exit(1)
63
+ rescue StandardError => e
64
+ Legate.logger.fatal("Unexpected error during agent wrap: #{e.message}")
65
+ Legate.logger.fatal(e.backtrace.join("\n"))
66
+ exit(1)
67
+ end
68
+
69
+ # --- 3. Create fast-mcp Server Instance ---
70
+ mcp_server = FastMcp::Server::Stdio.new(
71
+ server_info: {
72
+ name: 'Legate Agent Server',
73
+ version: Legate::VERSION
74
+ },
75
+ logger: Legate.logger
76
+ )
77
+ Legate.logger.info('Initialized FastMcp::Server::Stdio.')
78
+
79
+ # --- 4. Register the Wrapped Agent Tool ---
80
+ mcp_server.register_tool(AdaptedAgent)
81
+ Legate.logger.info("Registered adapted agent tool '#{AdaptedAgent.tool_name}' with fast-mcp server.")
82
+
83
+ # --- 5. Start the Server ---
84
+ Legate.logger.info("Starting MCP server on STDIO for agent '#{AGENT_NAME}'. Waiting for requests...")
85
+ puts "--- Legate MCP Agent Server (#{AGENT_NAME}) Ready --- "
86
+ begin
87
+ mcp_server.start
88
+ rescue Interrupt
89
+ Legate.logger.info('Received interrupt, shutting down server.')
90
+ rescue StandardError => e
91
+ Legate.logger.fatal("MCP server crashed: #{e.class} - #{e.message}")
92
+ Legate.logger.fatal(e.backtrace.join("\n"))
93
+ ensure
94
+ Legate.logger.info('MCP server stopped.')
95
+ end
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example: Exposing Legate Tools via MCP using Rack Middleware
5
+ #
6
+ # This example demonstrates how to wrap an Legate tool (CalculatorTool)
7
+ # using LegateToolAdapter and expose it via MCP using fast-mcp's
8
+ # Rack middleware integration.
9
+ #
10
+ # Requires:
11
+ # - legate gem with MCP support installed
12
+ # - fast-mcp gem installed
13
+ # - rack and puma gems installed (`bundle add rack puma`)
14
+ #
15
+ # To Run:
16
+ # 1. Execute this script: `bundle exec ruby examples/advanced/mcp/mcp_server_rack.rb`
17
+ # 2. The server will start on http://localhost:9292.
18
+ # 3. In another terminal, use mcp-inspector (select SSE transport):
19
+ # `npx @modelcontextprotocol/inspector`
20
+ # Connect to: `http://localhost:9292/mcp/sse`
21
+ # 4. In the inspector:
22
+ # - Call the 'my_calculator' tool with parameters like:
23
+ # { "a": 10, "b": 5, "op": "*" }
24
+ # - Observe the result.
25
+
26
+ $LOAD_PATH.unshift File.expand_path('../../lib', __dir__)
27
+ require 'legate'
28
+ Legate.load_environment # Handle Bundler, Dotenv, etc.
29
+
30
+ require 'fast_mcp'
31
+ require 'legate/mcp/server/legate_tool_adapter'
32
+ require 'legate/tools/calculator' # The Legate tool we want to expose
33
+ require 'rack'
34
+ require 'rack/handler/puma'
35
+
36
+ ENV['LEGATE_LOG_LEVEL'] = 'FATAL'
37
+
38
+ # Configure Legate logger
39
+ # Legate.configure { |c| c.log_level = Logger::INFO }
40
+
41
+ # --- Define a simple base Rack app ---
42
+ # This is the app that runs alongside the MCP middleware.
43
+ # You could replace this with your Rails/Sinatra app.
44
+ base_app = lambda do |env|
45
+ if env['PATH_INFO'] == '/'
46
+ [200, { 'Content-Type' => 'text/html' }, [
47
+ '<html><body>',
48
+ '<h1>Legate MCP Rack Server Example</h1>',
49
+ '<p>MCP endpoints are active at /mcp/sse and /mcp/messages.</p>',
50
+ '</body></html>'
51
+ ]]
52
+ else
53
+ [404, { 'Content-Type' => 'text/plain' }, ['Not Found']]
54
+ end
55
+ end
56
+
57
+ # --- Wrap the Legate Tool ---
58
+ begin
59
+ AdaptedCalculator = Legate::Mcp::Server::LegateToolAdapter.wrap(Legate::Tools::Calculator)
60
+ Legate.logger.info("Wrapped CalculatorTool as: #{AdaptedCalculator.tool_name}")
61
+ rescue ArgumentError => e
62
+ Legate.logger.fatal("Failed to wrap CalculatorTool: #{e.message}")
63
+ exit(1)
64
+ end
65
+
66
+ # --- Create the MCP Middleware ---
67
+ # This adds the /mcp/sse and /mcp/messages endpoints to the base_app.
68
+ mcp_middleware = FastMcp.rack_middleware(
69
+ base_app,
70
+ name: 'legate-rack-server',
71
+ version: '1.0.0',
72
+ logger: Legate.logger,
73
+ allowed_origins: ['localhost', '127.0.0.1'] # Optional: Customize allowed origins
74
+ ) do |server|
75
+ # Register the wrapped tool with the server instance managed by the middleware
76
+ server.register_tool(AdaptedCalculator)
77
+ Legate.logger.info('Registered adapted tools with fast-mcp middleware server.')
78
+ end
79
+
80
+ # --- Run the Rack Application with Puma ---
81
+ puts 'Starting Rack application with MCP middleware on http://localhost:9292'
82
+ puts 'MCP endpoints:'
83
+ puts ' - http://localhost:9292/mcp/sse (SSE endpoint)'
84
+ puts ' - http://localhost:9292/mcp/messages (JSON-RPC endpoint)'
85
+ puts 'Press Ctrl+C to stop'
86
+
87
+ Rack::Handler::Puma.run mcp_middleware, Port: 9292
88
+
89
+ Legate.logger.info('Server finished.')
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # If running from project root: bundle exec ruby examples/advanced/random_calculator.rb
5
+ require_relative '../../lib/legate'
6
+
7
+ # random_number is a demo tool — shipped but not registered by default — so this
8
+ # example opts into it explicitly. (calculator is a registered built-in.)
9
+ Legate::GlobalToolManager.register_tool(Legate::Tools::RandomNumberTool)
10
+
11
+ puts '--- Random Calculator Agent Example (Multi-Step Planner w/ Hash Results) ---'
12
+
13
+ # 1. --- Agent Definition ---
14
+ random_calculator_definition = Legate::AgentDefinition.new.define do |a|
15
+ a.name :random_calculator_agent # Changed from multi_step_hash_agent_001 for clarity
16
+ a.description 'An agent that uses random number and calculator tools.'
17
+ a.instruction 'Your goal is to follow multi-step instructions involving random numbers and calculations. Use the random_number tool first, then the calculator tool with the result.'
18
+ a.use_tool :random_number # Provided by Legate::Tools::RandomNumberTool
19
+ a.use_tool :calculator # Provided by Legate::Tools::Calculator
20
+ end
21
+
22
+ # 2. --- Agent Instantiation ---
23
+ agent = Legate::Agent.new(definition: random_calculator_definition)
24
+
25
+ puts "\nAgent '#{agent.name}' created."
26
+ puts "Agent tools loaded: #{agent.tools.map(&:name).join(', ')}"
27
+
28
+ # 3. --- Start Agent ---
29
+ agent.start
30
+ puts "\nAgent '#{agent.name}' started. Running: #{agent.running?}"
31
+
32
+ # 4. --- Session Setup ---
33
+ # Create a session service and session
34
+ session_service = Legate::SessionService::InMemory.new
35
+ session = session_service.create_session(app_name: agent.name, user_id: 'example_user')
36
+ session_id = session.id
37
+ puts "\nCreated session: #{session_id}"
38
+
39
+ # 5. --- Task Execution ---
40
+ task = 'Get a random number between 10 and 20, then multiply it by 3.'
41
+ puts "\nRunning high-level task via agent.run_task: '#{task}'"
42
+
43
+ # Set log level to DEBUG to see planner details if needed
44
+ # ENV['LEGATE_LOG_LEVEL'] = 'DEBUG'
45
+ # Legate.instance_variable_set(:@logger, nil) # Force logger re-init
46
+
47
+ begin
48
+ result_event = agent.run_task(
49
+ session_id: session_id,
50
+ user_input: task,
51
+ session_service: session_service
52
+ )
53
+ puts "Raw result event: #{result_event.inspect}" # Show the structure
54
+
55
+ # --- Updated Result Handling ---
56
+ puts "\nInterpreted Result:"
57
+ if result_event.is_a?(Legate::Event)
58
+ puts ' Status: Event Received'
59
+ puts " Role: #{result_event.role}"
60
+
61
+ content = result_event.content
62
+ if content.is_a?(Array)
63
+ puts ' Content Type: Multi-Step Plan Results'
64
+ any_errors = false
65
+ content.each_with_index do |step_hash, index|
66
+ print " Step #{index + 1}: "
67
+ if step_hash.is_a?(Hash) && step_hash[:status] == :success
68
+ puts "Success | Result: #{step_hash[:result]}"
69
+ elsif step_hash.is_a?(Hash) && step_hash[:status] == :error
70
+ puts "Error | Message: #{step_hash[:error_message]}"
71
+ any_errors = true
72
+ else
73
+ puts "Unknown Format | Data: #{step_hash.inspect}"
74
+ any_errors = true # Treat unexpected format as problematic
75
+ end
76
+ end
77
+ puts " Overall Plan Status: #{any_errors ? 'Completed with errors' : 'Completed successfully'}"
78
+ elsif content.is_a?(Hash) && content.key?(:status)
79
+ # Single step plan or a planning error
80
+ if content[:status] == :success
81
+ puts ' Content Type: Single Step Success'
82
+ puts " Result: #{content[:result]}"
83
+ else # status == :error or other
84
+ puts ' Content Type: Error (or Single Step Error)'
85
+ puts " Message: #{content[:error_message]}"
86
+ end
87
+ else
88
+ puts ' Content Type: String or Other Format'
89
+ puts " Content: #{content}"
90
+ end
91
+ else
92
+ puts ' Status: Unknown (Unexpected Format)'
93
+ puts " Raw Data: #{result_event.inspect}"
94
+ end
95
+ # --- End Updated Result Handling ---
96
+ rescue StandardError => e
97
+ puts "Error executing task: #{e.class} - #{e.message}"
98
+ puts e.backtrace.first(5).join("\n")
99
+ end
100
+
101
+ # 6. --- Stop Agent ---
102
+ agent.stop
103
+ puts "\nAgent '#{agent.name}' stopped. Running: #{agent.running?}"
104
+ puts "\n--- Example Complete ---"
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../../lib/legate'
5
+ # Require the specific tool class if not automatically loaded. SleepyTool provides :start_sleepy_job.
6
+ require_relative '../tools/sleepy_tool'
7
+ # Legate::Tools::CheckJobStatusTool (providing :check_job_status) is loaded by Legate core.
8
+
9
+ puts '--- Async Job Agent Example (Polling Status) ---'
10
+
11
+ # 1. --- Agent Definition ---
12
+ async_job_demo_definition = Legate::AgentDefinition.new.define do |a|
13
+ a.name :async_job_demo_agent
14
+ a.description 'An agent that starts a background job and can check its status.'
15
+ a.instruction 'You can start sleepy jobs and check their status. Use start_sleepy_job to initiate, and the system might use check_job_status for monitoring.'
16
+ a.use_tool :start_sleepy_job # Provided by Legate::Tools::SleepyTool
17
+ a.use_tool :check_job_status # Provided by Legate::Tools::CheckJobStatusTool
18
+ end
19
+
20
+ # 2. --- Agent Instantiation ---
21
+ agent = Legate::Agent.new(definition: async_job_demo_definition)
22
+
23
+ # Get an instance of the status checker tool directly (needed for manual polling in this example script)
24
+ status_checker_tool = Legate::GlobalToolManager.create_instance(:check_job_status)
25
+ unless status_checker_tool
26
+ puts 'Error: Status checker tool (:check_job_status) not found in GlobalToolManager.'
27
+ exit 1
28
+ end
29
+
30
+ puts "\nAgent '#{agent.name}' created with tools: #{agent.tools.map(&:name).join(', ')}"
31
+ puts "Status checker tool for manual polling: #{status_checker_tool.name}"
32
+
33
+ # 3. --- Start Agent (Needed for the initial task) ---
34
+ agent.start
35
+ puts 'Agent started.'
36
+
37
+ # 4. --- Session Setup ---
38
+ # We use a session service to store the conversation history, including tool results.
39
+ session_service = Legate::SessionService::InMemory.new
40
+ session = session_service.create_session(app_name: agent.name, user_id: 'demo_user')
41
+ session_id = session.id
42
+ puts "\nCreated session: #{session_id}"
43
+
44
+ # 5. --- Task Execution & Polling ---
45
+ # The task asks the agent to use the async tool.
46
+ job_duration = rand(3..8) # Random duration for the job
47
+ task = "Start a sleepy job for #{job_duration} seconds with message 'Demo complete'"
48
+ puts "\nExecuting task: '#{task}'"
49
+
50
+ job_id = nil
51
+ final_job_status = nil
52
+
53
+ begin
54
+ # --- Run the task that starts the job ---
55
+ agent.run_task(
56
+ session_id: session_id,
57
+ user_input: task,
58
+ session_service: session_service
59
+ )
60
+
61
+ # --- Check Session History for the Job ID ---
62
+ puts "\nTask finished. Checking session history for the job ID..."
63
+ current_session = session_service.get_session(session_id: session_id)
64
+ # Find the result event from the tool that starts the job
65
+ start_job_event = current_session&.events&.reverse&.find do |event|
66
+ event.role == :tool_result && event.tool_name == :start_sleepy_job
67
+ end
68
+
69
+ # --- Polling for Job Completion ---
70
+ if start_job_event&.content&.is_a?(Hash) && start_job_event.content[:status] == :pending
71
+ job_id = start_job_event.content[:job_id]
72
+ puts "Found pending job: #{job_id}. Starting polling using #{status_checker_tool.name}..."
73
+
74
+ # Add a small initial delay to give the worker time to start processing
75
+ sleep 0.5
76
+
77
+ max_attempts = 30
78
+ attempt = 0
79
+ start_time = Time.now
80
+ polling_context = Legate::ToolContext.new(session_id: session_id, app_name: 'polling_script', user_id: 'demo_user')
81
+
82
+ while attempt < max_attempts
83
+ attempt += 1
84
+
85
+ # Execute the status checker tool directly
86
+ status_result = status_checker_tool.execute({ job_id: job_id }, polling_context)
87
+
88
+ if status_result.is_a?(Hash)
89
+ final_job_status = status_result[:status]&.to_sym # Ensure symbol
90
+
91
+ case final_job_status
92
+ when :success
93
+ elapsed = Time.now - start_time
94
+ puts "\nJob completed successfully! (took #{elapsed.round(1)} seconds)"
95
+ puts "Result: #{status_result[:result]}"
96
+ break
97
+ when :error
98
+ elapsed = Time.now - start_time
99
+ puts "\nJob failed! (after #{elapsed.round(1)} seconds)"
100
+ puts "Error: #{status_result[:error_message]}"
101
+ break
102
+ when :pending
103
+ print '.' # Progress indicator
104
+ $stdout.flush
105
+ else
106
+ elapsed = Time.now - start_time
107
+ puts "\nUnexpected job status '#{final_job_status}' received after #{elapsed.round(1)}s. Stopping poll."
108
+ puts "Raw status content: #{status_result.inspect}"
109
+ break
110
+ end
111
+ else
112
+ elapsed = Time.now - start_time
113
+ puts "\nUnexpected result format from status checker tool after #{elapsed.round(1)}s: #{status_result.inspect}. Stopping poll."
114
+ final_job_status = :error # Treat unexpected format as an error
115
+ break
116
+ end
117
+
118
+ sleep 1 # Wait before next poll
119
+ end
120
+
121
+ if attempt >= max_attempts && final_job_status == :pending
122
+ elapsed = Time.now - start_time
123
+ puts "\nPolling timed out after #{max_attempts} attempts (#{elapsed.round(1)} seconds). Job status still pending."
124
+ end
125
+
126
+ elsif start_job_event
127
+ puts "Job start step did not return a pending status. Status: #{start_job_event.content[:status]}"
128
+ final_job_status = start_job_event.content[:status]
129
+ else
130
+ puts "Could not find the result event for '#{sleepy_tool.name}' in the session history."
131
+ final_job_status = :error # Indicate failure if job start wasn't found
132
+ end
133
+ rescue StandardError => e
134
+ puts "\nError during task execution or polling: #{e.class} - #{e.message}"
135
+ puts e.backtrace.first(5).join("\n")
136
+ final_job_status = :error # Indicate failure on exception
137
+ end
138
+
139
+ # 6. --- Stop Agent ---
140
+ puts "\nStopping agent..."
141
+ agent.stop
142
+ puts 'Agent stopped.'
143
+
144
+ # 7. --- Final Summary ---
145
+ puts "\n--- Example Summary ---"
146
+ if job_id
147
+ puts "Job ID: #{job_id}"
148
+ puts "Final Status: #{final_job_status || 'Unknown'}"
149
+ else
150
+ puts 'Job ID: Not found'
151
+ puts "Final Status: #{final_job_status || 'Error'}"
152
+ end
153
+ puts '--- Example Complete ---'
@@ -0,0 +1,110 @@
1
+ # File: examples/advanced/webhooks/webhook_e2e_runner.rb
2
+ # frozen_string_literal: true
3
+
4
+ # Add lib to load path
5
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __dir__))
6
+
7
+ require 'legate'
8
+ require_relative 'webhook_receiver_agent' # Ensure agent definition is loaded
9
+ require 'openssl'
10
+ require 'json'
11
+
12
+ # --- Configuration ---
13
+
14
+ # Ensure the secret is set (same as used in webhook_receiver_agent.rb)
15
+ # Set this environment variable before running the script:
16
+ # export WEBHOOK_RECEIVER_SECRET='a-very-secret-key'
17
+ webhook_secret = ENV['WEBHOOK_RECEIVER_SECRET']
18
+ unless webhook_secret
19
+ puts 'Error: WEBHOOK_RECEIVER_SECRET environment variable must be set.'
20
+ exit(1)
21
+ end
22
+
23
+ # Configure Legate for the E2E test
24
+ Legate.configure do |config|
25
+ # 1. Webhook Listener Settings
26
+ config.webhooks.listener_enabled = true
27
+ config.webhooks.listen_address = 'localhost' # Bind to localhost for this test
28
+ config.webhooks.listen_port = 9293 # Use a non-default port
29
+ config.webhooks.base_path = '/webhooks' # Default base path
30
+
31
+ # 2. Enable Dynamic Agent Handler
32
+ config.webhooks.enable_dynamic_agent_handler = true
33
+ # Keep default route pattern: /agents/:agent_name/trigger
34
+
35
+ # 3. Session service uses in-memory storage
36
+ config.session_service = Legate::SessionService::InMemory.new
37
+ end
38
+
39
+ # --- Verify Agent Definition Was Loaded and Registered Globally ---
40
+ # The `require_relative 'webhook_receiver_agent'` should have defined and globally registered the definition.
41
+ begin
42
+ retrieved_def_obj = Legate::GlobalDefinitionRegistry.find(:webhook_receiver)
43
+ raise 'Failed to retrieve :webhook_receiver definition object from Legate::GlobalDefinitionRegistry after loading.' unless retrieved_def_obj && retrieved_def_obj.is_a?(Legate::AgentDefinition) && retrieved_def_obj.name == :webhook_receiver
44
+
45
+ puts 'Verified :webhook_receiver definition object exists in GlobalDefinitionRegistry.'
46
+ rescue StandardError => e
47
+ puts "Error verifying agent definition in GlobalDefinitionRegistry: #{e.message}"
48
+ exit(1)
49
+ end
50
+
51
+ # --- Process Management ---
52
+ web_server_pid = nil
53
+
54
+ begin
55
+ puts "Starting Legate Web Server (with Webhook Listener) on port #{Legate.config.webhooks.listen_port}..."
56
+ puts '--- Legate Web Server Output START ---'
57
+ # Use rackup with the new config.ru
58
+ port = Legate.config.webhooks.listen_port
59
+ web_server_pid = Process.spawn("bundle exec rackup config.ru -p #{port}")
60
+ # Wait briefly for server to start
61
+ sleep 2
62
+
63
+ puts "\nProcess started: Web=#{web_server_pid}"
64
+ puts "Sending webhook trigger using webhook-example.rb...\n---"
65
+
66
+ # Execute the sender script
67
+ # Pass listener port/path via ENV vars so sender script knows where to send
68
+ sender_env = {
69
+ 'LEGATE_WEBHOOK_PORT' => Legate.config.webhooks.listen_port.to_s,
70
+ 'LEGATE_WEBHOOK_BASE_PATH' => Legate.config.webhooks.base_path,
71
+ 'WEBHOOK_RECEIVER_SECRET' => webhook_secret # Ensure sender uses the same secret
72
+ }
73
+ sender_success = system(sender_env, 'bundle exec ruby examples/webhook-example.rb')
74
+ puts '---'
75
+
76
+ unless sender_success
77
+ puts "\nWebhook sender script failed! Check output above."
78
+ # Don't exit immediately, try to clean up processes
79
+ end
80
+
81
+ puts "\nWaiting 5 seconds for job processing..."
82
+ sleep 5
83
+
84
+ # --- Verification (Basic Log Check) ---
85
+ puts "\nVerification:"
86
+ puts 'Please check the console output where the web server is running.'
87
+ puts 'You should see log messages indicating the webhook was received and processed.'
88
+ rescue Interrupt
89
+ puts "\nInterrupted. Cleaning up..."
90
+ rescue StandardError => e
91
+ puts "\nAn error occurred in the runner: #{e.message}"
92
+ puts e.backtrace.join("\n")
93
+ ensure
94
+ # --- Cleanup Background Processes ---
95
+ puts "\nCleaning up background processes..."
96
+ if web_server_pid
97
+ puts "Stopping Web server (PID: #{web_server_pid})..."
98
+ begin
99
+ Process.kill('TERM', web_server_pid)
100
+ rescue StandardError
101
+ nil
102
+ end
103
+ begin
104
+ Process.wait(web_server_pid)
105
+ rescue StandardError
106
+ nil
107
+ end # Wait briefly
108
+ end
109
+ puts 'Cleanup complete.'
110
+ end
@@ -0,0 +1,58 @@
1
+ # File: examples/advanced/webhooks/webhook_receiver_agent.rb
2
+ # frozen_string_literal: true
3
+
4
+ # Add lib to load path
5
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __dir__))
6
+
7
+ require 'legate'
8
+ require 'json'
9
+
10
+ # Example Agent designed to be triggered by an inbound webhook
11
+ # This script defines the agent and registers it globally.
12
+ # Another process (e.g., a web server running Legate::Web::WebhookListener)
13
+ # would look up this definition by name to handle incoming webhooks.
14
+
15
+ Legate.logger.debug 'Defining agent :webhook_receiver...'
16
+
17
+ webhook_receiver_definition = Legate::AgentDefinition.new.define do |a|
18
+ a.name(:webhook_receiver)
19
+ a.description('Receives simple webhook POSTs and logs the message.')
20
+ a.instruction('You will receive a simple text message. Log it clearly.')
21
+
22
+ # Tools - using echo tool for explicitness.
23
+ a.use_tool :echo
24
+
25
+ # --- Webhook Configuration ---
26
+ a.webhook_enabled(true)
27
+
28
+ # 1. Validator: Use HMAC-SHA256 (validator logic would be globally registered)
29
+ a.webhook_validator(:hmac_sha256)
30
+ # Secret for HMAC should be set in the environment where the webhook listener runs.
31
+ # For this definition, we can reference an ENV var that the listener would also use.
32
+ a.webhook_secret(ENV['WEBHOOK_RECEIVER_SECRET'] || 'default-secret-for-definition-if-not-set-in-env')
33
+
34
+ # 2. Transformer: Expects JSON like {"message": "...", "source": "..."}
35
+ # Returns the extracted message string for run_task input.
36
+ a.webhook_transformer(->(request_body) do
37
+ # Ensure request_body is parsed if it's a JSON string
38
+ parsed_body = request_body.is_a?(String) ? JSON.parse(request_body) : request_body
39
+ msg = parsed_body['message']
40
+ raise Legate::WebhookConfigurationError, "Missing or invalid 'message' in webhook payload." unless msg.is_a?(String) && !msg.empty?
41
+
42
+ "Received webhook message: '#{msg}'"
43
+ rescue JSON::ParserError => e
44
+ raise Legate::WebhookConfigurationError, "Invalid JSON in webhook payload: #{e.message}"
45
+ end)
46
+
47
+ # 3. Session Extractor: Use a static session ID for all triggers to this agent
48
+ # Alternatively, could extract based on payload, e.g., request_body['source']
49
+ a.webhook_session_extractor(->(_request_body) do # Mark _request_body as unused
50
+ 'webhook_receiver_test_session'
51
+ end)
52
+ end
53
+
54
+ # Register the definition globally so it can be found by name.
55
+ Legate::GlobalDefinitionRegistry.register(webhook_receiver_definition)
56
+
57
+ Legate.logger.debug "Agent definition '#{webhook_receiver_definition.name}' created and registered globally."
58
+ Legate.logger.debug 'Note: This script only defines and registers the agent. A separate webhook listener process is needed to use it.'