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,317 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example of using the Excon middleware for authentication
5
+ #
6
+ # This example demonstrates how to use the Legate::Auth::ExconMiddleware to automatically
7
+ # handle authentication for HTTP requests, including automatic token refresh and retry.
8
+ #
9
+ # Usage:
10
+ # ruby examples/advanced/auth/excon_middleware.rb
11
+
12
+ require 'bundler/setup'
13
+ require 'legate'
14
+ require 'json'
15
+ require 'excon'
16
+
17
+ puts 'Legate::Auth Excon Middleware Example'
18
+ puts '--------------------------------'
19
+
20
+ # Create a basic token store
21
+ token_store = Legate::Auth::TokenStore.new
22
+
23
+ # Enable mock mode for Excon to simulate HTTP requests
24
+ Excon.defaults[:mock] = true
25
+
26
+ # 1. Example with API Key Authentication
27
+ puts "\n1. API Key Authentication Example:"
28
+
29
+ # Set up our mock response for API Key authentication
30
+ Excon.stub({}) do |params|
31
+ if params[:headers] && params[:headers]['X-API-Key'] == 'test-api-key'
32
+ {
33
+ status: 200,
34
+ body: JSON.generate({
35
+ headers: params[:headers]
36
+ })
37
+ }
38
+ else
39
+ { status: 401, body: '{"error": "Unauthorized"}' }
40
+ end
41
+ end
42
+
43
+ # Create a middleware using the factory
44
+ api_key_middleware = Legate::Auth::MiddlewareFactory.create_api_key(
45
+ api_key: 'test-api-key',
46
+ location: :header,
47
+ name: 'X-API-Key',
48
+ token_store: token_store
49
+ )
50
+
51
+ # Create a connection with the middleware properly configured
52
+ connection = Excon.new('https://httpbin.org')
53
+
54
+ # Add our middleware to the stack if not already present
55
+ connection.data[:middlewares] = connection.data[:middlewares].dup
56
+ connection.data[:middlewares] << Legate::Auth::ExconMiddleware unless connection.data[:middlewares].include?(Legate::Auth::ExconMiddleware)
57
+
58
+ # Set our configured middleware instance
59
+ connection.data[:auth_middleware] = api_key_middleware
60
+
61
+ # Make a request - the middleware will automatically add the API key header
62
+ begin
63
+ response = connection.request(method: :get, path: '/headers')
64
+
65
+ puts 'Request Headers:'
66
+ headers = JSON.parse(response.body)['headers']
67
+ puts " X-API-Key: #{headers['X-Api-Key'] || headers['X-API-Key']}"
68
+ rescue StandardError => e
69
+ puts "Error: #{e.message}"
70
+ end
71
+
72
+ # 2. Example with Bearer Token Authentication
73
+ puts "\n2. Bearer Token Authentication Example:"
74
+
75
+ # Set up our mock response for Bearer authentication
76
+ Excon.stub({}) do |params|
77
+ if params[:headers] && params[:headers]['Authorization'] == 'Bearer test-bearer-token'
78
+ {
79
+ status: 200,
80
+ body: JSON.generate({
81
+ headers: params[:headers]
82
+ })
83
+ }
84
+ else
85
+ { status: 401, body: '{"error": "Unauthorized"}' }
86
+ end
87
+ end
88
+
89
+ # Create a connection with bearer token middleware
90
+ connection = Excon.new('https://httpbin.org')
91
+ bearer_middleware = Legate::Auth::MiddlewareFactory.create_bearer(
92
+ token: 'test-bearer-token',
93
+ token_store: token_store
94
+ )
95
+
96
+ # Configure the connection to use our middleware
97
+ connection.data[:middlewares] = connection.data[:middlewares].dup
98
+ connection.data[:middlewares] << Legate::Auth::ExconMiddleware unless connection.data[:middlewares].include?(Legate::Auth::ExconMiddleware)
99
+ connection.data[:auth_middleware] = bearer_middleware
100
+
101
+ # Make a request with bearer authentication
102
+ begin
103
+ response = connection.request(method: :get, path: '/headers')
104
+
105
+ puts 'Request Headers:'
106
+ headers = JSON.parse(response.body)['headers']
107
+ puts " Authorization: #{headers['Authorization']}"
108
+ rescue StandardError => e
109
+ puts "Error: #{e.message}"
110
+ end
111
+
112
+ # 3. Example with OAuth2 Client Credentials (simulation)
113
+ puts "\n3. OAuth2 Client Credentials Example (Simulated):"
114
+
115
+ # In a real scenario, this would communicate with an actual OAuth2 server
116
+ # For this example, we'll use a mock
117
+ class MockOAuth2Scheme < Legate::Auth::Scheme
118
+ def scheme_type
119
+ :oauth2
120
+ end
121
+
122
+ def supports_exchange?
123
+ true
124
+ end
125
+
126
+ def supports_refresh?
127
+ true
128
+ end
129
+
130
+ def exchange_token(_credential)
131
+ # Simulate token exchange
132
+ Legate::Auth::ExchangedCredential.new(
133
+ auth_type: :oauth2,
134
+ access_token: "mock-access-token-#{rand(1000)}",
135
+ refresh_token: "mock-refresh-token-#{rand(1000)}",
136
+ expires_at: Time.now + 3600,
137
+ scope: 'read write'
138
+ )
139
+ end
140
+
141
+ def refresh_token(token, _credential)
142
+ # Simulate token refresh
143
+ Legate::Auth::ExchangedCredential.new(
144
+ auth_type: :oauth2,
145
+ access_token: "mock-refreshed-token-#{rand(1000)}",
146
+ refresh_token: token.refresh_token,
147
+ expires_at: Time.now + 3600,
148
+ scope: token.scope
149
+ )
150
+ end
151
+
152
+ def apply_to_request(request, credential)
153
+ # Apply the token to the request
154
+ request[:headers] ||= {}
155
+
156
+ request[:headers]['Authorization'] = "Bearer #{credential.access_token}" if credential.is_a?(Legate::Auth::ExchangedCredential) && credential.access_token
157
+
158
+ request
159
+ end
160
+ end
161
+
162
+ # Set up stub for OAuth2 authentication to capture any Bearer token
163
+ Excon.stub({}) do |params|
164
+ if params[:headers] && params[:headers]['Authorization'] && params[:headers]['Authorization'].start_with?('Bearer ')
165
+ {
166
+ status: 200,
167
+ body: JSON.generate({
168
+ headers: params[:headers]
169
+ })
170
+ }
171
+ else
172
+ { status: 401, body: '{"error": "Unauthorized"}' }
173
+ end
174
+ end
175
+
176
+ begin
177
+ # Create the scheme and credential
178
+ oauth2_scheme = MockOAuth2Scheme.new
179
+ oauth2_credential = Legate::Auth::Credential.new(
180
+ auth_type: :oauth2,
181
+ client_id: 'test-client-id',
182
+ client_secret: 'test-client-secret'
183
+ )
184
+
185
+ # Create a connection
186
+ connection = Excon.new('https://httpbin.org')
187
+
188
+ # Create the middleware instance
189
+ oauth2_middleware = Legate::Auth::ExconMiddleware.new(
190
+ nil,
191
+ scheme: oauth2_scheme,
192
+ credential: oauth2_credential,
193
+ token_store: token_store,
194
+ auto_retry: true,
195
+ max_retries: 2,
196
+ backoff_strategy: :exponential,
197
+ backoff_factor: 0.5
198
+ )
199
+
200
+ # Properly configure the connection
201
+ connection.data[:middlewares] = connection.data[:middlewares].dup
202
+ connection.data[:middlewares] << Legate::Auth::ExconMiddleware unless connection.data[:middlewares].include?(Legate::Auth::ExconMiddleware)
203
+ connection.data[:auth_middleware] = oauth2_middleware
204
+
205
+ # Make a request - middleware will handle token exchange and apply the token
206
+ response = connection.request(method: :get, path: '/headers')
207
+
208
+ puts 'Request Headers:'
209
+ headers = JSON.parse(response.body)['headers']
210
+ puts " Authorization: #{headers['Authorization']}"
211
+
212
+ # Demonstrate token refresh by forcing another request
213
+ # In a real scenario, this would happen automatically when a token expires
214
+ puts "\nForcing token refresh and making another request:"
215
+ token = token_store.get(Legate::Auth::ToolIntegration.generate_cache_key(oauth2_scheme, oauth2_credential))
216
+ token_store.clear(Legate::Auth::ToolIntegration.generate_cache_key(oauth2_scheme, oauth2_credential))
217
+
218
+ # Make another request - middleware should get a new token
219
+ response = connection.request(method: :get, path: '/headers')
220
+
221
+ puts 'Request Headers (after refresh):'
222
+ headers = JSON.parse(response.body)['headers']
223
+ puts " Authorization: #{headers['Authorization']}"
224
+ rescue StandardError => e
225
+ puts "Error: #{e.message}"
226
+ end
227
+
228
+ # 4. Example with automatic retry on auth failure (simulation)
229
+ puts "\n4. Authentication Failure and Retry Example (Simulated):"
230
+
231
+ class MockRetryAuthScheme < Legate::Auth::Scheme
232
+ def initialize
233
+ @fail_count = 1 # Fail the first request, then succeed
234
+ end
235
+
236
+ def scheme_type
237
+ :custom
238
+ end
239
+
240
+ def apply_to_request(request, _credential)
241
+ request[:headers] ||= {}
242
+
243
+ if @fail_count > 0
244
+ @fail_count -= 1
245
+ # This will cause a 401 response
246
+ request[:headers]['X-Should-Fail'] = 'true'
247
+ else
248
+ # This will succeed
249
+ request[:headers]['X-Auth-Success'] = 'true'
250
+ end
251
+
252
+ request
253
+ end
254
+ end
255
+
256
+ begin
257
+ # Set up stubs for retry test
258
+ Excon.stub({}) do |params|
259
+ if params[:headers] && params[:headers]['X-Should-Fail'] == 'true'
260
+ # Simulate an authentication failure
261
+ {
262
+ status: 401,
263
+ body: '{"error": "Unauthorized"}'
264
+ }
265
+ elsif params[:headers] && params[:headers]['X-Auth-Success'] == 'true'
266
+ # Simulate success after retry
267
+ {
268
+ status: 200,
269
+ body: JSON.generate({
270
+ status: 'success',
271
+ headers: params[:headers]
272
+ })
273
+ }
274
+ else
275
+ # Default response
276
+ {
277
+ status: 400,
278
+ body: '{"error": "Bad Request"}'
279
+ }
280
+ end
281
+ end
282
+
283
+ # Create a simple middleware for the retry test
284
+ retry_scheme = MockRetryAuthScheme.new
285
+ retry_credential = Legate::Auth::Credential.new(auth_type: :custom)
286
+
287
+ # Create a middleware that will automatically retry on auth failure
288
+ retry_middleware = Legate::Auth::ExconMiddleware.new(
289
+ nil,
290
+ scheme: retry_scheme,
291
+ credential: retry_credential,
292
+ auto_retry: true,
293
+ max_retries: 1
294
+ )
295
+
296
+ # Set up a connection with our middleware
297
+ connection = Excon.new('https://httpbin.org')
298
+ connection.data[:middlewares] = connection.data[:middlewares].dup
299
+ connection.data[:middlewares] << Legate::Auth::ExconMiddleware unless connection.data[:middlewares].include?(Legate::Auth::ExconMiddleware)
300
+ connection.data[:auth_middleware] = retry_middleware
301
+
302
+ # Make a request that will fail and then retry automatically
303
+ puts 'Making request that will fail authentication and automatically retry:'
304
+ response = connection.request(method: :get, path: '/anything')
305
+
306
+ response_data = JSON.parse(response.body)
307
+ puts "Final response status: #{response.status}"
308
+ puts "Response contains X-Auth-Success header: #{!response_data['headers']['X-Auth-Success'].nil?}"
309
+
310
+ # Clean up stubs
311
+ Excon.stubs.clear
312
+ rescue StandardError => e
313
+ puts "Error: #{e.message}"
314
+ Excon.stubs.clear
315
+ end
316
+
317
+ puts "\nExample complete."
@@ -0,0 +1,399 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Enhanced Example of using the Excon middleware for authentication
5
+ #
6
+ # This example demonstrates how to use the Legate::Auth::ExconMiddleware with various
7
+ # authentication schemes, including automatic token refresh, retry functionality,
8
+ # and configurable backoff strategies.
9
+ #
10
+ # Usage:
11
+ # ruby examples/advanced/auth/excon_middleware_auth.rb
12
+
13
+ require 'bundler/setup'
14
+ require 'legate'
15
+ require 'json'
16
+ require 'excon'
17
+
18
+ puts 'Legate::Auth Enhanced Excon Middleware Example'
19
+ puts '----------------------------------------'
20
+
21
+ # Initialize a session service and token store
22
+ session_service = Legate::SessionService::Memory.new
23
+ token_store = Legate::Auth::TokenStore.new(session_service)
24
+
25
+ # 1. Example with API Key Authentication
26
+ puts "\n1. API Key Authentication Example:"
27
+
28
+ begin
29
+ # Create a connection using the convenience method
30
+ connection = Legate::Auth.create_api_key_connection(
31
+ 'https://httpbin.org',
32
+ api_key: 'test-api-key-1234',
33
+ location: 'header',
34
+ name: 'X-API-Key',
35
+ token_store: token_store,
36
+ auto_retry: true,
37
+ max_retries: 2,
38
+ backoff_strategy: :exponential,
39
+ backoff_factor: 0.5
40
+ )
41
+
42
+ # Make sure the middleware is added to the connection's middlewares
43
+ connection.data[:middlewares] << Legate::Auth::ExconMiddleware unless connection.data[:middlewares].include?(Legate::Auth::ExconMiddleware)
44
+
45
+ # Make a request - the middleware will automatically add the API key header
46
+ response = connection.request(method: :get, path: '/headers')
47
+
48
+ puts 'Request Headers:'
49
+ headers = JSON.parse(response.body)['headers']
50
+ puts " X-API-Key: #{headers['X-Api-Key']}"
51
+ rescue StandardError => e
52
+ puts "Error: #{e.message}"
53
+ end
54
+
55
+ # 2. Example with Bearer Token Authentication
56
+ puts "\n2. Bearer Token Authentication Example:"
57
+
58
+ begin
59
+ # Create a connection using the convenience method
60
+ connection = Legate::Auth.create_bearer_connection(
61
+ 'https://httpbin.org',
62
+ token: 'test-bearer-token-5678',
63
+ token_store: token_store,
64
+ auto_retry: true,
65
+ max_retries: 2,
66
+ backoff_strategy: :jitter, # Use jitter backoff to prevent thundering herd
67
+ backoff_factor: 0.5
68
+ )
69
+
70
+ # Make sure the middleware is added to the connection's middlewares
71
+ connection.data[:middlewares] << Legate::Auth::ExconMiddleware unless connection.data[:middlewares].include?(Legate::Auth::ExconMiddleware)
72
+
73
+ # Make a request - the middleware will automatically add the Bearer token
74
+ response = connection.request(method: :get, path: '/headers')
75
+
76
+ puts 'Request Headers:'
77
+ headers = JSON.parse(response.body)['headers']
78
+ puts " Authorization: #{headers['Authorization']}"
79
+ rescue StandardError => e
80
+ puts "Error: #{e.message}"
81
+ end
82
+
83
+ # 3. Example with Basic Authentication
84
+ puts "\n3. Basic Authentication Example:"
85
+
86
+ begin
87
+ # Create a connection using the convenience method
88
+ connection = Legate::Auth.create_basic_auth_connection(
89
+ 'https://httpbin.org',
90
+ username: 'testuser',
91
+ password: 'testpassword',
92
+ token_store: token_store,
93
+ auto_retry: true,
94
+ max_retries: 3,
95
+ backoff_strategy: :linear,
96
+ backoff_factor: 1.0
97
+ )
98
+
99
+ # Make sure the middleware is added to the connection's middlewares
100
+ connection.data[:middlewares] << Legate::Auth::ExconMiddleware unless connection.data[:middlewares].include?(Legate::Auth::ExconMiddleware)
101
+
102
+ # Make a request - the middleware will automatically add the Basic Auth header
103
+ response = connection.request(method: :get, path: '/headers')
104
+
105
+ puts 'Request Headers:'
106
+ headers = JSON.parse(response.body)['headers']
107
+ puts " Authorization: #{headers['Authorization']}"
108
+ rescue StandardError => e
109
+ puts "Error: #{e.message}"
110
+ end
111
+
112
+ # 4. Example with OAuth2 Client Credentials (simulation)
113
+ puts "\n4. OAuth2 Client Credentials Example (Simulated):"
114
+
115
+ # In a real scenario, this would communicate with an actual OAuth2 server
116
+ # For this example, we'll use a mock
117
+ class MockOAuth2Scheme < Legate::Auth::Scheme
118
+ def scheme_type
119
+ :oauth2
120
+ end
121
+
122
+ def supports_exchange?
123
+ true
124
+ end
125
+
126
+ def supports_refresh?
127
+ true
128
+ end
129
+
130
+ def exchange_token(_credential)
131
+ # Simulate token exchange
132
+ Legate::Auth::ExchangedCredential.new(
133
+ auth_type: :oauth2,
134
+ access_token: "mock-access-token-#{rand(1000)}",
135
+ refresh_token: "mock-refresh-token-#{rand(1000)}",
136
+ expires_at: Time.now + 10, # Short expiry for testing refresh
137
+ scope: 'read write'
138
+ )
139
+ end
140
+
141
+ def refresh_token(token, _credential)
142
+ # Simulate token refresh
143
+ Legate::Auth::ExchangedCredential.new(
144
+ auth_type: :oauth2,
145
+ access_token: "mock-refreshed-token-#{rand(1000)}",
146
+ refresh_token: token.refresh_token,
147
+ expires_at: Time.now + 3600,
148
+ scope: token.scope
149
+ )
150
+ end
151
+
152
+ def apply_to_request(request, credential)
153
+ # Apply the token to the request
154
+ request[:headers] ||= {}
155
+
156
+ if credential.is_a?(Legate::Auth::ExchangedCredential) && credential.access_token
157
+ request[:headers]['Authorization'] = "Bearer #{credential.access_token}"
158
+ elsif credential[:client_id] && credential[:client_secret]
159
+ # If we don't have a token yet but have client credentials, we could
160
+ # apply client auth here (in a real implementation)
161
+ basic_auth = Base64.strict_encode64("#{credential[:client_id]}:#{credential[:client_secret]}")
162
+ request[:headers]['Authorization'] = "Basic #{basic_auth}"
163
+ end
164
+
165
+ request
166
+ end
167
+ end
168
+
169
+ begin
170
+ # Create the scheme and credential
171
+ oauth2_scheme = MockOAuth2Scheme.new
172
+ oauth2_credential = Legate::Auth::Credential.new(
173
+ auth_type: :oauth2,
174
+ client_id: 'test-client-id',
175
+ client_secret: 'test-client-secret'
176
+ )
177
+
178
+ # Create a connection using the middleware factory
179
+ connection = Legate::Auth.create_connection(
180
+ 'https://httpbin.org',
181
+ scheme: oauth2_scheme,
182
+ credential: oauth2_credential,
183
+ token_store: token_store,
184
+ auto_retry: true,
185
+ max_retries: 2,
186
+ backoff_strategy: :exponential,
187
+ backoff_factor: 0.5
188
+ )
189
+
190
+ # Make sure the middleware is added to the connection's middlewares
191
+ connection.data[:middlewares] << Legate::Auth::ExconMiddleware unless connection.data[:middlewares].include?(Legate::Auth::ExconMiddleware)
192
+
193
+ # Make a request - middleware will handle token exchange and apply the token
194
+ response = connection.request(method: :get, path: '/headers')
195
+
196
+ puts 'Request Headers:'
197
+ headers = JSON.parse(response.body)['headers']
198
+ puts " Authorization: #{headers['Authorization']}"
199
+
200
+ # Wait for token to expire
201
+ puts "\nWaiting for token to expire (10 seconds)..."
202
+ sleep 11
203
+
204
+ # Make another request - middleware should refresh the token
205
+ response = connection.request(method: :get, path: '/headers')
206
+
207
+ puts 'Request Headers (after refresh):'
208
+ headers = JSON.parse(response.body)['headers']
209
+ puts " Authorization: #{headers['Authorization']}"
210
+ rescue StandardError => e
211
+ puts "Error: #{e.message}"
212
+ end
213
+
214
+ # 5. Example with automatic retry on auth failure (simulation)
215
+ puts "\n5. Authentication Failure and Retry Example (Simulated):"
216
+
217
+ class RetryCounterScheme < Legate::Auth::Scheme
218
+ def initialize
219
+ @attempts = 0
220
+ @max_failures = 2
221
+ end
222
+
223
+ def scheme_type
224
+ :custom
225
+ end
226
+
227
+ def apply_to_request(request, _credential)
228
+ request[:headers] ||= {}
229
+
230
+ @attempts += 1
231
+
232
+ if @attempts <= @max_failures
233
+ # This will cause a 401 response for the first N attempts
234
+ request[:headers]['X-Should-Fail'] = 'true'
235
+ request[:headers]['X-Attempt'] = @attempts.to_s
236
+ else
237
+ # This will succeed
238
+ request[:headers]['X-Auth-Success'] = 'true'
239
+ request[:headers]['X-Attempt'] = @attempts.to_s
240
+ end
241
+
242
+ request
243
+ end
244
+ end
245
+
246
+ begin
247
+ # Create a scheme that will fail a certain number of times
248
+ retry_scheme = RetryCounterScheme.new
249
+ retry_credential = Legate::Auth::Credential.new(auth_type: :custom)
250
+
251
+ # Create a middleware that will automatically retry on auth failure
252
+ retry_middleware = Legate::Auth::MiddlewareFactory.create(
253
+ scheme: retry_scheme,
254
+ credential: retry_credential,
255
+ auto_retry: true,
256
+ max_retries: 3,
257
+ backoff_strategy: :exponential,
258
+ backoff_factor: 0.1 # Small factor to make test run quickly
259
+ )
260
+
261
+ # Set up a connection with our middleware
262
+ connection = Excon.new('https://httpbin.org')
263
+ connection.data[:middlewares] ||= connection.data[:middlewares].dup
264
+ connection.data[:middlewares] << Legate::Auth::ExconMiddleware unless connection.data[:middlewares].include?(Legate::Auth::ExconMiddleware)
265
+ connection.data[:auth_middleware] = retry_middleware
266
+
267
+ # Use a stub to simulate a 401 response followed by success
268
+ Excon.stub({}) do |params|
269
+ if params[:headers] && params[:headers]['X-Should-Fail'] == 'true'
270
+ # Log the attempt
271
+ attempt = params[:headers]['X-Attempt']
272
+ puts "Request attempt #{attempt} - Authentication failed"
273
+
274
+ # Simulate an authentication failure
275
+ {
276
+ status: 401,
277
+ body: "{\"error\": \"Unauthorized\", \"attempt\": #{attempt}}"
278
+ }
279
+ else
280
+ # Log the successful attempt
281
+ attempt = params[:headers]['X-Attempt']
282
+ puts "Request attempt #{attempt} - Authentication succeeded"
283
+
284
+ # Simulate success
285
+ {
286
+ status: 200,
287
+ body: "{\"status\": \"success\", \"attempt\": #{attempt}}"
288
+ }
289
+ end
290
+ end
291
+
292
+ # Make a request that will fail and then retry automatically
293
+ puts 'Making request that will fail authentication and retry automatically:'
294
+ response = connection.request(method: :get, path: '/anything')
295
+
296
+ puts "Final response status: #{response[:status]}"
297
+ puts "Response body: #{response[:body]}"
298
+
299
+ # Clean up stubs
300
+ Excon.stubs.clear
301
+ rescue StandardError => e
302
+ puts "Error: #{e.message}"
303
+ Excon.stubs.clear
304
+ end
305
+
306
+ # 6. Example with retry on rate limiting (simulation)
307
+ puts "\n6. Rate Limiting and Retry Example (Simulated):"
308
+
309
+ class RateLimitScheme < Legate::Auth::Scheme
310
+ def initialize
311
+ @attempts = 0
312
+ @rate_limit_count = 2 # Will hit rate limit twice before succeeding
313
+ end
314
+
315
+ def scheme_type
316
+ :custom
317
+ end
318
+
319
+ def apply_to_request(request, _credential)
320
+ request[:headers] ||= {}
321
+
322
+ @attempts += 1
323
+ request[:headers]['X-Attempt'] = @attempts.to_s
324
+
325
+ request[:headers]['X-Rate-Limited'] = if @attempts <= @rate_limit_count
326
+ # This will trigger rate limiting
327
+ 'true'
328
+ else
329
+ # This will succeed
330
+ 'false'
331
+ end
332
+
333
+ request
334
+ end
335
+ end
336
+
337
+ begin
338
+ # Create a scheme that will trigger rate limiting
339
+ rate_limit_scheme = RateLimitScheme.new
340
+ rate_limit_credential = Legate::Auth::Credential.new(auth_type: :custom)
341
+
342
+ # Create a middleware that will handle rate limiting
343
+ rate_limit_middleware = Legate::Auth::MiddlewareFactory.create(
344
+ scheme: rate_limit_scheme,
345
+ credential: rate_limit_credential,
346
+ auto_retry: true,
347
+ max_retries: 5,
348
+ backoff_strategy: :fibonacci, # Use fibonacci backoff for rate limiting
349
+ backoff_factor: 0.1, # Small factor to make test run quickly
350
+ retry_on: [429] # Explicitly retry on 429 Too Many Requests
351
+ )
352
+
353
+ # Set up a connection with our middleware
354
+ connection = Excon.new('https://httpbin.org')
355
+ connection.data[:middlewares] ||= connection.data[:middlewares].dup
356
+ connection.data[:middlewares] << Legate::Auth::ExconMiddleware unless connection.data[:middlewares].include?(Legate::Auth::ExconMiddleware)
357
+ connection.data[:auth_middleware] = rate_limit_middleware
358
+
359
+ # Use a stub to simulate rate limiting
360
+ Excon.stub({}) do |params|
361
+ if params[:headers] && params[:headers]['X-Rate-Limited'] == 'true'
362
+ # Log the attempt
363
+ attempt = params[:headers]['X-Attempt']
364
+ puts "Request attempt #{attempt} - Rate limited"
365
+
366
+ # Simulate rate limiting with Retry-After header
367
+ {
368
+ status: 429,
369
+ headers: { 'Retry-After' => '1' }, # Suggest a 1 second retry time
370
+ body: "{\"error\": \"Too Many Requests\", \"attempt\": #{attempt}}"
371
+ }
372
+ else
373
+ # Log the successful attempt
374
+ attempt = params[:headers]['X-Attempt']
375
+ puts "Request attempt #{attempt} - Request succeeded"
376
+
377
+ # Simulate success
378
+ {
379
+ status: 200,
380
+ body: "{\"status\": \"success\", \"attempt\": #{attempt}}"
381
+ }
382
+ end
383
+ end
384
+
385
+ # Make a request that will be rate limited and then retry automatically
386
+ puts 'Making request that will be rate limited and retry automatically:'
387
+ response = connection.request(method: :get, path: '/anything')
388
+
389
+ puts "Final response status: #{response[:status]}"
390
+ puts "Response body: #{response[:body]}"
391
+
392
+ # Clean up stubs
393
+ Excon.stubs.clear
394
+ rescue StandardError => e
395
+ puts "Error: #{e.message}"
396
+ Excon.stubs.clear
397
+ end
398
+
399
+ puts "\nExample complete."