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,281 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'legate'
5
+ require 'legate/auth'
6
+ require 'legate/auth/runner'
7
+ require 'legate/auth/schemes/oauth2'
8
+ require 'legate/tool_context'
9
+ require 'legate/web/server'
10
+ require 'launchy'
11
+ require 'securerandom'
12
+
13
+ # This example demonstrates the fiber-based authentication flow
14
+ # It shows how to:
15
+ # 1. Set up an authentication runner
16
+ # 2. Use the auth_session method within a tool
17
+ # 3. Handle authentication requests and responses
18
+ # 4. Launch a browser for OAuth2 authorization
19
+ # 5. Start a temporary web server to receive the OAuth2 callback
20
+
21
+ class ExampleTool < Legate::Tool::Base
22
+ name :example_tool
23
+ description 'Example tool demonstrating fiber-based authentication'
24
+ version '1.0.0'
25
+
26
+ parameter :action, type: :symbol, required: true,
27
+ description: 'Action to perform (call_api, handle_response, run_with_server)'
28
+ parameter :request_id, type: :string, required: false,
29
+ description: 'The authentication request ID for handling responses'
30
+ parameter :response_uri, type: :string, required: false,
31
+ description: 'The response URI from the OAuth2 callback'
32
+ parameter :client_id, type: :string, required: false,
33
+ description: 'OAuth2 client ID'
34
+ parameter :client_secret, type: :string, required: false,
35
+ description: 'OAuth2 client secret'
36
+
37
+ def execute
38
+ case parameters[:action]
39
+ when :call_api
40
+ # Use with_authentication to create a fiber context with auth support
41
+ context.with_authentication do
42
+ call_api_with_auth
43
+ end
44
+ when :handle_response
45
+ # Handle an authentication response
46
+ handle_auth_response
47
+ when :run_with_server
48
+ # Run with a local server to automatically handle the OAuth2 callback
49
+ run_with_local_server
50
+ else
51
+ raise Legate::ToolError, "Unknown action: #{parameters[:action]}"
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def call_api_with_auth
58
+ # Define an OAuth2 scheme
59
+ oauth2_scheme = create_oauth2_scheme
60
+
61
+ # Define a credential
62
+ credential = create_credential
63
+
64
+ # Start an authentication session
65
+ # This will yield the fiber if authentication is needed
66
+ token = context.auth_session(
67
+ oauth2_scheme,
68
+ credential,
69
+ redirect_uri: 'http://localhost:3000/auth/callback'
70
+ )
71
+
72
+ # If we get here, we have a valid token
73
+ if token
74
+ # Simulate an API call with the token
75
+ api_call_result = simulate_api_call(token)
76
+
77
+ {
78
+ status: :success,
79
+ message: 'Successfully authenticated and called API',
80
+ token_type: token.token_type,
81
+ expires_in: token.expires_in,
82
+ scope: token.scope,
83
+ api_result: api_call_result
84
+ }
85
+ else
86
+ { status: :error, message: 'Failed to authenticate' }
87
+ end
88
+ end
89
+
90
+ def handle_auth_response
91
+ # Get required parameters
92
+ request_id = parameters[:request_id]
93
+ response_uri = parameters[:response_uri]
94
+
95
+ raise Legate::ToolArgumentError, 'Both request_id and response_uri are required' unless request_id && response_uri
96
+
97
+ # Create a response object
98
+ response = { 'response_uri' => response_uri }
99
+
100
+ # Handle the authentication response
101
+ result = context.handle_auth_response(request_id, response)
102
+
103
+ # Return the result
104
+ {
105
+ status: result[:status],
106
+ message: result[:status] == :completed ? 'Authentication completed' : 'Authentication in progress',
107
+ details: result
108
+ }
109
+ end
110
+
111
+ def run_with_local_server
112
+ # Create a web server to handle the OAuth2 callback
113
+ server = create_callback_server
114
+
115
+ begin
116
+ # Start the server
117
+ server_thread = Thread.new { server.start }
118
+
119
+ # Use with_authentication to create a fiber context with auth support
120
+ result = context.with_authentication do
121
+ # This callback will be called when an authentication request is yielded
122
+
123
+ # Call API with authentication
124
+ call_api_with_auth
125
+ end
126
+
127
+ result
128
+ ensure
129
+ # Stop the server
130
+ server.stop
131
+ # Wait for server thread to finish
132
+ server_thread.join if server_thread
133
+ end
134
+ end
135
+
136
+ def create_oauth2_scheme
137
+ Legate::Auth::Schemes::OAuth2.new(
138
+ authorization_url: 'https://accounts.google.com/o/oauth2/auth',
139
+ token_url: 'https://oauth2.googleapis.com/token',
140
+ scopes: %w[email profile],
141
+ use_pkce: true,
142
+ additional_params: {
143
+ prompt: 'consent',
144
+ access_type: 'offline'
145
+ }
146
+ )
147
+ end
148
+
149
+ def create_credential
150
+ client_id = parameters[:client_id] || ENV['OAUTH_CLIENT_ID'] || 'your-client-id'
151
+ client_secret = parameters[:client_secret] || ENV['OAUTH_CLIENT_SECRET'] || 'your-client-secret'
152
+
153
+ Legate::Auth::Credential.new(
154
+ auth_type: :oauth2,
155
+ client_id: client_id,
156
+ client_secret: client_secret
157
+ )
158
+ end
159
+
160
+ def simulate_api_call(token)
161
+ # In a real application, you would make an actual API call using the token
162
+ # This is just a simulation
163
+ {
164
+ api_name: 'Example API',
165
+ called_at: Time.now.iso8601,
166
+ authorization: "#{token.token_type} #{token.access_token[0..5]}...[truncated]"
167
+ }
168
+ end
169
+
170
+ def create_callback_server
171
+ # Create a simple web server to handle OAuth2 callbacks
172
+ server = Legate::Web::Server.new(port: 3000)
173
+
174
+ # Add a route to handle the OAuth2 callback
175
+ server.add_route('GET', '/auth/callback') do |req, res|
176
+ # Extract the authorization code from the query parameters
177
+ code = req.query['code']
178
+ state = req.query['state']
179
+ error = req.query['error']
180
+
181
+ if error
182
+ res.status = 400
183
+ res.body = "Authentication failed: #{error}"
184
+ elsif code
185
+ # Convert the request to a response URI
186
+ response_uri = "http://localhost:3000/auth/callback?#{req.query_string}"
187
+
188
+ # Find the active auth request
189
+ active_requests = context.instance_variable_get(:@auth_runner)&.instance_variable_get(:@active_coordinators)
190
+ request_id = active_requests&.keys&.first
191
+
192
+ if request_id
193
+ # Handle the auth response
194
+ result = context.handle_auth_response(request_id, { 'response_uri' => response_uri })
195
+
196
+ # Show a success page
197
+ res.status = 200
198
+ res.body = '<html><body><h1>Authentication Successful</h1><p>You can close this window and return to the application.</p></body></html>'
199
+ else
200
+ res.status = 400
201
+ res.body = 'No active authentication request found'
202
+ end
203
+ else
204
+ res.status = 400
205
+ res.body = 'Missing required parameters'
206
+ end
207
+ end
208
+
209
+ server
210
+ end
211
+
212
+ def handle_auth_request_with_browser(auth_request)
213
+ # Extract the authorization URL from the auth request
214
+ if auth_request[:auth_request][:type] == 'authorization_request'
215
+ auth_url = auth_request[:auth_request][:url]
216
+
217
+ # Open the browser with the authorization URL
218
+ puts "Opening browser to authorize: #{auth_url}"
219
+ Launchy.open(auth_url)
220
+
221
+ puts 'Waiting for OAuth2 callback...'
222
+ else
223
+ puts "Unhandled auth request type: #{auth_request[:auth_request][:type]}"
224
+ end
225
+
226
+ # Return nil to continue waiting for the callback
227
+ nil
228
+ end
229
+ end
230
+
231
+ # Register the tool
232
+ Legate.register_tool(ExampleTool)
233
+
234
+ # Example usage:
235
+ # 1. First call with action: :call_api
236
+ # - This will start authentication and yield an auth request
237
+ # 2. Use the request_id from the first call and pass it along with a response_uri
238
+ # - Call with action: :handle_response, request_id: "...", response_uri: "..."
239
+ # 3. Or use action: :run_with_server to launch a browser and handle the callback automatically
240
+ if $PROGRAM_NAME == __FILE__
241
+ # Setup a session service for the example
242
+ require 'legate/session_service/in_memory'
243
+ session_service = Legate::SessionService::InMemory.new
244
+
245
+ # Create a tool context with the session service
246
+ context = Legate::ToolContext.new(session_service: session_service)
247
+
248
+ # Create and run the tool
249
+ tool = ExampleTool.new
250
+
251
+ begin
252
+ # Run with a local server to automatically handle the OAuth2 callback
253
+ # This is the most user-friendly approach for a CLI tool
254
+ if ARGV.include?('--with-server')
255
+ result = tool.run(context, action: :run_with_server)
256
+ puts "Result: #{result.inspect}"
257
+ else
258
+ # Attempt to call API - this will yield for authentication
259
+ result = tool.run(context, action: :call_api)
260
+
261
+ # The result will include auth_request if authentication is needed
262
+ if result.is_a?(Hash) && result[:auth_request]
263
+ puts 'Authentication required. Please visit:'
264
+ puts result[:auth_request][:url] if result[:auth_request][:url]
265
+ puts "\nAfter completing authentication, run:"
266
+ puts "ruby #{__FILE__} --handle-response --request-id #{result[:request_id]} --response-uri 'PASTE_RESPONSE_URI_HERE'"
267
+ else
268
+ puts "Result: #{result.inspect}"
269
+ end
270
+ end
271
+ rescue StandardError => e
272
+ puts "Error: #{e.class}: #{e.message}"
273
+ puts e.backtrace.join("\n") if ENV['DEBUG']
274
+
275
+ if e.message.include?('Authentication session not available')
276
+ puts "\nNote: This example requires proper OAuth2 credentials."
277
+ puts 'You can provide them as environment variables:'
278
+ puts "OAUTH_CLIENT_ID='your-client-id' OAUTH_CLIENT_SECRET='your-client-secret' ruby #{__FILE__} --with-server"
279
+ end
280
+ end
281
+ end
@@ -0,0 +1,403 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'legate'
5
+ require 'legate/auth'
6
+ require 'legate/auth/runner'
7
+ require 'legate/auth/schemes/openid_connect'
8
+ require 'legate/tool_context'
9
+ require 'legate/web/server'
10
+ require 'launchy'
11
+ require 'securerandom'
12
+ require 'json'
13
+
14
+ # This example demonstrates the fiber-based OIDC (OpenID Connect) authentication flow
15
+ # It shows how to:
16
+ # 1. Set up an authentication runner with OIDC
17
+ # 2. Use the auth_session method within a tool
18
+ # 3. Handle authentication requests and responses
19
+ # 4. Retrieve user information from the identity provider
20
+ # 5. Launch a browser for OIDC authorization
21
+ # 6. Start a temporary web server to receive the OIDC callback
22
+
23
+ class OIDCExampleTool < Legate::Tool::Base
24
+ name :oidc_example_tool
25
+ description 'Example tool demonstrating fiber-based OIDC authentication'
26
+ version '1.0.0'
27
+
28
+ parameter :action, type: :symbol, required: true,
29
+ description: 'Action to perform (call_api, handle_response, run_with_server)'
30
+ parameter :request_id, type: :string, required: false,
31
+ description: 'The authentication request ID for handling responses'
32
+ parameter :response_uri, type: :string, required: false,
33
+ description: 'The response URI from the OIDC callback'
34
+ parameter :client_id, type: :string, required: false,
35
+ description: 'OIDC client ID'
36
+ parameter :client_secret, type: :string, required: false,
37
+ description: 'OIDC client secret'
38
+ parameter :provider, type: :string, required: false, default: 'google',
39
+ description: 'OIDC provider (google, auth0, etc.)'
40
+
41
+ def execute
42
+ case parameters[:action]
43
+ when :call_api
44
+ # Use with_authentication to create a fiber context with auth support
45
+ context.with_authentication do
46
+ call_api_with_auth
47
+ end
48
+ when :handle_response
49
+ # Handle an authentication response
50
+ handle_auth_response
51
+ when :run_with_server
52
+ # Run with a local server to automatically handle the OIDC callback
53
+ run_with_local_server
54
+ else
55
+ raise Legate::ToolError, "Unknown action: #{parameters[:action]}"
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def call_api_with_auth
62
+ # Define an OIDC scheme
63
+ oidc_scheme = create_oidc_scheme
64
+
65
+ # Define a credential
66
+ credential = create_credential
67
+
68
+ # Start an authentication session
69
+ # This will yield the fiber if authentication is needed
70
+ token = context.auth_session(
71
+ oidc_scheme,
72
+ credential,
73
+ redirect_uri: 'http://localhost:3000/auth/callback'
74
+ )
75
+
76
+ # If we get here, we have a valid token
77
+ if token
78
+ # Get user info from the token metadata or fetch it if needed
79
+ user_info = token.metadata&.dig(:userinfo) || fetch_user_info(token, oidc_scheme)
80
+
81
+ # Simulate an API call with the token
82
+ api_call_result = simulate_api_call(token)
83
+
84
+ {
85
+ status: :success,
86
+ message: 'Successfully authenticated and called API',
87
+ token_type: token.token_type,
88
+ expires_in: token.expires_in,
89
+ scope: token.scope,
90
+ user_info: user_info,
91
+ api_result: api_call_result
92
+ }
93
+ else
94
+ { status: :error, message: 'Failed to authenticate' }
95
+ end
96
+ end
97
+
98
+ def handle_auth_response
99
+ # Get required parameters
100
+ request_id = parameters[:request_id]
101
+ response_uri = parameters[:response_uri]
102
+
103
+ raise Legate::ToolArgumentError, 'Both request_id and response_uri are required' unless request_id && response_uri
104
+
105
+ # Create a response object
106
+ response = { 'response_uri' => response_uri }
107
+
108
+ # Handle the authentication response
109
+ result = context.handle_auth_response(request_id, response)
110
+
111
+ # Return the result
112
+ {
113
+ status: result[:status],
114
+ message: result[:status] == :completed ? 'Authentication completed' : 'Authentication in progress',
115
+ details: result
116
+ }
117
+ end
118
+
119
+ def run_with_local_server
120
+ # Create a web server to handle the OIDC callback
121
+ server = create_callback_server
122
+
123
+ begin
124
+ # Start the server
125
+ server_thread = Thread.new { server.start }
126
+
127
+ # Use with_authentication to create a fiber context with auth support
128
+ result = context.with_authentication do
129
+ # This callback will be called when an authentication request is yielded
130
+
131
+ # Call API with authentication
132
+ call_api_with_auth
133
+ end
134
+
135
+ result
136
+ ensure
137
+ # Stop the server
138
+ server.stop
139
+ # Wait for server thread to finish
140
+ server_thread.join if server_thread
141
+ end
142
+ end
143
+
144
+ def create_oidc_scheme
145
+ # Configure based on the selected provider
146
+ case parameters[:provider]&.downcase
147
+ when 'google'
148
+ Legate::Auth::Schemes::OpenIDConnect.new(
149
+ authorization_url: 'https://accounts.google.com/o/oauth2/auth',
150
+ token_url: 'https://oauth2.googleapis.com/token',
151
+ userinfo_url: 'https://openidconnect.googleapis.com/v1/userinfo',
152
+ scopes: %w[openid email profile],
153
+ fetch_userinfo: true,
154
+ use_pkce: true,
155
+ additional_params: {
156
+ prompt: 'consent',
157
+ access_type: 'offline'
158
+ }
159
+ )
160
+ when 'auth0'
161
+ # Your Auth0 domain - replace with your actual domain
162
+ domain = ENV['AUTH0_DOMAIN'] || 'your-domain.auth0.com'
163
+
164
+ Legate::Auth::Schemes::OpenIDConnect.new(
165
+ authorization_url: "https://#{domain}/authorize",
166
+ token_url: "https://#{domain}/oauth/token",
167
+ userinfo_url: "https://#{domain}/userinfo",
168
+ scopes: %w[openid email profile],
169
+ fetch_userinfo: true,
170
+ use_pkce: true
171
+ )
172
+ else
173
+ # Default to a generic configuration
174
+ Legate::Auth::Schemes::OpenIDConnect.new(
175
+ authorization_url: 'https://example.com/authorize',
176
+ token_url: 'https://example.com/token',
177
+ userinfo_url: 'https://example.com/userinfo',
178
+ scopes: %w[openid email profile],
179
+ fetch_userinfo: true,
180
+ use_pkce: true
181
+ )
182
+ end
183
+ end
184
+
185
+ def create_credential
186
+ client_id = parameters[:client_id] || ENV['OIDC_CLIENT_ID'] || 'your-client-id'
187
+ client_secret = parameters[:client_secret] || ENV['OIDC_CLIENT_SECRET'] || 'your-client-secret'
188
+
189
+ Legate::Auth::Credential.new(
190
+ auth_type: :oidc,
191
+ client_id: client_id,
192
+ client_secret: client_secret
193
+ )
194
+ end
195
+
196
+ def fetch_user_info(token, oidc_scheme)
197
+ return {} unless token&.access_token && oidc_scheme.userinfo_url
198
+
199
+ begin
200
+ oidc_scheme.fetch_userinfo(token)
201
+ rescue Legate::Auth::Error => e
202
+ Legate.logger.warn("Failed to fetch userinfo: #{e.message}")
203
+ { error: e.message }
204
+ end
205
+ end
206
+
207
+ def simulate_api_call(token)
208
+ # In a real application, you would make an actual API call using the token
209
+ # This is just a simulation
210
+ {
211
+ api_name: 'Example OIDC API',
212
+ called_at: Time.now.iso8601,
213
+ authorization: "#{token.token_type} #{token.access_token[0..5]}...[truncated]"
214
+ }
215
+ end
216
+
217
+ def create_callback_server
218
+ # Create a simple web server to handle OIDC callbacks
219
+ server = Legate::Web::Server.new(port: 3000)
220
+
221
+ # Add a route to handle the OIDC callback
222
+ server.add_route('GET', '/auth/callback') do |req, res|
223
+ # Extract the authorization code from the query parameters
224
+ code = req.query['code']
225
+ state = req.query['state']
226
+ error = req.query['error']
227
+
228
+ if error
229
+ res.status = 400
230
+ res.body = "Authentication failed: #{error}"
231
+ elsif code
232
+ # Convert the request to a response URI
233
+ response_uri = "http://localhost:3000/auth/callback?#{req.query_string}"
234
+
235
+ # Find the active auth request
236
+ active_requests = context.instance_variable_get(:@auth_runner)&.instance_variable_get(:@active_coordinators)
237
+ request_id = active_requests&.keys&.first
238
+
239
+ if request_id
240
+ # Handle the auth response
241
+ result = context.handle_auth_response(request_id, { 'response_uri' => response_uri })
242
+
243
+ # Show a success page with user info if available
244
+ res.status = 200
245
+
246
+ # Try to extract user info if available
247
+ user_info = result[:credential]&.metadata&.dig(:userinfo)
248
+ user_info_html = ''
249
+
250
+ if user_info
251
+ user_info_html = "<div style='margin-top: 20px; padding: 10px; background-color: #f5f5f5; border-radius: 5px;'>"
252
+ user_info_html += '<h2>User Information</h2>'
253
+ user_info_html += '<ul>'
254
+
255
+ # Display some common OIDC user info fields
256
+ user_info_html += "<li><strong>Name:</strong> #{user_info['name']}</li>" if user_info['name']
257
+ user_info_html += "<li><strong>Email:</strong> #{user_info['email']}</li>" if user_info['email']
258
+ user_info_html += "<li><strong>Picture:</strong> <img src='#{user_info['picture']}' width='50' height='50' style='border-radius: 50%;'/></li>" if user_info['picture']
259
+
260
+ user_info_html += '</ul></div>'
261
+ end
262
+
263
+ res.body = <<~HTML
264
+ <!DOCTYPE html>
265
+ <html>
266
+ <head>
267
+ <title>Authentication Successful</title>
268
+ <style>
269
+ body { font-family: Arial, sans-serif; margin: 40px; text-align: center; }
270
+ h1 { color: #2c3e50; }
271
+ .container { max-width: 600px; margin: 0 auto; }
272
+ .success { color: #27ae60; }
273
+ </style>
274
+ </head>
275
+ <body>
276
+ <div class="container">
277
+ <h1>Authentication Successful</h1>
278
+ <p class="success">✓ You have successfully authenticated</p>
279
+ <p>You can close this window and return to the application.</p>
280
+ #{user_info_html}
281
+ </div>
282
+ </body>
283
+ </html>
284
+ HTML
285
+ else
286
+ res.status = 400
287
+ res.body = 'No active authentication request found'
288
+ end
289
+ else
290
+ res.status = 400
291
+ res.body = 'Missing required parameters'
292
+ end
293
+ end
294
+
295
+ server
296
+ end
297
+
298
+ def handle_auth_request_with_browser(auth_request)
299
+ # Extract the authorization URL from the auth request
300
+ if auth_request[:auth_request][:type] == 'authorization_request'
301
+ auth_url = auth_request[:auth_request][:url]
302
+
303
+ # Open the browser with the authorization URL
304
+ puts "Opening browser to authorize: #{auth_url}"
305
+ Launchy.open(auth_url)
306
+
307
+ puts 'Waiting for OIDC callback...'
308
+ else
309
+ puts "Unhandled auth request type: #{auth_request[:auth_request][:type]}"
310
+ end
311
+
312
+ # Return nil to continue waiting for the callback
313
+ nil
314
+ end
315
+ end
316
+
317
+ # Register the tool
318
+ Legate.register_tool(OIDCExampleTool)
319
+
320
+ # Example usage:
321
+ # 1. Run with a local server to automatically handle the OIDC callback:
322
+ # ruby fiber_oidc_example.rb --with-server --provider google
323
+ #
324
+ # 2. Set credentials via environment variables:
325
+ # OIDC_CLIENT_ID='your-client-id' OIDC_CLIENT_SECRET='your-client-secret' ruby fiber_oidc_example.rb --with-server
326
+ if $PROGRAM_NAME == __FILE__
327
+ # Setup a session service for the example
328
+ require 'legate/session_service/in_memory'
329
+ session_service = Legate::SessionService::InMemory.new
330
+
331
+ # Create a tool context with the session service
332
+ context = Legate::ToolContext.new(session_service: session_service)
333
+
334
+ # Process command line arguments
335
+ provider = 'google' # Default provider
336
+
337
+ ARGV.each_with_index do |arg, index|
338
+ provider = ARGV[index + 1] if arg == '--provider' && ARGV[index + 1]
339
+ end
340
+
341
+ # Create and run the tool
342
+ tool = OIDCExampleTool.new
343
+
344
+ begin
345
+ # Run with a local server to automatically handle the OIDC callback
346
+ if ARGV.include?('--with-server')
347
+ puts "Starting OIDC authentication flow with provider: #{provider}"
348
+ result = tool.run(context, action: :run_with_server, provider: provider)
349
+ puts "\nResult:"
350
+ begin
351
+ puts JSON.pretty_generate(result)
352
+ rescue StandardError
353
+ puts result.inspect
354
+ end
355
+ elsif ARGV.include?('--handle-response')
356
+ # Extract request_id and response_uri from args
357
+ request_id = nil
358
+ response_uri = nil
359
+
360
+ ARGV.each_with_index do |arg, index|
361
+ if arg == '--request-id' && ARGV[index + 1]
362
+ request_id = ARGV[index + 1]
363
+ elsif arg == '--response-uri' && ARGV[index + 1]
364
+ response_uri = ARGV[index + 1]
365
+ end
366
+ end
367
+
368
+ if request_id && response_uri
369
+ result = tool.run(context, action: :handle_response, request_id: request_id, response_uri: response_uri)
370
+ puts "Result: #{result.inspect}"
371
+ else
372
+ puts 'Error: --request-id and --response-uri parameters are required for --handle-response'
373
+ end
374
+ else
375
+ # Attempt to call API - this will yield for authentication
376
+ result = tool.run(context, action: :call_api, provider: provider)
377
+
378
+ # The result will include auth_request if authentication is needed
379
+ if result.is_a?(Hash) && result[:auth_request]
380
+ puts 'Authentication required. Please visit:'
381
+ puts result[:auth_request][:url] if result[:auth_request][:url]
382
+ puts "\nAfter completing authentication, run:"
383
+ puts "ruby #{__FILE__} --handle-response --request-id #{result[:request_id]} --response-uri 'PASTE_RESPONSE_URI_HERE'"
384
+ else
385
+ puts 'Result:'
386
+ begin
387
+ puts JSON.pretty_generate(result)
388
+ rescue StandardError
389
+ puts result.inspect
390
+ end
391
+ end
392
+ end
393
+ rescue StandardError => e
394
+ puts "Error: #{e.class}: #{e.message}"
395
+ puts e.backtrace.join("\n") if ENV['DEBUG']
396
+
397
+ if e.message.include?('Authentication session not available')
398
+ puts "\nNote: This example requires proper OIDC credentials."
399
+ puts 'You can provide them as environment variables:'
400
+ puts "OIDC_CLIENT_ID='your-client-id' OIDC_CLIENT_SECRET='your-client-secret' ruby #{__FILE__} --with-server --provider google"
401
+ end
402
+ end
403
+ end