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,134 @@
|
|
|
1
|
+
# File: lib/legate/mcp/util/schema_converter.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'dry-types' # Ensure dry-types is available for coercion
|
|
5
|
+
|
|
6
|
+
module Legate
|
|
7
|
+
module Mcp
|
|
8
|
+
module Util
|
|
9
|
+
# Utility class for converting between MCP JSON Schema, Legate Tool parameters,
|
|
10
|
+
# and Dry::Schema definitions.
|
|
11
|
+
class SchemaConverter
|
|
12
|
+
# Converts MCP JSON Schema properties and required array into Legate parameters hash.
|
|
13
|
+
# Handles basic types: string, integer, number, boolean.
|
|
14
|
+
# Logs warnings for unsupported types.
|
|
15
|
+
#
|
|
16
|
+
# @param json_schema_properties [Hash] The 'properties' hash from MCP inputSchema.
|
|
17
|
+
# @param json_schema_required_array [Array<String>] The 'required' array from MCP inputSchema.
|
|
18
|
+
# @return [Hash] Legate parameters hash { name: { type:, required:, description: } }.
|
|
19
|
+
def self.json_to_legate(json_schema_properties, json_schema_required_array = [])
|
|
20
|
+
# Return empty hash if input is invalid or not a Hash
|
|
21
|
+
return {} unless json_schema_properties.is_a?(Hash)
|
|
22
|
+
|
|
23
|
+
legate_params = {} # Reverted: Store a hash of param hashes
|
|
24
|
+
required_set = Set.new((json_schema_required_array || []).map(&:to_s))
|
|
25
|
+
|
|
26
|
+
json_schema_properties.each do |name, schema|
|
|
27
|
+
# ---> MODIFIED Check: Allow string or symbol key for type <---
|
|
28
|
+
is_valid_schema = schema.is_a?(Hash) && (schema.key?('type') || schema.key?(:type))
|
|
29
|
+
unless is_valid_schema
|
|
30
|
+
Legate.logger.warn("Skipping MCP property '#{name}': Invalid schema format or missing type. Schema: #{schema.inspect}")
|
|
31
|
+
next
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
param_name = name.to_sym
|
|
35
|
+
# Determine the type using either key
|
|
36
|
+
schema_type = schema['type'] || schema[:type]
|
|
37
|
+
# Build the inner parameter definition hash
|
|
38
|
+
legate_param_def = {
|
|
39
|
+
# ---> FIX: Check string name in required_set <---
|
|
40
|
+
required: required_set.include?(name.to_s),
|
|
41
|
+
# Use string or symbol key for description
|
|
42
|
+
description: schema['description'] || schema[:description] || ''
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Determine and add the type to the inner hash based on schema_type
|
|
46
|
+
case schema_type
|
|
47
|
+
when 'string'
|
|
48
|
+
legate_param_def[:type] = :string
|
|
49
|
+
when 'integer'
|
|
50
|
+
legate_param_def[:type] = :integer
|
|
51
|
+
when 'number'
|
|
52
|
+
legate_param_def[:type] = :numeric
|
|
53
|
+
when 'boolean'
|
|
54
|
+
legate_param_def[:type] = :boolean
|
|
55
|
+
when 'array'
|
|
56
|
+
legate_param_def[:type] = :array
|
|
57
|
+
else
|
|
58
|
+
Legate.logger.warn("MCP property '#{name}': Unsupported JSON Schema type '#{schema_type}'. Skipping.")
|
|
59
|
+
next
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Add the inner hash to the main hash, keyed by param_name
|
|
63
|
+
legate_params[param_name] = legate_param_def
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
legate_params # Return the hash of parameter hashes
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Converts Legate parameters hash into a Proc suitable for Dry::Schema's definition block.
|
|
70
|
+
# Handles basic types: :string, :integer, :numeric, :boolean.
|
|
71
|
+
# Logs warnings for unsupported types.
|
|
72
|
+
#
|
|
73
|
+
# @param legate_parameters_hash [Hash] The Legate parameters hash { name: { type:, required:, description: } }.
|
|
74
|
+
# @return [Proc] A Proc containing the Dry::Schema definition.
|
|
75
|
+
def self.legate_to_dry_schema(legate_parameters_hash)
|
|
76
|
+
Legate.logger.debug("Converting Legate params to Dry::Schema: #{legate_parameters_hash.inspect}")
|
|
77
|
+
return proc {} unless legate_parameters_hash.is_a?(Hash)
|
|
78
|
+
|
|
79
|
+
schema_lines = []
|
|
80
|
+
|
|
81
|
+
legate_parameters_hash.each do |name, definition|
|
|
82
|
+
unless definition.is_a?(Hash) && definition[:type]
|
|
83
|
+
Legate.logger.warn("Skipping Legate parameter '#{name}': Invalid definition format or missing type.")
|
|
84
|
+
next
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
required_or_optional = definition[:required] ? 'required' : 'optional'
|
|
88
|
+
dry_type_method = nil
|
|
89
|
+
type_spec = nil
|
|
90
|
+
|
|
91
|
+
case definition[:type]
|
|
92
|
+
when :string
|
|
93
|
+
dry_type_method = 'filled'
|
|
94
|
+
type_spec = ':string'
|
|
95
|
+
when :integer
|
|
96
|
+
dry_type_method = 'filled'
|
|
97
|
+
type_spec = ':integer'
|
|
98
|
+
when :numeric
|
|
99
|
+
# Use coercible float type to handle string inputs that represent numbers
|
|
100
|
+
dry_type_method = 'filled'
|
|
101
|
+
type_spec = 'Dry::Types[\'coercible.float\']'
|
|
102
|
+
when :boolean
|
|
103
|
+
dry_type_method = 'filled'
|
|
104
|
+
type_spec = ':bool'
|
|
105
|
+
when :array
|
|
106
|
+
Legate.logger.warn("Legate parameter '#{name}': Type :array basic mapping to Dry::Schema. Item types/validation not processed in V1.")
|
|
107
|
+
dry_type_method = 'value'
|
|
108
|
+
type_spec = ':array'
|
|
109
|
+
when :hash, :object
|
|
110
|
+
Legate.logger.warn("Legate parameter '#{name}': Type :#{definition[:type]} basic mapping to Dry::Schema :hash. Nested schema not processed in V1.")
|
|
111
|
+
dry_type_method = 'value'
|
|
112
|
+
type_spec = ':hash'
|
|
113
|
+
else
|
|
114
|
+
Legate.logger.warn("Legate parameter '#{name}': Unsupported Legate type '#{definition[:type]}'. Skipping.")
|
|
115
|
+
next
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Build the line
|
|
119
|
+
line = " #{required_or_optional}(:#{name})"
|
|
120
|
+
line += ".#{dry_type_method}" if dry_type_method
|
|
121
|
+
line += "(#{type_spec})" # Add the type specifier
|
|
122
|
+
|
|
123
|
+
schema_lines << line
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
schema_definition_string = schema_lines.join("\n")
|
|
127
|
+
Legate.logger.debug("Generated Dry::Schema definition string:\n#{schema_definition_string}")
|
|
128
|
+
|
|
129
|
+
proc { instance_eval(schema_definition_string) }
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
data/lib/legate/mcp.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# File: lib/legate/mcp.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# MCP errors are defined in legate/errors.rb (loaded by lib/legate.rb)
|
|
5
|
+
require_relative 'mcp/util/schema_converter'
|
|
6
|
+
require_relative 'mcp/connection/stdio'
|
|
7
|
+
require_relative 'mcp/client'
|
|
8
|
+
require_relative 'mcp/tool_wrapper'
|
|
9
|
+
require_relative 'mcp/connection_manager'
|
|
10
|
+
require_relative 'mcp/server/legate_tool_adapter'
|
|
11
|
+
require_relative 'mcp/server/legate_agent_adapter'
|
|
12
|
+
|
|
13
|
+
module Legate
|
|
14
|
+
# Module for Model Context Protocol (MCP) integration.
|
|
15
|
+
module Mcp
|
|
16
|
+
# Central point for MCP-related logging.
|
|
17
|
+
def self.logger
|
|
18
|
+
Legate.logger
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
logger.info('Legate::Mcp module loaded.')
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# File: lib/legate/plan_executor.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'json'
|
|
5
|
+
require_relative 'event'
|
|
6
|
+
require_relative 'tool_context'
|
|
7
|
+
|
|
8
|
+
module Legate
|
|
9
|
+
# Executes a planner-produced plan for an Agent: iterates the steps, injects
|
|
10
|
+
# prior-step results into parameters, runs each tool (with before/after-tool
|
|
11
|
+
# callbacks and delegation interception), and logs the tool_request/tool_result
|
|
12
|
+
# events. Extracted from Legate::Agent, which keeps thin execute_plan/execute_step
|
|
13
|
+
# delegators (the lifecycle entry points exercised directly by specs).
|
|
14
|
+
class PlanExecutor
|
|
15
|
+
# @param agent [Legate::Agent] the owning agent; the executor reads its
|
|
16
|
+
# tool registry, fallback mode, tool callbacks, and auth config.
|
|
17
|
+
def initialize(agent)
|
|
18
|
+
@agent = agent
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Executes a plan and returns { details: [...], last_result: <hash> }.
|
|
22
|
+
def execute_plan(plan, session, session_service, invocation_id)
|
|
23
|
+
session_id = session.id
|
|
24
|
+
|
|
25
|
+
# A planning failure returns a direct_result (a terminal result, no steps);
|
|
26
|
+
# surface it as-is so run_task builds a clean error Event.
|
|
27
|
+
return { details: [], last_result: plan[:direct_result] } if plan.is_a?(Hash) && plan[:direct_result]
|
|
28
|
+
|
|
29
|
+
# Extract steps based on the plan format
|
|
30
|
+
steps = nil
|
|
31
|
+
thought_process = nil
|
|
32
|
+
|
|
33
|
+
# Handle new plan structure with thought_process and steps
|
|
34
|
+
if plan.is_a?(Hash) && plan[:steps].is_a?(Array)
|
|
35
|
+
steps = plan[:steps]
|
|
36
|
+
thought_process = plan[:thought_process]
|
|
37
|
+
Legate.logger.info("Plan thought process: #{thought_process}") if thought_process
|
|
38
|
+
elsif plan.is_a?(Array)
|
|
39
|
+
# For backward compatibility with old format
|
|
40
|
+
steps = plan
|
|
41
|
+
else
|
|
42
|
+
msg = 'Invalid plan received from planner (not an Array or properly structured Hash).'
|
|
43
|
+
Legate.logger.error("#{msg} Plan: #{plan.inspect}")
|
|
44
|
+
return { details: [], last_result: { status: :error, error_message: msg } }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# --- Continue with original logic, using 'steps' variable ---
|
|
48
|
+
unless steps.is_a?(Array)
|
|
49
|
+
msg = 'Invalid steps structure in plan (not an Array).'
|
|
50
|
+
Legate.logger.error("#{msg} Steps: #{steps.inspect}")
|
|
51
|
+
return { details: [], last_result: { status: :error, error_message: msg } }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# --- Handle Empty Plan based on Fallback Mode ---
|
|
55
|
+
if steps.empty?
|
|
56
|
+
if @agent.fallback_mode == :echo
|
|
57
|
+
if @agent.tool_registry.find_class(:echo)
|
|
58
|
+
Legate.logger.warn("Plan is empty. Falling back to echo mode for session '#{session_id}'.")
|
|
59
|
+
# Reconstruct the plan to be a single echo step
|
|
60
|
+
# We need the original user input for this - fetch it from the session
|
|
61
|
+
# Find the *last* user event in case of corrections/multiple turns
|
|
62
|
+
original_user_input = session.events.reverse.find { |e|
|
|
63
|
+
e.role == :user
|
|
64
|
+
}&.content || '[Original input not found]'
|
|
65
|
+
steps = [{ tool: :echo, params: { message: original_user_input } }]
|
|
66
|
+
Legate.logger.debug("Reconstructed plan for echo fallback: #{steps.inspect}")
|
|
67
|
+
# Now continue execution with the modified plan
|
|
68
|
+
else
|
|
69
|
+
# Echo tool not available, default to error mode
|
|
70
|
+
msg = 'Planning failed and Echo fallback tool is not available to this agent.'
|
|
71
|
+
Legate.logger.warn(msg)
|
|
72
|
+
return { details: [], last_result: { status: :error, error_message: msg } }
|
|
73
|
+
end
|
|
74
|
+
else # Default or :error mode
|
|
75
|
+
msg = 'I cannot fulfill this request with the available tools (empty plan).'
|
|
76
|
+
Legate.logger.warn(msg)
|
|
77
|
+
return { details: [], last_result: { status: :error, error_message: msg } }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
# --- End Handle Empty Plan ---
|
|
81
|
+
|
|
82
|
+
Legate.logger.debug("Executing plan with #{steps.length} step(s) for session '#{session_id}': #{steps.inspect}")
|
|
83
|
+
previous_step_result_hash = nil
|
|
84
|
+
plan_execution_details = []
|
|
85
|
+
last_successful_or_pending_result = nil # <-- Store the original last hash
|
|
86
|
+
|
|
87
|
+
steps.each_with_index do |step, index|
|
|
88
|
+
# Log the step type for clarity
|
|
89
|
+
step_type_desc = if step[:step_type] == :sequential_sub_agent
|
|
90
|
+
"sequential sub-agent '#{step[:sub_agent_name]}'"
|
|
91
|
+
else
|
|
92
|
+
"tool '#{step[:tool]}'"
|
|
93
|
+
end
|
|
94
|
+
Legate.logger.debug("Executing step #{index + 1}/#{steps.length}: #{step_type_desc}")
|
|
95
|
+
Legate.logger.debug(" Step details: #{step.inspect}")
|
|
96
|
+
Legate.logger.debug(" Input (result hash from previous step): #{previous_step_result_hash.inspect}")
|
|
97
|
+
|
|
98
|
+
# --- Input Injection Logic (Updated for job_id) ---
|
|
99
|
+
current_params = JSON.parse(JSON.generate(step[:params]), symbolize_names: true)
|
|
100
|
+
current_params.transform_values! do |value|
|
|
101
|
+
injection_value = nil
|
|
102
|
+
if value.is_a?(String) && value.match?(/\[Result from step \d+\]|\[Result from previous step\]/i)
|
|
103
|
+
if previous_step_result_hash.is_a?(Hash) && %i[success pending].include?(previous_step_result_hash[:status])
|
|
104
|
+
# Prioritize :result, then :job_id (was workflow_id), then :message
|
|
105
|
+
if previous_step_result_hash.key?(:result)
|
|
106
|
+
prev_result = previous_step_result_hash[:result]
|
|
107
|
+
if prev_result.is_a?(Hash) && prev_result.key?(:status) && prev_result.key?(:result) # AgentTool nested result
|
|
108
|
+
injection_value = prev_result[:result]
|
|
109
|
+
Legate.logger.debug('Injecting nested result...')
|
|
110
|
+
else
|
|
111
|
+
injection_value = prev_result
|
|
112
|
+
Legate.logger.debug('Injecting direct result...')
|
|
113
|
+
end
|
|
114
|
+
elsif previous_step_result_hash.key?(:job_id) # <-- CHANGED from workflow_id
|
|
115
|
+
injection_value = previous_step_result_hash[:job_id]
|
|
116
|
+
Legate.logger.debug('Injecting job_id from previous step...')
|
|
117
|
+
elsif previous_step_result_hash.key?(:message)
|
|
118
|
+
injection_value = previous_step_result_hash[:message]
|
|
119
|
+
Legate.logger.debug('Injecting message from previous step...')
|
|
120
|
+
else
|
|
121
|
+
Legate.logger.warn("Cannot inject: Previous successful/pending step missing usable key (:result, :job_id, :message). Prev Hash: #{previous_step_result_hash.inspect}")
|
|
122
|
+
value
|
|
123
|
+
end
|
|
124
|
+
else
|
|
125
|
+
Legate.logger.warn("Cannot inject: Previous step failed or absent. Prev Hash: #{previous_step_result_hash.inspect}")
|
|
126
|
+
value
|
|
127
|
+
end
|
|
128
|
+
injection_value || value # Use injection if found, otherwise keep original
|
|
129
|
+
else
|
|
130
|
+
value # Not a placeholder string, keep original value
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
step_with_injected_params = step.merge(params: current_params)
|
|
134
|
+
Legate.logger.debug(" Params after potential injection: #{current_params.inspect}")
|
|
135
|
+
# --- End Input Injection Logic ---
|
|
136
|
+
|
|
137
|
+
# --- Execute Step --- #
|
|
138
|
+
current_result_hash = execute_step(step_with_injected_params, session, session_service, invocation_id)
|
|
139
|
+
|
|
140
|
+
# --- Sanitize for plan_details --- #
|
|
141
|
+
sanitized_result_for_plan = {}
|
|
142
|
+
if current_result_hash.is_a?(Hash)
|
|
143
|
+
sanitized_result_for_plan[:status] = current_result_hash[:status]
|
|
144
|
+
# Always include error keys, defaulting to nil if not present
|
|
145
|
+
sanitized_result_for_plan[:error_message] = current_result_hash[:error_message] # Defaults to nil if key missing
|
|
146
|
+
sanitized_result_for_plan[:error_class] = current_result_hash[:error_class] # Defaults to nil if key missing
|
|
147
|
+
# Include other relevant keys if present
|
|
148
|
+
sanitized_result_for_plan[:job_id] = current_result_hash[:job_id] if current_result_hash.key?(:job_id)
|
|
149
|
+
sanitized_result_for_plan[:message] = current_result_hash[:message] if current_result_hash.key?(:message)
|
|
150
|
+
# Only include :result value if it's simple
|
|
151
|
+
result_val = current_result_hash[:result]
|
|
152
|
+
if result_val.is_a?(String) || result_val.is_a?(Numeric) || [true, false, nil].include?(result_val)
|
|
153
|
+
sanitized_result_for_plan[:result] = result_val
|
|
154
|
+
elsif current_result_hash.key?(:result) # It exists but is complex
|
|
155
|
+
sanitized_result_for_plan[:result] = '[Complex Result Structure]'
|
|
156
|
+
end
|
|
157
|
+
else # Should not happen based on execute_step validation, but handle defensively
|
|
158
|
+
sanitized_result_for_plan[:status] = :error
|
|
159
|
+
sanitized_result_for_plan[:error_message] = "Invalid format from execute_step: #{current_result_hash.inspect}"
|
|
160
|
+
end
|
|
161
|
+
# --- END Sanitization ---
|
|
162
|
+
|
|
163
|
+
# --- Store SANITIZED step detail --- #
|
|
164
|
+
plan_execution_details << {
|
|
165
|
+
tool_name: step[:tool],
|
|
166
|
+
params: current_params,
|
|
167
|
+
result: sanitized_result_for_plan
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# --- Store ORIGINAL result and check for errors --- #
|
|
171
|
+
if current_result_hash[:status] == :error
|
|
172
|
+
Legate.logger.warn("Step #{index + 1} failed, stopping plan execution: #{current_result_hash[:error_message]}")
|
|
173
|
+
last_successful_or_pending_result = current_result_hash # Store the error hash as last result
|
|
174
|
+
break # Exit the loop
|
|
175
|
+
else
|
|
176
|
+
# Store successful or pending hash for potential injection AND final result
|
|
177
|
+
previous_step_result_hash = current_result_hash
|
|
178
|
+
last_successful_or_pending_result = current_result_hash
|
|
179
|
+
end
|
|
180
|
+
# --- End Stop on first error / Store last result --- #
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
Legate.logger.debug("Plan execution finished. Structured details collected: #{plan_execution_details.inspect}")
|
|
184
|
+
Legate.logger.debug("Plan execution finished. Original last result: #{last_successful_or_pending_result.inspect}")
|
|
185
|
+
|
|
186
|
+
# --- Return BOTH sanitized details AND original last result --- #
|
|
187
|
+
{ details: plan_execution_details, last_result: last_successful_or_pending_result }
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Executes a single step, logging :tool_request and :tool_result events via
|
|
191
|
+
# the session service.
|
|
192
|
+
# @return [Hash] A standard result hash { status:, result/error_message/job_id: }.
|
|
193
|
+
def execute_step(step, session, session_service, invocation_id = nil)
|
|
194
|
+
session_id = session.id
|
|
195
|
+
|
|
196
|
+
# --- Basic validation ---
|
|
197
|
+
unless step.is_a?(Hash) && step[:tool] && step[:params].is_a?(Hash)
|
|
198
|
+
error_msg = 'Invalid step format. Expected { tool: :symbol, params: {...} }'
|
|
199
|
+
Legate.logger.error(error_msg)
|
|
200
|
+
return { status: :error, error_message: error_msg }
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
raw_tool_name = step[:tool].to_s
|
|
204
|
+
|
|
205
|
+
# Validate tool name against known tools before converting to Symbol
|
|
206
|
+
# to prevent Symbol table exhaustion from untrusted input
|
|
207
|
+
known_names = @agent.tool_registry.available_tool_names.map(&:to_s)
|
|
208
|
+
is_delegation = raw_tool_name.start_with?('agent_transfer_to_')
|
|
209
|
+
unless known_names.include?(raw_tool_name) || is_delegation
|
|
210
|
+
error_msg = "Unknown tool '#{raw_tool_name}' — not in agent's tool registry"
|
|
211
|
+
Legate.logger.error(error_msg)
|
|
212
|
+
return { status: :error, error_message: error_msg }
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
tool_name = raw_tool_name.to_sym
|
|
216
|
+
params = step[:params].to_h
|
|
217
|
+
|
|
218
|
+
# --- Intercept Delegation Tools (MAS) ---
|
|
219
|
+
# If the model outputs "agent_transfer_to_xyz", map it to "delegate_task"
|
|
220
|
+
if tool_name.to_s.start_with?('agent_transfer_to_')
|
|
221
|
+
target_agent_name = tool_name.to_s.sub('agent_transfer_to_', '')
|
|
222
|
+
Legate.logger.info("Intercepted delegation tool '#{tool_name}'. Mapping to 'delegate_task' for target '#{target_agent_name}'.")
|
|
223
|
+
|
|
224
|
+
# Remap tool name
|
|
225
|
+
tool_name = :delegate_task
|
|
226
|
+
|
|
227
|
+
# Remap params: ensure target_agent_name is set
|
|
228
|
+
params[:target_agent_name] = target_agent_name
|
|
229
|
+
|
|
230
|
+
# Ensure 'task' param exists (model should provide it, but handle aliasing/defaults if needed)
|
|
231
|
+
# The prompt says: - task (string, required)
|
|
232
|
+
unless params.key?(:task)
|
|
233
|
+
# Fallback: if model used a different key like 'message' or 'input', map it to 'task'
|
|
234
|
+
if params.key?(:message)
|
|
235
|
+
params[:task] = params.delete(:message)
|
|
236
|
+
elsif params.key?(:input)
|
|
237
|
+
params[:task] = params.delete(:input)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
# --- End Delegation Interception ---
|
|
242
|
+
|
|
243
|
+
# --- Get the tool from our registry ---
|
|
244
|
+
tool = @agent.tool_registry.create_instance(tool_name)
|
|
245
|
+
unless tool
|
|
246
|
+
error_msg = "Tool '#{tool_name}' not found in available tools."
|
|
247
|
+
Legate.logger.error(error_msg)
|
|
248
|
+
return { status: :error, error_message: error_msg }
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# --- Prepare tool context with invocation_id and auth config ---
|
|
252
|
+
tool_context = Legate::ToolContext.new(
|
|
253
|
+
session_id: session.id,
|
|
254
|
+
user_id: session.user_id,
|
|
255
|
+
app_name: session.app_name,
|
|
256
|
+
session_service: session_service,
|
|
257
|
+
tool_registry: @agent.tool_registry,
|
|
258
|
+
invocation_id: invocation_id,
|
|
259
|
+
agent_auth_config: @agent.send(:build_agent_auth_config)
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# --- Log the tool request event ---
|
|
263
|
+
tool_request_event = Legate::Event.new(
|
|
264
|
+
role: :tool_request,
|
|
265
|
+
tool_name: tool_name,
|
|
266
|
+
content: params
|
|
267
|
+
)
|
|
268
|
+
session_service.append_event(session_id: session_id, event: tool_request_event)
|
|
269
|
+
|
|
270
|
+
# --- Execute before_tool_callback if defined ---
|
|
271
|
+
if @agent.before_tool_callback.is_a?(Proc)
|
|
272
|
+
Legate.logger.debug { "Agent '#{@agent.name}': Executing before_tool_callback for tool '#{tool_name}'." }
|
|
273
|
+
|
|
274
|
+
begin
|
|
275
|
+
# Execute the callback and check if it returns a result
|
|
276
|
+
override_result = @agent.before_tool_callback.call(tool, params.dup, tool_context)
|
|
277
|
+
|
|
278
|
+
# If the callback returns a result (not nil), use it instead of normal tool execution
|
|
279
|
+
if override_result
|
|
280
|
+
Legate.logger.info { "Agent '#{@agent.name}': before_tool_callback provided an override result for tool '#{tool_name}'." }
|
|
281
|
+
|
|
282
|
+
# Create a tool result event with the override result and any state changes
|
|
283
|
+
tool_result_event = Legate::Event.new(
|
|
284
|
+
role: :tool_result,
|
|
285
|
+
tool_name: tool_name,
|
|
286
|
+
content: override_result,
|
|
287
|
+
state_delta: tool_context.pending_state_delta
|
|
288
|
+
)
|
|
289
|
+
session_service.append_event(session_id: session_id, event: tool_result_event)
|
|
290
|
+
|
|
291
|
+
return override_result
|
|
292
|
+
end
|
|
293
|
+
rescue StandardError => e
|
|
294
|
+
Legate.logger.error { "Agent '#{@agent.name}': Error in before_tool_callback for tool '#{tool_name}': #{e.message}\n#{e.backtrace.join("\n")}" }
|
|
295
|
+
|
|
296
|
+
error_result = {
|
|
297
|
+
status: :error,
|
|
298
|
+
error_message: "Error in before_tool_callback: #{e.message}",
|
|
299
|
+
error_class: e.class.name
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
# Create a tool result event with the error
|
|
303
|
+
tool_result_event = Legate::Event.new(
|
|
304
|
+
role: :tool_result,
|
|
305
|
+
tool_name: tool_name,
|
|
306
|
+
content: error_result,
|
|
307
|
+
state_delta: tool_context.pending_state_delta
|
|
308
|
+
)
|
|
309
|
+
session_service.append_event(session_id: session_id, event: tool_result_event)
|
|
310
|
+
|
|
311
|
+
return error_result
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# --- Execute the tool ---
|
|
316
|
+
begin
|
|
317
|
+
Legate.logger.debug { "Executing tool '#{tool_name}' with params #{params.inspect}" }
|
|
318
|
+
final_tool_name_to_execute = tool_name
|
|
319
|
+
|
|
320
|
+
# For delegate_task tool, capture the delegate to show in logs
|
|
321
|
+
final_tool_name_to_execute = "#{tool_name} -> #{params[:target_agent_name]}" if tool_name == :delegate_task && params[:target_agent_name]
|
|
322
|
+
|
|
323
|
+
result = tool.execute(params, tool_context)
|
|
324
|
+
|
|
325
|
+
# --- Execute after_tool_callback if defined ---
|
|
326
|
+
if @agent.after_tool_callback.is_a?(Proc)
|
|
327
|
+
Legate.logger.debug { "Agent '#{@agent.name}': Executing after_tool_callback for tool '#{final_tool_name_to_execute}'." }
|
|
328
|
+
|
|
329
|
+
begin
|
|
330
|
+
# Execute the callback and let it modify the result if needed
|
|
331
|
+
modified_result = @agent.after_tool_callback.call(tool, params.dup, tool_context, result.dup)
|
|
332
|
+
|
|
333
|
+
# If the callback returned a modified result, use it
|
|
334
|
+
if modified_result && modified_result != result
|
|
335
|
+
Legate.logger.info { "Agent '#{@agent.name}': after_tool_callback modified the result for tool '#{final_tool_name_to_execute}'." }
|
|
336
|
+
result = modified_result
|
|
337
|
+
end
|
|
338
|
+
rescue StandardError => e
|
|
339
|
+
Legate.logger.error { "Agent '#{@agent.name}': Error in after_tool_callback for tool '#{final_tool_name_to_execute}': #{e.message}\n#{e.backtrace.join("\n")}" }
|
|
340
|
+
# Don't override the result completely on error, just log it
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# --- Log the tool result event ---
|
|
345
|
+
tool_result_event = Legate::Event.new(
|
|
346
|
+
role: :tool_result,
|
|
347
|
+
tool_name: tool_name,
|
|
348
|
+
content: result,
|
|
349
|
+
state_delta: tool_context.pending_state_delta
|
|
350
|
+
)
|
|
351
|
+
session_service.append_event(session_id: session_id, event: tool_result_event)
|
|
352
|
+
|
|
353
|
+
result
|
|
354
|
+
rescue StandardError => e
|
|
355
|
+
Legate.logger.error { "Error executing tool '#{tool_name}': #{e.message}\n#{e.backtrace.join("\n")}" }
|
|
356
|
+
|
|
357
|
+
error_result = {
|
|
358
|
+
status: :error,
|
|
359
|
+
error_message: "Tool '#{tool_name}' execution error: #{e.message}",
|
|
360
|
+
error_class: e.class.name # consistent with every other error hash + the plan-detail sanitizer
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
# Create a tool result event with the error
|
|
364
|
+
tool_result_event = Legate::Event.new(
|
|
365
|
+
role: :tool_result,
|
|
366
|
+
tool_name: tool_name,
|
|
367
|
+
content: error_result
|
|
368
|
+
)
|
|
369
|
+
session_service.append_event(session_id: session_id, event: tool_result_event)
|
|
370
|
+
|
|
371
|
+
error_result
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|