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,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ # File: lib/legate/agents/sequential_agent.rb
4
+ require_relative '../agent'
5
+
6
+ module Legate
7
+ module Agents
8
+ # SequentialAgent executes a series of sub-agents in a predefined order.
9
+ # Each sub-agent is executed one after another, with the same session and input.
10
+ class SequentialAgent < Legate::Agent
11
+ # Override run_task to execute sub-agents in sequence
12
+ # @param session_id [String] The session ID
13
+ # @param user_input [String] User input to process
14
+ # @param session_service [Legate::SessionService::Base] Session service for persistence
15
+ # @return [Legate::Event] The final agent event
16
+ def run_task(session_id:, user_input:, session_service:)
17
+ # Verify we have sequential sub-agents defined
18
+ unless @definition.sequential_sub_agent_names&.any?
19
+ err_msg = "SequentialAgent '#{name}' has no sequential_sub_agent_names defined."
20
+ Legate.logger.error(err_msg)
21
+ return Legate::Event.new(role: :agent, content: {
22
+ status: :error,
23
+ error_message: err_msg,
24
+ error_class: 'ConfigurationError'
25
+ })
26
+ end
27
+
28
+ # --- Pre-execution Checks --- #
29
+ unless running?
30
+ err_msg = "Agent '#{name}' is not running. Call agent.start before run_task, " \
31
+ 'or use agent.ask (which starts automatically).'
32
+ Legate.logger.error(err_msg)
33
+ return Legate::Event.new(role: :agent, content: { status: :error, error_message: err_msg })
34
+ end
35
+
36
+ session = session_service.get_session(session_id: session_id)
37
+ unless session
38
+ err_msg = "Session not found: #{session_id}"
39
+ Legate.logger.error(err_msg)
40
+ return Legate::Event.new(role: :agent, content: { status: :error, error_message: err_msg })
41
+ end
42
+ # --------------------------- #
43
+
44
+ # Log user input to the SequentialAgent itself
45
+ user_event = Legate::Event.new(role: :user, content: user_input)
46
+ session_service.append_event(session_id: session_id, event: user_event)
47
+
48
+ # Log the execution sequence start
49
+ Legate.logger.info("SequentialAgent '#{name}' starting execution of #{@definition.sequential_sub_agent_names.size} sub-agents in sequence.")
50
+
51
+ # Track results of all sub-agents
52
+ all_results = []
53
+ final_result = nil
54
+ current_input = user_input # Start with the original user input
55
+
56
+ # Execute each sub-agent in order
57
+ @definition.sequential_sub_agent_names.each_with_index do |sub_agent_name, index|
58
+ sub_agent = find_sub_agent(sub_agent_name)
59
+ unless sub_agent
60
+ err_msg = "Sub-agent '#{sub_agent_name}' not found for SequentialAgent '#{name}'."
61
+ Legate.logger.error(err_msg)
62
+ final_result = {
63
+ status: :error,
64
+ error_message: err_msg,
65
+ error_class: 'MissingSubAgentError',
66
+ step: index + 1,
67
+ total_steps: @definition.sequential_sub_agent_names.size,
68
+ previous_results: all_results.map.with_index { |r, i| { agent: @definition.sequential_sub_agent_names.to_a[i], result: r } }
69
+ }
70
+ break # Stop the sequence on error
71
+ end
72
+
73
+ # Start the sub-agent if it's not already running
74
+ sub_agent.start unless sub_agent.running?
75
+
76
+ # If this is not the first agent, try to augment its input with previous results
77
+ if index > 0 && all_results.any?
78
+ # Get previous agent name - handle both Array and Set types
79
+ previous_agent_name = nil
80
+ previous_agent_name = if @definition.sequential_sub_agent_names.is_a?(Set)
81
+ # For Set, convert to Array and get the element
82
+ @definition.sequential_sub_agent_names.to_a[index - 1]
83
+ else
84
+ # For Array, access directly
85
+ @definition.sequential_sub_agent_names[index - 1]
86
+ end
87
+
88
+ previous_output_key = find_sub_agent(previous_agent_name)&.definition&.output_key
89
+
90
+ if previous_output_key && session_service.respond_to?(:get_state)
91
+ # Get the previous agent's result from session state
92
+ previous_result = session_service.get_state(session_id: session_id, key: previous_output_key)
93
+
94
+ if previous_result && previous_result.is_a?(Hash) && previous_result[:result]
95
+ # Enhanced input with previous result
96
+ current_input = "#{current_input}\n\nHere is the result from the previous step (#{previous_agent_name}):\n#{previous_result[:result]}"
97
+ Legate.logger.info("Enhanced input for sub-agent '#{sub_agent_name}' with previous result from '#{previous_agent_name}'")
98
+ end
99
+ end
100
+ end
101
+
102
+ # Execute the sub-agent with the updated input
103
+ begin
104
+ Legate.logger.info("SequentialAgent '#{name}' executing sub-agent '#{sub_agent_name}' (step #{index + 1}/#{@definition.sequential_sub_agent_names.size}).")
105
+ sub_result = sub_agent.run_task(
106
+ session_id: session_id,
107
+ user_input: current_input,
108
+ session_service: session_service
109
+ )
110
+
111
+ # Record the result
112
+ all_results << sub_result.content
113
+
114
+ # Check for error to break sequence
115
+ if sub_result.content[:status] == :error
116
+ Legate.logger.warn("Sub-agent '#{sub_agent_name}' returned error, breaking sequence: #{sub_result.content[:error_message]}")
117
+ final_result = {
118
+ status: :error,
119
+ error_message: "Error in sub-agent '#{sub_agent_name}': #{sub_result.content[:error_message]}",
120
+ error_class: sub_result.content[:error_class] || 'SubAgentError',
121
+ step: index + 1,
122
+ total_steps: @definition.sequential_sub_agent_names.size,
123
+ sub_agent: sub_agent_name.to_s,
124
+ sub_result: sub_result.content,
125
+ previous_results: all_results.map.with_index { |r, i| { agent: @definition.sequential_sub_agent_names.to_a[i], result: r } }
126
+ }
127
+ break # Stop the sequence on error
128
+ elsif sub_result.content[:result]
129
+ # If the sub-agent succeeded, update the current_input to include its result for the next agent
130
+ # Store the raw result for potential state-based chaining in next iteration
131
+ current_input = sub_result.content[:result].to_s
132
+ end
133
+ rescue StandardError => e
134
+ Legate.logger.error("Error executing sub-agent '#{sub_agent_name}': #{e.class} - #{e.message}\n#{e.backtrace.join("\n")}")
135
+ final_result = {
136
+ status: :error,
137
+ error_message: "Exception in sub-agent '#{sub_agent_name}': #{e.message}",
138
+ error_class: e.class.name,
139
+ step: index + 1,
140
+ total_steps: @definition.sequential_sub_agent_names.size,
141
+ sub_agent: sub_agent_name.to_s,
142
+ previous_results: all_results.map.with_index { |r, i| { agent: @definition.sequential_sub_agent_names.to_a[i], result: r } }
143
+ }
144
+ break # Stop the sequence on error
145
+ end
146
+ end
147
+
148
+ # If we didn't set a final_result due to an error, create a success result with all sub-results
149
+ if final_result.nil?
150
+ final_result = {
151
+ status: :success,
152
+ result: "Completed sequential execution of #{@definition.sequential_sub_agent_names.size} sub-agents",
153
+ steps_completed: @definition.sequential_sub_agent_names.size,
154
+ sub_results: all_results.map.with_index { |r, i| { agent: @definition.sequential_sub_agent_names.to_a[i], result: r } }
155
+ }
156
+ end
157
+
158
+ # Create the final event
159
+ final_agent_event = Legate::Event.new(role: :agent, content: final_result)
160
+
161
+ # Log the final event to the session
162
+ session_service.append_event(session_id: session_id, event: final_agent_event)
163
+
164
+ # --- MAS: Store result in session state if output_key is defined --- #
165
+ if @definition.respond_to?(:output_key) && @definition.output_key && final_agent_event
166
+ output_value = final_agent_event.content # Store the entire content hash
167
+
168
+ # Make sure the output value is serializable
169
+ serialized_value = begin
170
+ # Convert to JSON and back to ensure it's serializable
171
+ JSON.parse(output_value.to_json)
172
+ rescue StandardError => e
173
+ Legate.logger.warn("SequentialAgent '#{@name}': Failed to serialize output value: #{e.message}. Using simplified value.")
174
+ { status: output_value[:status], result: output_value[:result].to_s }
175
+ end
176
+
177
+ Legate.logger.info("SequentialAgent '#{@name}' storing output to session state with key '#{@definition.output_key}' for session '#{session_id}'.")
178
+ if session_service.respond_to?(:set_state)
179
+ session_service.set_state(session_id: session_id, key: @definition.output_key, value: serialized_value)
180
+ else
181
+ Legate.logger.warn("SequentialAgent '#{@name}': Session service does not support :set_state. Cannot store output for key '#{@definition.output_key}'.")
182
+ end
183
+ end
184
+ # --- End MAS State Management --- #
185
+
186
+ final_agent_event
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # File: lib/legate/agents.rb
4
+ # This manifest file loads all agent implementations for workflow agents
5
+
6
+ require_relative 'agents/sequential_agent'
7
+ require_relative 'agents/parallel_agent'
8
+ require_relative 'agents/loop_agent'
9
+
10
+ module Legate
11
+ module Agents
12
+ # This module contains specialized agent implementations for workflow composition
13
+ end
14
+ end
@@ -0,0 +1,148 @@
1
+ # File: lib/legate/auth/config.rb
2
+ # frozen_string_literal: true
3
+
4
+ require 'securerandom'
5
+
6
+ module Legate
7
+ module Auth
8
+ # Configuration container used during the authentication flow.
9
+ # Holds the authentication scheme, credential, and request/response details
10
+ # needed for interactive authentication flows.
11
+ class Config
12
+ # @return [Legate::Auth::Scheme] The authentication scheme
13
+ attr_reader :scheme
14
+
15
+ # @return [Legate::Auth::Credential] The credential information
16
+ attr_reader :credential
17
+
18
+ # @return [String, nil] The unique ID for this authentication request
19
+ attr_reader :auth_request_id
20
+
21
+ # @return [String, nil] The authorization URI for interactive flows
22
+ attr_accessor :auth_uri
23
+
24
+ # @return [String, nil] The redirect URI for OAuth2/OIDC flows
25
+ attr_accessor :redirect_uri
26
+
27
+ # @return [String, nil] The state parameter for CSRF protection
28
+ attr_accessor :state
29
+
30
+ # @return [Hash, nil] The PKCE parameters (code_verifier, etc.)
31
+ attr_accessor :pkce
32
+
33
+ # @return [String, nil] The authorization response URI from the provider
34
+ attr_accessor :response_uri
35
+
36
+ # For backwards compatibility
37
+ alias auth_response_uri response_uri
38
+ alias auth_response_uri= response_uri=
39
+
40
+ # @return [Hash, nil] Additional options for the authentication process
41
+ attr_accessor :options
42
+
43
+ # Initialize a new authentication configuration
44
+ # @param scheme [Legate::Auth::Scheme] The authentication scheme
45
+ # @param credential [Legate::Auth::Credential] The credential information
46
+ # @param auth_request_id [String, nil] The unique ID for this authentication request
47
+ # @param options [Hash, nil] Additional options for the authentication process
48
+ def initialize(scheme:, credential:, auth_request_id: nil, options: {})
49
+ @scheme = scheme
50
+ @credential = credential
51
+ @auth_request_id = auth_request_id || Legate::Auth.generate_request_id
52
+ @options = options || {}
53
+ @auth_uri = nil
54
+ @redirect_uri = nil
55
+ @state = nil
56
+ @pkce = nil
57
+ @response_uri = nil
58
+ end
59
+
60
+ # Build the authorization URI for interactive flows
61
+ # @param redirect_uri [String, nil] The redirect URI for the authorization request
62
+ # @param state [String, nil] A state parameter for CSRF protection
63
+ # @return [String, Hash] The authorization URI or a hash with URI and additional parameters
64
+ def build_authorization_uri(redirect_uri = nil, state = nil)
65
+ @redirect_uri = redirect_uri
66
+ @state = state || @options[:state] || SecureRandom.hex(16)
67
+
68
+ # For OAuth2 schemes with detailed return values including PKCE
69
+ result = @scheme.build_authorization_uri(self, @redirect_uri, @state)
70
+
71
+ if result.is_a?(Hash) && result[:uri]
72
+ @auth_uri = result[:uri]
73
+ @state = result[:state] if result[:state]
74
+ @pkce = result[:pkce] if result[:pkce]
75
+ @auth_uri
76
+ else
77
+ # For backwards compatibility with simpler schemes
78
+ @auth_uri = result
79
+ end
80
+ end
81
+
82
+ # Convert to a hash for serialization
83
+ # @param include_credentials [Boolean] Whether to include credential details (use carefully)
84
+ # @return [Hash] A hash representation of the config
85
+ def to_h(include_credentials: false)
86
+ {
87
+ auth_request_id: @auth_request_id,
88
+ scheme_type: @scheme.scheme_type,
89
+ auth_uri: @auth_uri,
90
+ redirect_uri: @redirect_uri,
91
+ state: @state,
92
+ pkce: @pkce,
93
+ response_uri: @response_uri,
94
+ options: @options
95
+ }.tap do |h|
96
+ h[:credential] = @credential.to_h if include_credentials
97
+ end
98
+ end
99
+
100
+ # Creates a Config from a hash representation
101
+ # @param hash [Hash] The hash representation
102
+ # @param scheme [Legate::Auth::Scheme] The authentication scheme (required if not recreating from complete data)
103
+ # @param credential [Legate::Auth::Credential] The credential information (required if not recreating from complete data)
104
+ # @return [Legate::Auth::Config] A new Config instance
105
+ # @raise [Legate::Auth::ConfigurationError] If required parameters are missing
106
+ def self.from_h(hash, scheme: nil, credential: nil)
107
+ scheme ||= hash[:scheme]
108
+ credential ||= hash[:credential]
109
+
110
+ raise Legate::Auth::ConfigurationError, 'Scheme and credential must be provided' unless scheme && credential
111
+
112
+ config = new(
113
+ scheme: scheme,
114
+ credential: credential,
115
+ auth_request_id: hash[:auth_request_id],
116
+ options: hash[:options] || {}
117
+ )
118
+
119
+ config.auth_uri = hash[:auth_uri]
120
+ config.redirect_uri = hash[:redirect_uri]
121
+ config.state = hash[:state]
122
+ config.pkce = hash[:pkce]
123
+
124
+ # Handle both new and old response URI keys
125
+ config.response_uri = hash[:response_uri] || hash[:auth_response_uri]
126
+
127
+ config
128
+ end
129
+
130
+ # Validates a response against this configuration
131
+ # @param response_config [Legate::Auth::Config] The response configuration
132
+ # @return [Boolean] True if the response is valid for this request
133
+ # @raise [Legate::Auth::ConfigurationError] If the response is invalid
134
+ def validate_response!(response_config)
135
+ # Check request ID
136
+ raise Legate::Auth::ConfigurationError, 'Authentication response ID does not match request ID' unless response_config.auth_request_id == @auth_request_id
137
+
138
+ # Check that we have an auth response URI
139
+ raise Legate::Auth::ConfigurationError, 'Authentication response does not contain a response URI' unless response_config.response_uri
140
+
141
+ # Check state if we had one
142
+ raise Legate::Auth::ConfigurationError, 'Authentication response state does not match request state' if @state && response_config.state && response_config.state != @state
143
+
144
+ true
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'error'
4
+ require_relative 'config'
5
+ require_relative 'credential'
6
+ require_relative 'exchanged_credential'
7
+ require_relative 'token_store'
8
+
9
+ module Legate
10
+ module Auth
11
+ # Base class for authentication coordinators that handle the fiber-based authentication flow.
12
+ # Coordinators are responsible for managing the state of an authentication flow,
13
+ # including pausing execution, waiting for user input/authentication, and resuming
14
+ # with appropriate credentials.
15
+ class Coordinator
16
+ # Default timeout for authentication flow (in seconds)
17
+ DEFAULT_TIMEOUT = 300
18
+
19
+ # Authentication status codes
20
+ module Status
21
+ PENDING = :pending
22
+ COMPLETED = :completed
23
+ FAILED = :failed
24
+ TIMEOUT = :timeout
25
+ CANCELLED = :cancelled
26
+ end
27
+
28
+ # Initialize a new authentication coordinator
29
+ # @param scheme [Legate::Auth::Scheme] The authentication scheme to use
30
+ # @param credential [Legate::Auth::Credential] The credential to use
31
+ # @param session_service [Legate::SessionService::Base] The session service for state persistence
32
+ # @param token_store [Legate::Auth::TokenStore, nil] Optional token store for caching tokens
33
+ # @param timeout [Integer, nil] Optional timeout in seconds (nil for no timeout)
34
+ def initialize(scheme:, credential:, session_service:, token_store: nil, timeout: DEFAULT_TIMEOUT)
35
+ @scheme = scheme
36
+ @credential = credential
37
+ @session_service = session_service
38
+ @token_store = token_store || TokenStore.new(session_service)
39
+ @timeout = timeout
40
+ @request_id = SecureRandom.uuid
41
+ @status = Status::PENDING
42
+ @start_time = nil
43
+ @auth_fiber = nil
44
+ @result = nil
45
+ @error = nil
46
+ end
47
+
48
+ # Start the authentication flow
49
+ # @return [Hash] The authentication request details to be sent to the client
50
+ def start
51
+ # Create a new fiber for this authentication flow
52
+ @auth_fiber = Fiber.new do
53
+ @start_time = Time.now
54
+ @result = authenticate
55
+ @status = Status::COMPLETED
56
+ rescue Legate::Auth::Error => e
57
+ @error = e
58
+ @status = Status::FAILED
59
+ nil
60
+ rescue StandardError => e
61
+ @error = Legate::Auth::Error.new("Unexpected error during authentication: #{e.message}", e)
62
+ @status = Status::FAILED
63
+ nil
64
+ end
65
+
66
+ # Start the fiber to get the initial authentication request
67
+ auth_request = @auth_fiber.resume
68
+
69
+ # Record start of authentication in session
70
+ save_auth_state
71
+
72
+ # Return the authentication request to be sent to the client
73
+ {
74
+ request_id: @request_id,
75
+ scheme_type: @scheme.scheme_type,
76
+ auth_request: auth_request
77
+ }
78
+ end
79
+
80
+ # Resume the authentication flow with a response from the client
81
+ # @param response [Hash] The response from the client
82
+ # @return [Legate::Auth::ExchangedCredential, nil] The resulting credential or nil on failure
83
+ def resume(response)
84
+ raise Legate::Auth::Error, "Authentication flow is not in progress (status: #{@status})" unless @status == Status::PENDING
85
+
86
+ # Check for timeout
87
+ if @timeout && Time.now - @start_time > @timeout
88
+ @status = Status::TIMEOUT
89
+ @error = Legate::Auth::Error.new("Authentication timed out after #{@timeout} seconds")
90
+ save_auth_state
91
+ return nil
92
+ end
93
+
94
+ # Resume the fiber with the client response
95
+ begin
96
+ result = @auth_fiber.resume(response)
97
+
98
+ # If the fiber yields again, we need more input from the client
99
+ if @auth_fiber.alive?
100
+ # Return the next authentication request
101
+ return {
102
+ request_id: @request_id,
103
+ scheme_type: @scheme.scheme_type,
104
+ auth_request: result
105
+ }
106
+ end
107
+
108
+ # Authentication completed
109
+ @status = Status::COMPLETED if @status == Status::PENDING
110
+ save_auth_state
111
+
112
+ # Store the token if we have one
113
+ if @result && @result.is_a?(Legate::Auth::ExchangedCredential) && @token_store
114
+ cache_key = generate_cache_key(@scheme, @credential)
115
+ @token_store.store(cache_key, @result)
116
+ end
117
+
118
+ @result
119
+ rescue StandardError => e
120
+ @error = Legate::Auth::Error.new("Error resuming authentication: #{e.message}", e)
121
+ @status = Status::FAILED
122
+ save_auth_state
123
+ nil
124
+ end
125
+ end
126
+
127
+ # Cancel the authentication flow
128
+ # @param reason [String, nil] Optional reason for cancellation
129
+ # @return [Boolean] True if the flow was successfully cancelled
130
+ def cancel(reason = nil)
131
+ return false unless @status == Status::PENDING
132
+
133
+ @status = Status::CANCELLED
134
+ @error = Legate::Auth::Error.new("Authentication cancelled#{reason ? ": #{reason}" : ''}")
135
+ save_auth_state
136
+ true
137
+ end
138
+
139
+ # Get the current status of the authentication flow
140
+ # @return [Symbol] The current status
141
+ attr_reader :status
142
+
143
+ # Get the error that occurred during authentication, if any
144
+ # @return [Legate::Auth::Error, nil] The error or nil if no error occurred
145
+ attr_reader :error
146
+
147
+ # Get the result of the authentication flow
148
+ # @return [Legate::Auth::ExchangedCredential, nil] The resulting credential or nil if not completed
149
+ attr_reader :result
150
+
151
+ # Check if the authentication flow is complete
152
+ # @return [Boolean] True if the flow is complete (success or failure)
153
+ def complete?
154
+ @status != Status::PENDING
155
+ end
156
+
157
+ # Check if the authentication flow is successful
158
+ # @return [Boolean] True if the flow completed successfully
159
+ def success?
160
+ @status == Status::COMPLETED && @result
161
+ end
162
+
163
+ protected
164
+
165
+ # Main authentication method to be implemented by subclasses
166
+ # This method should use Fiber.yield to pause execution and wait for client input
167
+ # @return [Legate::Auth::ExchangedCredential] The authenticated credential
168
+ # @raise [Legate::Auth::Error] If authentication fails
169
+ def authenticate
170
+ raise NotImplementedError, "Subclasses must implement the 'authenticate' method"
171
+ end
172
+
173
+ # Generate a cache key for the authentication token
174
+ # @param scheme [Legate::Auth::Scheme] The authentication scheme
175
+ # @param credential [Legate::Auth::Credential] The credential
176
+ # @return [String] The cache key
177
+ def generate_cache_key(scheme, credential)
178
+ require 'digest/sha2'
179
+
180
+ # Create a unique key based on scheme and credential
181
+ parts = [
182
+ scheme.scheme_type.to_s,
183
+ credential.auth_type.to_s
184
+ ]
185
+
186
+ # Add scheme-specific information
187
+ case scheme.scheme_type
188
+ when :api_key
189
+ parts << credential[:api_key, resolve_env: false].to_s
190
+ when :http_bearer
191
+ parts << credential[:bearer_token, resolve_env: false].to_s
192
+ when :oauth2, :oidc
193
+ parts << credential[:client_id, resolve_env: false].to_s
194
+ parts << (credential[:scope, resolve_env: false] || '').to_s
195
+ when :service_account
196
+ parts << credential[:client_email, resolve_env: false].to_s
197
+ end
198
+
199
+ "auth_#{Digest::SHA256.hexdigest(parts.join(':'))}"
200
+ end
201
+
202
+ private
203
+
204
+ # Save the current authentication state to the session
205
+ def save_auth_state
206
+ state = {
207
+ request_id: @request_id,
208
+ scheme_type: @scheme.scheme_type,
209
+ status: @status,
210
+ start_time: @start_time&.iso8601,
211
+ error: @error&.message
212
+ }
213
+
214
+ @session_service.save_scoped_state('auth_flow', @request_id, state)
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../coordinator'
4
+ require_relative '../schemes/oauth2'
5
+ require_relative '../config'
6
+
7
+ module Legate
8
+ module Auth
9
+ module Coordinators
10
+ # OAuth2Coordinator handles the interactive OAuth2 authentication flow using fibers.
11
+ # It manages pausing execution to request user authorization, and resuming once
12
+ # the authorization code is received.
13
+ class OAuth2Coordinator < Coordinator
14
+ # Authentication steps for OAuth2
15
+ module Steps
16
+ AUTHORIZATION = :authorization
17
+ TOKEN_EXCHANGE = :token_exchange
18
+ end
19
+
20
+ # Initialize a new OAuth2 coordinator
21
+ # @param scheme [Legate::Auth::Schemes::OAuth2] The OAuth2 scheme
22
+ # @param credential [Legate::Auth::Credential] The credential with client information
23
+ # @param session_service [Legate::SessionService::Base] The session service
24
+ # @param token_store [Legate::Auth::TokenStore, nil] Optional token store
25
+ # @param timeout [Integer, nil] Optional timeout in seconds
26
+ # @param redirect_uri [String, nil] Optional redirect URI
27
+ def initialize(scheme:, credential:, session_service:, token_store: nil, timeout: DEFAULT_TIMEOUT, redirect_uri: nil)
28
+ super(scheme: scheme, credential: credential, session_service: session_service, token_store: token_store, timeout: timeout)
29
+
30
+ raise ArgumentError, "Expected an OAuth2 scheme, got #{scheme.class}" unless scheme.is_a?(Legate::Auth::Schemes::OAuth2)
31
+
32
+ raise ArgumentError, "Credential must have auth_type :oauth2, got #{credential.auth_type}" unless credential.auth_type == :oauth2
33
+
34
+ @redirect_uri = redirect_uri
35
+ @current_step = Steps::AUTHORIZATION
36
+ @auth_config = nil
37
+ end
38
+
39
+ protected
40
+
41
+ # Implement the OAuth2 authentication flow
42
+ # @return [Legate::Auth::ExchangedCredential] The authenticated credential
43
+ # @raise [Legate::Auth::Error] If authentication fails
44
+ def authenticate
45
+ # Step 1: Create authorization request
46
+ @current_step = Steps::AUTHORIZATION
47
+ authorization_response = request_authorization
48
+
49
+ # Step 2: Exchange code for tokens
50
+ @current_step = Steps::TOKEN_EXCHANGE
51
+ exchange_code_for_token(authorization_response)
52
+ end
53
+
54
+ private
55
+
56
+ # Request authorization from the user
57
+ # @return [Hash] The authorization response from the client
58
+ def request_authorization
59
+ # Create a config for the authorization request
60
+ @auth_config = Legate::Auth::Config.new(
61
+ scheme: @scheme,
62
+ credential: @credential,
63
+ options: { request_id: @request_id }
64
+ )
65
+
66
+ # Build the authorization URI
67
+ authorization_uri = @auth_config.build_authorization_uri(@redirect_uri)
68
+
69
+ # Yield to pause execution and wait for authorization response
70
+ response = Fiber.yield({
71
+ type: 'authorization_request',
72
+ authorization_url: authorization_uri[:uri],
73
+ state: authorization_uri[:state],
74
+ redirect_uri: @redirect_uri
75
+ })
76
+
77
+ # Validate the response
78
+ raise Legate::Auth::Error, "Invalid authorization response: expected Hash, got #{response.class}" unless response.is_a?(Hash)
79
+
80
+ raise Legate::Auth::Error, 'Missing response_uri in authorization response' unless response['response_uri']
81
+
82
+ response
83
+ end
84
+
85
+ # Exchange the authorization code for tokens
86
+ # @param authorization_response [Hash] The authorization response from the client
87
+ # @return [Legate::Auth::ExchangedCredential] The authenticated credential
88
+ # @raise [Legate::Auth::TokenExchangeError] If token exchange fails
89
+ def exchange_code_for_token(authorization_response)
90
+ # Update the config with the response URI
91
+ @auth_config.response_uri = authorization_response['response_uri']
92
+
93
+ # Exchange the code for tokens
94
+ @scheme.exchange_token(@auth_config, @credential)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end