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,514 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example of using OpenID Connect (OIDC) authentication with Legate
5
+ #
6
+ # This example demonstrates how to use OIDC authentication to make authenticated requests
7
+ # and retrieve user profile information from an identity provider.
8
+ #
9
+ # Usage:
10
+ # ruby examples/advanced/auth/oidc_auth.rb [--with-server]
11
+ # ruby examples/advanced/auth/oidc_auth.rb --client-id=YOUR_CLIENT_ID --client-secret=YOUR_CLIENT_SECRET --provider=google
12
+ #
13
+ # Set these environment variables to use your own OIDC provider:
14
+ # OIDC_CLIENT_ID
15
+ # OIDC_CLIENT_SECRET
16
+ # OIDC_PROVIDER (default: google, supported: google, auth0)
17
+ # OIDC_REDIRECT_URI (default: http://localhost:3000/auth/callback)
18
+
19
+ require 'bundler/setup'
20
+ require 'legate'
21
+ require 'legate/auth'
22
+ require 'legate/auth/runner'
23
+ require 'legate/auth/schemes/openid_connect'
24
+ require 'legate/tool_context'
25
+ require 'legate/web/server'
26
+ require 'launchy'
27
+ require 'securerandom'
28
+ require 'optparse'
29
+ require 'json'
30
+ require 'fileutils'
31
+
32
+ # Parse command line arguments
33
+ options = {
34
+ with_server: false,
35
+ client_id: ENV['OIDC_CLIENT_ID'],
36
+ client_secret: ENV['OIDC_CLIENT_SECRET'],
37
+ provider: ENV['OIDC_PROVIDER'] || 'google',
38
+ redirect_uri: ENV['OIDC_REDIRECT_URI'] || 'http://localhost:3000/auth/callback',
39
+ handle_response: false,
40
+ request_id: nil,
41
+ response_uri: nil,
42
+ use_refresh_token: false,
43
+ verbose: false
44
+ }
45
+
46
+ OptionParser.new do |opts|
47
+ opts.banner = 'Usage: ruby examples/advanced/auth/oidc_auth.rb [options]'
48
+
49
+ opts.on('--with-server', 'Start a local server to handle the OIDC callback') do
50
+ options[:with_server] = true
51
+ end
52
+
53
+ opts.on('--client-id=ID', 'OIDC client ID') do |id|
54
+ options[:client_id] = id
55
+ end
56
+
57
+ opts.on('--client-secret=SECRET', 'OIDC client secret') do |secret|
58
+ options[:client_secret] = secret
59
+ end
60
+
61
+ opts.on('--provider=PROVIDER', 'OIDC provider (google, auth0)') do |provider|
62
+ options[:provider] = provider
63
+ end
64
+
65
+ opts.on('--redirect-uri=URI', 'OIDC redirect URI') do |uri|
66
+ options[:redirect_uri] = uri
67
+ end
68
+
69
+ opts.on('--handle-response', 'Handle an authentication response') do
70
+ options[:handle_response] = true
71
+ end
72
+
73
+ opts.on('--request-id=ID', 'Authentication request ID') do |id|
74
+ options[:request_id] = id
75
+ end
76
+
77
+ opts.on('--response-uri=URI', 'Authentication response URI') do |uri|
78
+ options[:response_uri] = uri
79
+ end
80
+
81
+ opts.on('--use-refresh-token', 'Use a saved refresh token if available') do
82
+ options[:use_refresh_token] = true
83
+ end
84
+
85
+ opts.on('--verbose', 'Enable verbose output') do
86
+ options[:verbose] = true
87
+ end
88
+
89
+ opts.on('--help', 'Show this help message') do
90
+ puts opts
91
+ exit
92
+ end
93
+ end.parse!
94
+
95
+ # Validate required parameters
96
+ if options[:client_id].nil? || options[:client_id].empty?
97
+ puts 'Error: OIDC client ID is required'
98
+ puts 'Please provide it via --client-id or OIDC_CLIENT_ID environment variable'
99
+ exit 1
100
+ end
101
+
102
+ if options[:client_secret].nil? || options[:client_secret].empty?
103
+ puts 'Error: OIDC client secret is required'
104
+ puts 'Please provide it via --client-secret or OIDC_CLIENT_SECRET environment variable'
105
+ exit 1
106
+ end
107
+
108
+ # Setup the session service
109
+ session_service = Legate::SessionService::InMemory.new
110
+
111
+ # Create a tool context with the session service
112
+ # In a real application, you would use Redis or another persistent storage
113
+ context = Legate::ToolContext.new(session_service: session_service)
114
+
115
+ # Create an OIDC scheme based on the selected provider
116
+ oidc_scheme = case options[:provider]&.downcase
117
+ when 'google'
118
+ Legate::Auth::Schemes::OpenIDConnect.new(
119
+ authorization_url: 'https://accounts.google.com/o/oauth2/auth',
120
+ token_url: 'https://oauth2.googleapis.com/token',
121
+ userinfo_url: 'https://openidconnect.googleapis.com/v1/userinfo',
122
+ jwks_uri: 'https://www.googleapis.com/oauth2/v3/certs',
123
+ scopes: %w[openid email profile],
124
+ fetch_userinfo: true,
125
+ use_pkce: true,
126
+ additional_params: {
127
+ prompt: 'consent',
128
+ access_type: 'offline'
129
+ }
130
+ )
131
+ when 'auth0'
132
+ # Your Auth0 domain - replace with your actual domain
133
+ domain = ENV['AUTH0_DOMAIN'] || 'your-domain.auth0.com'
134
+
135
+ Legate::Auth::Schemes::OpenIDConnect.new(
136
+ authorization_url: "https://#{domain}/authorize",
137
+ token_url: "https://#{domain}/oauth/token",
138
+ userinfo_url: "https://#{domain}/userinfo",
139
+ jwks_uri: "https://#{domain}/.well-known/jwks.json",
140
+ scopes: %w[openid email profile],
141
+ fetch_userinfo: true,
142
+ use_pkce: true
143
+ )
144
+ else
145
+ puts "Warning: Unsupported provider '#{options[:provider]}'. Using Google as default."
146
+
147
+ Legate::Auth::Schemes::OpenIDConnect.new(
148
+ authorization_url: 'https://accounts.google.com/o/oauth2/auth',
149
+ token_url: 'https://oauth2.googleapis.com/token',
150
+ userinfo_url: 'https://openidconnect.googleapis.com/v1/userinfo',
151
+ jwks_uri: 'https://www.googleapis.com/oauth2/v3/certs',
152
+ scopes: %w[openid email profile],
153
+ fetch_userinfo: true,
154
+ use_pkce: true,
155
+ additional_params: {
156
+ prompt: 'consent',
157
+ access_type: 'offline'
158
+ }
159
+ )
160
+ end
161
+
162
+ # Create the OIDC credential
163
+ credential = Legate::Auth::Credential.new(
164
+ auth_type: :oidc,
165
+ client_id: options[:client_id],
166
+ client_secret: options[:client_secret]
167
+ )
168
+
169
+ # Token storage file for refresh tokens
170
+ token_storage_file = File.expand_path('~/.oidc_example_token.json')
171
+
172
+ # Helper to save token to file
173
+ def save_token_to_file(token, file_path)
174
+ # Convert token to a hash for storage
175
+ token_data = {
176
+ access_token: token.access_token,
177
+ refresh_token: token.refresh_token,
178
+ token_type: token.token_type,
179
+ expires_at: token.expires_at.to_i,
180
+ scope: token.scope,
181
+ id_token: token[:id_token],
182
+ metadata: token.metadata
183
+ }.compact
184
+
185
+ # Save to file
186
+ FileUtils.mkdir_p(File.dirname(file_path))
187
+ File.write(file_path, JSON.pretty_generate(token_data))
188
+ puts "Token saved to #{file_path}"
189
+ end
190
+
191
+ # Helper to load token from file
192
+ def load_token_from_file(file_path)
193
+ return nil unless File.exist?(file_path)
194
+
195
+ begin
196
+ token_data = JSON.parse(File.read(file_path), symbolize_names: true)
197
+
198
+ # Create an exchanged credential from the stored data
199
+ Legate::Auth::ExchangedCredential.new(
200
+ auth_type: :oidc,
201
+ access_token: token_data[:access_token],
202
+ refresh_token: token_data[:refresh_token],
203
+ token_type: token_data[:token_type],
204
+ expires_at: Time.at(token_data[:expires_at]),
205
+ scope: token_data[:scope],
206
+ id_token: token_data[:id_token],
207
+ metadata: token_data[:metadata]
208
+ )
209
+ rescue StandardError => e
210
+ puts "Error loading token: #{e.message}"
211
+ nil
212
+ end
213
+ end
214
+
215
+ # Create a web server to handle the OIDC callback
216
+ def create_callback_server(context, request_id_holder)
217
+ server = Legate::Web::Server.new(port: 3000)
218
+
219
+ # Add a route to handle the OIDC callback
220
+ server.add_route('GET', '/auth/callback') do |req, res|
221
+ # Extract the authorization code from the query parameters
222
+ code = req.query['code']
223
+ state = req.query['state']
224
+ error = req.query['error']
225
+
226
+ if error
227
+ res.status = 400
228
+ res.body = "Authentication failed: #{error}"
229
+ elsif code
230
+ # Convert the request to a response URI
231
+ response_uri = "http://localhost:3000/auth/callback?#{req.query_string}"
232
+
233
+ # Find the active auth request
234
+ request_id = request_id_holder[:id] || nil
235
+
236
+ if request_id
237
+ # Handle the auth response
238
+ result = context.handle_auth_response(request_id, { 'response_uri' => response_uri })
239
+
240
+ # Show a success page
241
+ res.status = 200
242
+ res.body = <<~HTML
243
+ <!DOCTYPE html>
244
+ <html>
245
+ <head>
246
+ <title>OIDC Authentication Successful</title>
247
+ <style>
248
+ body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
249
+ h1 { color: #4CAF50; }
250
+ .box { border: 1px solid #ddd; border-radius: 5px; padding: 20px; margin-top: 20px; }
251
+ </style>
252
+ </head>
253
+ <body>
254
+ <h1>Authentication Successful</h1>
255
+ <p>You have successfully authenticated with the OpenID Connect provider.</p>
256
+ <div class="box">
257
+ <p>You can close this window and return to the application.</p>
258
+ </div>
259
+ </body>
260
+ </html>
261
+ HTML
262
+ else
263
+ res.status = 400
264
+ res.body = 'No active authentication request found'
265
+ end
266
+ else
267
+ res.status = 400
268
+ res.body = 'Missing required parameters'
269
+ end
270
+ end
271
+
272
+ server
273
+ end
274
+
275
+ # Helper to fetch user information
276
+ def fetch_user_info(token, oidc_scheme)
277
+ return {} unless token&.access_token && oidc_scheme.userinfo_url
278
+
279
+ begin
280
+ require 'faraday'
281
+
282
+ conn = Faraday.new do |builder|
283
+ builder.adapter Faraday.default_adapter
284
+ end
285
+
286
+ response = conn.get(oidc_scheme.userinfo_url) do |req|
287
+ req.headers['Authorization'] = "#{token.token_type} #{token.access_token}"
288
+ req.headers['Accept'] = 'application/json'
289
+ end
290
+
291
+ if response.status == 200
292
+ JSON.parse(response.body, symbolize_names: true)
293
+ else
294
+ { error: "Failed to fetch user info: #{response.status}" }
295
+ end
296
+ rescue StandardError => e
297
+ { error: "Error fetching user info: #{e.message}" }
298
+ end
299
+ end
300
+
301
+ def display_user_info(user_info)
302
+ return puts 'No user info available' if user_info.nil? || user_info.empty?
303
+
304
+ puts "\nUser Information:"
305
+ puts " Name : #{user_info[:name] || 'N/A'}"
306
+ puts " Email : #{user_info[:email] || 'N/A'}"
307
+ puts " Picture : #{user_info[:picture] ? 'Available' : 'N/A'}"
308
+ puts " Locale : #{user_info[:locale] || 'N/A'}"
309
+
310
+ puts " Subject ID : #{user_info[:sub]}" if user_info[:sub]
311
+
312
+ # Display additional fields if available
313
+ additional_fields = user_info.keys - %i[name email picture locale sub error]
314
+ return unless additional_fields.any?
315
+
316
+ puts ' Additional Information:'
317
+ additional_fields.each do |field|
318
+ puts " #{field}: #{user_info[field]}"
319
+ end
320
+ end
321
+
322
+ def make_authenticated_request(token, oidc_scheme)
323
+ # In a real application, you would make an API call to an OIDC-protected endpoint
324
+ # This is just a simulation to show the token information
325
+ puts "\nMaking an authenticated request with token:"
326
+ puts " Access Token : #{token.access_token[0..9]}...#{token.access_token[-4..-1]}"
327
+ puts " Token Type : #{token.token_type}"
328
+ puts " Expires In : #{(token.expires_at - Time.now).to_i} seconds"
329
+ puts " Scopes : #{token.scope}"
330
+
331
+ # Print ID token if available
332
+ puts " ID Token : #{token[:id_token][0..9]}...#{token[:id_token][-4..-1]}" if token[:id_token]
333
+
334
+ # Print refresh token if available
335
+ puts " Refresh Token: #{token.refresh_token[0..5]}...#{token.refresh_token[-4..-1]}" if token.refresh_token
336
+
337
+ # Get user info from token metadata or fetch it
338
+ user_info = token.metadata&.dig(:userinfo)
339
+
340
+ if !user_info && token.access_token
341
+ puts "\nFetching user information..."
342
+ user_info = fetch_user_info(token, oidc_scheme)
343
+
344
+ # Store user info in token metadata for future use
345
+ if user_info && !user_info[:error]
346
+ token.metadata ||= {}
347
+ token.metadata[:userinfo] = user_info
348
+ end
349
+ end
350
+
351
+ # Display user information
352
+ display_user_info(user_info)
353
+
354
+ puts "\nAuthenticated request successful!"
355
+
356
+ {
357
+ status: 'success',
358
+ authenticated: true,
359
+ auth_method: 'OIDC',
360
+ token_expiry: token.expires_at,
361
+ scopes: token.scope,
362
+ user_info: user_info
363
+ }
364
+ end
365
+
366
+ # Main execution logic
367
+ begin
368
+ # Try to use saved refresh token
369
+ saved_token = nil
370
+ if options[:use_refresh_token] && File.exist?(token_storage_file)
371
+ puts 'Found saved token, attempting to use it...'
372
+ saved_token = load_token_from_file(token_storage_file)
373
+
374
+ if saved_token
375
+ if saved_token.expired?
376
+ puts 'Saved token is expired, attempting to refresh it...'
377
+
378
+ # Create a token store
379
+ token_store = Legate::Auth::TokenStore.new(session_service: session_service)
380
+
381
+ # Create a token manager
382
+ token_manager = Legate::Auth::TokenManager.new(token_store)
383
+
384
+ # Refresh the token
385
+ begin
386
+ refreshed_token = token_manager.refresh_token(oidc_scheme, credential, saved_token)
387
+ puts 'Token refreshed successfully!'
388
+
389
+ # Save the refreshed token
390
+ save_token_to_file(refreshed_token, token_storage_file)
391
+
392
+ # Make a request with the refreshed token
393
+ result = make_authenticated_request(refreshed_token, oidc_scheme)
394
+ puts "\nAuthentication via refresh token successful!"
395
+ exit 0
396
+ rescue StandardError => e
397
+ puts "Token refresh failed: #{e.message}"
398
+ puts 'Will proceed with interactive authentication...'
399
+ # Continue with interactive flow below
400
+ end
401
+ else
402
+ puts 'Saved token is still valid, using it...'
403
+ result = make_authenticated_request(saved_token, oidc_scheme)
404
+ puts "\nAuthentication with saved token successful!"
405
+ exit 0
406
+ end
407
+ end
408
+ end
409
+
410
+ # Handle authentication response if requested
411
+ if options[:handle_response]
412
+ unless options[:request_id] && options[:response_uri]
413
+ puts 'Error: Both --request-id and --response-uri are required with --handle-response'
414
+ exit 1
415
+ end
416
+
417
+ puts "Handling authentication response for request ID: #{options[:request_id]}"
418
+
419
+ # Create a response object
420
+ response = { 'response_uri' => options[:response_uri] }
421
+
422
+ # Handle the authentication response
423
+ result = context.handle_auth_response(options[:request_id], response)
424
+
425
+ if result[:status] == :completed
426
+ puts 'Authentication completed successfully!'
427
+ token = result[:credential]
428
+
429
+ # Save the token for future use
430
+ save_token_to_file(token, token_storage_file)
431
+
432
+ # Make a request with the token
433
+ make_authenticated_request(token, oidc_scheme)
434
+ else
435
+ puts 'Authentication not completed:'
436
+ puts result.inspect
437
+ end
438
+
439
+ exit 0
440
+ end
441
+
442
+ # Start server if requested
443
+ if options[:with_server]
444
+ puts 'Starting local server to handle OIDC callback...'
445
+ request_id_holder = { id: nil }
446
+ server = create_callback_server(context, request_id_holder)
447
+
448
+ begin
449
+ # Start the server
450
+ server_thread = Thread.new { server.start }
451
+
452
+ # Use with_authentication to create a fiber context with auth support
453
+ result = context.with_authentication do
454
+ # This callback will be called when an authentication request is yielded
455
+
456
+ # Authenticate with the scheme and credential
457
+ token = context.auth_session(
458
+ oidc_scheme,
459
+ credential,
460
+ redirect_uri: options[:redirect_uri]
461
+ )
462
+
463
+ if token
464
+ # Save the token for future use
465
+ save_token_to_file(token, token_storage_file)
466
+
467
+ # Make authenticated request
468
+ make_authenticated_request(token, oidc_scheme)
469
+ else
470
+ puts 'Failed to get authentication token'
471
+ { status: :error, message: 'Authentication failed' }
472
+ end
473
+ end
474
+
475
+ puts "Result: #{result.inspect}"
476
+ ensure
477
+ # Stop the server
478
+ server.stop
479
+ # Wait for server thread to finish
480
+ server_thread.join if server_thread
481
+ end
482
+ else
483
+ # Manual flow without a local server
484
+ puts 'Starting OIDC authentication process...'
485
+
486
+ # Create an auth runner
487
+ auth_runner = Legate::Auth::Runner.new(session_service: session_service)
488
+
489
+ # Start authentication
490
+ auth_result = auth_runner.authenticate(oidc_scheme, credential,
491
+ redirect_uri: options[:redirect_uri])
492
+
493
+ # Check if we got a token immediately (unlikely for OIDC)
494
+ if auth_result.is_a?(Legate::Auth::ExchangedCredential)
495
+ puts 'Received token immediately (unusual for OIDC):'
496
+ make_authenticated_request(auth_result, oidc_scheme)
497
+ else
498
+ # We should have an authentication request
499
+ request_id = auth_result[:request_id]
500
+ auth_request = auth_result[:auth_request]
501
+
502
+ puts 'Authentication required. Please visit this URL to authorize:'
503
+ puts " #{auth_request[:url]}"
504
+ puts
505
+ puts 'After authorization, you will be redirected. Copy the entire URL from your browser'
506
+ puts 'and run this command to complete authentication:'
507
+ puts
508
+ puts " ruby #{__FILE__} --handle-response --request-id #{request_id} --response-uri 'PASTE_URL_HERE'"
509
+ end
510
+ end
511
+ rescue StandardError => e
512
+ puts "Error: #{e.class}: #{e.message}"
513
+ puts e.backtrace.join("\n") if options[:verbose]
514
+ end