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,453 @@
1
+ # File: lib/legate/auth.rb
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'auth/error'
5
+ require_relative 'auth/scheme'
6
+ require_relative 'auth/credential'
7
+ require_relative 'auth/config'
8
+ require_relative 'auth/exchanged_credential'
9
+ require_relative 'auth/encryption'
10
+ require_relative 'auth/token_store'
11
+ require_relative 'auth/schemes'
12
+ require_relative 'auth/coordinator'
13
+ require_relative 'auth/coordinators/oauth2_coordinator'
14
+ require_relative 'auth/coordinators/oidc_coordinator'
15
+ require_relative 'auth/coordinators/service_account_coordinator'
16
+ require_relative 'auth/excon_middleware'
17
+ require_relative 'auth/middleware_factory'
18
+ require_relative 'auth/http_client_utils'
19
+
20
+ module Legate
21
+ # The Auth module provides authentication capabilities for Legate tools.
22
+ # It supports various authentication schemes such as API Key, Bearer Token,
23
+ # OAuth2, OpenID Connect, and Service Accounts.
24
+ #
25
+ # @example Configure a tool with authentication
26
+ # credential = Legate::Auth::Credential.new(
27
+ # auth_type: :api_key,
28
+ # api_key: ENV['API_KEY']
29
+ # )
30
+ #
31
+ # scheme = Legate::Auth::Schemes::ApiKey.new(
32
+ # location: :header,
33
+ # name: 'X-API-Key'
34
+ # )
35
+ #
36
+ # # Configure the tool with authentication
37
+ # tool.configure(auth: {
38
+ # scheme: scheme,
39
+ # credential: credential
40
+ # })
41
+ module Auth
42
+ # Version of the authentication module
43
+ VERSION = '0.1.0'
44
+
45
+ # Global mutex for access to the OAuth callback state
46
+ @oauth_mutex = Mutex.new
47
+
48
+ # Condition variable for OAuth callbacks
49
+ @oauth_condition = ConditionVariable.new
50
+
51
+ # OAuth callback response URI
52
+ @oauth_response_uri = nil
53
+
54
+ # Configuration store for Auth sessions
55
+ @config_store = {}
56
+
57
+ # Token store for credentials - initialize with a default in-memory session service
58
+ @token_store = begin
59
+ session_service = Legate::SessionService::InMemory.new
60
+ TokenStore.new(session_service)
61
+ end
62
+
63
+ class << self
64
+ # Generate a unique request ID
65
+ # @return [String] A unique request ID
66
+ def generate_request_id
67
+ require 'securerandom'
68
+ SecureRandom.uuid
69
+ end
70
+
71
+ # Apply authentication to a request
72
+ # @param request [Hash] The request to apply authentication to
73
+ # @param credential [Legate::Auth::Credential, Legate::Auth::ExchangedCredential] The credential to use
74
+ # @param scheme [Legate::Auth::Scheme, nil] The authentication scheme (if not using a stored config)
75
+ # @return [Hash] The authenticated request
76
+ # @raise [Legate::Auth::CredentialError] If the credential is invalid or missing required fields
77
+ def apply_authentication(request, credential, scheme = nil)
78
+ if credential.is_a?(ExchangedCredential) && credential.provider_id
79
+ # Look up the scheme from the stored config
80
+ scheme ||= get_scheme_for_provider(credential.provider_id)
81
+ end
82
+
83
+ raise Legate::Auth::CredentialError, 'Authentication scheme is required' unless scheme
84
+
85
+ # Apply the authentication to the request
86
+ scheme.apply_to_request(request, credential)
87
+ end
88
+
89
+ # Authenticate using a coordinator
90
+ # @param coordinator [Legate::Auth::Coordinator] The authentication coordinator
91
+ # @return [Legate::Auth::ExchangedCredential] The authenticated credential
92
+ # @raise [Legate::Auth::Error] If authentication fails
93
+ def authenticate_with_coordinator(coordinator)
94
+ # Start the authentication flow
95
+ auth_request = coordinator.start
96
+
97
+ # For non-interactive coordinators, the result might be available immediately
98
+ if coordinator.complete?
99
+ return coordinator.result if coordinator.success?
100
+
101
+ raise coordinator.error || Legate::Auth::Error.new('Authentication failed')
102
+
103
+ end
104
+
105
+ # For interactive coordinators, we need to wait for user interaction
106
+ # This method should only be used with non-interactive coordinators
107
+ # or in testing scenarios where we can directly resume the coordinator
108
+ raise Legate::Auth::Error.new('Coordinator requires interaction but no handler provided')
109
+ rescue StandardError => e
110
+ # Wrap any errors that aren't already Legate::Auth::Error
111
+ raise e.is_a?(Legate::Auth::Error) ? e : Legate::Auth::Error.new(e.message)
112
+ end
113
+
114
+ # Start the OAuth2 authentication flow
115
+ # @param provider_id [String] A unique identifier for the provider
116
+ # @param scheme [Legate::Auth::Scheme] The authentication scheme
117
+ # @param credential [Legate::Auth::Credential] The credential to use
118
+ # @param redirect_uri [String, nil] The redirect URI for the authorization request
119
+ # @param options [Hash, nil] Additional options for the authentication process
120
+ # @return [String] The authorization URI to redirect the user to
121
+ # @raise [Legate::Auth::ConfigurationError] If the configuration is invalid
122
+ def start_oauth_flow(provider_id, scheme, credential, redirect_uri = nil, options = {})
123
+ # Create a new config
124
+ config = Config.new(scheme: scheme, credential: credential, options: options)
125
+
126
+ # Build the authorization URI
127
+ auth_uri = config.build_authorization_uri(redirect_uri)
128
+
129
+ # Store the config
130
+ @config_store[provider_id] = config
131
+
132
+ auth_uri
133
+ end
134
+
135
+ # Handle an OAuth callback
136
+ # @param response_uri [String] The callback URI from the OAuth provider
137
+ # @return [Boolean] True if the callback was successfully handled
138
+ # @raise [Legate::Auth::ConfigurationError] If the response is invalid
139
+ def handle_oauth_callback(response_uri)
140
+ @oauth_mutex.synchronize do
141
+ @oauth_response_uri = response_uri
142
+ @oauth_condition.signal
143
+ end
144
+
145
+ true
146
+ end
147
+
148
+ # Wait for the OAuth callback to be received
149
+ # @param timeout [Integer, nil] The timeout in seconds (nil for no timeout)
150
+ # @return [String, nil] The response URI from the OAuth provider
151
+ def wait_for_oauth_callback(timeout = nil)
152
+ response_uri = nil
153
+
154
+ @oauth_mutex.synchronize do
155
+ if timeout
156
+ # Wait with timeout
157
+ @oauth_condition.wait(@oauth_mutex, timeout) if @oauth_response_uri.nil?
158
+ else
159
+ # Wait indefinitely
160
+ @oauth_condition.wait(@oauth_mutex) while @oauth_response_uri.nil?
161
+ end
162
+
163
+ response_uri = @oauth_response_uri
164
+ @oauth_response_uri = nil
165
+ end
166
+
167
+ response_uri
168
+ end
169
+
170
+ # Exchange an authorization code for tokens
171
+ # @param provider_id [String] The provider ID
172
+ # @param response_uri [String] The callback URI from the OAuth provider
173
+ # @return [Legate::Auth::ExchangedCredential] The exchanged credential with tokens
174
+ # @raise [Legate::Auth::ConfigurationError] If the configuration is invalid
175
+ # @raise [Legate::Auth::TokenExchangeError] If the token exchange fails
176
+ def exchange_oauth_code(provider_id, response_uri)
177
+ config = @config_store[provider_id]
178
+
179
+ raise Legate::Auth::ConfigurationError, "No stored configuration for provider #{provider_id}" unless config
180
+
181
+ # Create a response config with the response URI
182
+ response_config = Config.new(
183
+ scheme: config.scheme,
184
+ credential: config.credential,
185
+ auth_request_id: config.auth_request_id
186
+ )
187
+ response_config.response_uri = response_uri
188
+ response_config.state = config.state
189
+
190
+ # Validate the response
191
+ config.validate_response!(response_config)
192
+
193
+ # Exchange the code for tokens
194
+ exchanged_credential = config.scheme.exchange_token(response_config, config.credential)
195
+
196
+ # Add the provider ID to the credential
197
+ exchanged_credential.provider_id = provider_id
198
+
199
+ # Store the credential in the token store
200
+ @token_store.store(provider_id, exchanged_credential)
201
+
202
+ exchanged_credential
203
+ end
204
+
205
+ # Get a stored exchanged credential for a provider
206
+ # @param provider_id [String] The provider ID
207
+ # @return [Legate::Auth::ExchangedCredential, nil] The stored credential or nil if not found
208
+ def get_exchanged_credential(provider_id)
209
+ @token_store.retrieve(provider_id)
210
+ end
211
+
212
+ # Complete the OAuth2 flow with a callback URI
213
+ # @param provider_id [String] The provider ID
214
+ # @param response_uri [String, nil] The callback URI from the OAuth provider
215
+ # @return [Legate::Auth::ExchangedCredential] The exchanged credential with tokens
216
+ # @raise [Legate::Auth::ConfigurationError] If the configuration is invalid
217
+ # @raise [Legate::Auth::TokenExchangeError] If the token exchange fails
218
+ def complete_oauth_flow(provider_id, response_uri = nil)
219
+ # If no response URI is provided, wait for the callback
220
+ response_uri ||= wait_for_oauth_callback
221
+
222
+ raise Legate::Auth::ConfigurationError, 'No response URI provided or received' unless response_uri
223
+
224
+ # Exchange the code for tokens
225
+ exchange_oauth_code(provider_id, response_uri)
226
+ end
227
+
228
+ # Refresh an access token
229
+ # @param provider_id [String] The provider ID
230
+ # @return [Legate::Auth::ExchangedCredential] The refreshed credential
231
+ # @raise [Legate::Auth::TokenRefreshError] If the token refresh fails
232
+ def refresh_token(provider_id)
233
+ # Get the stored credential
234
+ exchanged_credential = get_exchanged_credential(provider_id)
235
+
236
+ raise Legate::Auth::TokenRefreshError, "No stored credential for provider #{provider_id}" unless exchanged_credential
237
+
238
+ # Get the scheme
239
+ scheme = get_scheme_for_provider(provider_id)
240
+
241
+ raise Legate::Auth::TokenRefreshError, "Scheme for provider #{provider_id} does not support token refresh" unless scheme && scheme.supports_refresh?
242
+
243
+ # Get the original credential
244
+ config = @config_store[provider_id]
245
+
246
+ raise Legate::Auth::TokenRefreshError, "No stored configuration for provider #{provider_id}" unless config
247
+
248
+ # Refresh the token
249
+ refreshed_credential = scheme.refresh_token(exchanged_credential, config.credential)
250
+
251
+ # Add the provider ID to the credential
252
+ refreshed_credential.provider_id = provider_id
253
+
254
+ # Store the refreshed credential
255
+ @token_store.store(provider_id, refreshed_credential)
256
+
257
+ refreshed_credential
258
+ end
259
+
260
+ # Get the scheme for a provider
261
+ # @param provider_id [String] The provider ID
262
+ # @return [Legate::Auth::Scheme, nil] The scheme or nil if not found
263
+ private def get_scheme_for_provider(provider_id)
264
+ config = @config_store[provider_id]
265
+ config&.scheme
266
+ end
267
+
268
+ # Get the global token store instance
269
+ # @return [Legate::Auth::TokenStore] The global token store
270
+ attr_accessor :token_store
271
+
272
+ # Set the global token store instance
273
+ # @param store [Legate::Auth::TokenStore] The token store to use
274
+ # @return [Legate::Auth::TokenStore] The token store
275
+
276
+ # Create an authentication middleware for the specified scheme and credential
277
+ # @param scheme [Legate::Auth::Scheme] The authentication scheme to use
278
+ # @param credential [Legate::Auth::Credential] The credential to use
279
+ # @param options [Hash] Additional options for the middleware
280
+ # @return [Legate::Auth::ExconMiddleware] The configured middleware
281
+ def create_middleware(scheme:, credential:, **options)
282
+ Legate::Auth::MiddlewareFactory.create(
283
+ scheme: scheme,
284
+ credential: credential,
285
+ token_store: options[:token_store] || token_store,
286
+ token_manager: options[:token_manager],
287
+ **options
288
+ )
289
+ end
290
+
291
+ # Configure an existing Excon connection with authentication
292
+ # @param connection [Excon::Connection] The connection to configure
293
+ # @param scheme [Legate::Auth::Scheme] The authentication scheme to use
294
+ # @param credential [Legate::Auth::Credential] The credential to use
295
+ # @param options [Hash] Additional options for the middleware
296
+ # @return [Excon::Connection] The configured connection
297
+ def configure_connection(connection, scheme:, credential:, **options)
298
+ Legate::Auth::HttpClientUtils.configure_connection(
299
+ connection,
300
+ scheme: scheme,
301
+ credential: credential,
302
+ token_store: options[:token_store] || token_store,
303
+ token_manager: options[:token_manager],
304
+ **options
305
+ )
306
+ end
307
+
308
+ # Create a new Excon connection with authentication
309
+ # @param url [String] The URL for the connection
310
+ # @param scheme [Legate::Auth::Scheme] The authentication scheme to use
311
+ # @param credential [Legate::Auth::Credential] The credential to use
312
+ # @param options [Hash] Additional options for the connection and middleware
313
+ # @return [Excon::Connection] The configured connection
314
+ def create_connection(url, scheme:, credential:, **options)
315
+ Legate::Auth::HttpClientUtils.create_connection(
316
+ url,
317
+ scheme: scheme,
318
+ credential: credential,
319
+ token_store: options[:token_store] || token_store,
320
+ token_manager: options[:token_manager],
321
+ **options
322
+ )
323
+ end
324
+
325
+ # Create a new Excon connection with API key authentication
326
+ # @param url [String] The URL for the connection
327
+ # @param api_key [String] The API key to use
328
+ # @param location [String] Where to place the API key ('header', 'query', 'cookie')
329
+ # @param name [String] The name of the parameter/header
330
+ # @param options [Hash] Additional options for the connection and middleware
331
+ # @return [Excon::Connection] The configured connection
332
+ def create_api_key_connection(url, api_key:, location: 'header', name: 'X-API-Key', **options)
333
+ Legate::Auth::HttpClientUtils.create_api_key_connection(
334
+ url,
335
+ api_key: api_key,
336
+ location: location,
337
+ name: name,
338
+ token_store: options[:token_store] || token_store,
339
+ token_manager: options[:token_manager],
340
+ **options
341
+ )
342
+ end
343
+
344
+ # Create a new Excon connection with bearer token authentication
345
+ # @param url [String] The URL for the connection
346
+ # @param token [String] The bearer token to use
347
+ # @param options [Hash] Additional options for the connection and middleware
348
+ # @return [Excon::Connection] The configured connection
349
+ def create_bearer_connection(url, token:, **options)
350
+ Legate::Auth::HttpClientUtils.create_bearer_connection(
351
+ url,
352
+ token: token,
353
+ token_store: options[:token_store] || token_store,
354
+ token_manager: options[:token_manager],
355
+ **options
356
+ )
357
+ end
358
+
359
+ # Create a new Excon connection with OAuth2 authentication
360
+ # @param url [String] The URL for the connection
361
+ # @param client_id [String] The OAuth client ID
362
+ # @param client_secret [String] The OAuth client secret
363
+ # @param authorization_url [String] The authorization URL for the OAuth provider
364
+ # @param token_url [String] The token URL for the OAuth provider
365
+ # @param scopes [Array<String>, String, nil] The scopes to request
366
+ # @param options [Hash] Additional options for the connection and middleware
367
+ # @return [Excon::Connection] The configured connection
368
+ def create_oauth2_connection(url, client_id:, client_secret:, authorization_url:, token_url:, scopes: nil, **options)
369
+ Legate::Auth::HttpClientUtils.create_oauth2_connection(
370
+ url,
371
+ client_id: client_id,
372
+ client_secret: client_secret,
373
+ authorization_url: authorization_url,
374
+ token_url: token_url,
375
+ scopes: scopes,
376
+ token_store: options[:token_store] || token_store,
377
+ token_manager: options[:token_manager],
378
+ **options
379
+ )
380
+ end
381
+
382
+ # Create a new Excon connection with OpenID Connect authentication
383
+ # @param url [String] The URL for the connection
384
+ # @param client_id [String] The client ID to use
385
+ # @param client_secret [String] The client secret to use
386
+ # @param discovery_url [String] The OIDC discovery URL
387
+ # @param options [Hash] Additional options for the connection and middleware
388
+ # @return [Excon::Connection] The configured connection
389
+ def create_oidc_connection(url, client_id:, client_secret:, discovery_url:, **options)
390
+ Legate::Auth::HttpClientUtils.create_oidc_connection(
391
+ url,
392
+ client_id: client_id,
393
+ client_secret: client_secret,
394
+ discovery_url: discovery_url,
395
+ token_store: options[:token_store] || token_store,
396
+ token_manager: options[:token_manager],
397
+ **options
398
+ )
399
+ end
400
+
401
+ # Create a new Excon connection with service account authentication
402
+ # @param url [String] The URL for the connection
403
+ # @param service_account_key [String, Hash] The service account key as JSON string or Hash
404
+ # @param scopes [Array<String>, String, nil] The scopes to request
405
+ # @param audience [String, nil] The audience for the token
406
+ # @param options [Hash] Additional options for the connection and middleware
407
+ # @return [Excon::Connection] The configured connection
408
+ def create_service_account_connection(url, service_account_key:, scopes: nil, audience: nil, **options)
409
+ Legate::Auth::HttpClientUtils.create_service_account_connection(
410
+ url,
411
+ service_account_key: service_account_key,
412
+ scopes: scopes,
413
+ audience: audience,
414
+ token_store: options[:token_store] || token_store,
415
+ token_manager: options[:token_manager],
416
+ **options
417
+ )
418
+ end
419
+
420
+ # Create a new Excon connection with Basic authentication
421
+ # @param url [String] The URL for the connection
422
+ # @param username [String] The username to use
423
+ # @param password [String] The password to use
424
+ # @param options [Hash] Additional options for the connection and middleware
425
+ # @return [Excon::Connection] The configured connection
426
+ def create_basic_auth_connection(url, username:, password:, **options)
427
+ Legate::Auth::HttpClientUtils.create_basic_auth_connection(
428
+ url,
429
+ username: username,
430
+ password: password,
431
+ token_store: options[:token_store] || token_store,
432
+ token_manager: options[:token_manager],
433
+ **options
434
+ )
435
+ end
436
+
437
+ # Create a new Excon connection using a previously configured provider
438
+ # @param url [String] The URL for the connection
439
+ # @param provider_id [String] The provider ID to use
440
+ # @param options [Hash] Additional options for the connection and middleware
441
+ # @return [Excon::Connection] The configured connection
442
+ def create_connection_from_provider(url, provider_id, **options)
443
+ Legate::Auth::HttpClientUtils.create_connection_from_provider(
444
+ url,
445
+ provider_id,
446
+ token_store: options[:token_store] || token_store,
447
+ token_manager: options[:token_manager],
448
+ **options
449
+ )
450
+ end
451
+ end
452
+ end
453
+ end
@@ -0,0 +1,71 @@
1
+ # File: lib/legate/callbacks/callback_context.rb
2
+ # frozen_string_literal: true
3
+
4
+ require 'securerandom'
5
+ require_relative '../session_service/base' # For type hinting if used
6
+
7
+ module Legate
8
+ module Callbacks
9
+ # Context object passed to agent lifecycle and model interaction callbacks.
10
+ class CallbackContext
11
+ attr_reader :agent_name, :invocation_id, :session_id, :user_id, :app_name, :session_service, :logger
12
+
13
+ # Expose pending state delta for inspection but not direct modification
14
+ attr_reader :pending_state_delta
15
+
16
+ # @param agent_name [Symbol]
17
+ # @param invocation_id [String]
18
+ # @param session_id [String]
19
+ # @param user_id [String]
20
+ # @param app_name [String]
21
+ # @param session_service [Legate::SessionService::Base]
22
+ # @param logger [Logger]
23
+ def initialize(agent_name:, invocation_id:, session_id:, user_id:, app_name:, session_service:, logger: Legate.logger)
24
+ @agent_name = agent_name
25
+ @invocation_id = invocation_id
26
+ @session_id = session_id
27
+ @user_id = user_id
28
+ @app_name = app_name
29
+ @session_service = session_service
30
+ @pending_state_delta = {} # Internal mutable hash
31
+ end
32
+
33
+ # Retrieves a value from the session state.
34
+ # @param key [Symbol, String] The key to retrieve
35
+ # @return [Object, nil] The value or nil if not found
36
+ def state_get(key)
37
+ Legate.logger.debug { "[CallbackContext] state_get for key: #{key} in session: #{@session_id}" }
38
+ @session_service.get_state(session_id: @session_id, key: key)
39
+ rescue StandardError => e
40
+ Legate.logger.error { "[CallbackContext] Error in state_get for key '#{key}': #{e.message}" }
41
+ nil
42
+ end
43
+
44
+ # Sets a value in the pending state delta. This change will be applied
45
+ # to the session state by the Legate framework after the callback completes.
46
+ # @param key [Symbol, String] The key to set
47
+ # @param value [Object] The value to store (should be serializable)
48
+ def state_set(key, value)
49
+ Legate.logger.debug { "[CallbackContext] state_set for key: #{key} to value: #{value.inspect} (pending)" }
50
+ @pending_state_delta[key.to_sym] = value
51
+ end
52
+
53
+ # Merges a hash into the pending state delta.
54
+ # @param hash_to_merge [Hash] The hash to merge into the pending state delta
55
+ def state_update(hash_to_merge)
56
+ unless hash_to_merge.is_a?(Hash)
57
+ Legate.logger.warn { "[CallbackContext] state_update called with non-hash: #{hash_to_merge.class}" }
58
+ return
59
+ end
60
+
61
+ Legate.logger.debug { "[CallbackContext] state_update with hash: #{hash_to_merge.inspect} (pending)" }
62
+ @pending_state_delta.merge!(hash_to_merge.transform_keys(&:to_sym))
63
+ end
64
+
65
+ # Clears any accumulated pending state changes within this context instance.
66
+ def clear_pending_state_delta!
67
+ @pending_state_delta = {}
68
+ end
69
+ end
70
+ end
71
+ end