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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +345 -0
- data/bin/legate +13 -0
- data/examples/00_quickstart.rb +51 -0
- data/examples/01_simple_agent.rb +105 -0
- data/examples/02_multi_tool_agent.rb +140 -0
- data/examples/03_custom_tool.rb +93 -0
- data/examples/04_agent_instructions.rb +84 -0
- data/examples/05_state_and_sessions.rb +91 -0
- data/examples/06_callbacks.rb +186 -0
- data/examples/07_async_jobs.rb +112 -0
- data/examples/08_loop_agent.rb +197 -0
- data/examples/09_sequential_workflow.rb +40 -0
- data/examples/10_parallel_workflow.rb +34 -0
- data/examples/11_agent_delegation.rb +24 -0
- data/examples/12_http_client_tool.rb +156 -0
- data/examples/13_authentication.rb +220 -0
- data/examples/14_mcp_client.rb +154 -0
- data/examples/15_mcp_server.rb +79 -0
- data/examples/16_webhooks.rb +91 -0
- data/examples/README_sequential_agents.md +164 -0
- data/examples/advanced/auth/cookie_auth_tool.rb +146 -0
- data/examples/advanced/auth/custom_auth_flows_example.rb +626 -0
- data/examples/advanced/auth/excon_middleware.rb +317 -0
- data/examples/advanced/auth/excon_middleware_auth.rb +399 -0
- data/examples/advanced/auth/fiber_auth_example.rb +281 -0
- data/examples/advanced/auth/fiber_oidc_example.rb +403 -0
- data/examples/advanced/auth/httpbin_bearer_tool.rb +159 -0
- data/examples/advanced/auth/oauth2_auth.rb +419 -0
- data/examples/advanced/auth/oidc_auth.rb +514 -0
- data/examples/advanced/auth/openweather_api.rb +251 -0
- data/examples/advanced/auth/openweather_tool.rb +153 -0
- data/examples/advanced/auth/query_param_middleware_test.rb +138 -0
- data/examples/advanced/auth/service_account.rb +135 -0
- data/examples/advanced/auth/test_with_httpbin.rb +202 -0
- data/examples/advanced/auth/token_lifecycle_example.rb +428 -0
- data/examples/advanced/callback_monitoring.rb +679 -0
- data/examples/advanced/mas/fixed_delegation_example.rb +191 -0
- data/examples/advanced/mas/loop_workflow.rb +28 -0
- data/examples/advanced/mas/mock_planner.rb +77 -0
- data/examples/advanced/mas/proper_delegation_example.rb +276 -0
- data/examples/advanced/mcp/legate_mcp_server_resource_example.rb +182 -0
- data/examples/advanced/mcp/mcp_resource_server_example.rb +309 -0
- data/examples/advanced/mcp/mcp_server_async.rb +76 -0
- data/examples/advanced/mcp/mcp_server_async_tools.rb +122 -0
- data/examples/advanced/mcp/mcp_server_legate_agent.rb +95 -0
- data/examples/advanced/mcp/mcp_server_rack.rb +89 -0
- data/examples/advanced/random_calculator.rb +104 -0
- data/examples/advanced/sleep_agent.rb +153 -0
- data/examples/advanced/webhooks/webhook_e2e_runner.rb +110 -0
- data/examples/advanced/webhooks/webhook_receiver_agent.rb +58 -0
- data/examples/advanced/workflows/task_refinement_loop_agent.rb +278 -0
- data/examples/advanced/workflows/travel_planner_auto_sequential.rb +444 -0
- data/examples/advanced/workflows/travel_planner_parallel.rb +656 -0
- data/examples/advanced/workflows/travel_planner_sequential.rb +512 -0
- data/examples/tools/oauth2_example.rb +136 -0
- data/examples/tools/sleepy_tool.rb +42 -0
- data/lib/legate/activity_log.rb +71 -0
- data/lib/legate/agent.rb +959 -0
- data/lib/legate/agent_code_generator.rb +185 -0
- data/lib/legate/agent_definition.rb +812 -0
- data/lib/legate/agentic/decision.rb +49 -0
- data/lib/legate/agentic/loop.rb +134 -0
- data/lib/legate/agentic.rb +5 -0
- data/lib/legate/agents/loop_agent.rb +248 -0
- data/lib/legate/agents/parallel_agent.rb +163 -0
- data/lib/legate/agents/sequential_agent.rb +190 -0
- data/lib/legate/agents.rb +14 -0
- data/lib/legate/auth/config.rb +148 -0
- data/lib/legate/auth/coordinator.rb +218 -0
- data/lib/legate/auth/coordinators/oauth2_coordinator.rb +99 -0
- data/lib/legate/auth/coordinators/oidc_coordinator.rb +68 -0
- data/lib/legate/auth/coordinators/service_account_coordinator.rb +122 -0
- data/lib/legate/auth/credential.rb +157 -0
- data/lib/legate/auth/encryption.rb +108 -0
- data/lib/legate/auth/error.rb +94 -0
- data/lib/legate/auth/exchanged_credential.rb +180 -0
- data/lib/legate/auth/excon_middleware.rb +285 -0
- data/lib/legate/auth/http_client_utils.rb +364 -0
- data/lib/legate/auth/manager.rb +531 -0
- data/lib/legate/auth/manager_store.rb +394 -0
- data/lib/legate/auth/middleware_factory.rb +290 -0
- data/lib/legate/auth/runner.rb +279 -0
- data/lib/legate/auth/scheme.rb +125 -0
- data/lib/legate/auth/schemes/api_key.rb +212 -0
- data/lib/legate/auth/schemes/google_service_account.rb +108 -0
- data/lib/legate/auth/schemes/http_bearer.rb +98 -0
- data/lib/legate/auth/schemes/oauth2.rb +396 -0
- data/lib/legate/auth/schemes/openid_connect.rb +346 -0
- data/lib/legate/auth/schemes/service_account.rb +388 -0
- data/lib/legate/auth/schemes.rb +40 -0
- data/lib/legate/auth/token_manager.rb +362 -0
- data/lib/legate/auth/token_store.rb +86 -0
- data/lib/legate/auth/tool_context_extension.rb +97 -0
- data/lib/legate/auth/tool_integration.rb +188 -0
- data/lib/legate/auth/url_guard.rb +81 -0
- data/lib/legate/auth.rb +453 -0
- data/lib/legate/callbacks/callback_context.rb +71 -0
- data/lib/legate/cli/agent_commands.rb +950 -0
- data/lib/legate/cli/auth_commands.rb +520 -0
- data/lib/legate/cli/base_command.rb +24 -0
- data/lib/legate/cli/deployment_commands.rb +934 -0
- data/lib/legate/cli/output_helper.rb +108 -0
- data/lib/legate/cli/session_commands.rb +138 -0
- data/lib/legate/cli/skaffold_commands.rb +223 -0
- data/lib/legate/cli/tool_commands.rb +261 -0
- data/lib/legate/cli/web_commands.rb +182 -0
- data/lib/legate/cli.rb +40 -0
- data/lib/legate/configuration/webhooks.rb +113 -0
- data/lib/legate/configuration.rb +39 -0
- data/lib/legate/definition_store.rb +23 -0
- data/lib/legate/errors.rb +118 -0
- data/lib/legate/event.rb +161 -0
- data/lib/legate/gemini_ai_beta_patch.rb +39 -0
- data/lib/legate/generators/agent_generator.rb +412 -0
- data/lib/legate/generators/code_validator.rb +48 -0
- data/lib/legate/generators/legate/install_generator.rb +35 -0
- data/lib/legate/generators/legate/templates/create_legate_tables.rb.tt +36 -0
- data/lib/legate/generators/legate/templates/initializer.rb +18 -0
- data/lib/legate/generators/runtime_tool_loader.rb +76 -0
- data/lib/legate/generators/tool_generator.rb +408 -0
- data/lib/legate/generators.rb +11 -0
- data/lib/legate/global_definition_registry.rb +506 -0
- data/lib/legate/global_tool_manager.rb +135 -0
- data/lib/legate/llm/adapter.rb +69 -0
- data/lib/legate/llm/gemini.rb +172 -0
- data/lib/legate/llm/ollama.rb +80 -0
- data/lib/legate/llm.rb +34 -0
- data/lib/legate/mcp/client.rb +320 -0
- data/lib/legate/mcp/connection/sse.rb +292 -0
- data/lib/legate/mcp/connection/stdio.rb +273 -0
- data/lib/legate/mcp/connection_manager.rb +103 -0
- data/lib/legate/mcp/server/legate_agent_adapter.rb +170 -0
- data/lib/legate/mcp/server/legate_direct_agent_adapter.rb +140 -0
- data/lib/legate/mcp/server/legate_tool_adapter.rb +119 -0
- data/lib/legate/mcp/tool_wrapper.rb +138 -0
- data/lib/legate/mcp/util/schema_converter.rb +134 -0
- data/lib/legate/mcp.rb +23 -0
- data/lib/legate/plan_executor.rb +375 -0
- data/lib/legate/planner.rb +839 -0
- data/lib/legate/rails/railtie.rb +43 -0
- data/lib/legate/rails.rb +9 -0
- data/lib/legate/redaction.rb +32 -0
- data/lib/legate/session.rb +299 -0
- data/lib/legate/session_service/active_record.rb +300 -0
- data/lib/legate/session_service/base.rb +68 -0
- data/lib/legate/session_service/event_broadcast.rb +74 -0
- data/lib/legate/session_service/in_memory.rb +188 -0
- data/lib/legate/tool/metadata_dsl.rb +122 -0
- data/lib/legate/tool.rb +276 -0
- data/lib/legate/tool_code_generator.rb +103 -0
- data/lib/legate/tool_context.rb +350 -0
- data/lib/legate/tool_loader.rb +39 -0
- data/lib/legate/tool_registry.rb +73 -0
- data/lib/legate/tool_result.rb +61 -0
- data/lib/legate/tools/agent_tool.rb +187 -0
- data/lib/legate/tools/base/http_client.rb +319 -0
- data/lib/legate/tools/base/safe_url.rb +56 -0
- data/lib/legate/tools/base_async_job_tool.rb +91 -0
- data/lib/legate/tools/calculator.rb +89 -0
- data/lib/legate/tools/cat_facts.rb +81 -0
- data/lib/legate/tools/check_job_status_tool.rb +48 -0
- data/lib/legate/tools/current_time_tool.rb +64 -0
- data/lib/legate/tools/echo.rb +43 -0
- data/lib/legate/tools/http_request_tool.rb +105 -0
- data/lib/legate/tools/random_number_tool.rb +64 -0
- data/lib/legate/tools/read_webpage_tool.rb +92 -0
- data/lib/legate/tools/sleepy_tool.rb +74 -0
- data/lib/legate/tools/webhook_tool.rb +146 -0
- data/lib/legate/version.rb +5 -0
- data/lib/legate/web/app.rb +984 -0
- data/lib/legate/web/public/css/main.css +4980 -0
- data/lib/legate/web/public/images/favicon-256.png +0 -0
- data/lib/legate/web/public/images/favicon-32.png +0 -0
- data/lib/legate/web/public/images/legate-logo-dark.png +0 -0
- data/lib/legate/web/public/images/legate-logo-light.png +0 -0
- data/lib/legate/web/public/js/legate.js +616 -0
- data/lib/legate/web/public/styles/main.scss +4402 -0
- data/lib/legate/web/routes/agent_authentication_routes.rb +530 -0
- data/lib/legate/web/routes/agent_definition_routes.rb +803 -0
- data/lib/legate/web/routes/agent_generator_routes.rb +80 -0
- data/lib/legate/web/routes/agent_interaction_routes.rb +734 -0
- data/lib/legate/web/routes/agent_runtime_routes.rb +323 -0
- data/lib/legate/web/routes/api_routes.rb +56 -0
- data/lib/legate/web/routes/authentication_routes.rb +1541 -0
- data/lib/legate/web/routes/core_routes.rb +111 -0
- data/lib/legate/web/routes/documentation_routes.rb +220 -0
- data/lib/legate/web/routes/tool_generator_routes.rb +81 -0
- data/lib/legate/web/routes/tools_ui_routes.rb +207 -0
- data/lib/legate/web/sass_compiler.rb +73 -0
- data/lib/legate/web/views/_active_session_info.slim +25 -0
- data/lib/legate/web/views/_activity_list.slim +55 -0
- data/lib/legate/web/views/_agent_card.slim +56 -0
- data/lib/legate/web/views/_agent_generator_modal.slim +382 -0
- data/lib/legate/web/views/_agent_status_controls.slim +71 -0
- data/lib/legate/web/views/_agent_tool_table.slim +74 -0
- data/lib/legate/web/views/_chat_message.slim +95 -0
- data/lib/legate/web/views/_display_agent_configuration.slim +26 -0
- data/lib/legate/web/views/_display_agent_description.slim +11 -0
- data/lib/legate/web/views/_display_agent_fallback.slim +15 -0
- data/lib/legate/web/views/_display_agent_hierarchy.slim +93 -0
- data/lib/legate/web/views/_display_agent_instruction.slim +17 -0
- data/lib/legate/web/views/_display_agent_mcp.slim +13 -0
- data/lib/legate/web/views/_display_agent_model.slim +17 -0
- data/lib/legate/web/views/_display_agent_name.slim +42 -0
- data/lib/legate/web/views/_display_agent_output_key.slim +26 -0
- data/lib/legate/web/views/_display_agent_type.slim +65 -0
- data/lib/legate/web/views/_edit_agent_configuration.slim +74 -0
- data/lib/legate/web/views/_edit_agent_description.slim +16 -0
- data/lib/legate/web/views/_edit_agent_fallback.slim +25 -0
- data/lib/legate/web/views/_edit_agent_hierarchy.slim +98 -0
- data/lib/legate/web/views/_edit_agent_instruction.slim +49 -0
- data/lib/legate/web/views/_edit_agent_mcp.slim +33 -0
- data/lib/legate/web/views/_edit_agent_model.slim +23 -0
- data/lib/legate/web/views/_edit_agent_output_key.slim +36 -0
- data/lib/legate/web/views/_edit_agent_tools.slim +40 -0
- data/lib/legate/web/views/_edit_agent_type.slim +67 -0
- data/lib/legate/web/views/_session_error.slim +4 -0
- data/lib/legate/web/views/_skeleton.slim +69 -0
- data/lib/legate/web/views/_tool_card.slim +9 -0
- data/lib/legate/web/views/_tool_generator_modal.slim +311 -0
- data/lib/legate/web/views/agent.slim +436 -0
- data/lib/legate/web/views/agent_auth.slim +562 -0
- data/lib/legate/web/views/agents.slim +369 -0
- data/lib/legate/web/views/auth.slim +112 -0
- data/lib/legate/web/views/auth_credential_detail.slim +327 -0
- data/lib/legate/web/views/auth_credentials.slim +261 -0
- data/lib/legate/web/views/auth_debug.slim +94 -0
- data/lib/legate/web/views/auth_mapping_detail.slim +151 -0
- data/lib/legate/web/views/auth_mapping_new.slim +123 -0
- data/lib/legate/web/views/auth_mappings.slim +120 -0
- data/lib/legate/web/views/auth_scheme_detail.slim +274 -0
- data/lib/legate/web/views/auth_schemes.slim +259 -0
- data/lib/legate/web/views/auth_test.slim +418 -0
- data/lib/legate/web/views/chat.slim +192 -0
- data/lib/legate/web/views/docs_index.slim +105 -0
- data/lib/legate/web/views/docs_show.slim +105 -0
- data/lib/legate/web/views/error_404.slim +5 -0
- data/lib/legate/web/views/index.slim +148 -0
- data/lib/legate/web/views/layout.slim +144 -0
- data/lib/legate/web/views/tool_detail.slim +87 -0
- data/lib/legate/web/views/tools.slim +50 -0
- data/lib/legate/web/webhook_listener.rb +367 -0
- data/lib/legate/web.rb +9 -0
- data/lib/legate.rb +220 -0
- data/public/docs/advanced/callbacks.md +828 -0
- data/public/docs/advanced/mcp_schema_conversion.md +59 -0
- data/public/docs/authentication/api_reference/config.md +210 -0
- data/public/docs/authentication/api_reference/credential.md +246 -0
- data/public/docs/authentication/api_reference/encryption.md +218 -0
- data/public/docs/authentication/api_reference/exchanged_credential.md +271 -0
- data/public/docs/authentication/api_reference/excon_middleware.md +175 -0
- data/public/docs/authentication/api_reference/index.md +30 -0
- data/public/docs/authentication/api_reference/scheme.md +250 -0
- data/public/docs/authentication/api_reference/schemes/api_key.md +175 -0
- data/public/docs/authentication/api_reference/schemes/google_service_account.md +221 -0
- data/public/docs/authentication/api_reference/schemes/http_bearer.md +169 -0
- data/public/docs/authentication/api_reference/schemes/oauth2.md +343 -0
- data/public/docs/authentication/api_reference/schemes/oidc.md +73 -0
- data/public/docs/authentication/api_reference/schemes/openid_connect.md +311 -0
- data/public/docs/authentication/api_reference/schemes/service_account.md +287 -0
- data/public/docs/authentication/api_reference/token_manager.md +221 -0
- data/public/docs/authentication/api_reference/token_store.md +146 -0
- data/public/docs/authentication/api_reference/tool_context_extension.md +166 -0
- data/public/docs/authentication/guides/api_key.md +190 -0
- data/public/docs/authentication/guides/bearer.md +172 -0
- data/public/docs/authentication/guides/configuration.md +255 -0
- data/public/docs/authentication/guides/custom_flow.md +523 -0
- data/public/docs/authentication/guides/index.md +24 -0
- data/public/docs/authentication/guides/migration.md +435 -0
- data/public/docs/authentication/guides/oauth2.md +252 -0
- data/public/docs/authentication/guides/oidc.md +241 -0
- data/public/docs/authentication/guides/overview.md +155 -0
- data/public/docs/authentication/guides/secure_storage.md +301 -0
- data/public/docs/authentication/guides/service_account.md +228 -0
- data/public/docs/authentication/guides/token_lifecycle.md +295 -0
- data/public/docs/authentication/guides/web_ui_integration.md +504 -0
- data/public/docs/authentication/index.md +58 -0
- data/public/docs/authentication/troubleshooting/credential_storage.md +550 -0
- data/public/docs/authentication/troubleshooting/environment_variables.md +540 -0
- data/public/docs/authentication/troubleshooting/index.md +11 -0
- data/public/docs/authentication/troubleshooting/oauth2_issues.md +220 -0
- data/public/docs/authentication/troubleshooting/oidc_issues.md +412 -0
- data/public/docs/authentication/troubleshooting/token_refresh.md +338 -0
- data/public/docs/cli/legate_cli_usage.md +363 -0
- data/public/docs/core_concepts/legate_agent_lifecycle.md +124 -0
- data/public/docs/core_concepts/legate_architecture_overview.md +110 -0
- data/public/docs/core_concepts/legate_configuration.md +116 -0
- data/public/docs/core_concepts/legate_definition_store.md +102 -0
- data/public/docs/core_concepts/legate_planner.md +94 -0
- data/public/docs/core_concepts/legate_session_service.md +104 -0
- data/public/docs/error_handling/legate_error_handling.md +122 -0
- data/public/docs/examples.md +199 -0
- data/public/docs/getting_started.md +111 -0
- data/public/docs/guides/agentic_agents.md +137 -0
- data/public/docs/guides/ai_code_generators.md +437 -0
- data/public/docs/guides/auto_loading.md +326 -0
- data/public/docs/guides/configuring_agent_webhooks.md +219 -0
- data/public/docs/guides/http_client_usage.md +264 -0
- data/public/docs/guides/llm_providers.md +137 -0
- data/public/docs/guides/mcp_client_integration.md +232 -0
- data/public/docs/guides/mcp_server_exposure.md +206 -0
- data/public/docs/guides/rails_integration.md +128 -0
- data/public/docs/guides/sending_outbound_webhooks.md +227 -0
- data/public/docs/guides/streaming.md +112 -0
- data/public/docs/guides/webhooks.md +288 -0
- data/public/docs/introduction.md +51 -0
- data/public/docs/multi_agent_systems/advanced_features.md +57 -0
- data/public/docs/multi_agent_systems/agent_delegation.md +190 -0
- data/public/docs/multi_agent_systems/agent_hierarchy.md +49 -0
- data/public/docs/multi_agent_systems/state_management.md +47 -0
- data/public/docs/multi_agent_systems/workflow_agents.md +72 -0
- data/public/docs/tools/legate_built_in_tools.md +332 -0
- data/public/docs/tools/legate_tools_and_registry.md +263 -0
- data/public/docs/web_ui/legate_web_ui.md +137 -0
- metadata +823 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# File: examples/advanced/mcp/mcp_server_legate_agent.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# --- Example: Exposing an Legate::Agent via MCP using fast-mcp ---
|
|
5
|
+
#
|
|
6
|
+
# This script demonstrates how to wrap an existing Legate::Agent definition
|
|
7
|
+
# and expose it as a single tool on an MCP server
|
|
8
|
+
# run over STDIO using the fast-mcp gem and LegateAgentAdapter.
|
|
9
|
+
#
|
|
10
|
+
# Prerequisites:
|
|
11
|
+
# - Run `bundle install` in the legate directory.
|
|
12
|
+
# - Ensure the `fast-mcp` gem is available (e.g., via Gemfile path).
|
|
13
|
+
# - An agent definition registered in GlobalDefinitionRegistry.
|
|
14
|
+
# Replace `MY_AGENT_NAME` below with the actual name.
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# 1. Replace `MY_AGENT_NAME` with your agent's name.
|
|
18
|
+
# 2. Run this script from the legate root directory:
|
|
19
|
+
# `bundle exec ruby examples/advanced/mcp/mcp_server_legate_agent.rb`
|
|
20
|
+
# 3. The script will start and wait for MCP JSON-RPC messages on STDIN.
|
|
21
|
+
# 4. Use an MCP client connected to this script's STDIO to interact.
|
|
22
|
+
#
|
|
23
|
+
# Example Interaction (using a generic client):
|
|
24
|
+
# -> {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{}}}
|
|
25
|
+
# <- {"jsonrpc":"2.0","id":1,"result":{"capabilities":{},"serverInfo":{"name":"Legate Agent Server","version":"x.y.z"}}}
|
|
26
|
+
# -> {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
|
|
27
|
+
# <- {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"run_agent_MY_AGENT_NAME","description":"Runs the Legate Agent 'MY_AGENT_NAME' with the given prompt.","inputSchema":{"type":"object","properties":{"prompt":{"type":"string","description":"The user input/prompt for the agent"}},"required":["prompt"]}}]}}
|
|
28
|
+
# -> {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"run_agent_MY_AGENT_NAME","arguments":{"prompt":"Hello agent!"}}}
|
|
29
|
+
# <- {"jsonrpc":"2.0","id":3,"result":"Hello from the agent!"} // (or whatever the agent returns)
|
|
30
|
+
#
|
|
31
|
+
# -------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __dir__)
|
|
34
|
+
require 'legate'
|
|
35
|
+
Legate.load_environment # Handle Bundler, Dotenv, etc.
|
|
36
|
+
require 'legate/mcp'
|
|
37
|
+
require 'legate/mcp/server/legate_agent_adapter' # Load the Agent adapter
|
|
38
|
+
require 'legate/session_service/in_memory' # Using in-memory for temporary sessions
|
|
39
|
+
require 'fast_mcp'
|
|
40
|
+
|
|
41
|
+
# --- ** Configuration: Replace with your agent name ** ---
|
|
42
|
+
AGENT_NAME = 'my_agent' # <<< CHANGE THIS
|
|
43
|
+
# ---------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
Legate.configure do |config|
|
|
46
|
+
# config.log_level = :debug
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# --- 1. Create Session Service Instance ---
|
|
50
|
+
# LegateAgentAdapter needs this to create temporary sessions for each call.
|
|
51
|
+
# Using InMemory for simplicity, but RedisSessionService would also work.
|
|
52
|
+
session_service = Legate::SessionService::InMemory.new
|
|
53
|
+
Legate.logger.info("Using session service: #{session_service.class}")
|
|
54
|
+
|
|
55
|
+
# --- 2. Wrap the Legate Agent Definition ---
|
|
56
|
+
begin
|
|
57
|
+
AdaptedAgent = Legate::Mcp::Server::LegateAgentAdapter.wrap(AGENT_NAME, session_service)
|
|
58
|
+
Legate.logger.info("Successfully wrapped Legate Agent definition '#{AGENT_NAME}' for MCP.")
|
|
59
|
+
rescue Legate::Mcp::Error => e
|
|
60
|
+
Legate.logger.fatal("Failed to wrap Legate Agent: #{e.message}")
|
|
61
|
+
Legate.logger.fatal("Please ensure the agent '#{AGENT_NAME}' is defined.")
|
|
62
|
+
exit(1)
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
Legate.logger.fatal("Unexpected error during agent wrap: #{e.message}")
|
|
65
|
+
Legate.logger.fatal(e.backtrace.join("\n"))
|
|
66
|
+
exit(1)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# --- 3. Create fast-mcp Server Instance ---
|
|
70
|
+
mcp_server = FastMcp::Server::Stdio.new(
|
|
71
|
+
server_info: {
|
|
72
|
+
name: 'Legate Agent Server',
|
|
73
|
+
version: Legate::VERSION
|
|
74
|
+
},
|
|
75
|
+
logger: Legate.logger
|
|
76
|
+
)
|
|
77
|
+
Legate.logger.info('Initialized FastMcp::Server::Stdio.')
|
|
78
|
+
|
|
79
|
+
# --- 4. Register the Wrapped Agent Tool ---
|
|
80
|
+
mcp_server.register_tool(AdaptedAgent)
|
|
81
|
+
Legate.logger.info("Registered adapted agent tool '#{AdaptedAgent.tool_name}' with fast-mcp server.")
|
|
82
|
+
|
|
83
|
+
# --- 5. Start the Server ---
|
|
84
|
+
Legate.logger.info("Starting MCP server on STDIO for agent '#{AGENT_NAME}'. Waiting for requests...")
|
|
85
|
+
puts "--- Legate MCP Agent Server (#{AGENT_NAME}) Ready --- "
|
|
86
|
+
begin
|
|
87
|
+
mcp_server.start
|
|
88
|
+
rescue Interrupt
|
|
89
|
+
Legate.logger.info('Received interrupt, shutting down server.')
|
|
90
|
+
rescue StandardError => e
|
|
91
|
+
Legate.logger.fatal("MCP server crashed: #{e.class} - #{e.message}")
|
|
92
|
+
Legate.logger.fatal(e.backtrace.join("\n"))
|
|
93
|
+
ensure
|
|
94
|
+
Legate.logger.info('MCP server stopped.')
|
|
95
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example: Exposing Legate Tools via MCP using Rack Middleware
|
|
5
|
+
#
|
|
6
|
+
# This example demonstrates how to wrap an Legate tool (CalculatorTool)
|
|
7
|
+
# using LegateToolAdapter and expose it via MCP using fast-mcp's
|
|
8
|
+
# Rack middleware integration.
|
|
9
|
+
#
|
|
10
|
+
# Requires:
|
|
11
|
+
# - legate gem with MCP support installed
|
|
12
|
+
# - fast-mcp gem installed
|
|
13
|
+
# - rack and puma gems installed (`bundle add rack puma`)
|
|
14
|
+
#
|
|
15
|
+
# To Run:
|
|
16
|
+
# 1. Execute this script: `bundle exec ruby examples/advanced/mcp/mcp_server_rack.rb`
|
|
17
|
+
# 2. The server will start on http://localhost:9292.
|
|
18
|
+
# 3. In another terminal, use mcp-inspector (select SSE transport):
|
|
19
|
+
# `npx @modelcontextprotocol/inspector`
|
|
20
|
+
# Connect to: `http://localhost:9292/mcp/sse`
|
|
21
|
+
# 4. In the inspector:
|
|
22
|
+
# - Call the 'my_calculator' tool with parameters like:
|
|
23
|
+
# { "a": 10, "b": 5, "op": "*" }
|
|
24
|
+
# - Observe the result.
|
|
25
|
+
|
|
26
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __dir__)
|
|
27
|
+
require 'legate'
|
|
28
|
+
Legate.load_environment # Handle Bundler, Dotenv, etc.
|
|
29
|
+
|
|
30
|
+
require 'fast_mcp'
|
|
31
|
+
require 'legate/mcp/server/legate_tool_adapter'
|
|
32
|
+
require 'legate/tools/calculator' # The Legate tool we want to expose
|
|
33
|
+
require 'rack'
|
|
34
|
+
require 'rack/handler/puma'
|
|
35
|
+
|
|
36
|
+
ENV['LEGATE_LOG_LEVEL'] = 'FATAL'
|
|
37
|
+
|
|
38
|
+
# Configure Legate logger
|
|
39
|
+
# Legate.configure { |c| c.log_level = Logger::INFO }
|
|
40
|
+
|
|
41
|
+
# --- Define a simple base Rack app ---
|
|
42
|
+
# This is the app that runs alongside the MCP middleware.
|
|
43
|
+
# You could replace this with your Rails/Sinatra app.
|
|
44
|
+
base_app = lambda do |env|
|
|
45
|
+
if env['PATH_INFO'] == '/'
|
|
46
|
+
[200, { 'Content-Type' => 'text/html' }, [
|
|
47
|
+
'<html><body>',
|
|
48
|
+
'<h1>Legate MCP Rack Server Example</h1>',
|
|
49
|
+
'<p>MCP endpoints are active at /mcp/sse and /mcp/messages.</p>',
|
|
50
|
+
'</body></html>'
|
|
51
|
+
]]
|
|
52
|
+
else
|
|
53
|
+
[404, { 'Content-Type' => 'text/plain' }, ['Not Found']]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# --- Wrap the Legate Tool ---
|
|
58
|
+
begin
|
|
59
|
+
AdaptedCalculator = Legate::Mcp::Server::LegateToolAdapter.wrap(Legate::Tools::Calculator)
|
|
60
|
+
Legate.logger.info("Wrapped CalculatorTool as: #{AdaptedCalculator.tool_name}")
|
|
61
|
+
rescue ArgumentError => e
|
|
62
|
+
Legate.logger.fatal("Failed to wrap CalculatorTool: #{e.message}")
|
|
63
|
+
exit(1)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# --- Create the MCP Middleware ---
|
|
67
|
+
# This adds the /mcp/sse and /mcp/messages endpoints to the base_app.
|
|
68
|
+
mcp_middleware = FastMcp.rack_middleware(
|
|
69
|
+
base_app,
|
|
70
|
+
name: 'legate-rack-server',
|
|
71
|
+
version: '1.0.0',
|
|
72
|
+
logger: Legate.logger,
|
|
73
|
+
allowed_origins: ['localhost', '127.0.0.1'] # Optional: Customize allowed origins
|
|
74
|
+
) do |server|
|
|
75
|
+
# Register the wrapped tool with the server instance managed by the middleware
|
|
76
|
+
server.register_tool(AdaptedCalculator)
|
|
77
|
+
Legate.logger.info('Registered adapted tools with fast-mcp middleware server.')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# --- Run the Rack Application with Puma ---
|
|
81
|
+
puts 'Starting Rack application with MCP middleware on http://localhost:9292'
|
|
82
|
+
puts 'MCP endpoints:'
|
|
83
|
+
puts ' - http://localhost:9292/mcp/sse (SSE endpoint)'
|
|
84
|
+
puts ' - http://localhost:9292/mcp/messages (JSON-RPC endpoint)'
|
|
85
|
+
puts 'Press Ctrl+C to stop'
|
|
86
|
+
|
|
87
|
+
Rack::Handler::Puma.run mcp_middleware, Port: 9292
|
|
88
|
+
|
|
89
|
+
Legate.logger.info('Server finished.')
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# If running from project root: bundle exec ruby examples/advanced/random_calculator.rb
|
|
5
|
+
require_relative '../../lib/legate'
|
|
6
|
+
|
|
7
|
+
# random_number is a demo tool — shipped but not registered by default — so this
|
|
8
|
+
# example opts into it explicitly. (calculator is a registered built-in.)
|
|
9
|
+
Legate::GlobalToolManager.register_tool(Legate::Tools::RandomNumberTool)
|
|
10
|
+
|
|
11
|
+
puts '--- Random Calculator Agent Example (Multi-Step Planner w/ Hash Results) ---'
|
|
12
|
+
|
|
13
|
+
# 1. --- Agent Definition ---
|
|
14
|
+
random_calculator_definition = Legate::AgentDefinition.new.define do |a|
|
|
15
|
+
a.name :random_calculator_agent # Changed from multi_step_hash_agent_001 for clarity
|
|
16
|
+
a.description 'An agent that uses random number and calculator tools.'
|
|
17
|
+
a.instruction 'Your goal is to follow multi-step instructions involving random numbers and calculations. Use the random_number tool first, then the calculator tool with the result.'
|
|
18
|
+
a.use_tool :random_number # Provided by Legate::Tools::RandomNumberTool
|
|
19
|
+
a.use_tool :calculator # Provided by Legate::Tools::Calculator
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# 2. --- Agent Instantiation ---
|
|
23
|
+
agent = Legate::Agent.new(definition: random_calculator_definition)
|
|
24
|
+
|
|
25
|
+
puts "\nAgent '#{agent.name}' created."
|
|
26
|
+
puts "Agent tools loaded: #{agent.tools.map(&:name).join(', ')}"
|
|
27
|
+
|
|
28
|
+
# 3. --- Start Agent ---
|
|
29
|
+
agent.start
|
|
30
|
+
puts "\nAgent '#{agent.name}' started. Running: #{agent.running?}"
|
|
31
|
+
|
|
32
|
+
# 4. --- Session Setup ---
|
|
33
|
+
# Create a session service and session
|
|
34
|
+
session_service = Legate::SessionService::InMemory.new
|
|
35
|
+
session = session_service.create_session(app_name: agent.name, user_id: 'example_user')
|
|
36
|
+
session_id = session.id
|
|
37
|
+
puts "\nCreated session: #{session_id}"
|
|
38
|
+
|
|
39
|
+
# 5. --- Task Execution ---
|
|
40
|
+
task = 'Get a random number between 10 and 20, then multiply it by 3.'
|
|
41
|
+
puts "\nRunning high-level task via agent.run_task: '#{task}'"
|
|
42
|
+
|
|
43
|
+
# Set log level to DEBUG to see planner details if needed
|
|
44
|
+
# ENV['LEGATE_LOG_LEVEL'] = 'DEBUG'
|
|
45
|
+
# Legate.instance_variable_set(:@logger, nil) # Force logger re-init
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
result_event = agent.run_task(
|
|
49
|
+
session_id: session_id,
|
|
50
|
+
user_input: task,
|
|
51
|
+
session_service: session_service
|
|
52
|
+
)
|
|
53
|
+
puts "Raw result event: #{result_event.inspect}" # Show the structure
|
|
54
|
+
|
|
55
|
+
# --- Updated Result Handling ---
|
|
56
|
+
puts "\nInterpreted Result:"
|
|
57
|
+
if result_event.is_a?(Legate::Event)
|
|
58
|
+
puts ' Status: Event Received'
|
|
59
|
+
puts " Role: #{result_event.role}"
|
|
60
|
+
|
|
61
|
+
content = result_event.content
|
|
62
|
+
if content.is_a?(Array)
|
|
63
|
+
puts ' Content Type: Multi-Step Plan Results'
|
|
64
|
+
any_errors = false
|
|
65
|
+
content.each_with_index do |step_hash, index|
|
|
66
|
+
print " Step #{index + 1}: "
|
|
67
|
+
if step_hash.is_a?(Hash) && step_hash[:status] == :success
|
|
68
|
+
puts "Success | Result: #{step_hash[:result]}"
|
|
69
|
+
elsif step_hash.is_a?(Hash) && step_hash[:status] == :error
|
|
70
|
+
puts "Error | Message: #{step_hash[:error_message]}"
|
|
71
|
+
any_errors = true
|
|
72
|
+
else
|
|
73
|
+
puts "Unknown Format | Data: #{step_hash.inspect}"
|
|
74
|
+
any_errors = true # Treat unexpected format as problematic
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
puts " Overall Plan Status: #{any_errors ? 'Completed with errors' : 'Completed successfully'}"
|
|
78
|
+
elsif content.is_a?(Hash) && content.key?(:status)
|
|
79
|
+
# Single step plan or a planning error
|
|
80
|
+
if content[:status] == :success
|
|
81
|
+
puts ' Content Type: Single Step Success'
|
|
82
|
+
puts " Result: #{content[:result]}"
|
|
83
|
+
else # status == :error or other
|
|
84
|
+
puts ' Content Type: Error (or Single Step Error)'
|
|
85
|
+
puts " Message: #{content[:error_message]}"
|
|
86
|
+
end
|
|
87
|
+
else
|
|
88
|
+
puts ' Content Type: String or Other Format'
|
|
89
|
+
puts " Content: #{content}"
|
|
90
|
+
end
|
|
91
|
+
else
|
|
92
|
+
puts ' Status: Unknown (Unexpected Format)'
|
|
93
|
+
puts " Raw Data: #{result_event.inspect}"
|
|
94
|
+
end
|
|
95
|
+
# --- End Updated Result Handling ---
|
|
96
|
+
rescue StandardError => e
|
|
97
|
+
puts "Error executing task: #{e.class} - #{e.message}"
|
|
98
|
+
puts e.backtrace.first(5).join("\n")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# 6. --- Stop Agent ---
|
|
102
|
+
agent.stop
|
|
103
|
+
puts "\nAgent '#{agent.name}' stopped. Running: #{agent.running?}"
|
|
104
|
+
puts "\n--- Example Complete ---"
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../../lib/legate'
|
|
5
|
+
# Require the specific tool class if not automatically loaded. SleepyTool provides :start_sleepy_job.
|
|
6
|
+
require_relative '../tools/sleepy_tool'
|
|
7
|
+
# Legate::Tools::CheckJobStatusTool (providing :check_job_status) is loaded by Legate core.
|
|
8
|
+
|
|
9
|
+
puts '--- Async Job Agent Example (Polling Status) ---'
|
|
10
|
+
|
|
11
|
+
# 1. --- Agent Definition ---
|
|
12
|
+
async_job_demo_definition = Legate::AgentDefinition.new.define do |a|
|
|
13
|
+
a.name :async_job_demo_agent
|
|
14
|
+
a.description 'An agent that starts a background job and can check its status.'
|
|
15
|
+
a.instruction 'You can start sleepy jobs and check their status. Use start_sleepy_job to initiate, and the system might use check_job_status for monitoring.'
|
|
16
|
+
a.use_tool :start_sleepy_job # Provided by Legate::Tools::SleepyTool
|
|
17
|
+
a.use_tool :check_job_status # Provided by Legate::Tools::CheckJobStatusTool
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# 2. --- Agent Instantiation ---
|
|
21
|
+
agent = Legate::Agent.new(definition: async_job_demo_definition)
|
|
22
|
+
|
|
23
|
+
# Get an instance of the status checker tool directly (needed for manual polling in this example script)
|
|
24
|
+
status_checker_tool = Legate::GlobalToolManager.create_instance(:check_job_status)
|
|
25
|
+
unless status_checker_tool
|
|
26
|
+
puts 'Error: Status checker tool (:check_job_status) not found in GlobalToolManager.'
|
|
27
|
+
exit 1
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
puts "\nAgent '#{agent.name}' created with tools: #{agent.tools.map(&:name).join(', ')}"
|
|
31
|
+
puts "Status checker tool for manual polling: #{status_checker_tool.name}"
|
|
32
|
+
|
|
33
|
+
# 3. --- Start Agent (Needed for the initial task) ---
|
|
34
|
+
agent.start
|
|
35
|
+
puts 'Agent started.'
|
|
36
|
+
|
|
37
|
+
# 4. --- Session Setup ---
|
|
38
|
+
# We use a session service to store the conversation history, including tool results.
|
|
39
|
+
session_service = Legate::SessionService::InMemory.new
|
|
40
|
+
session = session_service.create_session(app_name: agent.name, user_id: 'demo_user')
|
|
41
|
+
session_id = session.id
|
|
42
|
+
puts "\nCreated session: #{session_id}"
|
|
43
|
+
|
|
44
|
+
# 5. --- Task Execution & Polling ---
|
|
45
|
+
# The task asks the agent to use the async tool.
|
|
46
|
+
job_duration = rand(3..8) # Random duration for the job
|
|
47
|
+
task = "Start a sleepy job for #{job_duration} seconds with message 'Demo complete'"
|
|
48
|
+
puts "\nExecuting task: '#{task}'"
|
|
49
|
+
|
|
50
|
+
job_id = nil
|
|
51
|
+
final_job_status = nil
|
|
52
|
+
|
|
53
|
+
begin
|
|
54
|
+
# --- Run the task that starts the job ---
|
|
55
|
+
agent.run_task(
|
|
56
|
+
session_id: session_id,
|
|
57
|
+
user_input: task,
|
|
58
|
+
session_service: session_service
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# --- Check Session History for the Job ID ---
|
|
62
|
+
puts "\nTask finished. Checking session history for the job ID..."
|
|
63
|
+
current_session = session_service.get_session(session_id: session_id)
|
|
64
|
+
# Find the result event from the tool that starts the job
|
|
65
|
+
start_job_event = current_session&.events&.reverse&.find do |event|
|
|
66
|
+
event.role == :tool_result && event.tool_name == :start_sleepy_job
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# --- Polling for Job Completion ---
|
|
70
|
+
if start_job_event&.content&.is_a?(Hash) && start_job_event.content[:status] == :pending
|
|
71
|
+
job_id = start_job_event.content[:job_id]
|
|
72
|
+
puts "Found pending job: #{job_id}. Starting polling using #{status_checker_tool.name}..."
|
|
73
|
+
|
|
74
|
+
# Add a small initial delay to give the worker time to start processing
|
|
75
|
+
sleep 0.5
|
|
76
|
+
|
|
77
|
+
max_attempts = 30
|
|
78
|
+
attempt = 0
|
|
79
|
+
start_time = Time.now
|
|
80
|
+
polling_context = Legate::ToolContext.new(session_id: session_id, app_name: 'polling_script', user_id: 'demo_user')
|
|
81
|
+
|
|
82
|
+
while attempt < max_attempts
|
|
83
|
+
attempt += 1
|
|
84
|
+
|
|
85
|
+
# Execute the status checker tool directly
|
|
86
|
+
status_result = status_checker_tool.execute({ job_id: job_id }, polling_context)
|
|
87
|
+
|
|
88
|
+
if status_result.is_a?(Hash)
|
|
89
|
+
final_job_status = status_result[:status]&.to_sym # Ensure symbol
|
|
90
|
+
|
|
91
|
+
case final_job_status
|
|
92
|
+
when :success
|
|
93
|
+
elapsed = Time.now - start_time
|
|
94
|
+
puts "\nJob completed successfully! (took #{elapsed.round(1)} seconds)"
|
|
95
|
+
puts "Result: #{status_result[:result]}"
|
|
96
|
+
break
|
|
97
|
+
when :error
|
|
98
|
+
elapsed = Time.now - start_time
|
|
99
|
+
puts "\nJob failed! (after #{elapsed.round(1)} seconds)"
|
|
100
|
+
puts "Error: #{status_result[:error_message]}"
|
|
101
|
+
break
|
|
102
|
+
when :pending
|
|
103
|
+
print '.' # Progress indicator
|
|
104
|
+
$stdout.flush
|
|
105
|
+
else
|
|
106
|
+
elapsed = Time.now - start_time
|
|
107
|
+
puts "\nUnexpected job status '#{final_job_status}' received after #{elapsed.round(1)}s. Stopping poll."
|
|
108
|
+
puts "Raw status content: #{status_result.inspect}"
|
|
109
|
+
break
|
|
110
|
+
end
|
|
111
|
+
else
|
|
112
|
+
elapsed = Time.now - start_time
|
|
113
|
+
puts "\nUnexpected result format from status checker tool after #{elapsed.round(1)}s: #{status_result.inspect}. Stopping poll."
|
|
114
|
+
final_job_status = :error # Treat unexpected format as an error
|
|
115
|
+
break
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
sleep 1 # Wait before next poll
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
if attempt >= max_attempts && final_job_status == :pending
|
|
122
|
+
elapsed = Time.now - start_time
|
|
123
|
+
puts "\nPolling timed out after #{max_attempts} attempts (#{elapsed.round(1)} seconds). Job status still pending."
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
elsif start_job_event
|
|
127
|
+
puts "Job start step did not return a pending status. Status: #{start_job_event.content[:status]}"
|
|
128
|
+
final_job_status = start_job_event.content[:status]
|
|
129
|
+
else
|
|
130
|
+
puts "Could not find the result event for '#{sleepy_tool.name}' in the session history."
|
|
131
|
+
final_job_status = :error # Indicate failure if job start wasn't found
|
|
132
|
+
end
|
|
133
|
+
rescue StandardError => e
|
|
134
|
+
puts "\nError during task execution or polling: #{e.class} - #{e.message}"
|
|
135
|
+
puts e.backtrace.first(5).join("\n")
|
|
136
|
+
final_job_status = :error # Indicate failure on exception
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# 6. --- Stop Agent ---
|
|
140
|
+
puts "\nStopping agent..."
|
|
141
|
+
agent.stop
|
|
142
|
+
puts 'Agent stopped.'
|
|
143
|
+
|
|
144
|
+
# 7. --- Final Summary ---
|
|
145
|
+
puts "\n--- Example Summary ---"
|
|
146
|
+
if job_id
|
|
147
|
+
puts "Job ID: #{job_id}"
|
|
148
|
+
puts "Final Status: #{final_job_status || 'Unknown'}"
|
|
149
|
+
else
|
|
150
|
+
puts 'Job ID: Not found'
|
|
151
|
+
puts "Final Status: #{final_job_status || 'Error'}"
|
|
152
|
+
end
|
|
153
|
+
puts '--- Example Complete ---'
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# File: examples/advanced/webhooks/webhook_e2e_runner.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Add lib to load path
|
|
5
|
+
$LOAD_PATH.unshift(File.expand_path('../../lib', __dir__))
|
|
6
|
+
|
|
7
|
+
require 'legate'
|
|
8
|
+
require_relative 'webhook_receiver_agent' # Ensure agent definition is loaded
|
|
9
|
+
require 'openssl'
|
|
10
|
+
require 'json'
|
|
11
|
+
|
|
12
|
+
# --- Configuration ---
|
|
13
|
+
|
|
14
|
+
# Ensure the secret is set (same as used in webhook_receiver_agent.rb)
|
|
15
|
+
# Set this environment variable before running the script:
|
|
16
|
+
# export WEBHOOK_RECEIVER_SECRET='a-very-secret-key'
|
|
17
|
+
webhook_secret = ENV['WEBHOOK_RECEIVER_SECRET']
|
|
18
|
+
unless webhook_secret
|
|
19
|
+
puts 'Error: WEBHOOK_RECEIVER_SECRET environment variable must be set.'
|
|
20
|
+
exit(1)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Configure Legate for the E2E test
|
|
24
|
+
Legate.configure do |config|
|
|
25
|
+
# 1. Webhook Listener Settings
|
|
26
|
+
config.webhooks.listener_enabled = true
|
|
27
|
+
config.webhooks.listen_address = 'localhost' # Bind to localhost for this test
|
|
28
|
+
config.webhooks.listen_port = 9293 # Use a non-default port
|
|
29
|
+
config.webhooks.base_path = '/webhooks' # Default base path
|
|
30
|
+
|
|
31
|
+
# 2. Enable Dynamic Agent Handler
|
|
32
|
+
config.webhooks.enable_dynamic_agent_handler = true
|
|
33
|
+
# Keep default route pattern: /agents/:agent_name/trigger
|
|
34
|
+
|
|
35
|
+
# 3. Session service uses in-memory storage
|
|
36
|
+
config.session_service = Legate::SessionService::InMemory.new
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# --- Verify Agent Definition Was Loaded and Registered Globally ---
|
|
40
|
+
# The `require_relative 'webhook_receiver_agent'` should have defined and globally registered the definition.
|
|
41
|
+
begin
|
|
42
|
+
retrieved_def_obj = Legate::GlobalDefinitionRegistry.find(:webhook_receiver)
|
|
43
|
+
raise 'Failed to retrieve :webhook_receiver definition object from Legate::GlobalDefinitionRegistry after loading.' unless retrieved_def_obj && retrieved_def_obj.is_a?(Legate::AgentDefinition) && retrieved_def_obj.name == :webhook_receiver
|
|
44
|
+
|
|
45
|
+
puts 'Verified :webhook_receiver definition object exists in GlobalDefinitionRegistry.'
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
puts "Error verifying agent definition in GlobalDefinitionRegistry: #{e.message}"
|
|
48
|
+
exit(1)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# --- Process Management ---
|
|
52
|
+
web_server_pid = nil
|
|
53
|
+
|
|
54
|
+
begin
|
|
55
|
+
puts "Starting Legate Web Server (with Webhook Listener) on port #{Legate.config.webhooks.listen_port}..."
|
|
56
|
+
puts '--- Legate Web Server Output START ---'
|
|
57
|
+
# Use rackup with the new config.ru
|
|
58
|
+
port = Legate.config.webhooks.listen_port
|
|
59
|
+
web_server_pid = Process.spawn("bundle exec rackup config.ru -p #{port}")
|
|
60
|
+
# Wait briefly for server to start
|
|
61
|
+
sleep 2
|
|
62
|
+
|
|
63
|
+
puts "\nProcess started: Web=#{web_server_pid}"
|
|
64
|
+
puts "Sending webhook trigger using webhook-example.rb...\n---"
|
|
65
|
+
|
|
66
|
+
# Execute the sender script
|
|
67
|
+
# Pass listener port/path via ENV vars so sender script knows where to send
|
|
68
|
+
sender_env = {
|
|
69
|
+
'LEGATE_WEBHOOK_PORT' => Legate.config.webhooks.listen_port.to_s,
|
|
70
|
+
'LEGATE_WEBHOOK_BASE_PATH' => Legate.config.webhooks.base_path,
|
|
71
|
+
'WEBHOOK_RECEIVER_SECRET' => webhook_secret # Ensure sender uses the same secret
|
|
72
|
+
}
|
|
73
|
+
sender_success = system(sender_env, 'bundle exec ruby examples/webhook-example.rb')
|
|
74
|
+
puts '---'
|
|
75
|
+
|
|
76
|
+
unless sender_success
|
|
77
|
+
puts "\nWebhook sender script failed! Check output above."
|
|
78
|
+
# Don't exit immediately, try to clean up processes
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
puts "\nWaiting 5 seconds for job processing..."
|
|
82
|
+
sleep 5
|
|
83
|
+
|
|
84
|
+
# --- Verification (Basic Log Check) ---
|
|
85
|
+
puts "\nVerification:"
|
|
86
|
+
puts 'Please check the console output where the web server is running.'
|
|
87
|
+
puts 'You should see log messages indicating the webhook was received and processed.'
|
|
88
|
+
rescue Interrupt
|
|
89
|
+
puts "\nInterrupted. Cleaning up..."
|
|
90
|
+
rescue StandardError => e
|
|
91
|
+
puts "\nAn error occurred in the runner: #{e.message}"
|
|
92
|
+
puts e.backtrace.join("\n")
|
|
93
|
+
ensure
|
|
94
|
+
# --- Cleanup Background Processes ---
|
|
95
|
+
puts "\nCleaning up background processes..."
|
|
96
|
+
if web_server_pid
|
|
97
|
+
puts "Stopping Web server (PID: #{web_server_pid})..."
|
|
98
|
+
begin
|
|
99
|
+
Process.kill('TERM', web_server_pid)
|
|
100
|
+
rescue StandardError
|
|
101
|
+
nil
|
|
102
|
+
end
|
|
103
|
+
begin
|
|
104
|
+
Process.wait(web_server_pid)
|
|
105
|
+
rescue StandardError
|
|
106
|
+
nil
|
|
107
|
+
end # Wait briefly
|
|
108
|
+
end
|
|
109
|
+
puts 'Cleanup complete.'
|
|
110
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# File: examples/advanced/webhooks/webhook_receiver_agent.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Add lib to load path
|
|
5
|
+
$LOAD_PATH.unshift(File.expand_path('../../lib', __dir__))
|
|
6
|
+
|
|
7
|
+
require 'legate'
|
|
8
|
+
require 'json'
|
|
9
|
+
|
|
10
|
+
# Example Agent designed to be triggered by an inbound webhook
|
|
11
|
+
# This script defines the agent and registers it globally.
|
|
12
|
+
# Another process (e.g., a web server running Legate::Web::WebhookListener)
|
|
13
|
+
# would look up this definition by name to handle incoming webhooks.
|
|
14
|
+
|
|
15
|
+
Legate.logger.debug 'Defining agent :webhook_receiver...'
|
|
16
|
+
|
|
17
|
+
webhook_receiver_definition = Legate::AgentDefinition.new.define do |a|
|
|
18
|
+
a.name(:webhook_receiver)
|
|
19
|
+
a.description('Receives simple webhook POSTs and logs the message.')
|
|
20
|
+
a.instruction('You will receive a simple text message. Log it clearly.')
|
|
21
|
+
|
|
22
|
+
# Tools - using echo tool for explicitness.
|
|
23
|
+
a.use_tool :echo
|
|
24
|
+
|
|
25
|
+
# --- Webhook Configuration ---
|
|
26
|
+
a.webhook_enabled(true)
|
|
27
|
+
|
|
28
|
+
# 1. Validator: Use HMAC-SHA256 (validator logic would be globally registered)
|
|
29
|
+
a.webhook_validator(:hmac_sha256)
|
|
30
|
+
# Secret for HMAC should be set in the environment where the webhook listener runs.
|
|
31
|
+
# For this definition, we can reference an ENV var that the listener would also use.
|
|
32
|
+
a.webhook_secret(ENV['WEBHOOK_RECEIVER_SECRET'] || 'default-secret-for-definition-if-not-set-in-env')
|
|
33
|
+
|
|
34
|
+
# 2. Transformer: Expects JSON like {"message": "...", "source": "..."}
|
|
35
|
+
# Returns the extracted message string for run_task input.
|
|
36
|
+
a.webhook_transformer(->(request_body) do
|
|
37
|
+
# Ensure request_body is parsed if it's a JSON string
|
|
38
|
+
parsed_body = request_body.is_a?(String) ? JSON.parse(request_body) : request_body
|
|
39
|
+
msg = parsed_body['message']
|
|
40
|
+
raise Legate::WebhookConfigurationError, "Missing or invalid 'message' in webhook payload." unless msg.is_a?(String) && !msg.empty?
|
|
41
|
+
|
|
42
|
+
"Received webhook message: '#{msg}'"
|
|
43
|
+
rescue JSON::ParserError => e
|
|
44
|
+
raise Legate::WebhookConfigurationError, "Invalid JSON in webhook payload: #{e.message}"
|
|
45
|
+
end)
|
|
46
|
+
|
|
47
|
+
# 3. Session Extractor: Use a static session ID for all triggers to this agent
|
|
48
|
+
# Alternatively, could extract based on payload, e.g., request_body['source']
|
|
49
|
+
a.webhook_session_extractor(->(_request_body) do # Mark _request_body as unused
|
|
50
|
+
'webhook_receiver_test_session'
|
|
51
|
+
end)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Register the definition globally so it can be found by name.
|
|
55
|
+
Legate::GlobalDefinitionRegistry.register(webhook_receiver_definition)
|
|
56
|
+
|
|
57
|
+
Legate.logger.debug "Agent definition '#{webhook_receiver_definition.name}' created and registered globally."
|
|
58
|
+
Legate.logger.debug 'Note: This script only defines and registers the agent. A separate webhook listener process is needed to use it.'
|