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,159 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'legate'
6
+ require 'legate/auth'
7
+ require 'legate/auth/schemes/http_bearer'
8
+ require 'json'
9
+
10
+ # Example of using Legate::Auth with bearer token authentication against httpbin.org
11
+ # This example demonstrates how to use bearer tokens for authentication using the Legate tool framework.
12
+
13
+ module Legate
14
+ module Tools
15
+ class HttpbinBearer < Legate::Tool
16
+ include Legate::Tools::Base::HttpClient
17
+
18
+ # Tool metadata
19
+ tool_description 'Makes authenticated requests to httpbin.org using bearer token'
20
+
21
+ parameter :endpoint,
22
+ type: :string,
23
+ description: 'The httpbin endpoint to call (e.g., bearer, headers)',
24
+ required: true
25
+
26
+ def initialize(options = {})
27
+ super()
28
+ @auth_scheme = options[:auth_scheme]
29
+ @auth_credential = options[:auth_credential]
30
+ setup_http_client(
31
+ base_url: 'https://httpbin.org',
32
+ options: {
33
+ connect_timeout: 3,
34
+ read_timeout: 3,
35
+ write_timeout: 3
36
+ }
37
+ )
38
+ end
39
+
40
+ private
41
+
42
+ def perform_execution(params, _context)
43
+ # Extract parameters
44
+ endpoint = params[:endpoint]
45
+
46
+ # Prepare request with authentication
47
+ request = {
48
+ method: :get,
49
+ path: "/#{endpoint}",
50
+ headers: {
51
+ 'Accept' => 'application/json'
52
+ }
53
+ }
54
+
55
+ # Apply authentication if configured
56
+ request = @auth_scheme.apply_to_request(request, @auth_credential) if @auth_scheme && @auth_credential
57
+
58
+ # Make the request using HttpClient's helper with any auth headers
59
+ response = http_get(
60
+ request[:path],
61
+ headers: request[:headers]
62
+ )
63
+
64
+ # Parse and return the response
65
+ begin
66
+ data = JSON.parse(response.body)
67
+ case response.status
68
+ when 200
69
+ {
70
+ status: :success,
71
+ message: 'Successfully authenticated with bearer token',
72
+ result: data
73
+ }
74
+ when 401
75
+ {
76
+ status: :error,
77
+ message: 'Authentication failed. Invalid or missing bearer token.',
78
+ error: data['message'] || 'Unauthorized'
79
+ }
80
+ else
81
+ {
82
+ status: :error,
83
+ message: "Request failed with status #{response.status}",
84
+ error: data
85
+ }
86
+ end
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
+ if $0 == __FILE__
96
+ puts 'Bearer Token Authentication Example'
97
+ puts '--------------------------------'
98
+
99
+ begin
100
+ # Create session service for token storage
101
+ session_service = Legate::SessionService::InMemory.new
102
+ token_store = Legate::Auth::TokenStore.new(session_service)
103
+
104
+ # Create a bearer token scheme
105
+ scheme = Legate::Auth::Schemes::HTTPBearer.new
106
+
107
+ # Create a credential with the bearer token
108
+ # In a real application, you would get this from an environment variable
109
+ credential = Legate::Auth::Credential.new(
110
+ auth_type: :http_bearer,
111
+ bearer_token: 'test-bearer-token'
112
+ )
113
+
114
+ # Create our HttpbinBearer tool instance with authentication
115
+ bearer_tool = Legate::Tools::HttpbinBearer.new(
116
+ auth_scheme: scheme,
117
+ auth_credential: credential
118
+ )
119
+
120
+ # Example endpoints to test
121
+ endpoints = %w[bearer headers]
122
+
123
+ # Test each endpoint
124
+ endpoints.each do |endpoint|
125
+ puts "\nTesting endpoint: /#{endpoint}"
126
+
127
+ begin
128
+ result = bearer_tool.execute(
129
+ endpoint: endpoint
130
+ )
131
+
132
+ if result[:status] == :success
133
+ response_data = result[:result]
134
+ puts "Success: #{result[:message]}"
135
+
136
+ case endpoint
137
+ when 'bearer'
138
+ puts 'Bearer auth response:'
139
+ puts JSON.pretty_generate(response_data)
140
+ when 'headers'
141
+ puts 'Headers in request:'
142
+ puts JSON.pretty_generate(response_data['headers'])
143
+ end
144
+ else
145
+ puts "Error: #{result[:message]}"
146
+ puts "Details: #{result[:error]}"
147
+ end
148
+ rescue StandardError => e
149
+ puts "Error testing #{endpoint}: #{e.message}"
150
+ puts e.backtrace.join("\n") if ENV['DEBUG']
151
+ end
152
+ end
153
+ rescue StandardError => e
154
+ puts "Error: #{e.message}"
155
+ puts e.backtrace.join("\n") if ENV['DEBUG']
156
+ end
157
+
158
+ puts "\nExample complete."
159
+ end
@@ -0,0 +1,419 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example of using OAuth2 authentication with Legate
5
+ #
6
+ # This example demonstrates how to use OAuth2 authentication to make authenticated requests
7
+ # to an API that requires OAuth2 authorization. It shows both the interactive and non-interactive
8
+ # (with refresh token) approaches.
9
+ #
10
+ # Usage:
11
+ # ruby examples/advanced/auth/oauth2_auth.rb [--with-server]
12
+ # ruby examples/advanced/auth/oauth2_auth.rb --client-id=YOUR_CLIENT_ID --client-secret=YOUR_CLIENT_SECRET
13
+ #
14
+ # Set these environment variables to use your own OAuth2 provider:
15
+ # OAUTH_CLIENT_ID
16
+ # OAUTH_CLIENT_SECRET
17
+ # OAUTH_AUTH_URL (default: https://accounts.google.com/o/oauth2/auth)
18
+ # OAUTH_TOKEN_URL (default: https://oauth2.googleapis.com/token)
19
+ # OAUTH_REDIRECT_URI (default: http://localhost:3000/auth/callback)
20
+
21
+ require 'bundler/setup'
22
+ require 'legate'
23
+ require 'legate/auth'
24
+ require 'legate/auth/runner'
25
+ require 'legate/auth/schemes/oauth2'
26
+ require 'legate/tool_context'
27
+ require 'legate/web/server'
28
+ require 'launchy'
29
+ require 'securerandom'
30
+ require 'optparse'
31
+ require 'json'
32
+ require 'fileutils'
33
+
34
+ # Parse command line arguments
35
+ options = {
36
+ with_server: false,
37
+ client_id: ENV['OAUTH_CLIENT_ID'],
38
+ client_secret: ENV['OAUTH_CLIENT_SECRET'],
39
+ auth_url: ENV['OAUTH_AUTH_URL'] || 'https://accounts.google.com/o/oauth2/auth',
40
+ token_url: ENV['OAUTH_TOKEN_URL'] || 'https://oauth2.googleapis.com/token',
41
+ redirect_uri: ENV['OAUTH_REDIRECT_URI'] || 'http://localhost:3000/auth/callback',
42
+ scopes: ENV['OAUTH_SCOPES'] || 'email profile',
43
+ handle_response: false,
44
+ request_id: nil,
45
+ response_uri: nil,
46
+ use_refresh_token: false,
47
+ verbose: false
48
+ }
49
+
50
+ OptionParser.new do |opts|
51
+ opts.banner = 'Usage: ruby examples/advanced/auth/oauth2_auth.rb [options]'
52
+
53
+ opts.on('--with-server', 'Start a local server to handle the OAuth2 callback') do
54
+ options[:with_server] = true
55
+ end
56
+
57
+ opts.on('--client-id=ID', 'OAuth2 client ID') do |id|
58
+ options[:client_id] = id
59
+ end
60
+
61
+ opts.on('--client-secret=SECRET', 'OAuth2 client secret') do |secret|
62
+ options[:client_secret] = secret
63
+ end
64
+
65
+ opts.on('--auth-url=URL', 'OAuth2 authorization URL') do |url|
66
+ options[:auth_url] = url
67
+ end
68
+
69
+ opts.on('--token-url=URL', 'OAuth2 token URL') do |url|
70
+ options[:token_url] = url
71
+ end
72
+
73
+ opts.on('--redirect-uri=URI', 'OAuth2 redirect URI') do |uri|
74
+ options[:redirect_uri] = uri
75
+ end
76
+
77
+ opts.on('--scopes=SCOPES', 'OAuth2 scopes (space-separated)') do |scopes|
78
+ options[:scopes] = scopes
79
+ end
80
+
81
+ opts.on('--handle-response', 'Handle an authentication response') do
82
+ options[:handle_response] = true
83
+ end
84
+
85
+ opts.on('--request-id=ID', 'Authentication request ID') do |id|
86
+ options[:request_id] = id
87
+ end
88
+
89
+ opts.on('--response-uri=URI', 'Authentication response URI') do |uri|
90
+ options[:response_uri] = uri
91
+ end
92
+
93
+ opts.on('--use-refresh-token', 'Use a saved refresh token if available') do
94
+ options[:use_refresh_token] = true
95
+ end
96
+
97
+ opts.on('--verbose', 'Enable verbose output') do
98
+ options[:verbose] = true
99
+ end
100
+
101
+ opts.on('--help', 'Show this help message') do
102
+ puts opts
103
+ exit
104
+ end
105
+ end.parse!
106
+
107
+ # Validate required parameters
108
+ if options[:client_id].nil? || options[:client_id].empty?
109
+ puts 'Error: OAuth2 client ID is required'
110
+ puts 'Please provide it via --client-id or OAUTH_CLIENT_ID environment variable'
111
+ exit 1
112
+ end
113
+
114
+ if options[:client_secret].nil? || options[:client_secret].empty?
115
+ puts 'Error: OAuth2 client secret is required'
116
+ puts 'Please provide it via --client-secret or OAUTH_CLIENT_SECRET environment variable'
117
+ exit 1
118
+ end
119
+
120
+ # Setup the session service
121
+ session_service = Legate::SessionService::InMemory.new
122
+
123
+ # Create a tool context with the session service
124
+ # In a real application, you would use Redis or another persistent storage
125
+ context = Legate::ToolContext.new(session_service: session_service)
126
+
127
+ # Create an OAuth2 scheme
128
+ oauth2_scheme = Legate::Auth::Schemes::OAuth2.new(
129
+ authorization_url: options[:auth_url],
130
+ token_url: options[:token_url],
131
+ scopes: options[:scopes].split(' '),
132
+ use_pkce: true,
133
+ additional_params: {
134
+ prompt: 'consent',
135
+ access_type: 'offline'
136
+ }
137
+ )
138
+
139
+ # Create the OAuth2 credential
140
+ credential = Legate::Auth::Credential.new(
141
+ auth_type: :oauth2,
142
+ client_id: options[:client_id],
143
+ client_secret: options[:client_secret]
144
+ )
145
+
146
+ # Token storage file for refresh tokens
147
+ token_storage_file = File.expand_path('~/.oauth2_example_token.json')
148
+
149
+ # Helper to save token to file
150
+ def save_token_to_file(token, file_path)
151
+ # Convert token to a hash for storage
152
+ token_data = {
153
+ access_token: token.access_token,
154
+ refresh_token: token.refresh_token,
155
+ token_type: token.token_type,
156
+ expires_at: token.expires_at.to_i,
157
+ scope: token.scope
158
+ }.compact
159
+
160
+ # Save to file
161
+ FileUtils.mkdir_p(File.dirname(file_path))
162
+ File.write(file_path, JSON.pretty_generate(token_data))
163
+ puts "Token saved to #{file_path}"
164
+ end
165
+
166
+ # Helper to load token from file
167
+ def load_token_from_file(file_path)
168
+ return nil unless File.exist?(file_path)
169
+
170
+ begin
171
+ token_data = JSON.parse(File.read(file_path), symbolize_names: true)
172
+
173
+ # Create an exchanged credential from the stored data
174
+ Legate::Auth::ExchangedCredential.new(
175
+ auth_type: :oauth2,
176
+ access_token: token_data[:access_token],
177
+ refresh_token: token_data[:refresh_token],
178
+ token_type: token_data[:token_type],
179
+ expires_at: Time.at(token_data[:expires_at]),
180
+ scope: token_data[:scope]
181
+ )
182
+ rescue StandardError => e
183
+ puts "Error loading token: #{e.message}"
184
+ nil
185
+ end
186
+ end
187
+
188
+ # Create a web server to handle the OAuth2 callback
189
+ def create_callback_server(context, request_id_holder)
190
+ server = Legate::Web::Server.new(port: 3000)
191
+
192
+ # Add a route to handle the OAuth2 callback
193
+ server.add_route('GET', '/auth/callback') do |req, res|
194
+ # Extract the authorization code from the query parameters
195
+ code = req.query['code']
196
+ state = req.query['state']
197
+ error = req.query['error']
198
+
199
+ if error
200
+ res.status = 400
201
+ res.body = "Authentication failed: #{error}"
202
+ elsif code
203
+ # Convert the request to a response URI
204
+ response_uri = "http://localhost:3000/auth/callback?#{req.query_string}"
205
+
206
+ # Find the active auth request
207
+ request_id = request_id_holder[:id] || nil
208
+
209
+ if request_id
210
+ # Handle the auth response
211
+ result = context.handle_auth_response(request_id, { 'response_uri' => response_uri })
212
+
213
+ # Show a success page
214
+ res.status = 200
215
+ res.body = <<~HTML
216
+ <!DOCTYPE html>
217
+ <html>
218
+ <head>
219
+ <title>Authentication Successful</title>
220
+ <style>
221
+ body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
222
+ h1 { color: #4CAF50; }
223
+ .box { border: 1px solid #ddd; border-radius: 5px; padding: 20px; margin-top: 20px; }
224
+ </style>
225
+ </head>
226
+ <body>
227
+ <h1>Authentication Successful</h1>
228
+ <p>You have successfully authenticated with the OAuth2 provider.</p>
229
+ <div class="box">
230
+ <p>You can close this window and return to the application.</p>
231
+ </div>
232
+ </body>
233
+ </html>
234
+ HTML
235
+ else
236
+ res.status = 400
237
+ res.body = 'No active authentication request found'
238
+ end
239
+ else
240
+ res.status = 400
241
+ res.body = 'Missing required parameters'
242
+ end
243
+ end
244
+
245
+ server
246
+ end
247
+
248
+ def make_authenticated_request(token)
249
+ # In a real application, you would make an API call to an OAuth2-protected endpoint
250
+ # This is just a simulation to show the token information
251
+ puts "\nMaking an authenticated request with token:"
252
+ puts " Access Token : #{token.access_token[0..9]}...#{token.access_token[-4..-1]}"
253
+ puts " Token Type : #{token.token_type}"
254
+ puts " Expires In : #{(token.expires_at - Time.now).to_i} seconds"
255
+ puts " Scopes : #{token.scope}"
256
+
257
+ # Print refresh token if available
258
+ puts " Refresh Token: #{token.refresh_token[0..5]}...#{token.refresh_token[-4..-1]}" if token.refresh_token
259
+
260
+ puts "\nAuthenticated request successful!"
261
+
262
+ {
263
+ status: 'success',
264
+ authenticated: true,
265
+ auth_method: 'OAuth2',
266
+ token_expiry: token.expires_at,
267
+ scopes: token.scope
268
+ }
269
+ end
270
+
271
+ # Main execution logic
272
+ begin
273
+ # Try to use saved refresh token
274
+ saved_token = nil
275
+ if options[:use_refresh_token] && File.exist?(token_storage_file)
276
+ puts 'Found saved token, attempting to use it...'
277
+ saved_token = load_token_from_file(token_storage_file)
278
+
279
+ if saved_token
280
+ if saved_token.expired?
281
+ puts 'Saved token is expired, attempting to refresh it...'
282
+
283
+ # Create a token store
284
+ token_store = Legate::Auth::TokenStore.new(session_service: session_service)
285
+
286
+ # Create a token manager
287
+ token_manager = Legate::Auth::TokenManager.new(token_store)
288
+
289
+ # Refresh the token
290
+ begin
291
+ refreshed_token = token_manager.refresh_token(oauth2_scheme, credential, saved_token)
292
+ puts 'Token refreshed successfully!'
293
+
294
+ # Save the refreshed token
295
+ save_token_to_file(refreshed_token, token_storage_file)
296
+
297
+ # Make a request with the refreshed token
298
+ result = make_authenticated_request(refreshed_token)
299
+ puts "\nAuthentication via refresh token successful!"
300
+ exit 0
301
+ rescue StandardError => e
302
+ puts "Token refresh failed: #{e.message}"
303
+ puts 'Will proceed with interactive authentication...'
304
+ # Continue with interactive flow below
305
+ end
306
+ else
307
+ puts 'Saved token is still valid, using it...'
308
+ result = make_authenticated_request(saved_token)
309
+ puts "\nAuthentication with saved token successful!"
310
+ exit 0
311
+ end
312
+ end
313
+ end
314
+
315
+ # Handle authentication response if requested
316
+ if options[:handle_response]
317
+ unless options[:request_id] && options[:response_uri]
318
+ puts 'Error: Both --request-id and --response-uri are required with --handle-response'
319
+ exit 1
320
+ end
321
+
322
+ puts "Handling authentication response for request ID: #{options[:request_id]}"
323
+
324
+ # Create a response object
325
+ response = { 'response_uri' => options[:response_uri] }
326
+
327
+ # Handle the authentication response
328
+ result = context.handle_auth_response(options[:request_id], response)
329
+
330
+ if result[:status] == :completed
331
+ puts 'Authentication completed successfully!'
332
+ token = result[:credential]
333
+
334
+ # Save the token for future use
335
+ save_token_to_file(token, token_storage_file)
336
+
337
+ # Make a request with the token
338
+ make_authenticated_request(token)
339
+ else
340
+ puts 'Authentication not completed:'
341
+ puts result.inspect
342
+ end
343
+
344
+ exit 0
345
+ end
346
+
347
+ # Start server if requested
348
+ if options[:with_server]
349
+ puts 'Starting local server to handle OAuth2 callback...'
350
+ request_id_holder = { id: nil }
351
+ server = create_callback_server(context, request_id_holder)
352
+
353
+ begin
354
+ # Start the server
355
+ server_thread = Thread.new { server.start }
356
+
357
+ # Use with_authentication to create a fiber context with auth support
358
+ result = context.with_authentication do
359
+ # This callback will be called when an authentication request is yielded
360
+
361
+ # Authenticate with the scheme and credential
362
+ token = context.auth_session(
363
+ oauth2_scheme,
364
+ credential,
365
+ redirect_uri: options[:redirect_uri]
366
+ )
367
+
368
+ if token
369
+ # Save the token for future use
370
+ save_token_to_file(token, token_storage_file)
371
+
372
+ # Make authenticated request
373
+ make_authenticated_request(token)
374
+ else
375
+ puts 'Failed to get authentication token'
376
+ { status: :error, message: 'Authentication failed' }
377
+ end
378
+ end
379
+
380
+ puts "Result: #{result.inspect}"
381
+ ensure
382
+ # Stop the server
383
+ server.stop
384
+ # Wait for server thread to finish
385
+ server_thread.join if server_thread
386
+ end
387
+ else
388
+ # Manual flow without a local server
389
+ puts 'Starting OAuth2 authentication process...'
390
+
391
+ # Create an auth runner
392
+ auth_runner = Legate::Auth::Runner.new(session_service: session_service)
393
+
394
+ # Start authentication
395
+ auth_result = auth_runner.authenticate(oauth2_scheme, credential,
396
+ redirect_uri: options[:redirect_uri])
397
+
398
+ # Check if we got a token immediately (unlikely for OAuth2)
399
+ if auth_result.is_a?(Legate::Auth::ExchangedCredential)
400
+ puts 'Received token immediately (unusual for OAuth2):'
401
+ make_authenticated_request(auth_result)
402
+ else
403
+ # We should have an authentication request
404
+ request_id = auth_result[:request_id]
405
+ auth_request = auth_result[:auth_request]
406
+
407
+ puts 'Authentication required. Please visit this URL to authorize:'
408
+ puts " #{auth_request[:url]}"
409
+ puts
410
+ puts 'After authorization, you will be redirected. Copy the entire URL from your browser'
411
+ puts 'and run this command to complete authentication:'
412
+ puts
413
+ puts " ruby #{__FILE__} --handle-response --request-id #{request_id} --response-uri 'PASTE_URL_HERE'"
414
+ end
415
+ end
416
+ rescue StandardError => e
417
+ puts "Error: #{e.class}: #{e.message}"
418
+ puts e.backtrace.join("\n") if options[:verbose]
419
+ end