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,251 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example of using Legate::Auth with OpenWeatherMap API
5
+ #
6
+ # This example demonstrates different ways to authenticate with OpenWeatherMap API
7
+ # using both direct Excon calls and Legate::Auth middleware.
8
+ #
9
+ # Usage:
10
+ # ruby examples/advanced/auth/openweather_api.rb
11
+
12
+ require 'bundler/setup'
13
+ require 'legate'
14
+ require 'legate/auth' # Explicitly require the auth module
15
+ require 'json'
16
+ require 'excon'
17
+ require 'legate/session_service/in_memory'
18
+ require 'uri'
19
+
20
+ # Enable debug mode
21
+ ENV['DEBUG'] = 'true'
22
+
23
+ # API Key for OpenWeatherMap
24
+ OPENWEATHER_API_KEY = ENV['API_KEY']
25
+
26
+ puts 'OpenWeatherMap API Authentication Example'
27
+ puts '----------------------------------------'
28
+ puts "API Key: #{OPENWEATHER_API_KEY[0..5]}...#{OPENWEATHER_API_KEY[-4..-1]}"
29
+
30
+ # Create session service for token storage
31
+ session_service = Legate::SessionService::InMemory.new
32
+
33
+ # Create a basic token store for caching
34
+ token_store = Legate::Auth::TokenStore.new(session_service)
35
+
36
+ # Create the ApiKey scheme for our OpenWeatherMap API
37
+ api_key_scheme = Legate::Auth::Schemes::ApiKey.new
38
+
39
+ # Create the credential to use with the scheme
40
+ api_key_credential = Legate::Auth::Credential.new(
41
+ auth_type: :api_key,
42
+ api_key: OPENWEATHER_API_KEY,
43
+ location: 'query',
44
+ name: 'appid'
45
+ )
46
+
47
+ puts "\nDEMO 1: Direct API call with Excon (no Legate middleware)"
48
+ puts '------------------------------------------------------'
49
+
50
+ begin
51
+ # Make a direct Excon request to the OpenWeatherMap API
52
+ url = 'https://api.openweathermap.org/data/2.5/weather?q=London,uk&units=metric'
53
+
54
+ # Manually add the API key to the URL
55
+ url_with_key = "#{url}&appid=#{OPENWEATHER_API_KEY}"
56
+
57
+ puts "Making request to: #{url_with_key.gsub(OPENWEATHER_API_KEY, 'API_KEY_REDACTED')}"
58
+
59
+ response = Excon.get(url_with_key,
60
+ connect_timeout: 30,
61
+ read_timeout: 30,
62
+ write_timeout: 30)
63
+
64
+ puts "Response Status: #{response.status}"
65
+
66
+ if response.status == 200
67
+ weather_data = JSON.parse(response.body)
68
+ puts "Location: #{weather_data['name']}, #{weather_data['sys']['country']}"
69
+ puts "Weather: #{weather_data['weather'][0]['main']} - #{weather_data['weather'][0]['description']}"
70
+ puts "Temperature: #{weather_data['main']['temp']}°C (Feels like: #{weather_data['main']['feels_like']}°C)"
71
+ puts "Humidity: #{weather_data['main']['humidity']}%"
72
+ puts "Wind: #{weather_data['wind']['speed']} m/s"
73
+ else
74
+ puts "Error: #{response.status} - #{response.body}"
75
+ end
76
+ rescue StandardError => e
77
+ puts "Request Error: #{e.message}"
78
+ puts e.backtrace.join("\n") if ENV['DEBUG']
79
+ end
80
+
81
+ puts "\nDEMO 2: Using Legate::Auth to apply authentication manually"
82
+ puts '------------------------------------------------------'
83
+
84
+ begin
85
+ # Create a request hash (without authentication)
86
+ request = {
87
+ method: :get,
88
+ path: '/data/2.5/weather',
89
+ query: {
90
+ q: 'Paris,fr',
91
+ units: 'metric'
92
+ },
93
+ headers: {}
94
+ }
95
+
96
+ # Use Legate::Auth::ToolIntegration to apply authentication to the request
97
+ puts "Applying authentication with params: #{request.inspect}"
98
+ request_with_auth = Legate::Auth::ToolIntegration.apply_authentication(
99
+ request,
100
+ api_key_scheme,
101
+ api_key_credential,
102
+ token_store
103
+ )
104
+
105
+ puts "Authentication applied: #{request_with_auth.inspect}"
106
+
107
+ # Extract the query parameters - we need to handle this manually
108
+ query_params = request_with_auth[:query]
109
+
110
+ # Add appid parameter manually if it's not already present
111
+ query_hash = if query_params.is_a?(Hash)
112
+ # If it's already a hash, we can use it directly
113
+ query_params
114
+ elsif query_params.is_a?(String)
115
+ # Parse the query string into a hash
116
+ params = {}
117
+ URI.decode_www_form(query_params).each do |key, value|
118
+ params[key] = value
119
+ end
120
+ params
121
+ else
122
+ # Default to an empty hash if we can't parse it
123
+ { q: 'Paris,fr', units: 'metric' }
124
+ end
125
+
126
+ # Make sure the API key is in the query parameters
127
+ query_hash['appid'] ||= OPENWEATHER_API_KEY
128
+
129
+ # Construct the full URL from the request with proper query params
130
+ base_url = 'https://api.openweathermap.org'
131
+ path = request_with_auth[:path]
132
+
133
+ # Convert the hash to a query string
134
+ query_string = URI.encode_www_form(query_hash)
135
+
136
+ # Full URL
137
+ full_url = "#{base_url}#{path}?#{query_string}"
138
+ puts "Request URL with auth applied: #{full_url.gsub(OPENWEATHER_API_KEY, 'API_KEY_REDACTED')}"
139
+
140
+ # Make the request
141
+ response = Excon.get(full_url,
142
+ connect_timeout: 30,
143
+ read_timeout: 30,
144
+ write_timeout: 30)
145
+
146
+ puts "Response Status: #{response.status}"
147
+
148
+ if response.status == 200
149
+ weather_data = JSON.parse(response.body)
150
+ puts "Location: #{weather_data['name']}, #{weather_data['sys']['country']}"
151
+ puts "Weather: #{weather_data['weather'][0]['main']} - #{weather_data['weather'][0]['description']}"
152
+ puts "Temperature: #{weather_data['main']['temp']}°C (Feels like: #{weather_data['main']['feels_like']}°C)"
153
+ puts "Humidity: #{weather_data['main']['humidity']}%"
154
+ puts "Wind: #{weather_data['wind']['speed']} m/s"
155
+ else
156
+ puts "Error: #{response.status} - #{response.body}"
157
+ end
158
+ rescue StandardError => e
159
+ puts "Request Error: #{e.message}"
160
+ puts e.backtrace.join("\n") if ENV['DEBUG']
161
+ end
162
+
163
+ puts "\nDEMO 3: Using API Key directly with Excon"
164
+ puts '-------------------------------------'
165
+
166
+ begin
167
+ # Create a request with direct query params approach
168
+ puts 'Making direct query parameter request...'
169
+
170
+ # Use a direct approach without middleware
171
+ query_params = {
172
+ q: 'Tokyo,jp',
173
+ units: 'metric',
174
+ appid: OPENWEATHER_API_KEY
175
+ }
176
+
177
+ # Create the connection
178
+ connection = Excon.new('https://api.openweathermap.org',
179
+ connect_timeout: 10,
180
+ read_timeout: 10,
181
+ write_timeout: 10)
182
+
183
+ # Make the request directly
184
+ response = connection.request(
185
+ method: :get,
186
+ path: '/data/2.5/weather',
187
+ query: query_params
188
+ )
189
+
190
+ puts "Response Status: #{response.status}"
191
+
192
+ if response.status == 200
193
+ weather_data = JSON.parse(response.body)
194
+ puts "Location: #{weather_data['name']}, #{weather_data['sys']['country']}"
195
+ puts "Weather: #{weather_data['weather'][0]['main']} - #{weather_data['weather'][0]['description']}"
196
+ puts "Temperature: #{weather_data['main']['temp']}°C (Feels like: #{weather_data['main']['feels_like']}°C)"
197
+ puts "Humidity: #{weather_data['main']['humidity']}%"
198
+ puts "Wind: #{weather_data['wind']['speed']} m/s"
199
+ else
200
+ puts "Error: #{response.status} - #{response.body}"
201
+ end
202
+ rescue StandardError => e
203
+ puts "Request Error: #{e.message}"
204
+ puts e.backtrace.join("\n") if ENV['DEBUG']
205
+ end
206
+
207
+ puts "\nDEMO 4: Using Legate::Auth middleware (with fixed implementation)"
208
+ puts '---------------------------------------------------------'
209
+
210
+ begin
211
+ puts 'Creating connection with Legate::Auth middleware...'
212
+
213
+ # Use the fixed middleware via Legate::Auth.create_api_key_connection
214
+ connection = Legate::Auth.create_api_key_connection(
215
+ 'https://api.openweathermap.org',
216
+ api_key: OPENWEATHER_API_KEY,
217
+ location: 'query',
218
+ name: 'appid',
219
+ token_store: token_store
220
+ )
221
+
222
+ puts 'Connection created. Making request to get weather for New York...'
223
+
224
+ # Make the request through the middleware
225
+ response = connection.request(
226
+ method: :get,
227
+ path: '/data/2.5/weather',
228
+ query: {
229
+ q: 'New York,us',
230
+ units: 'metric'
231
+ }
232
+ )
233
+
234
+ puts "Response Status: #{response.status}"
235
+
236
+ if response.status == 200
237
+ weather_data = JSON.parse(response.body)
238
+ puts "Location: #{weather_data['name']}, #{weather_data['sys']['country']}"
239
+ puts "Weather: #{weather_data['weather'][0]['main']} - #{weather_data['weather'][0]['description']}"
240
+ puts "Temperature: #{weather_data['main']['temp']}°C (Feels like: #{weather_data['main']['feels_like']}°C)"
241
+ puts "Humidity: #{weather_data['main']['humidity']}%"
242
+ puts "Wind: #{weather_data['wind']['speed']} m/s"
243
+ else
244
+ puts "Error: #{response.status} - #{response.body}"
245
+ end
246
+ rescue StandardError => e
247
+ puts "Request Error: #{e.message}"
248
+ puts e.backtrace.join("\n") if ENV['DEBUG']
249
+ end
250
+
251
+ puts "\nExample complete."
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example of using Legate::Auth with OpenWeatherMap API using the tool-based approach
5
+ # and proper authentication handling through HttpClient.
6
+ #
7
+ # Usage:
8
+ # ruby examples/advanced/auth/openweather_tool.rb
9
+
10
+ require 'bundler/setup'
11
+ require 'legate'
12
+ require 'legate/auth'
13
+ require 'json'
14
+ # API Key for OpenWeatherMap
15
+ OPENWEATHER_API_KEY = ENV['API_KEY']
16
+
17
+ puts 'OpenWeatherMap API Tool-Based Authentication Example'
18
+ puts '------------------------------------------------'
19
+ puts "API Key: #{OPENWEATHER_API_KEY[0..5]}...#{OPENWEATHER_API_KEY[-4..-1]}"
20
+
21
+ # First, let's create a tool class for OpenWeather API
22
+ module Legate
23
+ module Tools
24
+ class OpenWeather < Legate::Tool
25
+ include Legate::Tools::Base::HttpClient
26
+
27
+ # Tool metadata
28
+ tool_description 'Fetches weather data from OpenWeatherMap API'
29
+
30
+ parameter :city,
31
+ type: :string,
32
+ description: 'The city to get weather for',
33
+ required: true
34
+
35
+ parameter :country_code,
36
+ type: :string,
37
+ description: 'The country code (e.g., uk, us, jp)',
38
+ required: true
39
+
40
+ parameter :units,
41
+ type: :string,
42
+ description: 'The units to use (metric or imperial)',
43
+ required: false
44
+
45
+ def initialize(options = {})
46
+ super()
47
+ @auth_scheme = options[:auth_scheme]
48
+ @auth_credential = options[:auth_credential]
49
+ setup_http_client(
50
+ base_url: 'https://api.openweathermap.org',
51
+ options: {
52
+ connect_timeout: 3,
53
+ read_timeout: 3,
54
+ write_timeout: 3
55
+ }
56
+ )
57
+ end
58
+
59
+ private
60
+
61
+ def perform_execution(params, _context)
62
+ # Extract parameters
63
+ city = params[:city]
64
+ country_code = params[:country_code]
65
+ units = params.fetch(:units, 'metric')
66
+
67
+ # Prepare query parameters
68
+ query = {
69
+ q: "#{city},#{country_code}",
70
+ units: units
71
+ }
72
+
73
+ # If we have auth credentials, let them be applied through the auth scheme
74
+ if @auth_scheme && @auth_credential
75
+ request = { query: query }
76
+ modified_request = @auth_scheme.apply_to_request(request, @auth_credential)
77
+ query = modified_request[:query]
78
+ end
79
+
80
+ # Make the request using HttpClient's helper
81
+ response = http_get('/data/2.5/weather', query: query)
82
+
83
+ # Parse and return the response
84
+ begin
85
+ data = JSON.parse(response.body)
86
+ { status: :success, result: data }
87
+ rescue JSON::ParserError => e
88
+ raise Legate::ToolError, "Failed to parse API response: #{e.message}"
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ puts "\nDEMO 1: Using OpenWeather Tool with API Key Authentication"
96
+ puts '-----------------------------------------------------'
97
+
98
+ begin
99
+ # Create an API Key scheme
100
+ scheme = Legate::Auth::Schemes::ApiKey.new
101
+
102
+ # Create a credential with the API key
103
+ credential = Legate::Auth::Credential.new(
104
+ auth_type: :api_key,
105
+ api_key: OPENWEATHER_API_KEY,
106
+ location: 'query',
107
+ name: 'appid'
108
+ )
109
+
110
+ # Create our OpenWeather tool instance with authentication
111
+ weather_tool = Legate::Tools::OpenWeather.new(
112
+ auth_scheme: scheme,
113
+ auth_credential: credential
114
+ )
115
+
116
+ # Example cities to check weather for
117
+ cities = [
118
+ { city: 'London', country: 'uk' },
119
+ { city: 'Tokyo', country: 'jp' },
120
+ { city: 'New York', country: 'us' }
121
+ ]
122
+
123
+ # Get weather for each city
124
+ cities.each do |location|
125
+ puts "\nChecking weather for #{location[:city]}, #{location[:country].upcase}..."
126
+
127
+ begin
128
+ result = weather_tool.execute(
129
+ city: location[:city],
130
+ country_code: location[:country]
131
+ )
132
+
133
+ if result[:status] == :success
134
+ weather_data = result[:result]
135
+ puts "Location: #{weather_data['name']}, #{weather_data['sys']['country']}"
136
+ puts "Weather: #{weather_data['weather'][0]['main']} - #{weather_data['weather'][0]['description']}"
137
+ puts "Temperature: #{weather_data['main']['temp']}°C (Feels like: #{weather_data['main']['feels_like']}°C)"
138
+ puts "Humidity: #{weather_data['main']['humidity']}%"
139
+ puts "Wind: #{weather_data['wind']['speed']} m/s"
140
+ else
141
+ puts "Error: #{result[:error_message]}"
142
+ end
143
+ rescue StandardError => e
144
+ puts "Error getting weather for #{location[:city]}: #{e.message}"
145
+ puts e.backtrace.join("\n") if ENV['DEBUG']
146
+ end
147
+ end
148
+ rescue StandardError => e
149
+ puts "Error: #{e.message}"
150
+ puts e.backtrace.join("\n") if ENV['DEBUG']
151
+ end
152
+
153
+ puts "\nExample complete."
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Test focusing on query parameter handling in middleware
5
+ #
6
+ # Usage:
7
+ # ruby examples/advanced/auth/query_param_middleware_test.rb
8
+
9
+ require 'bundler/setup'
10
+ require 'json'
11
+ require 'excon'
12
+ require 'uri'
13
+
14
+ # Middleware that correctly adds a query parameter
15
+ class QueryParamMiddleware < Excon::Middleware::Base
16
+ def initialize(stack)
17
+ puts 'QueryParamMiddleware initialized'
18
+ @stack = stack
19
+ @api_key = 'test-api-key-123'
20
+ @api_key_name = 'apikey'
21
+ super(stack)
22
+ end
23
+
24
+ def request_call(datum)
25
+ puts 'QueryParamMiddleware request_call invoked'
26
+ puts " Method: #{datum[:method]}, Path: #{datum[:path]}"
27
+ puts " Original query: #{datum[:query].inspect}"
28
+ puts " Connect timeout: #{datum[:connect_timeout]}"
29
+ puts " Read timeout: #{datum[:read_timeout]}"
30
+ puts " Write timeout: #{datum[:write_timeout]}"
31
+
32
+ # Add API key to query parameters
33
+ query = datum[:query] || {}
34
+
35
+ # Convert query to Hash if it's a string
36
+ if query.is_a?(String)
37
+ params = {}
38
+ URI.decode_www_form(query).each do |key, value|
39
+ params[key] = value
40
+ end
41
+ query = params
42
+ end
43
+
44
+ # Add API key parameter
45
+ query[@api_key_name] = @api_key
46
+
47
+ # Update the datum with the modified query
48
+ datum[:query] = query
49
+
50
+ # Debug the updated query
51
+ puts " Modified query: #{datum[:query].inspect}"
52
+
53
+ # Continue with the middleware stack - pass through the modified datum
54
+ @stack.request_call(datum)
55
+ end
56
+
57
+ def response_call(datum)
58
+ puts 'QueryParamMiddleware response_call invoked'
59
+ # Pass the datum through unmodified
60
+ @stack.response_call(datum)
61
+ end
62
+ end
63
+
64
+ puts 'Query Parameter Middleware Test'
65
+ puts '----------------------------'
66
+
67
+ puts "\nTest 1: Direct API call with Excon"
68
+ puts '-------------------------------'
69
+
70
+ begin
71
+ # Make a direct Excon request
72
+ url = 'https://httpbin.org/get?test=value'
73
+
74
+ puts "Making direct request to: #{url}"
75
+
76
+ # Create the connection with normal timeouts
77
+ response = Excon.get(url,
78
+ connect_timeout: 10,
79
+ read_timeout: 10,
80
+ write_timeout: 10)
81
+
82
+ puts "Response Status: #{response.status}"
83
+
84
+ if response.status == 200
85
+ data = JSON.parse(response.body)
86
+ puts "Data received: #{data['args'].inspect}"
87
+ else
88
+ puts "Error: #{response.status} - #{response.body}"
89
+ end
90
+ rescue StandardError => e
91
+ puts "Request Error: #{e.message}"
92
+ puts e.backtrace.join("\n")
93
+ end
94
+
95
+ puts "\nTest 2: Query Parameter Middleware"
96
+ puts '-------------------------------'
97
+
98
+ begin
99
+ # Create a connection with our custom middleware
100
+ connection = Excon.new('https://httpbin.org',
101
+ middlewares: [
102
+ Excon::Middleware::ResponseParser,
103
+ Excon::Middleware::Expects,
104
+ Excon::Middleware::Idempotent,
105
+ Excon::Middleware::Instrumentor,
106
+ Excon::Middleware::Mock,
107
+ QueryParamMiddleware # Our query parameter middleware
108
+ ],
109
+ connect_timeout: 10,
110
+ read_timeout: 10,
111
+ write_timeout: 10)
112
+
113
+ puts 'Connection created with query parameter middleware'
114
+ puts 'Making request...'
115
+
116
+ # Make the request
117
+ response = connection.request(
118
+ method: :get,
119
+ path: '/get',
120
+ query: {
121
+ test: 'value'
122
+ }
123
+ )
124
+
125
+ puts "Response Status: #{response.status}"
126
+
127
+ if response.status == 200
128
+ data = JSON.parse(response.body)
129
+ puts "Data received: #{data['args'].inspect}"
130
+ else
131
+ puts "Error: #{response.status} - #{response.body}"
132
+ end
133
+ rescue StandardError => e
134
+ puts "Request Error: #{e.message}"
135
+ puts e.backtrace.join("\n")
136
+ end
137
+
138
+ puts "\nTest complete."
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example of using the ServiceAccount authentication with automatic token exchange and refresh
5
+ #
6
+ # This example demonstrates how to authenticate using a service account JSON key file
7
+ # and make requests that are automatically authenticated.
8
+ #
9
+ # Usage:
10
+ # ruby examples/advanced/auth/service_account.rb [path/to/service_account_key.json]
11
+
12
+ require 'bundler/setup'
13
+ require 'legate'
14
+ require 'json'
15
+ require 'fileutils'
16
+
17
+ # Check if a service account key file was provided
18
+ key_file_path = ARGV[0] || ENV['SERVICE_ACCOUNT_KEY_FILE']
19
+ unless key_file_path
20
+ puts 'Error: Please provide a service account key JSON file as an argument'
21
+ puts 'Usage: ruby examples/advanced/auth/service_account.rb [path/to/service_account_key.json]'
22
+ puts 'Alternatively, set the SERVICE_ACCOUNT_KEY_FILE environment variable'
23
+ exit 1
24
+ end
25
+
26
+ # Ensure the key file exists
27
+ unless File.exist?(key_file_path)
28
+ puts "Error: Service account key file not found: #{key_file_path}"
29
+ exit 1
30
+ end
31
+
32
+ # Load the service account key file
33
+ begin
34
+ service_account_key = JSON.parse(File.read(key_file_path))
35
+ puts "Loaded service account key for: #{service_account_key['client_email']}"
36
+ rescue JSON::ParserError => e
37
+ puts "Error parsing service account key file: #{e.message}"
38
+ exit 1
39
+ end
40
+
41
+ # Create the Auth::Credential for the service account
42
+ # You can use either the raw JSON key or the individual components
43
+ credential = if ENV['USE_RAW_JSON'] == 'true'
44
+ Legate::Auth::Credential.new(
45
+ auth_type: :service_account,
46
+ service_account_key: File.read(key_file_path)
47
+ )
48
+ else
49
+ Legate::Auth::Credential.new(
50
+ auth_type: :service_account,
51
+ client_email: service_account_key['client_email'],
52
+ private_key: service_account_key['private_key'],
53
+ token_uri: service_account_key['token_uri']
54
+ )
55
+ end
56
+
57
+ # Create a Google Service Account scheme with the appropriate scopes
58
+ # You can use other service account implementations as well
59
+ scheme = Legate::Auth::Schemes::GoogleServiceAccount.new(
60
+ scopes: ['https://www.googleapis.com/auth/cloud-platform']
61
+ # Optional custom audience if needed:
62
+ # audience: 'https://your-api.example.com'
63
+ )
64
+
65
+ # Create a session service to manage authentication state
66
+ session_service = Legate::SessionService::InMemory.new
67
+
68
+ # Create a token store (optional but recommended for caching tokens)
69
+ token_store = Legate::Auth::TokenStore.new(session_service: session_service)
70
+
71
+ # Create the service account coordinator
72
+ coordinator = Legate::Auth::Coordinators::ServiceAccountCoordinator.new(
73
+ scheme: scheme,
74
+ credential: credential,
75
+ session_service: session_service,
76
+ token_store: token_store
77
+ )
78
+
79
+ # Authenticate and get the token
80
+ begin
81
+ token = Legate::Auth.authenticate_with_coordinator(coordinator)
82
+ puts 'Successfully authenticated with service account'
83
+ puts "Access token: #{token.access_token[0..10]}... (expires in #{token[:expires_in]} seconds)"
84
+ rescue Legate::Auth::Error => e
85
+ puts "Authentication failed: #{e.message}"
86
+ exit 1
87
+ end
88
+
89
+ # Example of making an authenticated request with the token
90
+ require 'faraday'
91
+
92
+ puts "\nMaking an authenticated request..."
93
+ conn = Faraday.new(url: 'https://www.googleapis.com/storage/v1') do |builder|
94
+ builder.request :authorization, 'Bearer', token.access_token
95
+ builder.request :json
96
+ builder.response :json
97
+ builder.adapter Faraday.default_adapter
98
+ end
99
+
100
+ # Example: List Google Cloud Storage buckets
101
+ begin
102
+ response = conn.get('b', { project: service_account_key['project_id'] })
103
+
104
+ if response.success?
105
+ puts "Request succeeded with status: #{response.status}"
106
+ puts "Found #{response.body['items']&.length || 0} buckets"
107
+
108
+ if response.body['items']&.any?
109
+ puts "\nBucket names:"
110
+ response.body['items'].each do |bucket|
111
+ puts "- #{bucket['name']}"
112
+ end
113
+ end
114
+ else
115
+ puts "Request failed with status: #{response.status}"
116
+ puts "Error: #{response.body['error']&.dig('message') || response.body.inspect}"
117
+ end
118
+ rescue StandardError => e
119
+ puts "Request failed: #{e.message}"
120
+ end
121
+
122
+ puts "\nToken refresh demonstration:"
123
+ puts "Current token will expire in #{token[:expires_in]} seconds"
124
+ puts 'Explicitly refreshing token...'
125
+
126
+ # Example of refreshing a token
127
+ begin
128
+ refreshed_token = coordinator.refresh(token)
129
+ puts 'Token refreshed successfully'
130
+ puts "New access token: #{refreshed_token.access_token[0..10]}... (expires in #{refreshed_token[:expires_in]} seconds)"
131
+ rescue Legate::Auth::Error => e
132
+ puts "Token refresh failed: #{e.message}"
133
+ end
134
+
135
+ puts "\nDemonstration completed successfully!"