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,202 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Test with httpbin.org which is more reliable than OpenWeatherMap API
5
+ #
6
+ # Usage:
7
+ # ruby examples/advanced/auth/test_with_httpbin.rb
8
+
9
+ require 'bundler/setup'
10
+ require 'legate'
11
+ require 'legate/auth'
12
+ require 'json'
13
+ require 'excon'
14
+ require 'legate/session_service/in_memory'
15
+
16
+ # Enable debug mode
17
+ ENV['DEBUG'] = 'true'
18
+
19
+ puts 'Testing Legate::Auth::ExconMiddleware with httpbin.org'
20
+ puts '--------------------------------------------'
21
+
22
+ # Create session service and token store
23
+ session_service = Legate::SessionService::InMemory.new
24
+ token_store = Legate::Auth::TokenStore.new(session_service)
25
+
26
+ # Test API Key
27
+ API_KEY = 'test-api-key-123'
28
+
29
+ # Create the ApiKey scheme and credential
30
+ api_key_scheme = Legate::Auth::Schemes::ApiKey.new
31
+ api_key_credential = Legate::Auth::Credential.new(
32
+ auth_type: :api_key,
33
+ api_key: API_KEY,
34
+ location: 'query',
35
+ name: 'apikey'
36
+ )
37
+
38
+ puts "\nTest 1: Direct API call with Excon"
39
+ puts '-------------------------------'
40
+
41
+ begin
42
+ # Make a direct Excon request
43
+ url = "https://httpbin.org/get?test=value&apikey=#{API_KEY}"
44
+
45
+ puts "Making direct request to: #{url}"
46
+
47
+ # Create the connection with normal timeouts
48
+ response = Excon.get(url,
49
+ connect_timeout: 10,
50
+ read_timeout: 10,
51
+ write_timeout: 10)
52
+
53
+ puts "Response Status: #{response.status}"
54
+
55
+ if response.status == 200
56
+ data = JSON.parse(response.body)
57
+ puts "Data received: #{data['args'].inspect}"
58
+ else
59
+ puts "Error: #{response.status} - #{response.body}"
60
+ end
61
+ rescue StandardError => e
62
+ puts "Request Error: #{e.message}"
63
+ puts e.backtrace.join("\n") if ENV['DEBUG']
64
+ end
65
+
66
+ puts "\nTest 2: Fixed Legate::Auth::ExconMiddleware"
67
+ puts '------------------------------------'
68
+
69
+ begin
70
+ # Create our middleware instance directly
71
+ middleware = Legate::Auth::ExconMiddleware.new(
72
+ nil,
73
+ scheme: api_key_scheme,
74
+ credential: api_key_credential,
75
+ token_store: token_store
76
+ )
77
+
78
+ # Create a connection and add our middleware directly
79
+ connection = Excon.new('https://httpbin.org',
80
+ connect_timeout: 10,
81
+ read_timeout: 10,
82
+ write_timeout: 10)
83
+
84
+ # Get the default middleware stack
85
+ default_middlewares = connection.data[:middlewares].dup
86
+
87
+ # Append our middleware to the stack
88
+ connection.data[:middlewares] = default_middlewares + [Legate::Auth::ExconMiddleware]
89
+
90
+ # Store our middleware instance in the connection
91
+ connection.data[:auth_middleware] = middleware
92
+
93
+ puts 'Connection created with middleware'
94
+ puts 'Making request with Legate::Auth::ExconMiddleware...'
95
+
96
+ # Make the request
97
+ response = connection.request(
98
+ method: :get,
99
+ path: '/get',
100
+ query: {
101
+ test: 'value'
102
+ }
103
+ )
104
+
105
+ puts "Response Status: #{response.status}"
106
+
107
+ if response.status == 200
108
+ data = JSON.parse(response.body)
109
+ puts "Data received: #{data['args'].inspect}"
110
+ else
111
+ puts "Error: #{response.status} - #{response.body}"
112
+ end
113
+ rescue StandardError => e
114
+ puts "Request Error: #{e.message}"
115
+ puts e.backtrace.join("\n") if ENV['DEBUG']
116
+ end
117
+
118
+ puts "\nTest 3: Using HttpClientUtils.create_connection"
119
+ puts '-------------------------------------------'
120
+
121
+ begin
122
+ # Use the Legate::Auth::HttpClientUtils.create_connection method
123
+ puts 'Creating connection using HttpClientUtils...'
124
+
125
+ connection = Legate::Auth::HttpClientUtils.create_connection(
126
+ 'https://httpbin.org',
127
+ scheme: api_key_scheme,
128
+ credential: api_key_credential,
129
+ token_store: token_store,
130
+ connect_timeout: 10,
131
+ read_timeout: 10,
132
+ write_timeout: 10
133
+ )
134
+
135
+ puts 'Connection created using factory method'
136
+ puts 'Making request with connection from HttpClientUtils...'
137
+
138
+ # Make the request
139
+ response = connection.request(
140
+ method: :get,
141
+ path: '/get',
142
+ query: {
143
+ test: 'utils'
144
+ }
145
+ )
146
+
147
+ puts "Response Status: #{response.status}"
148
+
149
+ if response.status == 200
150
+ data = JSON.parse(response.body)
151
+ puts "Data received: #{data['args'].inspect}"
152
+ else
153
+ puts "Error: #{response.status} - #{response.body}"
154
+ end
155
+ rescue StandardError => e
156
+ puts "Request Error: #{e.message}"
157
+ puts e.backtrace.join("\n") if ENV['DEBUG']
158
+ end
159
+
160
+ puts "\nTest 4: Using convenience method for API Key"
161
+ puts '-----------------------------------------'
162
+
163
+ begin
164
+ # Use the convenience method
165
+ puts 'Creating connection with convenience method...'
166
+
167
+ connection = Legate::Auth.create_api_key_connection(
168
+ 'https://httpbin.org',
169
+ api_key: API_KEY,
170
+ location: :query,
171
+ name: 'apikey',
172
+ token_store: token_store,
173
+ connect_timeout: 10,
174
+ read_timeout: 10,
175
+ write_timeout: 10
176
+ )
177
+
178
+ puts 'Connection created with convenience method'
179
+ puts 'Making request with middleware...'
180
+
181
+ response = connection.request(
182
+ method: :get,
183
+ path: '/get',
184
+ query: {
185
+ test: 'convenience'
186
+ }
187
+ )
188
+
189
+ puts "Response Status: #{response.status}"
190
+
191
+ if response.status == 200
192
+ data = JSON.parse(response.body)
193
+ puts "Data received: #{data['args'].inspect}"
194
+ else
195
+ puts "Error: #{response.status} - #{response.body}"
196
+ end
197
+ rescue StandardError => e
198
+ puts "Request Error: #{e.message}"
199
+ puts e.backtrace.join("\n") if ENV['DEBUG']
200
+ end
201
+
202
+ puts "\nTest complete."
@@ -0,0 +1,428 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Token Lifecycle Management Example
5
+ #
6
+ # This example demonstrates comprehensive token lifecycle management including:
7
+ # - Token acquisition and storage
8
+ # - Automatic token refresh on expiration
9
+ # - Token invalidation and error handling
10
+ # - Manual token management operations
11
+ # - Integration with different authentication schemes
12
+ #
13
+ # Usage:
14
+ # ruby examples/advanced/auth/token_lifecycle_example.rb [--scheme oauth2|service_account] [--demo-mode]
15
+
16
+ require 'bundler/setup'
17
+ require 'legate'
18
+ require 'legate/auth'
19
+ require 'legate/auth/token_manager'
20
+ require 'legate/auth/token_store'
21
+ require 'optparse'
22
+ require 'json'
23
+ require 'time'
24
+
25
+ # Helper method to get auth-related keys from the session service
26
+ def get_auth_keys(session_service)
27
+ if session_service.respond_to?(:scoped_states)
28
+ # For InMemory session service, check the scoped_states directly
29
+ session_service.scoped_states.keys.select { |key| key.start_with?('auth:') }
30
+
31
+ elsif session_service.respond_to?(:keys)
32
+ # For Redis-based session service
33
+ session_service.keys('auth:*')
34
+ else
35
+ []
36
+ end
37
+ end
38
+
39
+ # Parse command line options
40
+ options = {
41
+ scheme: 'oauth2',
42
+ demo_mode: false,
43
+ verbose: false
44
+ }
45
+
46
+ OptionParser.new do |opts|
47
+ opts.banner = "Usage: ruby #{__FILE__} [options]"
48
+
49
+ opts.on('--scheme SCHEME', %w[oauth2 service_account], 'Authentication scheme to demonstrate (oauth2, service_account)') do |scheme|
50
+ options[:scheme] = scheme
51
+ end
52
+
53
+ opts.on('--demo-mode', 'Run in demo mode with simulated tokens') do
54
+ options[:demo_mode] = true
55
+ end
56
+
57
+ opts.on('--verbose', 'Enable verbose output') do
58
+ options[:verbose] = true
59
+ end
60
+
61
+ opts.on('--help', 'Show this help') do
62
+ puts opts
63
+ exit
64
+ end
65
+ end.parse!
66
+
67
+ puts '=== Token Lifecycle Management Example ==='
68
+ puts "Scheme: #{options[:scheme]}"
69
+ puts "Demo Mode: #{options[:demo_mode] ? 'enabled' : 'disabled'}"
70
+ puts
71
+
72
+ # Setup session service for token storage
73
+ session_service = Legate::SessionService::InMemory.new
74
+ puts '✓ Session service initialized'
75
+
76
+ # Create token store for persistent token management
77
+ token_store = Legate::Auth::TokenStore.new(session_service)
78
+ puts '✓ Token store created'
79
+
80
+ # Create token manager for automatic lifecycle management
81
+ token_manager = Legate::Auth::TokenManager.new(token_store)
82
+ puts '✓ Token manager initialized'
83
+
84
+ # Configure callbacks for token lifecycle events
85
+ token_manager.on(:before_expiry) do |event|
86
+ puts "⏰ Token approaching expiration: #{event[:token]&.access_token&.[](0..15)}..."
87
+ end
88
+
89
+ token_manager.on(:refresh_success) do |event|
90
+ expires_in = event[:token]&.expires_at ? (event[:token].expires_at - Time.now).to_i : 0
91
+ puts "🔄 Token refreshed successfully: #{event[:token]&.access_token&.[](0..15)}... (expires in #{expires_in} seconds)"
92
+ end
93
+
94
+ token_manager.on(:refresh_failure) do |event|
95
+ puts "⚠️ Token refresh failed: #{event[:error]&.message || 'Unknown error'}"
96
+ end
97
+
98
+ token_manager.on(:invalidated) do |event|
99
+ puts "❌ Token invalidated: #{event[:cache_key]}"
100
+ end
101
+
102
+ puts '✓ Token lifecycle callbacks configured'
103
+
104
+ # Helper method to create credentials and schemes
105
+ def create_auth_components(scheme_type, demo_mode: false)
106
+ case scheme_type
107
+ when 'oauth2'
108
+ scheme = if demo_mode
109
+ # Create a mock OAuth2 scheme for demo
110
+ Legate::Auth::Schemes::OAuth2.new(
111
+ authorization_url: 'https://example.com/oauth/authorize',
112
+ token_url: 'https://example.com/oauth/token',
113
+ scopes: %w[read write]
114
+ )
115
+ else
116
+ Legate::Auth::Schemes::OAuth2.new(
117
+ authorization_url: ENV['OAUTH_AUTH_URL'] || 'https://accounts.google.com/o/oauth2/auth',
118
+ token_url: ENV['OAUTH_TOKEN_URL'] || 'https://oauth2.googleapis.com/token',
119
+ scopes: %w[email profile]
120
+ )
121
+ end
122
+
123
+ credential = Legate::Auth::Credential.new(
124
+ auth_type: :oauth2,
125
+ client_id: ENV['OAUTH_CLIENT_ID'] || 'demo-client-id',
126
+ client_secret: ENV['OAUTH_CLIENT_SECRET'] || 'demo-client-secret'
127
+ )
128
+
129
+ [scheme, credential]
130
+
131
+ when 'service_account'
132
+ # Set test environment for demo mode to skip full validation
133
+ ENV['RSPEC_ENV'] = 'test' if demo_mode
134
+
135
+ scheme = Legate::Auth::Schemes::GoogleServiceAccount.new(
136
+ scopes: ['https://www.googleapis.com/auth/cloud-platform']
137
+ )
138
+
139
+ if demo_mode
140
+ # Create demo service account credential
141
+ demo_key = {
142
+ 'type' => 'service_account',
143
+ 'project_id' => 'demo-project',
144
+ 'private_key_id' => 'demo-key-id',
145
+ 'private_key' => "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...\n-----END PRIVATE KEY-----\n",
146
+ 'client_email' => 'demo@demo-project.iam.gserviceaccount.com',
147
+ 'client_id' => '123456789',
148
+ 'auth_uri' => 'https://accounts.google.com/o/oauth2/auth',
149
+ 'token_uri' => 'https://oauth2.googleapis.com/token'
150
+ }
151
+
152
+ credential = Legate::Auth::Credential.new(
153
+ auth_type: :google_service_account,
154
+ service_account_key: demo_key.to_json,
155
+ client_email: demo_key['client_email']
156
+ )
157
+ else
158
+ credential = Legate::Auth::Credential.new(
159
+ auth_type: :google_service_account,
160
+ service_account_key: ENV['SERVICE_ACCOUNT_KEY'] || File.read(ENV['SERVICE_ACCOUNT_KEY_FILE'])
161
+ )
162
+ end
163
+
164
+ [scheme, credential]
165
+
166
+ else
167
+ raise ArgumentError, "Unsupported scheme: #{scheme_type}"
168
+ end
169
+ end
170
+
171
+ # Helper method to create a demo token for testing
172
+ def create_demo_token(scheme_type)
173
+ case scheme_type
174
+ when 'oauth2'
175
+ Legate::Auth::ExchangedCredential.new(
176
+ auth_type: :oauth2,
177
+ access_token: 'demo_access_token_' + SecureRandom.hex(8),
178
+ refresh_token: 'demo_refresh_token_' + SecureRandom.hex(8),
179
+ token_type: 'Bearer',
180
+ expires_at: Time.now + 30, # Expires in 30 seconds for demo
181
+ scope: 'email profile'
182
+ )
183
+ when 'service_account'
184
+ Legate::Auth::ExchangedCredential.new(
185
+ auth_type: :google_service_account,
186
+ access_token: 'demo_sa_token_' + SecureRandom.hex(8),
187
+ token_type: 'Bearer',
188
+ expires_at: Time.now + 45, # Expires in 45 seconds for demo
189
+ scope: 'https://www.googleapis.com/auth/cloud-platform'
190
+ )
191
+ end
192
+ end
193
+
194
+ # Create authentication components
195
+ begin
196
+ scheme, credential = create_auth_components(options[:scheme], demo_mode: options[:demo_mode])
197
+ puts '✓ Authentication scheme and credential created'
198
+ rescue StandardError => e
199
+ puts "❌ Failed to create authentication components: #{e.message}"
200
+ puts "\nTip: Use --demo-mode to run with simulated credentials" unless options[:demo_mode]
201
+ exit 1
202
+ end
203
+
204
+ # Token lifecycle demonstration
205
+ puts "\n=== Token Lifecycle Demonstration ==="
206
+
207
+ token_key = "#{options[:scheme]}_demo_token"
208
+
209
+ # 1. Initial token acquisition
210
+ puts "\n1. Initial Token Acquisition"
211
+ puts '─' * 40
212
+
213
+ if options[:demo_mode]
214
+ # In demo mode, simulate getting a token
215
+ demo_token = create_demo_token(options[:scheme])
216
+ token_store.store(token_key, demo_token)
217
+ current_token = demo_token
218
+ puts '✓ Demo token created and stored'
219
+ else
220
+ # In real mode, attempt to get a token through the token manager
221
+ puts 'Attempting to acquire token through token manager...'
222
+ current_token = token_manager.get_token(scheme, credential)
223
+
224
+ if current_token
225
+ puts '✓ Token acquired successfully'
226
+ else
227
+ puts '⚠️ Token acquisition failed (this is expected for interactive flows in CLI)'
228
+ puts 'Creating a demo token for lifecycle demonstration...'
229
+ current_token = create_demo_token(options[:scheme])
230
+ token_store.store(token_key, current_token)
231
+ end
232
+ end
233
+
234
+ # Display token information
235
+ if current_token
236
+ puts "\nToken Information:"
237
+ puts " Type: #{current_token.token_type}"
238
+ puts " Access Token: #{current_token.access_token[0..15]}..."
239
+ puts " Expires At: #{current_token.expires_at}"
240
+ puts " Expires In: #{(current_token.expires_at - Time.now).to_i} seconds"
241
+ puts " Scope: #{current_token[:scope]}" if current_token[:scope]
242
+ puts " Has Refresh Token: #{current_token.refresh_token ? 'Yes' : 'No'}"
243
+ end
244
+
245
+ # 2. Token retrieval from storage
246
+ puts "\n2. Token Retrieval from Storage"
247
+ puts '─' * 40
248
+
249
+ retrieved_token = token_store.get(token_key)
250
+ if retrieved_token
251
+ puts '✓ Token successfully retrieved from storage'
252
+ puts " Retrieved token matches stored token: #{current_token.access_token == retrieved_token.access_token}"
253
+ else
254
+ puts '❌ Failed to retrieve token from storage'
255
+ end
256
+
257
+ # 3. Token expiration checking
258
+ puts "\n3. Token Expiration Checking"
259
+ puts '─' * 40
260
+
261
+ expires_in_seconds = (current_token.expires_at - Time.now).to_i
262
+
263
+ puts "Current time: #{Time.now}"
264
+ puts "Token expires at: #{current_token.expires_at}"
265
+ puts "Token expired?: #{current_token.expired?}"
266
+ puts "Token expires in: #{expires_in_seconds} seconds"
267
+
268
+ if expires_in_seconds > 0
269
+ puts 'Token is currently valid'
270
+ else
271
+ puts 'Token has already expired'
272
+ end
273
+
274
+ # 4. Automatic token refresh demonstration
275
+ puts "\n4. Automatic Token Refresh"
276
+ puts '─' * 40
277
+
278
+ if current_token.refresh_token && !options[:demo_mode]
279
+ puts 'Token has refresh token - attempting refresh...'
280
+
281
+ begin
282
+ refreshed_token = token_manager.refresh_token(scheme, credential, current_token)
283
+ if refreshed_token
284
+ puts '✓ Token refreshed successfully'
285
+ puts " New access token: #{refreshed_token.access_token[0..15]}..."
286
+ puts " New expiry: #{refreshed_token.expires_at}"
287
+ current_token = refreshed_token
288
+ else
289
+ puts '⚠️ Token refresh returned nil'
290
+ end
291
+ rescue StandardError => e
292
+ puts "❌ Token refresh failed: #{e.message}"
293
+ end
294
+ else
295
+ puts 'Token refresh not available (no refresh token or demo mode)'
296
+
297
+ if options[:demo_mode]
298
+ puts 'Simulating token refresh in demo mode...'
299
+ refreshed_demo = create_demo_token(options[:scheme])
300
+ token_store.store(token_key, refreshed_demo)
301
+ puts "✓ Demo token 'refreshed' (new token created)"
302
+ current_token = refreshed_demo
303
+ end
304
+ end
305
+
306
+ # 5. Waiting for expiration (demo)
307
+ current_expires_in = (current_token.expires_at - Time.now).to_i
308
+ if options[:demo_mode] && current_expires_in > 0 && current_expires_in < 60
309
+ puts "\n5. Waiting for Token Expiration (Demo)"
310
+ puts '─' * 40
311
+
312
+ puts "Token expires in #{current_expires_in} seconds"
313
+ puts 'Waiting for expiration... (press Ctrl+C to skip)'
314
+
315
+ begin
316
+ sleep_time = [current_expires_in + 1, 5].min # Max 5 seconds wait
317
+ sleep(sleep_time)
318
+
319
+ puts 'Checking token status after wait...'
320
+ puts "Token expired?: #{current_token.expired?}"
321
+
322
+ # Demonstrate automatic refresh on next access
323
+ puts 'Attempting to get token (should trigger refresh if expired)...'
324
+ fresh_token = token_manager.get_token(scheme, credential, force_refresh: current_token.expired?)
325
+
326
+ puts '✓ Automatic refresh triggered on expired token access' if fresh_token && fresh_token.access_token != current_token.access_token
327
+ rescue Interrupt
328
+ puts "\n⏭️ Skipped expiration wait"
329
+ end
330
+ end
331
+
332
+ # 6. Manual token operations
333
+ puts "\n6. Manual Token Operations"
334
+ puts '─' * 40
335
+
336
+ # Force refresh
337
+ puts 'Force refreshing token...'
338
+ if options[:demo_mode]
339
+ new_demo_token = create_demo_token(options[:scheme])
340
+ token_store.store(token_key, new_demo_token)
341
+ puts '✓ Demo token force refreshed'
342
+ else
343
+ force_refreshed = token_manager.get_token(scheme, credential, force_refresh: true)
344
+ if force_refreshed
345
+ puts '✓ Token force refreshed'
346
+ else
347
+ puts '⚠️ Force refresh failed'
348
+ end
349
+ end
350
+
351
+ # Check for multiple tokens
352
+ puts "\nChecking all stored tokens..."
353
+ all_tokens = get_auth_keys(session_service)
354
+ puts "Found #{all_tokens.length} authentication-related keys in storage:"
355
+ all_tokens.each { |key| puts " - #{key}" }
356
+
357
+ # 7. Token invalidation
358
+ puts "\n7. Token Invalidation"
359
+ puts '─' * 40
360
+
361
+ puts 'Invalidating token...'
362
+ # Generate the proper cache key for the token manager
363
+ require_relative '../../../lib/legate/auth/tool_integration'
364
+ cache_key = Legate::Auth::ToolIntegration.generate_cache_key(scheme, credential)
365
+ token_manager.invalidate_token(cache_key)
366
+
367
+ # Verify invalidation
368
+ invalidated_token = token_store.get(token_key)
369
+ if invalidated_token.nil?
370
+ puts '✓ Token successfully invalidated and removed from storage'
371
+ else
372
+ puts '⚠️ Token still exists in storage after invalidation'
373
+ end
374
+
375
+ # 8. Error handling demonstration
376
+ puts "\n8. Error Handling"
377
+ puts '─' * 40
378
+
379
+ puts 'Attempting to get invalidated token...'
380
+ result = token_manager.get_token(scheme, credential)
381
+ if result.nil?
382
+ puts '✓ Correctly returned nil for invalidated token'
383
+ else
384
+ puts "⚠️ Unexpectedly returned a token: #{result.access_token[0..15]}..."
385
+ end
386
+
387
+ # Try token operations on non-existent token
388
+ puts 'Attempting to refresh non-existent token...'
389
+ begin
390
+ fake_token = create_demo_token(options[:scheme])
391
+ fake_token.instance_variable_set(:@access_token, 'invalid_token')
392
+ refresh_result = token_manager.refresh_token(scheme, credential, fake_token)
393
+ if refresh_result
394
+ puts '⚠️ Unexpectedly succeeded refreshing invalid token'
395
+ else
396
+ puts '✓ Correctly failed to refresh invalid token'
397
+ end
398
+ rescue StandardError => e
399
+ puts "✓ Correctly raised error for invalid token refresh: #{e.class}"
400
+ end
401
+
402
+ # 9. Cleanup demonstration
403
+ puts "\n9. Cleanup"
404
+ puts '─' * 40
405
+
406
+ puts 'Clearing all tokens...'
407
+ token_store.clear_all
408
+ remaining_tokens = get_auth_keys(session_service)
409
+ puts "Remaining auth tokens after cleanup: #{remaining_tokens.length}"
410
+
411
+ if remaining_tokens.empty?
412
+ puts '✓ All tokens successfully cleared'
413
+ else
414
+ puts "⚠️ Some tokens remain: #{remaining_tokens}"
415
+ end
416
+
417
+ puts "\n=== Token Lifecycle Demonstration Complete ==="
418
+ puts "\nKey concepts demonstrated:"
419
+ puts '• Token acquisition and storage'
420
+ puts '• Automatic expiration detection'
421
+ puts '• Token refresh mechanisms'
422
+ puts '• Manual token operations'
423
+ puts '• Error handling and recovery'
424
+ puts '• Token invalidation and cleanup'
425
+ puts '• Event-based lifecycle callbacks'
426
+ puts "\nThis example shows how the Legate authentication system handles"
427
+ puts 'the complete token lifecycle automatically, with manual override'
428
+ puts 'capabilities when needed.'