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,350 @@
1
+ # File: lib/legate/tool_context.rb
2
+ # frozen_string_literal: true
3
+
4
+ module Legate
5
+ # Provides contextual information to Legate::Tool#perform_execution
6
+ # Includes session details and a reference to the agent's tool registry.
7
+ # Read-only.
8
+ class ToolContext
9
+ attr_reader :session_id, :user_id, :app_name, :tool_registry, :session_service, :logger, :invocation_id
10
+
11
+ # Expose pending state delta for inspection but not direct modification
12
+ attr_reader :pending_state_delta
13
+
14
+ # Agent-specific authentication configuration
15
+ # @return [Hash, nil] The agent's auth config or nil if not configured
16
+ attr_reader :agent_auth_config
17
+
18
+ # @param session_id [String] The ID of the current session.
19
+ # @param user_id [String] The user ID associated with the session.
20
+ # @param app_name [String] The application/agent name associated with the session.
21
+ # @param tool_registry [Legate::ToolRegistry] The tool registry instance of the agent executing the tool.
22
+ # @param session_service [Legate::SessionService::Base, nil] The session service instance.
23
+ # @param logger [Logger, nil] The logger instance.
24
+ # @param invocation_id [String, nil] The ID of the current agent invocation.
25
+ # @param agent_auth_config [Hash, nil] Agent-specific authentication configuration.
26
+ def initialize(session_id:, user_id:, app_name:, tool_registry: nil, session_service: nil, logger: Legate.logger, invocation_id: nil, agent_auth_config: nil)
27
+ @session_id = session_id
28
+ @user_id = user_id
29
+ @app_name = app_name
30
+ @tool_registry = tool_registry
31
+ @session_service = session_service
32
+ @invocation_id = invocation_id
33
+ @pending_state_delta = {}
34
+ @token_manager = nil
35
+ @agent_auth_config = agent_auth_config
36
+ end
37
+
38
+ # Retrieves a value from the session state via the session_service.
39
+ # @param key [Symbol, String] The key to retrieve
40
+ # @return [Object, nil] The value or nil if not found
41
+ def state_get(key)
42
+ unless @session_service
43
+ Legate.logger.warn { '[ToolContext] state_get called but no session_service available.' }
44
+ return nil
45
+ end
46
+
47
+ Legate.logger.debug { "[ToolContext] state_get for key: #{key} in session: #{@session_id}" }
48
+ @session_service.get_state(session_id: @session_id, key: key)
49
+ rescue StandardError => e
50
+ Legate.logger.error { "[ToolContext] Error in state_get for key '#{key}': #{e.message}" }
51
+ nil
52
+ end
53
+
54
+ # Sets a value in the pending state delta for this context.
55
+ # @param key [Symbol, String] The key to set
56
+ # @param value [Object] The value to store (should be serializable)
57
+ def state_set(key, value)
58
+ Legate.logger.debug { "[ToolContext] state_set for key: #{key} to value: #{value.inspect} (pending)" }
59
+ @pending_state_delta[key.to_sym] = value
60
+ end
61
+
62
+ # Merges a hash into the pending state delta for this context.
63
+ # @param hash_to_merge [Hash] The hash to merge into the pending state delta
64
+ def state_update(hash_to_merge)
65
+ unless hash_to_merge.is_a?(Hash)
66
+ Legate.logger.warn { "[ToolContext] state_update called with non-hash: #{hash_to_merge.class}" }
67
+ return
68
+ end
69
+
70
+ Legate.logger.debug { "[ToolContext] state_update with hash: #{hash_to_merge.inspect} (pending)" }
71
+ @pending_state_delta.merge!(hash_to_merge.transform_keys(&:to_sym))
72
+ end
73
+
74
+ # Clears any accumulated pending state changes within this context instance.
75
+ def clear_pending_state_delta!
76
+ @pending_state_delta = {}
77
+ end
78
+
79
+ # Authentication-related methods
80
+
81
+ # Apply authentication to a request using the specified scheme and credential
82
+ # @param request [Hash] The request to authenticate
83
+ # @param scheme [Legate::Auth::Scheme] The authentication scheme to use
84
+ # @param credential [Legate::Auth::Credential] The credential to use
85
+ # @return [Hash] The authenticated request
86
+ def authenticate_request(request, scheme, credential)
87
+ require_relative 'auth/tool_integration'
88
+
89
+ # Try to use token manager if available
90
+ token_manager = get_token_manager
91
+ if token_manager
92
+ # First get the token from token manager
93
+ token = token_manager.get_token(scheme, credential, force_refresh: false)
94
+
95
+ # Then apply authentication
96
+ return Legate::Auth::ToolIntegration.apply_authentication(
97
+ request,
98
+ scheme,
99
+ credential,
100
+ nil,
101
+ token_manager
102
+ )
103
+ end
104
+
105
+ # Fall back to token store if token manager not available
106
+ token_store = get_token_store
107
+
108
+ # Apply authentication
109
+ Legate::Auth::ToolIntegration.apply_authentication(request, scheme, credential, token_store)
110
+ end
111
+
112
+ # Check if a response indicates an authentication error
113
+ # @param response [Hash] The response to check
114
+ # @return [Boolean] True if the response indicates an authentication error
115
+ def authentication_error?(response)
116
+ require_relative 'auth/tool_integration'
117
+ Legate::Auth::ToolIntegration.authentication_error?(response)
118
+ end
119
+
120
+ # Check if a request likely requires authentication
121
+ # @param request [Hash] The request to check
122
+ # @return [Boolean] True if the request likely requires authentication
123
+ def requires_authentication?(request)
124
+ require_relative 'auth/tool_integration'
125
+ Legate::Auth::ToolIntegration.requires_authentication?(request)
126
+ end
127
+
128
+ # Get an authentication token from the session cache
129
+ # @param scheme [Legate::Auth::Scheme] The authentication scheme
130
+ # @param credential [Legate::Auth::Credential] The credential
131
+ # @param force_refresh [Boolean] Whether to force a token refresh
132
+ # @return [Legate::Auth::ExchangedCredential, nil] The token if available
133
+ def get_token(scheme, credential, force_refresh: false)
134
+ # Try to use token manager if available
135
+ token_manager = get_token_manager
136
+ return token_manager.get_token(scheme, credential, force_refresh: force_refresh) if token_manager
137
+
138
+ # Fall back to old mechanism if token manager not available
139
+ token_store = get_token_store
140
+ return nil unless token_store
141
+
142
+ require_relative 'auth/tool_integration'
143
+ cache_key = Legate::Auth::ToolIntegration.generate_cache_key(scheme, credential)
144
+ token_store.get(cache_key)
145
+ end
146
+
147
+ # Refresh an authentication token
148
+ # @param scheme [Legate::Auth::Scheme] The authentication scheme
149
+ # @param credential [Legate::Auth::Credential] The credential
150
+ # @param token [Legate::Auth::ExchangedCredential, nil] The current token, if available
151
+ # @return [Legate::Auth::ExchangedCredential, nil] The refreshed token if successful
152
+ def refresh_token(scheme, credential, token = nil)
153
+ # Try to use token manager if available
154
+ token_manager = get_token_manager
155
+ return token_manager.refresh_token(scheme, credential, token) if token_manager
156
+
157
+ # Fall back to direct refresh if token manager not available
158
+ if token && scheme.supports_refresh? && token.refreshable?
159
+ begin
160
+ refreshed = scheme.refresh_token(token, credential)
161
+ store_token(scheme, credential, refreshed)
162
+ return refreshed
163
+ rescue Legate::Auth::TokenRefreshError => e
164
+ Legate.logger.error("Failed to refresh token: #{e.message}")
165
+ return nil
166
+ end
167
+ end
168
+
169
+ nil
170
+ end
171
+
172
+ # Store an authentication token in the session cache
173
+ # @param scheme [Legate::Auth::Scheme] The authentication scheme
174
+ # @param credential [Legate::Auth::Credential] The credential
175
+ # @param token [Legate::Auth::ExchangedCredential] The token to cache
176
+ # @return [Boolean] True if the token was stored successfully
177
+ def store_token(scheme, credential, token)
178
+ token_store = get_token_store
179
+ return false unless token_store
180
+
181
+ require_relative 'auth/tool_integration'
182
+ cache_key = Legate::Auth::ToolIntegration.generate_cache_key(scheme, credential)
183
+ token_store.store(cache_key, token)
184
+ true
185
+ end
186
+
187
+ # Clear a cached authentication token
188
+ # @param scheme [Legate::Auth::Scheme] The authentication scheme
189
+ # @param credential [Legate::Auth::Credential] The credential
190
+ # @return [Boolean] True if the token was cleared successfully
191
+ def clear_token(scheme, credential)
192
+ token_store = get_token_store
193
+ return false unless token_store
194
+
195
+ require_relative 'auth/tool_integration'
196
+ cache_key = Legate::Auth::ToolIntegration.generate_cache_key(scheme, credential)
197
+ token_store.clear(cache_key)
198
+ true
199
+ end
200
+
201
+ # Revoke a token with the authentication provider
202
+ # @param scheme [Legate::Auth::Scheme] The authentication scheme
203
+ # @param credential [Legate::Auth::Credential] The credential
204
+ # @param token [Legate::Auth::ExchangedCredential] The token to revoke
205
+ # @return [Boolean] True if the token was revoked successfully
206
+ def revoke_token(scheme, credential, token)
207
+ # Try to use token manager if available
208
+ token_manager = get_token_manager
209
+ return token_manager.revoke_token(scheme, credential, token) if token_manager
210
+
211
+ # Fall back to direct revocation if token manager not available
212
+ unless scheme.respond_to?(:revoke_token)
213
+ Legate.logger.warn("Scheme #{scheme.scheme_type} does not support token revocation")
214
+ return false
215
+ end
216
+
217
+ begin
218
+ result = scheme.revoke_token(token, credential)
219
+ clear_token(scheme, credential) if result
220
+ result
221
+ rescue Legate::Auth::Error => e
222
+ Legate.logger.error("Failed to revoke token: #{e.message}")
223
+ false
224
+ end
225
+ end
226
+
227
+ # Handle authentication for a request, automatically selecting the appropriate scheme and credential
228
+ # Checks agent-specific auth config first, then falls back to global Auth::Manager
229
+ # @param request [Hash] The request to authenticate
230
+ # @param options [Hash] Options for authentication (e.g., credential_name, scheme_type)
231
+ # @return [Hash] The authenticated request or the original request if authentication not possible
232
+ def handle_request_auth(request, options = {})
233
+ # Skip if request doesn't need authentication
234
+ return request unless requires_authentication?(request)
235
+
236
+ require_relative 'auth/manager'
237
+
238
+ begin
239
+ auth_manager = Legate::Auth::Manager.instance
240
+ scheme = nil
241
+ credential = nil
242
+
243
+ # First, check agent-specific URL mappings
244
+ if @agent_auth_config && @agent_auth_config[:url_mappings]&.any?
245
+ scheme, credential = find_agent_auth_for_url(request[:url], auth_manager)
246
+ if scheme && credential
247
+ Legate.logger.debug { "[ToolContext] Using agent-specific auth for URL: #{request[:url]}" }
248
+ return authenticate_request(request, scheme, credential)
249
+ end
250
+ end
251
+
252
+ # Fall back to global Auth::Manager lookup
253
+ scheme, credential = auth_manager.find_scheme_and_credential(
254
+ url: request[:url],
255
+ scheme_type: options[:scheme_type],
256
+ credential_name: options[:credential_name]
257
+ )
258
+
259
+ # Apply authentication if found
260
+ return authenticate_request(request, scheme, credential) if scheme && credential
261
+ rescue StandardError => e
262
+ Legate.logger.error("Error in automatic authentication: #{e.message}")
263
+ end
264
+
265
+ # Return the original request if no authentication applied
266
+ request
267
+ end
268
+
269
+ # Find authentication for a URL using agent-specific mappings
270
+ # @param url [String] The URL to find authentication for
271
+ # @param auth_manager [Legate::Auth::Manager] The auth manager to resolve schemes/credentials
272
+ # @return [Array<Legate::Auth::Scheme, Legate::Auth::Credential>, nil] The scheme and credential, or nil if not found
273
+ def find_agent_auth_for_url(url, auth_manager)
274
+ return nil unless @agent_auth_config && @agent_auth_config[:url_mappings]
275
+
276
+ @agent_auth_config[:url_mappings].each do |mapping|
277
+ pattern = mapping[:pattern]
278
+ next unless pattern
279
+
280
+ matched = if pattern.is_a?(Regexp)
281
+ !!(url =~ pattern)
282
+ elsif pattern.is_a?(String)
283
+ if pattern.include?('*')
284
+ # Convert glob pattern to regex
285
+ regex = Regexp.new('^' + Regexp.escape(pattern).gsub('\\*', '.*') + '$')
286
+ !!(url =~ regex)
287
+ else
288
+ url == pattern || url.start_with?(pattern)
289
+ end
290
+ else
291
+ false
292
+ end
293
+
294
+ next unless matched
295
+
296
+ scheme_name = mapping[:scheme_name]
297
+ credential_name = mapping[:credential_name]
298
+
299
+ # Resolve from Auth::Manager
300
+ scheme = auth_manager.get_scheme(scheme_name)
301
+ credential = auth_manager.get_credential(credential_name)
302
+
303
+ return [scheme, credential] if scheme && credential
304
+
305
+ Legate.logger.warn { "[ToolContext] Agent auth mapping matched but scheme '#{scheme_name}' or credential '#{credential_name}' not found in Auth::Manager" }
306
+ end
307
+
308
+ nil
309
+ end
310
+
311
+ # Create or get the token manager for this context
312
+ # @return [Legate::Auth::TokenManager, nil] The token manager if available
313
+ def get_token_manager
314
+ return @token_manager if @token_manager
315
+
316
+ token_store = get_token_store
317
+ return nil unless token_store
318
+
319
+ require_relative 'auth/token_manager'
320
+ @token_manager = Legate::Auth::TokenManager.new(token_store)
321
+ end
322
+
323
+ def to_h
324
+ {
325
+ session_id: @session_id,
326
+ user_id: @user_id,
327
+ app_name: @app_name,
328
+ invocation_id: @invocation_id,
329
+ tool_registry_object_id: @tool_registry&.object_id,
330
+ session_service_present: !@session_service.nil?
331
+ }
332
+ end
333
+
334
+ private
335
+
336
+ # Get the token store for caching authentication tokens
337
+ # @return [Legate::Auth::TokenStore, nil] The token store if available
338
+ def get_token_store
339
+ return nil unless @session_service
340
+
341
+ # TokenStore works with any session service that supports save_scoped_state/load_scoped_state
342
+ if @session_service.respond_to?(:save_scoped_state) && @session_service.respond_to?(:load_scoped_state)
343
+ require_relative 'auth/token_store'
344
+ return Legate::Auth::TokenStore.new(@session_service)
345
+ end
346
+
347
+ nil
348
+ end
349
+ end
350
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Legate
6
+ # Responsible for discovering and loading tool definition files from the filesystem.
7
+ # This decouples filesystem traversal and file loading from the Agent execution logic.
8
+ module ToolLoader
9
+ # Discovers and loads tool definition files from specified paths.
10
+ # @param paths [Array<String>] An array of directory paths to search.
11
+ # @return [void]
12
+ def self.load_from_paths(paths)
13
+ return if paths.nil? || paths.empty?
14
+
15
+ Legate.logger.debug("Starting tool discovery in paths: #{paths.inspect}")
16
+
17
+ paths.each do |path|
18
+ absolute_dir_path = File.expand_path(path, Dir.pwd)
19
+
20
+ unless Dir.exist?(absolute_dir_path)
21
+ Legate.logger.warn("Tool discovery path does not exist or is not a directory: '#{path}' (resolved to '#{absolute_dir_path}'). Skipping.")
22
+ next
23
+ end
24
+
25
+ Dir.glob(File.join(absolute_dir_path, '*.rb')).each do |absolute_file_path|
26
+ Legate.logger.debug("Attempting to load tool file using 'require': #{absolute_file_path}")
27
+ # Use require instead of load to prevent re-registration issues
28
+ require absolute_file_path
29
+ Legate.logger.debug("Successfully required (or already required): #{absolute_file_path}")
30
+ rescue LoadError, SyntaxError => e
31
+ Legate.logger.error("Failed to require/eval tool file '#{absolute_file_path}': #{e.class} - #{e.message}")
32
+ rescue StandardError => e
33
+ Legate.logger.error("Error encountered while requiring/processing tool file '#{absolute_file_path}': #{e.class} - #{e.message}")
34
+ end
35
+ end
36
+ Legate.logger.debug('Finished tool discovery.')
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,73 @@
1
+ # File: lib/legate/tool_registry.rb
2
+ # frozen_string_literal: true
3
+
4
+ # require 'logger' # Not needed directly if only using Legate.logger
5
+
6
+ module Legate
7
+ # Manages a collection of tool definitions for a specific agent instance.
8
+ class ToolRegistry
9
+ attr_reader :tools # Make tools readable
10
+
11
+ # Initialize an empty tool registry.
12
+ def initialize
13
+ @tools = {} # Stores { name_symbol => tool_class } for this instance
14
+ end
15
+
16
+ # Register a tool class with this registry instance.
17
+ # @param name [Symbol] The symbolic name of the tool.
18
+ # @param klass [Class] The tool class (must inherit from Legate::Tool).
19
+ def register(name, klass)
20
+ logger = Legate.logger # Get the central logger instance
21
+
22
+ unless klass < Legate::Tool
23
+ logger.error("ToolRegistry: Attempted to register non-tool class: #{klass.inspect} for name '#{name}'.")
24
+ return false
25
+ end
26
+
27
+ name_symbol = name.to_sym # Ensure it's a symbol
28
+
29
+ if @tools.key?(name_symbol)
30
+ # Use the local variable 'logger'
31
+ logger.warn("ToolRegistry: Tool '#{name_symbol}' is already registered in this registry. Overwriting with class #{klass}.")
32
+ else
33
+ # Use the local variable 'logger'
34
+ logger.info("ToolRegistry: Registering tool '#{name_symbol}' with class #{klass} in this registry.")
35
+ end
36
+ @tools[name_symbol] = klass
37
+ true # Indicate success
38
+ end
39
+
40
+ # Find a tool class by its name symbol within this registry.
41
+ # @param name_symbol [Symbol] The symbolic name of the tool.
42
+ # @return [Class, nil] The tool class or nil if not found.
43
+ def find_class(name_symbol)
44
+ found_class = @tools[name_symbol.to_sym]
45
+ Legate.logger.debug("[ToolRegistry #{@object_id}] find_class(#{name_symbol.inspect}): Found class in @tools: #{found_class.inspect}")
46
+ found_class
47
+ end
48
+
49
+ # Create an instance of a tool by its name symbol using the class registered here.
50
+ # @param name_symbol [Symbol] The symbolic name of the tool.
51
+ # @return [Legate::Tool, nil] An instance of the tool or nil if instantiation fails or class not found.
52
+ def create_instance(name_symbol)
53
+ klass = find_class(name_symbol)
54
+ Legate.logger.debug("[ToolRegistry #{@object_id}] create_instance(#{name_symbol.inspect}): Found class: #{klass.inspect}")
55
+ klass&.new
56
+ end
57
+
58
+ # Get the set of registered tool name symbols.
59
+ # @return [Array<Symbol>] The registered tool names.
60
+ def available_tool_names
61
+ @tools.keys
62
+ end
63
+
64
+ # Get a list of available tools registered in this instance with basic info.
65
+ # @return [Array<Hash>] An array of hashes, each with :name and :description.
66
+ def list_tools
67
+ @tools.values.map do |klass|
68
+ metadata = klass.tool_metadata # Get the full { name:, description:, parameters: } hash
69
+ metadata # Return the full hash
70
+ end.sort_by { |t| t[:name].to_s }
71
+ end
72
+ end # End ToolRegistry class
73
+ end # End Legate module
@@ -0,0 +1,61 @@
1
+ # File: lib/legate/tool_result.rb
2
+ # frozen_string_literal: true
3
+
4
+ module Legate
5
+ # A typed, immutable result a tool's #perform_execution may return instead of
6
+ # building the canonical `{ status:, result:/error_message:/job_id: }` hash by
7
+ # hand (R11, additive). Tool#execute normalizes it to that hash, so everything
8
+ # downstream is unchanged — this is purely an authoring convenience.
9
+ #
10
+ # def perform_execution(params, _context)
11
+ # ToolResult.success("Hello, #{params[:name]}!")
12
+ # end
13
+ #
14
+ # Pairs with the Event#answer / #success? / #error? accessors.
15
+ ToolResult = Data.define(:status, :result, :error_message, :job_id, :message) do
16
+ # @return [ToolResult] a successful result carrying an optional value
17
+ def self.success(value = nil)
18
+ new(status: :success, result: value, error_message: nil, job_id: nil, message: nil)
19
+ end
20
+
21
+ # @return [ToolResult] an error result
22
+ def self.error(message)
23
+ new(status: :error, result: nil, error_message: message.to_s, job_id: nil, message: nil)
24
+ end
25
+
26
+ # @return [ToolResult] a pending (async) result referencing a job
27
+ def self.pending(job_id:, message: nil)
28
+ new(status: :pending, result: nil, error_message: nil, job_id: job_id, message: message)
29
+ end
30
+
31
+ def success?
32
+ status == :success
33
+ end
34
+
35
+ def error?
36
+ status == :error
37
+ end
38
+
39
+ def pending?
40
+ status == :pending
41
+ end
42
+
43
+ # The canonical result hash the rest of Legate speaks. Only the keys relevant
44
+ # to the status are included, matching what hand-built tool hashes produce.
45
+ # @return [Hash]
46
+ def to_h
47
+ case status
48
+ when :success
49
+ { status: :success, result: result }
50
+ when :error
51
+ { status: :error, error_message: error_message }
52
+ when :pending
53
+ out = { status: :pending, job_id: job_id }
54
+ out[:message] = message unless message.nil?
55
+ out
56
+ else
57
+ { status: status }
58
+ end
59
+ end
60
+ end
61
+ end