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,89 @@
1
+ # File: lib/legate/tools/calculator.rb
2
+ # frozen_string_literal: true
3
+
4
+ # Removed require 'logger' - Use Legate.logger
5
+ require_relative '../tool' # Ensure base class is loaded
6
+
7
+ module Legate
8
+ module Tools
9
+ # A simple calculator tool supporting basic arithmetic operations.
10
+ class Calculator < Tool
11
+ # --- New DSL Metadata ---
12
+ # Name :calculator will be inferred
13
+ tool_description 'Calculates the result of an arithmetic operation. Requires two numbers (operand1, operand2) and the operation name (operation: "add", "subtract", "multiply", "divide", or symbols +, -, *, /).'
14
+
15
+ parameter :operand1,
16
+ type: :numeric,
17
+ description: 'The first number for the calculation.',
18
+ required: true
19
+
20
+ parameter :operand2,
21
+ type: :numeric,
22
+ description: 'The second number for the calculation.',
23
+ required: true
24
+
25
+ parameter :operation,
26
+ type: :string,
27
+ description: 'The operation to perform (e.g., "add", "subtract", "multiply", "divide", "+", "-", "*", "/").',
28
+ required: true
29
+
30
+ private
31
+
32
+ # @param params [Hash] Contains operand1, operand2, operation.
33
+ # @param _context [Legate::ToolContext, nil] The execution context (unused here).
34
+ def perform_execution(params, _context)
35
+ op1_str = params.fetch('operand1') { params.fetch(:operand1, nil) }
36
+ op2_str = params.fetch('operand2') { params.fetch(:operand2, nil) }
37
+ operation = params.fetch('operation') { params.fetch(:operation, nil) }&.to_s&.downcase
38
+
39
+ begin
40
+ # Validate inputs first
41
+ begin
42
+ op1 = Float(op1_str)
43
+ op2 = Float(op2_str)
44
+ rescue ArgumentError, TypeError
45
+ err_msg = "Invalid numeric input provided for operands. Op1: '#{op1_str}', Op2: '#{op2_str}'"
46
+ Legate.logger.error("Calculator Tool Argument Error: #{err_msg}")
47
+ raise Legate::ToolArgumentError, err_msg
48
+ end
49
+
50
+ valid_ops = %w[add subtract multiply divide + - * /]
51
+ unless valid_ops.include?(operation)
52
+ err_msg = "Unsupported operation: '#{operation}'. Use add, subtract, multiply, or divide (or +, -, *, /)."
53
+ Legate.logger.warn("Calculator Tool Argument Warning: #{err_msg}")
54
+ raise Legate::ToolArgumentError, err_msg
55
+ end
56
+
57
+ if %w[divide /].include?(operation) && op2.zero?
58
+ err_msg = 'Division by zero is not allowed.'
59
+ Legate.logger.error("Calculator Tool Argument Error: #{err_msg}")
60
+ raise Legate::ToolArgumentError, err_msg
61
+ end
62
+
63
+ # Perform calculation
64
+ result_val = case operation
65
+ when 'add', '+' then op1 + op2
66
+ when 'subtract', '-' then op1 - op2
67
+ when 'multiply', '*' then op1 * op2
68
+ when 'divide', '/' then op1 / op2 # Zero already checked
69
+ # No else needed due to validation above
70
+ end
71
+
72
+ Legate.logger.info("Calculator Tool: #{op1} #{operation} #{op2} = #{result_val}")
73
+ { status: :success, result: result_val }
74
+
75
+ # Catch potential unexpected errors during execution logic itself
76
+ rescue Legate::ToolArgumentError => e # Re-raise argument errors
77
+ raise e
78
+ rescue Legate::ToolError => e # Catch specific Legate tool errors if they occur somehow
79
+ Legate.logger.error("Calculator Tool Legate::ToolError: #{e.message}")
80
+ raise e # Re-raise to be handled by the agent
81
+ rescue StandardError => e
82
+ # Wrap unexpected errors in a ToolError
83
+ Legate.logger.error("Calculator Tool: Unexpected internal error during calculation: #{e.class} - #{e.message}")
84
+ raise Legate::ToolError, "Calculation failed due to an unexpected internal error: #{e.message}"
85
+ end
86
+ end # End perform_execution
87
+ end # End Calculator class
88
+ end # End Tools module
89
+ end # End Legate module
@@ -0,0 +1,81 @@
1
+ # File: lib/legate/tools/cat_facts.rb
2
+ # frozen_string_literal: true
3
+
4
+ # Removed Faraday and JSON requires, handled by HttpClient
5
+ require_relative '../tool'
6
+ require_relative 'base/http_client' # Include the base module
7
+
8
+ module Legate
9
+ module Tools
10
+ # Tool to fetch a random cat fact from an online API.
11
+ class CatFacts < Legate::Tool
12
+ include Legate::Tools::Base::HttpClient # Include the mixin
13
+
14
+ # --- New DSL Metadata ---
15
+ # Name :cat_facts will be inferred
16
+ tool_description 'Fetches a random cat fact from an online API.'
17
+ # No parameters needed, so no `parameter` calls.
18
+ # --- End New DSL Metadata ---
19
+
20
+ # The base URL for the Cat Fact API.
21
+ CAT_FACT_BASE_URL = 'https://catfact.ninja'
22
+
23
+ # Initializes the tool instance.
24
+ # Sets up the HTTP client using the base module.
25
+ def initialize(**options)
26
+ super(**options)
27
+ # Use the base module to set up the client
28
+ # It handles initialization errors and logging internally.
29
+ setup_http_client(base_url: CAT_FACT_BASE_URL)
30
+ end
31
+
32
+ private
33
+
34
+ # The main execution method required by the Legate::Tool base class.
35
+ # It delegates the actual work to the fetch_cat_fact helper method.
36
+ def perform_execution(_params, _context)
37
+ fetch_cat_fact
38
+ end
39
+
40
+ # Helper method to perform the HTTP request using the HttpClient module
41
+ # and handle responses/errors.
42
+ # Returns the standardized result hash.
43
+ #
44
+ # @return [Hash] A hash with :status (:success or :error) and :result/:error_message.
45
+ # @raise [Legate::ToolError] Propagates errors from http_get, parse_json_response, or validation.
46
+ def fetch_cat_fact
47
+ Legate.logger.info('Fetching cat fact using HttpClient...')
48
+
49
+ # Perform the GET request using the base module helper
50
+ # Network/HTTP/Timeout errors are automatically handled and raised as Legate::ToolError
51
+ response = http_get('/fact')
52
+
53
+ # Parse the JSON response body directly
54
+ begin
55
+ data = JSON.parse(response.body)
56
+ rescue JSON::ParserError => e
57
+ raise Legate::ToolError.new("Failed to parse JSON response from Cat Fact API: #{e.message}", cause: e)
58
+ end
59
+
60
+ fact = data['fact'] # Extract the 'fact' field
61
+
62
+ # Check if a valid fact was received
63
+ if fact && !fact.empty?
64
+ Legate.logger.info('Cat fact fetched successfully.')
65
+ { status: :success, result: fact }
66
+ else
67
+ # Raise an error if the expected field is missing or empty
68
+ err_msg = "Cat fact API response did not contain a valid 'fact' field."
69
+ Legate.logger.warn(err_msg)
70
+ raise Legate::ToolError, err_msg
71
+ end
72
+
73
+ # No need for extensive rescue blocks here anymore.
74
+ # Legate::ToolError from http_get, parse_json_response, or the validation
75
+ # will propagate up and be handled by the Legate runtime.
76
+ # StandardError might still occur in unexpected places, but the base
77
+ # HttpClient tries to catch most common issues.
78
+ end # end fetch_cat_fact
79
+ end # End CatFacts class
80
+ end # End Tools module
81
+ end # End Legate module
@@ -0,0 +1,48 @@
1
+ # File: lib/legate/tools/check_job_status_tool.rb
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../tool'
5
+ require_relative '../errors'
6
+ require 'json'
7
+
8
+ module Legate
9
+ module Tools
10
+ # A built-in tool to check the status and retrieve results for a background job
11
+ # initiated by a Legate async tool.
12
+ class CheckJobStatusTool < Legate::Tool
13
+ # --- New DSL Metadata ---
14
+ # Name :check_job_status_tool will be inferred
15
+ self.explicit_tool_name = :check_job_status # Keep original name
16
+
17
+ tool_description 'Checks the status and retrieves the result of a previously started background job using its ID.'
18
+
19
+ parameter :job_id,
20
+ type: :string,
21
+ description: 'The ID of the job to check status for',
22
+ required: true
23
+ # --- End New DSL Metadata ---
24
+
25
+ private
26
+
27
+ # @param params [Hash] Must contain :job_id.
28
+ # @param _context [Legate::ToolContext, nil] The execution context (unused here).
29
+ # @return [Hash] { status: :success/:error, ... }
30
+ def perform_execution(params, _context)
31
+ job_id = params[:job_id]
32
+ return { status: :error, error_message: 'Missing job_id parameter.' } unless job_id && !job_id.strip.empty?
33
+
34
+ result = Legate::Tools::BaseAsyncJobTool.job_results[job_id]
35
+
36
+ if result.nil?
37
+ { status: :success, job_id: job_id, job_status: 'unknown', message: 'Job ID not found.' }
38
+ else
39
+ job_status = result['status']
40
+ response = { status: :success, job_id: job_id, job_status: job_status }
41
+ response[:result] = result['result'] if result['result']
42
+ response[:error_message] = result['error_message'] if result['error_message']
43
+ response
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,64 @@
1
+ # File: lib/legate/tools/current_time_tool.rb
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../tool'
5
+ require 'time'
6
+
7
+ module Legate
8
+ module Tools
9
+ # Returns the current date and time.
10
+ #
11
+ # Language models don't know the current time, so this is a common building
12
+ # block for scheduling, "how long ago", and freshness checks. Returns UTC by
13
+ # default; accepts "UTC", "local", or a fixed UTC offset (e.g. "+09:00").
14
+ # Named IANA zones (e.g. "America/New_York") are intentionally not supported
15
+ # to avoid a timezone-database dependency and process-global TZ mutation.
16
+ class CurrentTime < Legate::Tool
17
+ OFFSET_PATTERN = /\A[+-]\d{2}:?\d{2}\z/
18
+
19
+ tool_name # inferred: :current_time
20
+ tool_description 'Returns the current date and time (ISO 8601, epoch, and an optional custom format). ' \
21
+ 'Accepts a timezone of "UTC" (default), "local", or a fixed UTC offset like "+09:00".'
22
+
23
+ parameter :timezone, type: :string, required: false,
24
+ description: 'Timezone: "UTC" (default), "local", or a fixed offset such as "+05:30" or "-0800".'
25
+ parameter :format, type: :string, required: false,
26
+ description: 'Optional strftime format (e.g. "%A, %B %-d, %Y"). Defaults to ISO 8601.'
27
+
28
+ private
29
+
30
+ def perform_execution(params, _context)
31
+ now = Time.now.utc
32
+ tz = params[:timezone].to_s.strip
33
+ base = localize(now, tz)
34
+ return { status: :error, error_message: unsupported_tz_message(tz) } unless base
35
+
36
+ fmt = params[:format].to_s
37
+ {
38
+ status: :success,
39
+ result: {
40
+ iso8601: base.iso8601,
41
+ formatted: fmt.empty? ? base.iso8601 : base.strftime(fmt),
42
+ epoch: now.to_i,
43
+ timezone: tz.empty? ? 'UTC' : tz
44
+ }
45
+ }
46
+ rescue ArgumentError => e
47
+ { status: :error, error_message: "Invalid format or timezone: #{e.message}" }
48
+ end
49
+
50
+ def localize(utc_now, zone)
51
+ case zone.downcase
52
+ when '', 'utc', 'z' then utc_now
53
+ when 'local' then utc_now.getlocal
54
+ else
55
+ zone.match?(OFFSET_PATTERN) ? utc_now.getlocal(zone) : nil
56
+ end
57
+ end
58
+
59
+ def unsupported_tz_message(zone)
60
+ "Unsupported timezone '#{zone}'. Use 'UTC', 'local', or a fixed offset like '+09:00'."
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,43 @@
1
+ # File: lib/legate/tools/echo.rb
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../tool'
5
+
6
+ module Legate
7
+ module Tools
8
+ class Echo < Tool
9
+ # --- New DSL Metadata ---
10
+ # Name :echo will be inferred
11
+ tool_description 'Echoes back the provided message.'
12
+
13
+ parameter :message,
14
+ type: :string,
15
+ description: 'The message to echo',
16
+ required: true
17
+ # --- End New DSL Metadata ---
18
+
19
+ private
20
+
21
+ # @param params [Hash] Contains :message.
22
+ # @param _context [Legate::ToolContext, nil] The execution context (unused here).
23
+ def perform_execution(params, _context)
24
+ # Fetch validated parameter
25
+ message = params.fetch('message') { params.fetch(:message, nil) }
26
+
27
+ # This check is belts-and-suspenders; validation should catch missing required params.
28
+ unless message
29
+ err_msg = 'Internal Error: Message parameter missing in perform_execution for Echo tool after validation.'
30
+ Legate.logger.error(err_msg)
31
+ raise Legate::ToolError, err_msg
32
+ end
33
+
34
+ # Simple success case
35
+ { status: :success, result: message }
36
+ rescue StandardError => e # Catch any truly unexpected errors during fetch/processing
37
+ Legate.logger.error("Echo Tool: Unexpected error: #{e.class} - #{e.message}")
38
+ # Wrap unexpected errors in a ToolError
39
+ raise Legate::ToolError, "Unexpected error in Echo tool: #{e.message}"
40
+ end
41
+ end # End Echo class
42
+ end # End Tools module
43
+ end # End Legate module
@@ -0,0 +1,105 @@
1
+ # File: lib/legate/tools/http_request_tool.rb
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../tool'
5
+ require_relative 'base/http_client'
6
+ require_relative 'base/safe_url'
7
+
8
+ module Legate
9
+ module Tools
10
+ # General-purpose HTTP client tool.
11
+ #
12
+ # Makes a request to a URL and returns the status code, response headers, and
13
+ # body. It is SSRF-safe (private/loopback/link-local hosts are blocked and the
14
+ # connection is pinned to the validated IP) and auth-aware (configured auth
15
+ # URL-mappings are applied automatically; pass `headers` for manual auth).
16
+ #
17
+ # A non-2xx response is returned as a normal result (with its status_code) so
18
+ # an agent can inspect it; only network/SSRF/timeout failures are errors.
19
+ class HttpRequest < Legate::Tool
20
+ include Legate::Tools::Base::HttpClient
21
+
22
+ # Cap the returned body so a large download can't blow up the context/LLM.
23
+ MAX_BODY_BYTES = 1_000_000
24
+ ALLOWED_METHODS = %w[GET POST PUT PATCH DELETE HEAD].freeze
25
+
26
+ tool_name # inferred: :http_request
27
+ tool_description 'Makes an HTTP request to a URL and returns the status code, headers, and body. ' \
28
+ 'Supports GET (default), POST, PUT, PATCH, DELETE, and HEAD. Blocks private and ' \
29
+ 'loopback addresses (SSRF-safe) and applies configured authentication for matching URLs.'
30
+
31
+ parameter :url, type: :string, required: true,
32
+ description: 'The full URL to request (must be http or https).'
33
+ parameter :method, type: :string, required: false,
34
+ description: 'HTTP method: GET (default), POST, PUT, PATCH, DELETE, or HEAD.'
35
+ parameter :headers, type: :hash, required: false,
36
+ description: 'Optional request headers.'
37
+ parameter :body, type: %i[hash string], required: false,
38
+ description: 'Optional request body. A Hash is JSON-encoded with Content-Type: application/json.'
39
+ parameter :query, type: :hash, required: false,
40
+ description: 'Optional query-string parameters.'
41
+
42
+ def initialize(**options)
43
+ super(**options)
44
+ # Targets are passed absolute per-request; the base URL is only a required placeholder.
45
+ setup_http_client(base_url: 'https://placeholder.invalid')
46
+ end
47
+
48
+ private
49
+
50
+ def perform_execution(params, context)
51
+ url = params.fetch(:url)
52
+ method = (params[:method] || 'GET').to_s.upcase
53
+ return { status: :error, error_message: "Unsupported HTTP method: #{method}" } unless ALLOWED_METHODS.include?(method)
54
+
55
+ uri, pinned_ip = Legate::Tools::Base::SafeUrl.resolve!(url)
56
+ headers = apply_auth(context, method, url, stringify_headers(params[:headers] || {}))
57
+
58
+ response = make_request(
59
+ method.downcase.to_sym, url,
60
+ body: params[:body],
61
+ query: params[:query] || {},
62
+ headers: headers,
63
+ options: { resolved_ip: pinned_ip, original_host: uri.host }
64
+ )
65
+ { status: :success, result: build_result(url, response) }
66
+ rescue Legate::ToolHttpError => e
67
+ # A non-2xx response still completed; surface its details rather than erroring.
68
+ return { status: :success, result: build_result(url, e.response) } if e.response
69
+
70
+ { status: :error, error_message: e.message }
71
+ rescue Legate::ToolError => e
72
+ { status: :error, error_message: e.message }
73
+ end
74
+
75
+ # Let the execution context apply any configured auth (URL mappings). It is a
76
+ # no-op when no auth is configured or the context doesn't support it.
77
+ def apply_auth(context, method, url, headers)
78
+ return headers unless context.respond_to?(:handle_request_auth)
79
+
80
+ request = context.handle_request_auth({ method: method.downcase.to_sym, url: url, headers: headers })
81
+ request.is_a?(Hash) && request[:headers] ? request[:headers] : headers
82
+ rescue StandardError => e
83
+ Legate.logger.warn("HttpRequest: auth application failed, sending unauthenticated: #{e.message}")
84
+ headers
85
+ end
86
+
87
+ def build_result(url, response)
88
+ body = response.body.to_s
89
+ truncated = body.bytesize > MAX_BODY_BYTES
90
+ body = body.byteslice(0, MAX_BODY_BYTES) if truncated
91
+ {
92
+ url: url,
93
+ status_code: response.status,
94
+ headers: response.headers,
95
+ body: body,
96
+ truncated: truncated
97
+ }
98
+ end
99
+
100
+ def stringify_headers(hash)
101
+ hash.transform_keys(&:to_s)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,64 @@
1
+ # File: lib/legate/tools/random_number_tool.rb
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../tool'
5
+
6
+ module Legate
7
+ module Tools
8
+ class RandomNumberTool < Legate::Tool
9
+ # --- New DSL Metadata ---
10
+ # Name will be inferred as :random_number_tool
11
+ self.explicit_tool_name = :random_number # Keep original name
12
+
13
+ tool_description 'Generates a random integer between a minimum and maximum value (inclusive). Defaults to 1-100.'
14
+
15
+ parameter :min,
16
+ type: :integer,
17
+ description: 'The minimum value for the random number (inclusive).',
18
+ required: false
19
+
20
+ parameter :max,
21
+ type: :integer,
22
+ description: 'The maximum value for the random number (inclusive).',
23
+ required: false
24
+ # --- End New DSL Metadata ---
25
+
26
+ private
27
+
28
+ # @param params [Hash] Contains min and max.
29
+ # @param _context [Legate::ToolContext, nil] The execution context (unused here).
30
+ def perform_execution(params, _context)
31
+ # Fetch parameters safely, providing defaults
32
+ min_val_str = params.fetch('min') { params.fetch(:min, '1') }
33
+ max_val_str = params.fetch('max') { params.fetch(:max, '100') }
34
+
35
+ begin
36
+ min_val = Integer(min_val_str)
37
+ max_val = Integer(max_val_str)
38
+ rescue ArgumentError, TypeError
39
+ err_msg = "Invalid integer input provided for min or max. Min: '#{min_val_str}', Max: '#{max_val_str}'"
40
+ Legate.logger.error("RandomNumberTool Argument Error: #{err_msg}")
41
+ raise Legate::ToolArgumentError, err_msg
42
+ end
43
+
44
+ # Check logical constraint
45
+ if min_val > max_val
46
+ err_msg = "Min value (#{min_val}) cannot be greater than Max value (#{max_val})."
47
+ Legate.logger.error("RandomNumberTool Argument Error: #{err_msg}")
48
+ raise Legate::ToolArgumentError, err_msg
49
+ end
50
+
51
+ # Perform the core logic
52
+ random_num = rand(min_val..max_val)
53
+ Legate.logger.info("RandomNumberTool generated: #{random_num} (Range: #{min_val}-#{max_val})")
54
+ { status: :success, result: random_num }
55
+ rescue Legate::ToolArgumentError => e # Re-raise specific argument errors
56
+ raise e
57
+ rescue StandardError => e # Catch unexpected errors during the process
58
+ Legate.logger.error("RandomNumberTool: Unexpected error: #{e.class} - #{e.message}")
59
+ # Wrap unexpected errors in a ToolError
60
+ raise Legate::ToolError, "Unexpected error in RandomNumber tool: #{e.message}"
61
+ end
62
+ end # End RandomNumberTool class
63
+ end # End Tools module
64
+ end # End Legate module
@@ -0,0 +1,92 @@
1
+ # File: lib/legate/tools/read_webpage_tool.rb
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../tool'
5
+ require_relative 'base/http_client'
6
+ require_relative 'base/safe_url'
7
+
8
+ module Legate
9
+ module Tools
10
+ # Fetches a web page and returns its readable text content with markup removed.
11
+ #
12
+ # This is the backbone of research/RAG agents: give it a URL and it returns
13
+ # the page title and plain text (script/style stripped, entities decoded,
14
+ # whitespace collapsed), capped to a sane size. SSRF-safe via {Base::SafeUrl}.
15
+ class ReadWebpage < Legate::Tool
16
+ include Legate::Tools::Base::HttpClient
17
+
18
+ DEFAULT_MAX_CHARS = 20_000
19
+ HARD_MAX_CHARS = 200_000
20
+ ENTITIES = { '&amp;' => '&', '&lt;' => '<', '&gt;' => '>', '&quot;' => '"',
21
+ '&#39;' => "'", '&apos;' => "'", '&nbsp;' => ' ' }.freeze
22
+
23
+ tool_name # inferred: :read_webpage
24
+ tool_description 'Fetches a web page and returns its readable text content (HTML markup removed) and title. ' \
25
+ 'Use this to read articles or documentation. Blocks private/loopback addresses (SSRF-safe).'
26
+
27
+ parameter :url, type: :string, required: true,
28
+ description: 'The URL of the page to read (http or https).'
29
+ parameter :max_chars, type: :integer, required: false,
30
+ description: "Maximum characters of text to return (default #{DEFAULT_MAX_CHARS})."
31
+
32
+ def initialize(**options)
33
+ super(**options)
34
+ setup_http_client(base_url: 'https://placeholder.invalid')
35
+ end
36
+
37
+ private
38
+
39
+ def perform_execution(params, _context)
40
+ url = params.fetch(:url)
41
+ limit = (params[:max_chars] || DEFAULT_MAX_CHARS).to_i.clamp(1, HARD_MAX_CHARS)
42
+
43
+ uri, pinned_ip = Legate::Tools::Base::SafeUrl.resolve!(url)
44
+ response = make_request(
45
+ :get, url,
46
+ headers: { 'Accept' => 'text/html,application/xhtml+xml,text/plain;q=0.9,*/*;q=0.8' },
47
+ options: { resolved_ip: pinned_ip, original_host: uri.host }
48
+ )
49
+
50
+ html = response.body.to_s
51
+ text = html_to_text(html)
52
+ truncated = text.length > limit
53
+ {
54
+ status: :success,
55
+ result: {
56
+ url: url,
57
+ title: extract_title(html),
58
+ text: truncated ? text[0, limit] : text,
59
+ truncated: truncated
60
+ }
61
+ }
62
+ rescue Legate::ToolError => e
63
+ { status: :error, error_message: e.message }
64
+ end
65
+
66
+ def extract_title(html)
67
+ match = html.match(%r{<title[^>]*>(.*?)</title>}im)
68
+ match ? decode_entities(match[1].strip) : nil
69
+ end
70
+
71
+ # Best-effort HTML → plain text without a parser dependency: drop
72
+ # script/style/comments, turn block boundaries into newlines, strip the
73
+ # remaining tags, decode common entities, and collapse whitespace.
74
+ def html_to_text(html)
75
+ text = html.dup
76
+ text.gsub!(%r{<head[^>]*>.*?</head>}im, ' ')
77
+ text.gsub!(%r{<(script|style)[^>]*>.*?</\1>}im, ' ')
78
+ text.gsub!(/<!--.*?-->/m, ' ')
79
+ text.gsub!(%r{</?(p|div|br|li|tr|h[1-6]|section|article|header|footer)[^>]*>}i, "\n")
80
+ text.gsub!(/<[^>]+>/, ' ')
81
+ text = decode_entities(text)
82
+ text.gsub(/[ \t]+/, ' ').gsub(/ *\n */, "\n").gsub(/\n{3,}/, "\n\n").strip
83
+ end
84
+
85
+ def decode_entities(str)
86
+ str = str.gsub(/&#(\d+);/) { [Regexp.last_match(1).to_i].pack('U') }
87
+ ENTITIES.each { |entity, char| str = str.gsub(entity, char) }
88
+ str
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,74 @@
1
+ # File: lib/legate/tools/sleepy_tool.rb
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'base_async_job_tool'
5
+
6
+ module Legate
7
+ module Tools
8
+ # An example Legate tool that starts a background job that sleeps for a specified duration.
9
+ # This tool demonstrates how to implement a BaseAsyncJobTool with a background worker.
10
+ class SleepyTool < BaseAsyncJobTool
11
+ # --- New DSL Metadata ---
12
+ # Name will be inferred as :sleepy_tool
13
+ self.explicit_tool_name = :start_sleepy_job # Keep original name
14
+
15
+ tool_description 'Starts a background job that sleeps for a specified duration and then returns a message.'
16
+
17
+ parameter :duration,
18
+ type: :integer,
19
+ description: 'How many seconds the job should sleep.',
20
+ required: true
21
+
22
+ parameter :message,
23
+ type: :string,
24
+ description: 'A message to include in the final result.',
25
+ required: true
26
+ # --- End New DSL Metadata ---
27
+
28
+ # Return the worker class to use.
29
+ def worker_class
30
+ SleepyWorker
31
+ end
32
+
33
+ # Prepare the arguments for the worker's perform method.
34
+ # Arguments must be JSON-serializable.
35
+ def prepare_job_arguments(params, _context)
36
+ duration = params[:duration].to_i
37
+ message = params[:message].to_s
38
+ [duration, message] # Must match SleepyWorker#perform signature (after jid)
39
+ end
40
+ end
41
+
42
+ # The worker that performs the actual sleep operation.
43
+ class SleepyWorker
44
+ # @param jid [String] The Job ID (passed as first argument by BaseAsyncJobTool).
45
+ # @param duration [Integer] How long to sleep in seconds.
46
+ # @param message [String] The message to return upon completion.
47
+ def perform(jid, duration, message)
48
+ # --- Store initial pending status --- #
49
+ begin
50
+ Legate::Tools::BaseAsyncJobTool.store_job_pending(jid)
51
+ rescue StandardError => e
52
+ # Log error but try to continue if possible
53
+ Legate.logger.error("[SleepyWorker JID: #{jid}] Failed to store initial pending status: #{e.message}")
54
+ end
55
+ # --- End store initial pending status --- #
56
+
57
+ Legate.logger.info("[SleepyWorker JID: #{jid}] Starting job. Sleeping for #{duration} seconds...")
58
+
59
+ begin
60
+ sleep duration.to_i
61
+ result_message = "Slept for #{duration} seconds. Your message: #{message}"
62
+ Legate.logger.info("[SleepyWorker JID: #{jid}] Job finished. Storing result.")
63
+ # Store the successful result using the helper from BaseAsyncJobTool
64
+ Legate::Tools::BaseAsyncJobTool.store_job_result(jid, result_message)
65
+ rescue StandardError => e
66
+ error_message = "Job failed after starting sleep: #{e.message}"
67
+ Legate.logger.error("[SleepyWorker JID: #{jid}] Job failed! Storing error. Error: #{error_message}")
68
+ # Store the error using the helper
69
+ Legate::Tools::BaseAsyncJobTool.store_job_error(jid, error_message, e.class.name)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end