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
data/lib/legate/tool.rb
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# File: lib/legate/tool.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative 'tool_registry'
|
|
5
|
+
require 'logger'
|
|
6
|
+
require_relative 'tool_result'
|
|
7
|
+
require_relative 'tool_context'
|
|
8
|
+
require_relative 'global_tool_manager'
|
|
9
|
+
require_relative 'tool/metadata_dsl'
|
|
10
|
+
require 'json'
|
|
11
|
+
|
|
12
|
+
module Legate
|
|
13
|
+
# Base class for all tools that can be used by Agents.
|
|
14
|
+
#
|
|
15
|
+
# Tools are the way agents interact with the outside world. To create a new tool,
|
|
16
|
+
# inherit from this class, use the DSL to define metadata, and implement
|
|
17
|
+
# the {#perform_execution} method.
|
|
18
|
+
#
|
|
19
|
+
# @example Creating a custom weather tool
|
|
20
|
+
# class WeatherTool < Legate::Tool
|
|
21
|
+
# tool_description 'Fetches current weather for a location'
|
|
22
|
+
#
|
|
23
|
+
# parameter :location, type: :string, required: true,
|
|
24
|
+
# description: 'City name (e.g. "San Francisco")'
|
|
25
|
+
#
|
|
26
|
+
# parameter :unit, type: :string, required: false,
|
|
27
|
+
# description: 'Temperature unit (celsius/fahrenheit)'
|
|
28
|
+
#
|
|
29
|
+
# private
|
|
30
|
+
#
|
|
31
|
+
# def perform_execution(params, context)
|
|
32
|
+
# location = params[:location]
|
|
33
|
+
# # ... fetch weather logic ...
|
|
34
|
+
# weather_data = "Sunny, 25C"
|
|
35
|
+
#
|
|
36
|
+
# {
|
|
37
|
+
# status: :success,
|
|
38
|
+
# result: weather_data
|
|
39
|
+
# }
|
|
40
|
+
# rescue => e
|
|
41
|
+
# {
|
|
42
|
+
# status: :error,
|
|
43
|
+
# error_message: "Failed to fetch weather: #{e.message}"
|
|
44
|
+
# }
|
|
45
|
+
# end
|
|
46
|
+
# end
|
|
47
|
+
#
|
|
48
|
+
class Tool
|
|
49
|
+
# --- Include the DSL ---
|
|
50
|
+
include MetadataDsl
|
|
51
|
+
|
|
52
|
+
# --- Class-level attributes --- Accessors defined via DSL, but keep old readers/define_metadata for backward compat ---
|
|
53
|
+
class << self
|
|
54
|
+
# Keep old readers for define_metadata compatibility
|
|
55
|
+
attr_reader :tool_name, :description, :parameters_definition
|
|
56
|
+
|
|
57
|
+
# Define the tool's static metadata.
|
|
58
|
+
# DEPRECATED: Use `tool_description`, `parameter`, and automatic name inference instead.
|
|
59
|
+
def define_metadata(name:, description:, parameters: {})
|
|
60
|
+
warn "[DEPRECATION] `define_metadata` is deprecated. Use `tool_description`, `parameter`, and rely on class name inference (or `self.explicit_tool_name = :my_name`) instead. Called from #{caller_locations(
|
|
61
|
+
1, 1
|
|
62
|
+
)[0].label}"
|
|
63
|
+
|
|
64
|
+
@tool_name = name.to_sym
|
|
65
|
+
@description = description
|
|
66
|
+
@parameters_definition = parameters
|
|
67
|
+
@_tool_metadata_cache = nil # Invalidate cache
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# --- Self-Registration Hook ---
|
|
72
|
+
# NOTE: We intentionally do NOT auto-register tools in the inherited hook.
|
|
73
|
+
# The reason is that `inherited` is called BEFORE the class body executes,
|
|
74
|
+
# so explicit_tool_name and other DSL methods haven't run yet. This would
|
|
75
|
+
# cause tools to be registered under their inferred name (e.g., :random_number_tool)
|
|
76
|
+
# instead of their explicit name (e.g., :random_number).
|
|
77
|
+
#
|
|
78
|
+
# Instead, built-in tools are explicitly registered in lib/legate.rb after their
|
|
79
|
+
# class definitions are complete. Custom tools should either:
|
|
80
|
+
# 1. Call `Legate::GlobalToolManager.register_tool(MyTool)` explicitly after definition
|
|
81
|
+
# 2. Be discovered via tool_paths when creating agents
|
|
82
|
+
def self.inherited(subclass)
|
|
83
|
+
super # Call parent's inherited if necessary
|
|
84
|
+
Legate.logger.debug("Tool subclass #{subclass} inherited. Tool will be registered when explicitly added to GlobalToolManager or an agent's tool registry.")
|
|
85
|
+
end
|
|
86
|
+
# --- End Hook ---
|
|
87
|
+
|
|
88
|
+
# Instance readers
|
|
89
|
+
attr_reader :name, :description, :parameters
|
|
90
|
+
|
|
91
|
+
# Initialize - Sets instance vars from class metadata
|
|
92
|
+
def initialize(**_options)
|
|
93
|
+
# Fetch metadata using the primary tool_metadata method (defined by DSL)
|
|
94
|
+
metadata = self.class.tool_metadata
|
|
95
|
+
@name = metadata[:name]
|
|
96
|
+
@description = metadata[:description]
|
|
97
|
+
@parameters = metadata[:parameters] || {}
|
|
98
|
+
|
|
99
|
+
# Lenient check for missing metadata
|
|
100
|
+
return unless @name.nil? || @name == :'' || @description.nil? || @description.empty?
|
|
101
|
+
|
|
102
|
+
is_anonymous = !self.class.name || self.class.name.empty? || self.class.name.start_with?('#<Class:')
|
|
103
|
+
unless is_anonymous
|
|
104
|
+
missing = []
|
|
105
|
+
missing << ':name' if @name.nil? || @name == :''
|
|
106
|
+
missing << ':description' if @description.nil? || @description.empty?
|
|
107
|
+
Legate.logger.warn("Tool class #{self.class} initialized with missing metadata: [#{missing.join(', ')}] using #{self.class.tool_metadata}. Tool may not function correctly.")
|
|
108
|
+
end
|
|
109
|
+
@description ||= ''
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Execute the tool
|
|
113
|
+
# @param params [Hash] Input parameters for the tool.
|
|
114
|
+
# @param context [Legate::ToolContext, nil] Contextual information (session details).
|
|
115
|
+
# @return [Hash] A hash with :status (:success, :error, :pending) and :result/:error_message/:job_id.
|
|
116
|
+
def execute(params = {}, context = nil)
|
|
117
|
+
coerced_params = validate_and_coerce_params(params)
|
|
118
|
+
Legate.logger.debug("Executing tool '#{@name}' with validated params: #{coerced_params.inspect} and context: #{context&.to_h.inspect}")
|
|
119
|
+
result = perform_execution(coerced_params, context)
|
|
120
|
+
# perform_execution may return a typed ToolResult or the canonical hash;
|
|
121
|
+
# normalize to the hash here so everything downstream is unchanged.
|
|
122
|
+
result.is_a?(Legate::ToolResult) ? result.to_h : result
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Validate the parameters (Deprecated: Use validate_and_coerce_params)
|
|
126
|
+
# This method is kept for backward compatibility and wraps the new logic,
|
|
127
|
+
# but ignores the coerced return value.
|
|
128
|
+
def validate_params(params)
|
|
129
|
+
validate_and_coerce_params(params)
|
|
130
|
+
nil
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Validate and coerce parameters based on metadata types
|
|
134
|
+
# @param params [Hash] Input parameters
|
|
135
|
+
# @return [Hash] New hash with symbol keys and coerced values
|
|
136
|
+
# @raise [Legate::ToolArgumentError] if validation fails
|
|
137
|
+
def validate_and_coerce_params(params)
|
|
138
|
+
# 1. Normalize keys to symbols
|
|
139
|
+
normalized_params = params.transform_keys(&:to_sym)
|
|
140
|
+
|
|
141
|
+
current_parameters = @parameters || {}
|
|
142
|
+
|
|
143
|
+
# 2. Check for missing required parameters
|
|
144
|
+
# Use symbol keys for check
|
|
145
|
+
required_param_names = current_parameters.select { |_, p| p[:required] }.keys
|
|
146
|
+
present_keys = normalized_params.keys
|
|
147
|
+
missing_params = required_param_names - present_keys
|
|
148
|
+
|
|
149
|
+
unless missing_params.empty?
|
|
150
|
+
msg = "Missing required parameters for tool '#{@name}': #{missing_params.join(', ')}."
|
|
151
|
+
msg += " Provided: [#{present_keys.empty? ? 'None' : present_keys.join(', ')}]."
|
|
152
|
+
|
|
153
|
+
Legate.logger.error("Validation failed: #{msg} Params: #{params.inspect}")
|
|
154
|
+
raise Legate::ToolArgumentError, msg
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# 3. Type Validation & Coercion
|
|
158
|
+
coerced_params = normalized_params.dup
|
|
159
|
+
|
|
160
|
+
current_parameters.each do |param_name, param_def|
|
|
161
|
+
# Only process if present
|
|
162
|
+
next unless coerced_params.key?(param_name)
|
|
163
|
+
|
|
164
|
+
value = coerced_params[param_name]
|
|
165
|
+
expected_type = param_def[:type]
|
|
166
|
+
next unless expected_type
|
|
167
|
+
|
|
168
|
+
begin
|
|
169
|
+
coerced_value = coerce_value(value, expected_type)
|
|
170
|
+
coerced_params[param_name] = coerced_value
|
|
171
|
+
rescue Legate::ToolArgumentError => e
|
|
172
|
+
# coerce_value raises ToolArgumentError (not ArgumentError/TypeError);
|
|
173
|
+
# re-raise with the parameter/tool context the bare message lacks.
|
|
174
|
+
raise Legate::ToolArgumentError, "Parameter '#{param_name}' for tool '#{@name}': #{e.message}"
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
coerced_params
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
private
|
|
182
|
+
|
|
183
|
+
# Coerce a value to the expected type
|
|
184
|
+
def coerce_value(value, type)
|
|
185
|
+
return value if value.nil?
|
|
186
|
+
|
|
187
|
+
case type
|
|
188
|
+
when :string
|
|
189
|
+
# Reject containers rather than silently producing inspect-garbage like
|
|
190
|
+
# "[1, 2]" / '{:a=>1}'; scalars (Numeric/Symbol/bool) stringify fine.
|
|
191
|
+
raise Legate::ToolArgumentError, "expected String, got #{value.class} (#{value.inspect})" if value.is_a?(Array) || value.is_a?(Hash)
|
|
192
|
+
|
|
193
|
+
value.to_s
|
|
194
|
+
when :integer
|
|
195
|
+
# Force base 10 for strings so "010"/"0x1f" aren't read as octal/hex;
|
|
196
|
+
# numbers (e.g. 123.0) still truncate via plain Integer().
|
|
197
|
+
begin
|
|
198
|
+
value.is_a?(String) ? Integer(value, 10) : Integer(value)
|
|
199
|
+
rescue ArgumentError, TypeError
|
|
200
|
+
raise Legate::ToolArgumentError, "expected Integer, got #{value.class} (#{value.inspect})"
|
|
201
|
+
end
|
|
202
|
+
when :float, :numeric
|
|
203
|
+
# :numeric treated as Float for broad compatibility
|
|
204
|
+
begin
|
|
205
|
+
Float(value)
|
|
206
|
+
rescue ArgumentError, TypeError
|
|
207
|
+
raise Legate::ToolArgumentError, "expected Numeric/Float, got #{value.class} (#{value.inspect})"
|
|
208
|
+
end
|
|
209
|
+
when :boolean
|
|
210
|
+
if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
211
|
+
value
|
|
212
|
+
elsif value.is_a?(String)
|
|
213
|
+
case value.downcase
|
|
214
|
+
when 'true', 't', 'yes', '1' then true
|
|
215
|
+
when 'false', 'f', 'no', '0' then false
|
|
216
|
+
else
|
|
217
|
+
raise Legate::ToolArgumentError, "expected Boolean, got String '#{value}'"
|
|
218
|
+
end
|
|
219
|
+
else
|
|
220
|
+
raise Legate::ToolArgumentError, "expected Boolean, got #{value.class} (#{value.inspect})"
|
|
221
|
+
end
|
|
222
|
+
when :array
|
|
223
|
+
if value.is_a?(Array)
|
|
224
|
+
value
|
|
225
|
+
elsif value.is_a?(String)
|
|
226
|
+
begin
|
|
227
|
+
parsed = JSON.parse(value)
|
|
228
|
+
raise ArgumentError unless parsed.is_a?(Array)
|
|
229
|
+
|
|
230
|
+
parsed
|
|
231
|
+
rescue StandardError
|
|
232
|
+
raise Legate::ToolArgumentError, "expected Array, got #{value.class} (#{value.inspect})"
|
|
233
|
+
end
|
|
234
|
+
else
|
|
235
|
+
raise Legate::ToolArgumentError, "expected Array, got #{value.class} (#{value.inspect})"
|
|
236
|
+
end
|
|
237
|
+
when :hash
|
|
238
|
+
if value.is_a?(Hash)
|
|
239
|
+
value
|
|
240
|
+
elsif value.is_a?(String)
|
|
241
|
+
begin
|
|
242
|
+
parsed = JSON.parse(value)
|
|
243
|
+
raise ArgumentError unless parsed.is_a?(Hash)
|
|
244
|
+
|
|
245
|
+
parsed
|
|
246
|
+
rescue StandardError
|
|
247
|
+
raise Legate::ToolArgumentError, "expected Hash, got #{value.class} (#{value.inspect})"
|
|
248
|
+
end
|
|
249
|
+
else
|
|
250
|
+
raise Legate::ToolArgumentError, "expected Hash, got #{value.class} (#{value.inspect})"
|
|
251
|
+
end
|
|
252
|
+
else
|
|
253
|
+
# Unknown type or 'any', return as is
|
|
254
|
+
value
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Perform the actual execution of the tool.
|
|
259
|
+
#
|
|
260
|
+
# This method must be implemented by subclasses to define the tool's behavior.
|
|
261
|
+
#
|
|
262
|
+
# @param params [Hash] The validated parameters to execute with. Keys are symbols.
|
|
263
|
+
# @param context [Legate::ToolContext] Contextual information (session, user, state).
|
|
264
|
+
#
|
|
265
|
+
# @return [Hash] The result hash containing:
|
|
266
|
+
# * :status [Symbol] :success, :error, or :pending
|
|
267
|
+
# * :result [Object] The output data (if success)
|
|
268
|
+
# * :error_message [String] Error description (if error)
|
|
269
|
+
# * :job_id [String] Job ID (if pending/async)
|
|
270
|
+
#
|
|
271
|
+
# @raise [NotImplementedError] if the subclass does not implement this method.
|
|
272
|
+
def perform_execution(params, context)
|
|
273
|
+
raise NotImplementedError, 'Subclasses must implement #perform_execution(params, context)'
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# File: lib/legate/tool_code_generator.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Legate
|
|
5
|
+
# Generates Ruby source code from tool metadata.
|
|
6
|
+
# Used to create downloadable .rb files for native tools from the web UI.
|
|
7
|
+
module ToolCodeGenerator
|
|
8
|
+
# Generate Ruby code from a tool's metadata.
|
|
9
|
+
# @param tool_name [Symbol, String] The tool name registered with GlobalToolManager.
|
|
10
|
+
# @return [String, nil] Valid Ruby source code or nil if tool not found.
|
|
11
|
+
def self.generate(tool_name)
|
|
12
|
+
tool_name_sym = tool_name.to_sym
|
|
13
|
+
tool_class = Legate::GlobalToolManager.find_class(tool_name_sym)
|
|
14
|
+
return nil unless tool_class
|
|
15
|
+
|
|
16
|
+
metadata = tool_class.tool_metadata
|
|
17
|
+
return nil unless metadata
|
|
18
|
+
|
|
19
|
+
name = metadata[:name] || tool_name_sym
|
|
20
|
+
description = metadata[:description] || 'No description provided'
|
|
21
|
+
parameters = metadata[:parameters] || {}
|
|
22
|
+
|
|
23
|
+
# Generate class name from tool name (snake_case to PascalCase)
|
|
24
|
+
class_name = name.to_s.split('_').map(&:capitalize).join
|
|
25
|
+
|
|
26
|
+
code = <<~RUBY
|
|
27
|
+
# frozen_string_literal: true
|
|
28
|
+
|
|
29
|
+
# Tool: #{name}
|
|
30
|
+
# Generated from Legate Web UI on #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}
|
|
31
|
+
|
|
32
|
+
require 'legate/tool'
|
|
33
|
+
|
|
34
|
+
module Legate
|
|
35
|
+
module Tools
|
|
36
|
+
class #{class_name} < Tool
|
|
37
|
+
tool_description #{ruby_string(description)}
|
|
38
|
+
|
|
39
|
+
RUBY
|
|
40
|
+
|
|
41
|
+
# Add parameter declarations
|
|
42
|
+
parameters.each do |param_name, param_opts|
|
|
43
|
+
code += generate_parameter(param_name, param_opts)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
code += <<~RUBY
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# @param params [Hash] The validated input parameters.
|
|
51
|
+
# @param context [Legate::ToolContext] The execution context.
|
|
52
|
+
# @return [Hash] Result with :status and :result or :error_message.
|
|
53
|
+
def perform_execution(params, context)
|
|
54
|
+
# TODO: Implement your tool logic here
|
|
55
|
+
#
|
|
56
|
+
# Available context methods:
|
|
57
|
+
# context.state_get(:key) - Read from session state
|
|
58
|
+
# context.state_set(:key, value) - Write to session state
|
|
59
|
+
# context.session_id - Current session ID
|
|
60
|
+
#
|
|
61
|
+
RUBY
|
|
62
|
+
|
|
63
|
+
# Add parameter access examples
|
|
64
|
+
parameters.each do |param_name, _param_opts|
|
|
65
|
+
code += " # #{param_name} = params[:#{param_name}]\n"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
code += <<~RUBY
|
|
69
|
+
|
|
70
|
+
# Return success result
|
|
71
|
+
{ status: :success, result: 'Tool executed successfully' }
|
|
72
|
+
|
|
73
|
+
rescue StandardError => e
|
|
74
|
+
{ status: :error, error_message: e.message }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Register the tool so agents can use it
|
|
81
|
+
Legate::GlobalToolManager.register_tool(Legate::Tools::#{class_name})
|
|
82
|
+
RUBY
|
|
83
|
+
|
|
84
|
+
code
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private_class_method def self.generate_parameter(param_name, param_opts)
|
|
88
|
+
type = param_opts[:type] || :string
|
|
89
|
+
description = param_opts[:description] || ''
|
|
90
|
+
required = param_opts[:required] || false
|
|
91
|
+
|
|
92
|
+
code = " parameter :#{param_name},\n"
|
|
93
|
+
code += " type: :#{type},\n"
|
|
94
|
+
code += " description: #{ruby_string(description)},\n"
|
|
95
|
+
code += " required: #{required}\n\n"
|
|
96
|
+
code
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private_class_method def self.ruby_string(value)
|
|
100
|
+
value.to_s.inspect
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|