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,276 @@
1
+ # File: lib/legate/tool.rb
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'tool_registry'
5
+ require 'logger'
6
+ require_relative 'tool_result'
7
+ require_relative 'tool_context'
8
+ require_relative 'global_tool_manager'
9
+ require_relative 'tool/metadata_dsl'
10
+ require 'json'
11
+
12
+ module Legate
13
+ # Base class for all tools that can be used by Agents.
14
+ #
15
+ # Tools are the way agents interact with the outside world. To create a new tool,
16
+ # inherit from this class, use the DSL to define metadata, and implement
17
+ # the {#perform_execution} method.
18
+ #
19
+ # @example Creating a custom weather tool
20
+ # class WeatherTool < Legate::Tool
21
+ # tool_description 'Fetches current weather for a location'
22
+ #
23
+ # parameter :location, type: :string, required: true,
24
+ # description: 'City name (e.g. "San Francisco")'
25
+ #
26
+ # parameter :unit, type: :string, required: false,
27
+ # description: 'Temperature unit (celsius/fahrenheit)'
28
+ #
29
+ # private
30
+ #
31
+ # def perform_execution(params, context)
32
+ # location = params[:location]
33
+ # # ... fetch weather logic ...
34
+ # weather_data = "Sunny, 25C"
35
+ #
36
+ # {
37
+ # status: :success,
38
+ # result: weather_data
39
+ # }
40
+ # rescue => e
41
+ # {
42
+ # status: :error,
43
+ # error_message: "Failed to fetch weather: #{e.message}"
44
+ # }
45
+ # end
46
+ # end
47
+ #
48
+ class Tool
49
+ # --- Include the DSL ---
50
+ include MetadataDsl
51
+
52
+ # --- Class-level attributes --- Accessors defined via DSL, but keep old readers/define_metadata for backward compat ---
53
+ class << self
54
+ # Keep old readers for define_metadata compatibility
55
+ attr_reader :tool_name, :description, :parameters_definition
56
+
57
+ # Define the tool's static metadata.
58
+ # DEPRECATED: Use `tool_description`, `parameter`, and automatic name inference instead.
59
+ def define_metadata(name:, description:, parameters: {})
60
+ warn "[DEPRECATION] `define_metadata` is deprecated. Use `tool_description`, `parameter`, and rely on class name inference (or `self.explicit_tool_name = :my_name`) instead. Called from #{caller_locations(
61
+ 1, 1
62
+ )[0].label}"
63
+
64
+ @tool_name = name.to_sym
65
+ @description = description
66
+ @parameters_definition = parameters
67
+ @_tool_metadata_cache = nil # Invalidate cache
68
+ end
69
+ end
70
+
71
+ # --- Self-Registration Hook ---
72
+ # NOTE: We intentionally do NOT auto-register tools in the inherited hook.
73
+ # The reason is that `inherited` is called BEFORE the class body executes,
74
+ # so explicit_tool_name and other DSL methods haven't run yet. This would
75
+ # cause tools to be registered under their inferred name (e.g., :random_number_tool)
76
+ # instead of their explicit name (e.g., :random_number).
77
+ #
78
+ # Instead, built-in tools are explicitly registered in lib/legate.rb after their
79
+ # class definitions are complete. Custom tools should either:
80
+ # 1. Call `Legate::GlobalToolManager.register_tool(MyTool)` explicitly after definition
81
+ # 2. Be discovered via tool_paths when creating agents
82
+ def self.inherited(subclass)
83
+ super # Call parent's inherited if necessary
84
+ Legate.logger.debug("Tool subclass #{subclass} inherited. Tool will be registered when explicitly added to GlobalToolManager or an agent's tool registry.")
85
+ end
86
+ # --- End Hook ---
87
+
88
+ # Instance readers
89
+ attr_reader :name, :description, :parameters
90
+
91
+ # Initialize - Sets instance vars from class metadata
92
+ def initialize(**_options)
93
+ # Fetch metadata using the primary tool_metadata method (defined by DSL)
94
+ metadata = self.class.tool_metadata
95
+ @name = metadata[:name]
96
+ @description = metadata[:description]
97
+ @parameters = metadata[:parameters] || {}
98
+
99
+ # Lenient check for missing metadata
100
+ return unless @name.nil? || @name == :'' || @description.nil? || @description.empty?
101
+
102
+ is_anonymous = !self.class.name || self.class.name.empty? || self.class.name.start_with?('#<Class:')
103
+ unless is_anonymous
104
+ missing = []
105
+ missing << ':name' if @name.nil? || @name == :''
106
+ missing << ':description' if @description.nil? || @description.empty?
107
+ Legate.logger.warn("Tool class #{self.class} initialized with missing metadata: [#{missing.join(', ')}] using #{self.class.tool_metadata}. Tool may not function correctly.")
108
+ end
109
+ @description ||= ''
110
+ end
111
+
112
+ # Execute the tool
113
+ # @param params [Hash] Input parameters for the tool.
114
+ # @param context [Legate::ToolContext, nil] Contextual information (session details).
115
+ # @return [Hash] A hash with :status (:success, :error, :pending) and :result/:error_message/:job_id.
116
+ def execute(params = {}, context = nil)
117
+ coerced_params = validate_and_coerce_params(params)
118
+ Legate.logger.debug("Executing tool '#{@name}' with validated params: #{coerced_params.inspect} and context: #{context&.to_h.inspect}")
119
+ result = perform_execution(coerced_params, context)
120
+ # perform_execution may return a typed ToolResult or the canonical hash;
121
+ # normalize to the hash here so everything downstream is unchanged.
122
+ result.is_a?(Legate::ToolResult) ? result.to_h : result
123
+ end
124
+
125
+ # Validate the parameters (Deprecated: Use validate_and_coerce_params)
126
+ # This method is kept for backward compatibility and wraps the new logic,
127
+ # but ignores the coerced return value.
128
+ def validate_params(params)
129
+ validate_and_coerce_params(params)
130
+ nil
131
+ end
132
+
133
+ # Validate and coerce parameters based on metadata types
134
+ # @param params [Hash] Input parameters
135
+ # @return [Hash] New hash with symbol keys and coerced values
136
+ # @raise [Legate::ToolArgumentError] if validation fails
137
+ def validate_and_coerce_params(params)
138
+ # 1. Normalize keys to symbols
139
+ normalized_params = params.transform_keys(&:to_sym)
140
+
141
+ current_parameters = @parameters || {}
142
+
143
+ # 2. Check for missing required parameters
144
+ # Use symbol keys for check
145
+ required_param_names = current_parameters.select { |_, p| p[:required] }.keys
146
+ present_keys = normalized_params.keys
147
+ missing_params = required_param_names - present_keys
148
+
149
+ unless missing_params.empty?
150
+ msg = "Missing required parameters for tool '#{@name}': #{missing_params.join(', ')}."
151
+ msg += " Provided: [#{present_keys.empty? ? 'None' : present_keys.join(', ')}]."
152
+
153
+ Legate.logger.error("Validation failed: #{msg} Params: #{params.inspect}")
154
+ raise Legate::ToolArgumentError, msg
155
+ end
156
+
157
+ # 3. Type Validation & Coercion
158
+ coerced_params = normalized_params.dup
159
+
160
+ current_parameters.each do |param_name, param_def|
161
+ # Only process if present
162
+ next unless coerced_params.key?(param_name)
163
+
164
+ value = coerced_params[param_name]
165
+ expected_type = param_def[:type]
166
+ next unless expected_type
167
+
168
+ begin
169
+ coerced_value = coerce_value(value, expected_type)
170
+ coerced_params[param_name] = coerced_value
171
+ rescue Legate::ToolArgumentError => e
172
+ # coerce_value raises ToolArgumentError (not ArgumentError/TypeError);
173
+ # re-raise with the parameter/tool context the bare message lacks.
174
+ raise Legate::ToolArgumentError, "Parameter '#{param_name}' for tool '#{@name}': #{e.message}"
175
+ end
176
+ end
177
+
178
+ coerced_params
179
+ end
180
+
181
+ private
182
+
183
+ # Coerce a value to the expected type
184
+ def coerce_value(value, type)
185
+ return value if value.nil?
186
+
187
+ case type
188
+ when :string
189
+ # Reject containers rather than silently producing inspect-garbage like
190
+ # "[1, 2]" / '{:a=>1}'; scalars (Numeric/Symbol/bool) stringify fine.
191
+ raise Legate::ToolArgumentError, "expected String, got #{value.class} (#{value.inspect})" if value.is_a?(Array) || value.is_a?(Hash)
192
+
193
+ value.to_s
194
+ when :integer
195
+ # Force base 10 for strings so "010"/"0x1f" aren't read as octal/hex;
196
+ # numbers (e.g. 123.0) still truncate via plain Integer().
197
+ begin
198
+ value.is_a?(String) ? Integer(value, 10) : Integer(value)
199
+ rescue ArgumentError, TypeError
200
+ raise Legate::ToolArgumentError, "expected Integer, got #{value.class} (#{value.inspect})"
201
+ end
202
+ when :float, :numeric
203
+ # :numeric treated as Float for broad compatibility
204
+ begin
205
+ Float(value)
206
+ rescue ArgumentError, TypeError
207
+ raise Legate::ToolArgumentError, "expected Numeric/Float, got #{value.class} (#{value.inspect})"
208
+ end
209
+ when :boolean
210
+ if value.is_a?(TrueClass) || value.is_a?(FalseClass)
211
+ value
212
+ elsif value.is_a?(String)
213
+ case value.downcase
214
+ when 'true', 't', 'yes', '1' then true
215
+ when 'false', 'f', 'no', '0' then false
216
+ else
217
+ raise Legate::ToolArgumentError, "expected Boolean, got String '#{value}'"
218
+ end
219
+ else
220
+ raise Legate::ToolArgumentError, "expected Boolean, got #{value.class} (#{value.inspect})"
221
+ end
222
+ when :array
223
+ if value.is_a?(Array)
224
+ value
225
+ elsif value.is_a?(String)
226
+ begin
227
+ parsed = JSON.parse(value)
228
+ raise ArgumentError unless parsed.is_a?(Array)
229
+
230
+ parsed
231
+ rescue StandardError
232
+ raise Legate::ToolArgumentError, "expected Array, got #{value.class} (#{value.inspect})"
233
+ end
234
+ else
235
+ raise Legate::ToolArgumentError, "expected Array, got #{value.class} (#{value.inspect})"
236
+ end
237
+ when :hash
238
+ if value.is_a?(Hash)
239
+ value
240
+ elsif value.is_a?(String)
241
+ begin
242
+ parsed = JSON.parse(value)
243
+ raise ArgumentError unless parsed.is_a?(Hash)
244
+
245
+ parsed
246
+ rescue StandardError
247
+ raise Legate::ToolArgumentError, "expected Hash, got #{value.class} (#{value.inspect})"
248
+ end
249
+ else
250
+ raise Legate::ToolArgumentError, "expected Hash, got #{value.class} (#{value.inspect})"
251
+ end
252
+ else
253
+ # Unknown type or 'any', return as is
254
+ value
255
+ end
256
+ end
257
+
258
+ # Perform the actual execution of the tool.
259
+ #
260
+ # This method must be implemented by subclasses to define the tool's behavior.
261
+ #
262
+ # @param params [Hash] The validated parameters to execute with. Keys are symbols.
263
+ # @param context [Legate::ToolContext] Contextual information (session, user, state).
264
+ #
265
+ # @return [Hash] The result hash containing:
266
+ # * :status [Symbol] :success, :error, or :pending
267
+ # * :result [Object] The output data (if success)
268
+ # * :error_message [String] Error description (if error)
269
+ # * :job_id [String] Job ID (if pending/async)
270
+ #
271
+ # @raise [NotImplementedError] if the subclass does not implement this method.
272
+ def perform_execution(params, context)
273
+ raise NotImplementedError, 'Subclasses must implement #perform_execution(params, context)'
274
+ end
275
+ end
276
+ end
@@ -0,0 +1,103 @@
1
+ # File: lib/legate/tool_code_generator.rb
2
+ # frozen_string_literal: true
3
+
4
+ module Legate
5
+ # Generates Ruby source code from tool metadata.
6
+ # Used to create downloadable .rb files for native tools from the web UI.
7
+ module ToolCodeGenerator
8
+ # Generate Ruby code from a tool's metadata.
9
+ # @param tool_name [Symbol, String] The tool name registered with GlobalToolManager.
10
+ # @return [String, nil] Valid Ruby source code or nil if tool not found.
11
+ def self.generate(tool_name)
12
+ tool_name_sym = tool_name.to_sym
13
+ tool_class = Legate::GlobalToolManager.find_class(tool_name_sym)
14
+ return nil unless tool_class
15
+
16
+ metadata = tool_class.tool_metadata
17
+ return nil unless metadata
18
+
19
+ name = metadata[:name] || tool_name_sym
20
+ description = metadata[:description] || 'No description provided'
21
+ parameters = metadata[:parameters] || {}
22
+
23
+ # Generate class name from tool name (snake_case to PascalCase)
24
+ class_name = name.to_s.split('_').map(&:capitalize).join
25
+
26
+ code = <<~RUBY
27
+ # frozen_string_literal: true
28
+
29
+ # Tool: #{name}
30
+ # Generated from Legate Web UI on #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}
31
+
32
+ require 'legate/tool'
33
+
34
+ module Legate
35
+ module Tools
36
+ class #{class_name} < Tool
37
+ tool_description #{ruby_string(description)}
38
+
39
+ RUBY
40
+
41
+ # Add parameter declarations
42
+ parameters.each do |param_name, param_opts|
43
+ code += generate_parameter(param_name, param_opts)
44
+ end
45
+
46
+ code += <<~RUBY
47
+
48
+ private
49
+
50
+ # @param params [Hash] The validated input parameters.
51
+ # @param context [Legate::ToolContext] The execution context.
52
+ # @return [Hash] Result with :status and :result or :error_message.
53
+ def perform_execution(params, context)
54
+ # TODO: Implement your tool logic here
55
+ #
56
+ # Available context methods:
57
+ # context.state_get(:key) - Read from session state
58
+ # context.state_set(:key, value) - Write to session state
59
+ # context.session_id - Current session ID
60
+ #
61
+ RUBY
62
+
63
+ # Add parameter access examples
64
+ parameters.each do |param_name, _param_opts|
65
+ code += " # #{param_name} = params[:#{param_name}]\n"
66
+ end
67
+
68
+ code += <<~RUBY
69
+
70
+ # Return success result
71
+ { status: :success, result: 'Tool executed successfully' }
72
+
73
+ rescue StandardError => e
74
+ { status: :error, error_message: e.message }
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ # Register the tool so agents can use it
81
+ Legate::GlobalToolManager.register_tool(Legate::Tools::#{class_name})
82
+ RUBY
83
+
84
+ code
85
+ end
86
+
87
+ private_class_method def self.generate_parameter(param_name, param_opts)
88
+ type = param_opts[:type] || :string
89
+ description = param_opts[:description] || ''
90
+ required = param_opts[:required] || false
91
+
92
+ code = " parameter :#{param_name},\n"
93
+ code += " type: :#{type},\n"
94
+ code += " description: #{ruby_string(description)},\n"
95
+ code += " required: #{required}\n\n"
96
+ code
97
+ end
98
+
99
+ private_class_method def self.ruby_string(value)
100
+ value.to_s.inspect
101
+ end
102
+ end
103
+ end