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,408 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'code_validator'
4
+ require_relative '../llm'
5
+
6
+ module Legate
7
+ module Generators
8
+ # AI-powered tool code generator. Uses the configured LLM adapter (Gemini by
9
+ # default) via Legate::LLM.
10
+ class ToolGenerator
11
+ class GenerationError < StandardError; end
12
+ class ApiKeyMissingError < GenerationError; end
13
+ class ApiError < GenerationError; end
14
+
15
+ # Model used for code generation (a capable model is worth it here). Passed
16
+ # to Legate::LLM.build_adapter; a configured non-Gemini factory may remap it.
17
+ GENERATION_MODEL = 'gemini-2.5-pro'
18
+
19
+ # Generate tool class code from a natural language description
20
+ # @param description [String] Natural language description of the tool to generate
21
+ # @return [Hash] { code: String, suggested_name: String, tool_type: String }
22
+ # @raise [ApiKeyMissingError] if GOOGLE_API_KEY is not set
23
+ # @raise [ApiError] if Gemini API fails
24
+ # @raise [GenerationError] for other generation failures
25
+ def self.generate(description:)
26
+ new.generate(description: description)
27
+ end
28
+
29
+ def generate(description:)
30
+ validate_description!(description)
31
+ adapter = Legate::LLM.build_adapter(model: GENERATION_MODEL)
32
+ raise ApiKeyMissingError, 'GOOGLE_API_KEY not configured. AI generation requires a Gemini API key.' unless adapter.available?
33
+
34
+ system_prompt = build_prompt
35
+ user_prompt = build_user_prompt(description)
36
+
37
+ generated_code = call_llm(adapter, system_prompt, user_prompt)
38
+ clean_code = clean_generated_code(generated_code)
39
+ CodeValidator.validate!(clean_code)
40
+ suggested_name = extract_tool_name(clean_code)
41
+ tool_type = detect_tool_type(clean_code)
42
+
43
+ { code: clean_code, suggested_name: suggested_name, tool_type: tool_type }
44
+ rescue CodeValidator::UnsafeCodeError => e
45
+ raise GenerationError, e.message
46
+ end
47
+
48
+ private
49
+
50
+ def validate_description!(description)
51
+ raise GenerationError, 'Description is required' if description.nil? || description.strip.empty?
52
+ raise GenerationError, 'Description too long. Maximum 5000 characters.' if description.length > 5000
53
+ end
54
+
55
+ def call_llm(adapter, system_prompt, user_prompt)
56
+ text = begin
57
+ adapter.generate("#{system_prompt}\n\n#{user_prompt}")
58
+ rescue StandardError => e
59
+ raise ApiError, "AI service communication error: #{e.message}"
60
+ end
61
+ raise GenerationError, 'AI service returned empty response. Please try again.' unless text && !text.strip.empty?
62
+
63
+ text
64
+ end
65
+
66
+ def build_user_prompt(description)
67
+ <<~PROMPT
68
+ Generate a Ruby tool class based on this description:
69
+
70
+ #{description}
71
+
72
+ Remember to output ONLY the Ruby code, no explanations or markdown formatting.
73
+ Determine the appropriate tool type (simple, HTTP API, or async) based on the description.
74
+ PROMPT
75
+ end
76
+
77
+ def clean_generated_code(code)
78
+ clean = code.strip
79
+ clean = clean.gsub(/\A```ruby\n?/, '').gsub(/\A```\n?/, '')
80
+ clean = clean.gsub(/\n?```\z/, '')
81
+ clean.strip
82
+ end
83
+
84
+ def extract_tool_name(code)
85
+ # Try to find class definition
86
+ if code =~ /class\s+(\w+)\s*<\s*(?:Legate::Tool|Legate::Tools::BaseAsyncJobTool)/
87
+ class_name = Regexp.last_match(1)
88
+ # Convert PascalCase to snake_case for filename
89
+ return class_name.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
90
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
91
+ .downcase
92
+ end
93
+ 'generated_tool'
94
+ end
95
+
96
+ def detect_tool_type(code)
97
+ if code.include?('BaseAsyncJobTool')
98
+ 'async'
99
+ elsif code.include?('HttpClient') || code.include?('http_get') || code.include?('http_post') ||
100
+ code.include?('http_head') || code.include?('http_put') || code.include?('http_delete')
101
+ 'http'
102
+ else
103
+ 'simple'
104
+ end
105
+ end
106
+
107
+ def build_prompt
108
+ <<~PROMPT
109
+ You are an expert Ruby developer specializing in Legate — AI Agent Framework for Ruby.
110
+ Your task is to generate complete, production-ready Ruby tool class code based on user descriptions.
111
+
112
+ ## Tool Types
113
+
114
+ Based on the user's description, determine which type of tool to generate:
115
+
116
+ 1. **Simple Tool** - For local computation, data transformation, no external calls
117
+ 2. **HTTP API Tool** - For calling external REST APIs, checking URLs/websites, or any HTTP requests
118
+ 3. **Async Tool** - For long-running background jobs (using threads)
119
+
120
+ ## Simple Tool Template
121
+
122
+ ```ruby
123
+ # frozen_string_literal: true
124
+
125
+ require 'legate/tool'
126
+
127
+ class MyTool < Legate::Tool
128
+ tool_description 'Brief description of what this tool does'
129
+
130
+ parameter :param_name,
131
+ type: :string, # :string, :integer, :number, :boolean, :array, :object
132
+ description: 'What this parameter is for',
133
+ required: true # or false for optional
134
+
135
+ parameter :optional_param,
136
+ type: :integer,
137
+ description: 'An optional parameter',
138
+ required: false
139
+
140
+ private
141
+
142
+ def perform_execution(params, context)
143
+ # params is a Hash with symbol keys
144
+ # context is Legate::ToolContext with session access
145
+
146
+ input = params[:param_name]
147
+
148
+ # Your logic here
149
+ result = process(input)
150
+
151
+ # Return success
152
+ { status: :success, result: result }
153
+
154
+ rescue StandardError => e
155
+ { status: :error, error_message: e.message }
156
+ end
157
+ end
158
+
159
+ # Register the tool so agents can use it
160
+ Legate::GlobalToolManager.register_tool(MyTool)
161
+ ```
162
+
163
+ ## HTTP API Tool Template
164
+
165
+ For tools that call external APIs, check URLs/websites, or make any HTTP requests.
166
+ ALWAYS use HttpClient for ANY HTTP operations - never use Net::HTTP directly.
167
+
168
+ Available HTTP methods:
169
+ - `http_get(path, query: {}, headers: {})` - GET request
170
+ - `http_head(path, query: {}, headers: {})` - HEAD request (headers only, no body - efficient for status checks)
171
+ - `http_post(path, body: {}, query: {}, headers: {})` - POST request
172
+ - `http_put(path, body: {}, query: {}, headers: {})` - PUT request
173
+ - `http_delete(path, query: {}, headers: {})` - DELETE request
174
+
175
+ ### Fixed Base URL Pattern (for single API)
176
+
177
+ ```ruby
178
+ # frozen_string_literal: true
179
+
180
+ require 'legate/tool'
181
+ require 'legate/tools/base/http_client'
182
+
183
+ class MyApiTool < Legate::Tool
184
+ include Legate::Tools::Base::HttpClient
185
+
186
+ tool_description 'Fetches data from External API'
187
+
188
+ parameter :query,
189
+ type: :string,
190
+ description: 'Search query',
191
+ required: true
192
+
193
+ def initialize(**options)
194
+ super
195
+ setup_http_client(
196
+ base_url: 'https://api.example.com/v1/',
197
+ headers: {
198
+ 'Accept' => 'application/json',
199
+ 'Authorization' => "Bearer \#{ENV['API_KEY']}"
200
+ }
201
+ )
202
+ end
203
+
204
+ private
205
+
206
+ def perform_execution(params, context)
207
+ query = params[:query]
208
+
209
+ # GET request (path is relative to base_url)
210
+ response = http_get('search', query: { q: query })
211
+
212
+ # POST request example:
213
+ # response = http_post('endpoint', body: { data: params[:data] })
214
+
215
+ data = JSON.parse(response.body)
216
+ { status: :success, result: data }
217
+
218
+ rescue Legate::ToolHttpError => e
219
+ { status: :error, error_message: "API error: \#{e.message}" }
220
+ rescue JSON::ParserError => e
221
+ { status: :error, error_message: "Invalid response: \#{e.message}" }
222
+ end
223
+ end
224
+
225
+ Legate::GlobalToolManager.register_tool(MyApiTool)
226
+ ```
227
+
228
+ ### Dynamic/Arbitrary URL Pattern (for URL checkers, webhooks, etc.)
229
+
230
+ For tools that need to call user-provided URLs (not a fixed API), use a placeholder
231
+ base_url and pass absolute URLs to the http_* methods:
232
+
233
+ ```ruby
234
+ # frozen_string_literal: true
235
+
236
+ require 'legate/tool'
237
+ require 'legate/tools/base/http_client'
238
+ require 'uri'
239
+
240
+ class UrlStatusChecker < Legate::Tool
241
+ include Legate::Tools::Base::HttpClient
242
+
243
+ tool_description 'Checks if a URL is reachable and returns its HTTP status code'
244
+
245
+ parameter :url,
246
+ type: :string,
247
+ description: 'The full URL to check (e.g., https://example.com)',
248
+ required: true
249
+
250
+ parameter :expected_status,
251
+ type: :integer,
252
+ description: 'Optional expected status code to validate against',
253
+ required: false
254
+
255
+ def initialize(**options)
256
+ super
257
+ # Use placeholder base_url - actual URLs will be absolute
258
+ setup_http_client(base_url: 'https://placeholder.invalid')
259
+ end
260
+
261
+ private
262
+
263
+ def perform_execution(params, context)
264
+ url = params[:url]
265
+ expected = params[:expected_status]
266
+
267
+ # Validate URL format
268
+ validate_url!(url)
269
+
270
+ # HEAD request is efficient - fetches headers only, no body
271
+ response = http_head(url)
272
+
273
+ result = {
274
+ url: url,
275
+ status_code: response.status,
276
+ reachable: (200..399).cover?(response.status)
277
+ }
278
+
279
+ if expected
280
+ result[:expected_status] = expected
281
+ result[:matches] = (response.status == expected)
282
+ end
283
+
284
+ { status: :success, result: result }
285
+
286
+ rescue URI::InvalidURIError => e
287
+ { status: :error, error_message: "Invalid URL: \#{e.message}" }
288
+ rescue Legate::ToolHttpError => e
289
+ # Non-2xx responses are caught here
290
+ { status: :error, error_message: "HTTP error: \#{e.message}" }
291
+ rescue Legate::ToolNetworkError => e
292
+ { status: :error, error_message: "Network error: \#{e.message}" }
293
+ rescue Legate::ToolTimeoutError => e
294
+ { status: :error, error_message: "Request timed out: \#{e.message}" }
295
+ end
296
+
297
+ def validate_url!(url)
298
+ uri = URI.parse(url)
299
+ unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
300
+ raise URI::InvalidURIError, 'URL must use http:// or https://'
301
+ end
302
+ raise URI::InvalidURIError, 'URL must have a host' if uri.host.nil? || uri.host.empty?
303
+ end
304
+ end
305
+
306
+ Legate::GlobalToolManager.register_tool(UrlStatusChecker)
307
+ ```
308
+
309
+ ## Async Tool Template
310
+
311
+ For long-running operations that should run in background:
312
+
313
+ ```ruby
314
+ # frozen_string_literal: true
315
+
316
+ require 'legate/tools/base_async_job_tool'
317
+
318
+ # The worker that does the actual work (runs in a background thread)
319
+ class MyWorker
320
+ def perform(jid, session_id, input_data)
321
+ # Mark job as started
322
+ Legate::Tools::BaseAsyncJobTool.store_job_pending(jid)
323
+
324
+ # Do the long-running work
325
+ result = process_data(input_data)
326
+
327
+ # Store the result
328
+ Legate::Tools::BaseAsyncJobTool.store_job_result(jid, result)
329
+
330
+ rescue StandardError => e
331
+ Legate::Tools::BaseAsyncJobTool.store_job_error(jid, e.message, e.class.name)
332
+ raise
333
+ end
334
+
335
+ private
336
+
337
+ def process_data(data)
338
+ # Your long-running logic here
339
+ sleep(5) # Simulating work
340
+ { processed: true, data: data }
341
+ end
342
+ end
343
+
344
+ # The Legate tool that enqueues the job
345
+ class MyAsyncTool < Legate::Tools::BaseAsyncJobTool
346
+ tool_description 'Starts a background job to process data'
347
+
348
+ parameter :data,
349
+ type: :string,
350
+ description: 'Data to process',
351
+ required: true
352
+
353
+ def worker_class
354
+ MyWorker
355
+ end
356
+
357
+ def prepare_job_arguments(params, context)
358
+ [context.session_id, params[:data]]
359
+ end
360
+ end
361
+
362
+ Legate::GlobalToolManager.register_tool(MyAsyncTool)
363
+ ```
364
+
365
+ ## ToolContext Methods
366
+
367
+ The `context` parameter provides access to:
368
+ - `context.state_get(:key)` - Read from session state
369
+ - `context.state_set(:key, value)` - Write to session state (applied after execution)
370
+ - `context.session_id` - Current session ID
371
+ - `context.user_id` - Current user ID
372
+ - `context.app_name` - Agent name
373
+ - `context.invocation_id` - Unique invocation ID
374
+
375
+ ## Parameter Types
376
+
377
+ - `:string` - Text values
378
+ - `:integer` - Whole numbers
379
+ - `:number` - Decimal numbers (float)
380
+ - `:boolean` - true/false
381
+ - `:array` - List of values
382
+ - `:object` - Nested hash/object
383
+
384
+ ## Output Requirements
385
+
386
+ 1. Output ONLY valid Ruby code - no markdown fences, no explanations
387
+ 2. Include appropriate requires at the top
388
+ 3. Include helpful comments explaining the code
389
+ 4. Use ENV variables for API keys and secrets (never hardcode)
390
+ 5. End with `Legate::GlobalToolManager.register_tool(ToolClass)`
391
+ 6. Use descriptive class names in PascalCase
392
+ 7. Include proper error handling
393
+
394
+ ## Determining Tool Type
395
+
396
+ - If description mentions: API, HTTP, fetch, external service, REST, URL, website,#{' '}
397
+ check site, status code, HEAD request, ping, request, web, endpoint, webhook,
398
+ download, upload, GET, POST, online, reachable → Use HTTP API Tool
399
+ - If description mentions: background, async, queue, long-running, process files → Use Async Tool
400
+ - Otherwise → Use Simple Tool
401
+
402
+ IMPORTANT: Any tool that makes network requests to URLs or APIs MUST use the HTTP API Tool pattern.
403
+ Never use Net::HTTP, Faraday, or other HTTP libraries directly - always use HttpClient.
404
+ PROMPT
405
+ end
406
+ end
407
+ end
408
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Entry point for Legate generators module
4
+ require_relative 'generators/agent_generator'
5
+ require_relative 'generators/tool_generator'
6
+
7
+ module Legate
8
+ # Generators for AI-powered code generation
9
+ module Generators
10
+ end
11
+ end