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,506 @@
|
|
|
1
|
+
# File: lib/legate/global_definition_registry.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Legate
|
|
7
|
+
# In-memory registry for AgentDefinition instances.
|
|
8
|
+
# Serves as both the runtime definition registry (used by agents) and as a
|
|
9
|
+
# drop-in replacement for the Redis-backed DefinitionStore used by the Web UI.
|
|
10
|
+
#
|
|
11
|
+
# Internal structure:
|
|
12
|
+
# @registry = { name_symbol => { definition: AgentDefinition, metadata: {} } }
|
|
13
|
+
#
|
|
14
|
+
# The `register`, `find`, `all`, and `clear!` methods maintain backward
|
|
15
|
+
# compatibility with the original API. The new methods (`get_definition`,
|
|
16
|
+
# `save_definition`, `update_definition`, `delete_definition`,
|
|
17
|
+
# `list_definitions`, `check_connection`, `definition_exists?`) provide
|
|
18
|
+
# the DefinitionStore interface the Web UI routes expect.
|
|
19
|
+
module GlobalDefinitionRegistry
|
|
20
|
+
@registry = {}
|
|
21
|
+
@mutex = Mutex.new
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Original API (backward-compatible)
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
# Registers an AgentDefinition instance.
|
|
28
|
+
# @param definition [Legate::AgentDefinition] The definition object to register.
|
|
29
|
+
# @return [Boolean] true if registered successfully, false otherwise.
|
|
30
|
+
def self.register(definition)
|
|
31
|
+
unless definition.is_a?(Legate::AgentDefinition) && definition.name.is_a?(Symbol)
|
|
32
|
+
Legate.logger.error("GlobalDefinitionRegistry: Invalid object passed to register: #{definition.inspect}")
|
|
33
|
+
return false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
name = definition.name
|
|
37
|
+
@mutex.synchronize do
|
|
38
|
+
if @registry.key?(name)
|
|
39
|
+
Legate.logger.warn("GlobalDefinitionRegistry: Overwriting existing definition for agent :#{name}")
|
|
40
|
+
# Preserve existing metadata when re-registering
|
|
41
|
+
existing_metadata = @registry[name][:metadata] || {}
|
|
42
|
+
@registry[name] = { definition: definition, metadata: existing_metadata }
|
|
43
|
+
else
|
|
44
|
+
@registry[name] = { definition: definition, metadata: {} }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
Legate.logger.debug("GlobalDefinitionRegistry: Registered definition for :#{name}")
|
|
48
|
+
true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Finds an AgentDefinition instance by name.
|
|
52
|
+
# @param name [Symbol] The name of the agent definition.
|
|
53
|
+
# @return [Legate::AgentDefinition, nil] The definition object or nil if not found.
|
|
54
|
+
def self.find(name)
|
|
55
|
+
unless name.is_a?(Symbol)
|
|
56
|
+
Legate.logger.warn("GlobalDefinitionRegistry: Find called with non-symbol key: #{name.inspect}")
|
|
57
|
+
return nil
|
|
58
|
+
end
|
|
59
|
+
entry = @mutex.synchronize { @registry[name] }
|
|
60
|
+
entry&.[](:definition)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Clears the registry (primarily for testing).
|
|
64
|
+
def self.clear!
|
|
65
|
+
@mutex.synchronize { @registry = {} }
|
|
66
|
+
Legate.logger.debug('GlobalDefinitionRegistry: Cleared.')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns the current registry hash mapping names to AgentDefinition objects.
|
|
70
|
+
# @return [Hash{Symbol => Legate::AgentDefinition}]
|
|
71
|
+
def self.all
|
|
72
|
+
@mutex.synchronize do
|
|
73
|
+
@registry.transform_values { |entry| entry[:definition] }.dup
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
# DefinitionStore-compatible API (used by Web UI routes)
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
# Retrieves a single agent definition as a hash with Web UI field names.
|
|
82
|
+
#
|
|
83
|
+
# Field name mapping from AgentDefinition#to_h:
|
|
84
|
+
# :tool_names -> :tools (Array of Symbols)
|
|
85
|
+
# :model_name -> :model (Symbol or nil)
|
|
86
|
+
# :mcp_servers -> :mcp_servers_json (JSON String)
|
|
87
|
+
#
|
|
88
|
+
# Metadata fields (e.g. :persistent_status, :last_run_at) are merged in.
|
|
89
|
+
#
|
|
90
|
+
# @param name [String, Symbol] The agent name.
|
|
91
|
+
# @return [Hash, nil] A hash with symbol keys in Web UI format, or nil if not found.
|
|
92
|
+
def self.get_definition(name)
|
|
93
|
+
sym_name = normalize_name(name)
|
|
94
|
+
return nil unless sym_name
|
|
95
|
+
|
|
96
|
+
entry = @mutex.synchronize { @registry[sym_name] }
|
|
97
|
+
return nil unless entry
|
|
98
|
+
|
|
99
|
+
build_web_hash(entry)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Saves a new agent definition. Supports two call signatures:
|
|
103
|
+
#
|
|
104
|
+
# 1. Keyword splat (from the create form):
|
|
105
|
+
# save_definition(name:, description:, tools:, model:, ...)
|
|
106
|
+
#
|
|
107
|
+
# 2. Two positional args (from the duplicate route):
|
|
108
|
+
# save_definition(new_name, definition_hash)
|
|
109
|
+
#
|
|
110
|
+
# @return [Boolean] true on success.
|
|
111
|
+
def self.save_definition(*args, **kwargs)
|
|
112
|
+
if args.length == 2
|
|
113
|
+
# Positional form: save_definition(new_name, definition_hash)
|
|
114
|
+
new_name = args[0]
|
|
115
|
+
definition_hash = args[1]
|
|
116
|
+
_save_from_hash(new_name, definition_hash)
|
|
117
|
+
elsif args.empty? && !kwargs.empty?
|
|
118
|
+
# Keyword form: save_definition(name:, description:, tools:, model:, ...)
|
|
119
|
+
_save_from_keywords(**kwargs)
|
|
120
|
+
else
|
|
121
|
+
raise ArgumentError, 'save_definition expects either (name, hash) or keyword arguments'
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Updates specific fields of an existing agent definition's metadata.
|
|
126
|
+
# This is used for things like persistent_status, last_run_at, and also
|
|
127
|
+
# for updating definition fields via the Web UI edit forms.
|
|
128
|
+
#
|
|
129
|
+
# @param name [String, Symbol] The agent name.
|
|
130
|
+
# @param updates [Hash] A hash of field names to new values.
|
|
131
|
+
# @return [Boolean] true if the agent was found and updated, false otherwise.
|
|
132
|
+
def self.update_definition(name, updates)
|
|
133
|
+
sym_name = normalize_name(name)
|
|
134
|
+
return false unless sym_name
|
|
135
|
+
|
|
136
|
+
@mutex.synchronize do
|
|
137
|
+
entry = @registry[sym_name]
|
|
138
|
+
return false unless entry
|
|
139
|
+
|
|
140
|
+
definition = entry[:definition]
|
|
141
|
+
# Snapshot for atomic rollback: a web edit that left the definition in a
|
|
142
|
+
# state the constructor would reject (e.g. cleared instruction) used to
|
|
143
|
+
# persist silently. We apply the batch, then validate! once and restore
|
|
144
|
+
# the prior state on failure. (update_definition_field reassigns ivars
|
|
145
|
+
# rather than mutating in place, so a shallow snapshot is a faithful
|
|
146
|
+
# rollback.)
|
|
147
|
+
ivar_snapshot = definition&.instance_variables&.to_h { |iv| [iv, definition.instance_variable_get(iv)] }
|
|
148
|
+
metadata_snapshot = entry[:metadata].dup
|
|
149
|
+
|
|
150
|
+
updates.each do |key, value|
|
|
151
|
+
key_sym = key.to_sym
|
|
152
|
+
# Check if this is a field that should update the AgentDefinition itself
|
|
153
|
+
update_definition_field(definition, key_sym, value) if definition_field?(key_sym) && definition
|
|
154
|
+
# Always store in metadata as well (for fields like persistent_status,
|
|
155
|
+
# last_run_at, and as a cache for definition field overrides)
|
|
156
|
+
entry[:metadata][key_sym] = value
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
if definition
|
|
160
|
+
begin
|
|
161
|
+
definition.validate!
|
|
162
|
+
rescue StandardError => e
|
|
163
|
+
ivar_snapshot.each { |iv, val| definition.instance_variable_set(iv, val) }
|
|
164
|
+
entry[:metadata].replace(metadata_snapshot)
|
|
165
|
+
Legate.logger.error("GlobalDefinitionRegistry: Rejected update for :#{sym_name} (would leave the definition invalid): #{e.message}")
|
|
166
|
+
return false
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
Legate.logger.debug("GlobalDefinitionRegistry: Updated definition for :#{sym_name} with keys: #{updates.keys.join(', ')}")
|
|
172
|
+
true
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Deletes an agent definition from the registry.
|
|
176
|
+
# @param name [String, Symbol] The agent name.
|
|
177
|
+
# @return [Boolean] true if deleted (or didn't exist), false on error.
|
|
178
|
+
def self.delete_definition(name)
|
|
179
|
+
sym_name = normalize_name(name)
|
|
180
|
+
return true unless sym_name # Nothing to delete
|
|
181
|
+
|
|
182
|
+
@mutex.synchronize { @registry.delete(sym_name) }
|
|
183
|
+
Legate.logger.info("GlobalDefinitionRegistry: Deleted definition for :#{sym_name}")
|
|
184
|
+
true
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Returns an array of hashes, each in the same format as get_definition output.
|
|
188
|
+
# @return [Array<Hash>]
|
|
189
|
+
def self.list_definitions
|
|
190
|
+
entries = @mutex.synchronize { @registry.dup }
|
|
191
|
+
entries.map { |_name, entry| build_web_hash(entry) }
|
|
192
|
+
.compact
|
|
193
|
+
.sort_by { |d| d[:name].to_s }
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Always returns true for the in-memory store (no external connection to check).
|
|
197
|
+
# @return [Boolean] true
|
|
198
|
+
def self.check_connection
|
|
199
|
+
true
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Checks if an agent definition with the given name exists.
|
|
203
|
+
# @param name [String, Symbol] The agent name.
|
|
204
|
+
# @return [Boolean]
|
|
205
|
+
def self.definition_exists?(name)
|
|
206
|
+
sym_name = normalize_name(name)
|
|
207
|
+
return false unless sym_name
|
|
208
|
+
|
|
209
|
+
@mutex.synchronize { @registry.key?(sym_name) }
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# ---------------------------------------------------------------------------
|
|
213
|
+
# Private helpers
|
|
214
|
+
# ---------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
# Normalizes a name (String or Symbol) to a Symbol for internal use.
|
|
217
|
+
# @param name [String, Symbol, nil]
|
|
218
|
+
# @return [Symbol, nil]
|
|
219
|
+
def self.normalize_name(name)
|
|
220
|
+
return nil if name.nil?
|
|
221
|
+
|
|
222
|
+
str = name.to_s.strip
|
|
223
|
+
return nil if str.empty?
|
|
224
|
+
|
|
225
|
+
str.to_sym
|
|
226
|
+
end
|
|
227
|
+
private_class_method :normalize_name
|
|
228
|
+
|
|
229
|
+
# Builds a Web UI compatible hash from an internal registry entry.
|
|
230
|
+
# Maps AgentDefinition field names to Web UI field names and merges metadata.
|
|
231
|
+
def self.build_web_hash(entry)
|
|
232
|
+
definition = entry[:definition]
|
|
233
|
+
metadata = entry[:metadata] || {}
|
|
234
|
+
|
|
235
|
+
if definition
|
|
236
|
+
h = definition.to_h.dup
|
|
237
|
+
|
|
238
|
+
# Map :tool_names -> :tools (array of symbols)
|
|
239
|
+
h[:tools] = h.delete(:tool_names) || []
|
|
240
|
+
h[:tools] = h[:tools].map(&:to_sym) if h[:tools].is_a?(Array)
|
|
241
|
+
|
|
242
|
+
# Map :model_name -> :model
|
|
243
|
+
h[:model] = h.delete(:model_name)
|
|
244
|
+
|
|
245
|
+
# Convert :mcp_servers (Array) -> :mcp_servers_json (JSON String)
|
|
246
|
+
mcp_array = h.delete(:mcp_servers) || []
|
|
247
|
+
h[:mcp_servers_json] = mcp_array.is_a?(Array) ? mcp_array.to_json : '[]'
|
|
248
|
+
|
|
249
|
+
# Ensure persistent_status defaults to 'stopped'
|
|
250
|
+
h[:persistent_status] = metadata[:persistent_status] || 'stopped'
|
|
251
|
+
|
|
252
|
+
# Merge all metadata fields (last_run_at, etc.)
|
|
253
|
+
metadata.each do |key, value|
|
|
254
|
+
# Metadata overrides definition fields for display purposes
|
|
255
|
+
h[key] = value
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Ensure required defaults
|
|
259
|
+
h[:fallback_mode] = h[:fallback_mode] || :error
|
|
260
|
+
h[:instruction] ||= ''
|
|
261
|
+
h[:agent_type] = h[:agent_type]&.to_sym || :llm
|
|
262
|
+
h[:planning_strategy] = h[:planning_strategy]&.to_sym || :plan
|
|
263
|
+
h[:sub_agent_names] ||= []
|
|
264
|
+
h[:delegation_targets] ||= []
|
|
265
|
+
else
|
|
266
|
+
# Definition-less entry (created from hash via duplicate route)
|
|
267
|
+
h = metadata.dup
|
|
268
|
+
h[:persistent_status] ||= 'stopped'
|
|
269
|
+
h[:fallback_mode] ||= :error
|
|
270
|
+
h[:instruction] ||= ''
|
|
271
|
+
h[:agent_type] = h[:agent_type]&.to_sym || :llm
|
|
272
|
+
h[:planning_strategy] = h[:planning_strategy]&.to_sym || :plan
|
|
273
|
+
h[:tools] ||= []
|
|
274
|
+
h[:mcp_servers_json] ||= '[]'
|
|
275
|
+
h[:sub_agent_names] ||= []
|
|
276
|
+
h[:delegation_targets] ||= []
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
h
|
|
280
|
+
end
|
|
281
|
+
private_class_method :build_web_hash
|
|
282
|
+
|
|
283
|
+
# Saves a definition from keyword arguments (create form).
|
|
284
|
+
def self._save_from_keywords(name:, description: '', tools: [], model: nil,
|
|
285
|
+
fallback_mode: :error, mcp_servers_json: '[]',
|
|
286
|
+
instruction: '', webhook_enabled: false,
|
|
287
|
+
webhook_secret: nil, agent_type: :llm,
|
|
288
|
+
planning_strategy: :plan,
|
|
289
|
+
sub_agent_names: [], sequential_sub_agent_names: [],
|
|
290
|
+
parallel_sub_agent_names: [], loop_sub_agent_names: [],
|
|
291
|
+
output_key: nil, delegation_targets: [],
|
|
292
|
+
loop_max_iterations: nil, loop_condition_state_key: nil,
|
|
293
|
+
loop_condition_expected_value: nil,
|
|
294
|
+
auth_scheme_assignments: {}, auth_credential_assignments: {},
|
|
295
|
+
auth_url_mappings: [])
|
|
296
|
+
raise ArgumentError, 'Agent name cannot be empty.' if name.nil? || name.to_s.strip.empty?
|
|
297
|
+
|
|
298
|
+
sym_name = name.to_s.strip.to_sym
|
|
299
|
+
|
|
300
|
+
# Parse MCP servers JSON to array for AgentDefinition
|
|
301
|
+
mcp_array = parse_mcp_json(mcp_servers_json)
|
|
302
|
+
|
|
303
|
+
# Build a hash suitable for AgentDefinition.from_hash
|
|
304
|
+
definition_data = {
|
|
305
|
+
name: sym_name,
|
|
306
|
+
description: description || '',
|
|
307
|
+
instruction: instruction || '',
|
|
308
|
+
tool_names: normalize_tools(tools),
|
|
309
|
+
model_name: model,
|
|
310
|
+
temperature: nil,
|
|
311
|
+
fallback_mode: fallback_mode&.to_sym || :error,
|
|
312
|
+
mcp_servers: mcp_array,
|
|
313
|
+
webhook_enabled: !!webhook_enabled,
|
|
314
|
+
webhook_secret: webhook_secret,
|
|
315
|
+
agent_type: agent_type&.to_sym || :llm,
|
|
316
|
+
planning_strategy: planning_strategy&.to_sym || :plan,
|
|
317
|
+
sub_agent_names: Array(sub_agent_names).map(&:to_sym),
|
|
318
|
+
output_key: output_key&.to_sym,
|
|
319
|
+
sequential_sub_agent_names: Array(sequential_sub_agent_names).map(&:to_sym),
|
|
320
|
+
parallel_sub_agent_names: Array(parallel_sub_agent_names).map(&:to_sym),
|
|
321
|
+
loop_sub_agent_names: Array(loop_sub_agent_names).map(&:to_sym),
|
|
322
|
+
delegation_targets: Array(delegation_targets).map(&:to_sym),
|
|
323
|
+
loop_max_iterations: loop_max_iterations&.to_i,
|
|
324
|
+
loop_condition_state_key: loop_condition_state_key&.to_sym,
|
|
325
|
+
loop_condition_expected_value: loop_condition_expected_value,
|
|
326
|
+
auth_scheme_assignments: auth_scheme_assignments || {},
|
|
327
|
+
auth_credential_assignments: auth_credential_assignments || {},
|
|
328
|
+
auth_url_mappings: auth_url_mappings || []
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
definition = Legate::AgentDefinition.from_hash(definition_data)
|
|
332
|
+
|
|
333
|
+
@mutex.synchronize do
|
|
334
|
+
@registry[sym_name] = {
|
|
335
|
+
definition: definition,
|
|
336
|
+
metadata: { persistent_status: 'stopped' }
|
|
337
|
+
}
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
Legate.logger.info("GlobalDefinitionRegistry: Saved definition for :#{sym_name}")
|
|
341
|
+
true
|
|
342
|
+
end
|
|
343
|
+
private_class_method :_save_from_keywords
|
|
344
|
+
|
|
345
|
+
# Saves a definition from a name and hash (duplicate route).
|
|
346
|
+
def self._save_from_hash(new_name, definition_hash)
|
|
347
|
+
raise ArgumentError, 'Agent name cannot be empty.' if new_name.nil? || new_name.to_s.strip.empty?
|
|
348
|
+
|
|
349
|
+
sym_name = new_name.to_s.strip.to_sym
|
|
350
|
+
hash_data = definition_hash.is_a?(Hash) ? definition_hash.dup : {}
|
|
351
|
+
|
|
352
|
+
# Normalize field names for AgentDefinition.from_hash compatibility
|
|
353
|
+
hash_data[:name] = sym_name
|
|
354
|
+
|
|
355
|
+
# Map Web UI field names back to AgentDefinition field names
|
|
356
|
+
hash_data[:tool_names] = hash_data.delete(:tools) if hash_data.key?(:tools) && !hash_data.key?(:tool_names)
|
|
357
|
+
hash_data[:model_name] = hash_data.delete(:model) if hash_data.key?(:model) && !hash_data.key?(:model_name)
|
|
358
|
+
if hash_data.key?(:mcp_servers_json) && !hash_data.key?(:mcp_servers)
|
|
359
|
+
mcp_json = hash_data.delete(:mcp_servers_json)
|
|
360
|
+
hash_data[:mcp_servers] = parse_mcp_json(mcp_json)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Normalize tools to symbols
|
|
364
|
+
hash_data[:tool_names] = hash_data[:tool_names].map(&:to_sym) if hash_data[:tool_names].is_a?(Array)
|
|
365
|
+
|
|
366
|
+
# Try to create an AgentDefinition from the hash
|
|
367
|
+
definition = begin
|
|
368
|
+
Legate::AgentDefinition.from_hash(hash_data)
|
|
369
|
+
rescue StandardError => e
|
|
370
|
+
Legate.logger.warn("GlobalDefinitionRegistry: Could not create AgentDefinition from hash for :#{sym_name}: #{e.message}")
|
|
371
|
+
nil
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Extract metadata fields that are not part of AgentDefinition
|
|
375
|
+
metadata = {}
|
|
376
|
+
metadata_keys = %i[persistent_status last_run_at]
|
|
377
|
+
metadata_keys.each do |mk|
|
|
378
|
+
metadata[mk] = hash_data[mk] if hash_data.key?(mk)
|
|
379
|
+
end
|
|
380
|
+
metadata[:persistent_status] ||= 'stopped'
|
|
381
|
+
|
|
382
|
+
@mutex.synchronize do
|
|
383
|
+
@registry[sym_name] = {
|
|
384
|
+
definition: definition,
|
|
385
|
+
metadata: metadata
|
|
386
|
+
}
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
Legate.logger.info("GlobalDefinitionRegistry: Saved definition for :#{sym_name} (from hash)")
|
|
390
|
+
true
|
|
391
|
+
end
|
|
392
|
+
private_class_method :_save_from_hash
|
|
393
|
+
|
|
394
|
+
# Parses an MCP servers JSON string into an array.
|
|
395
|
+
def self.parse_mcp_json(mcp_json)
|
|
396
|
+
return [] if mcp_json.nil? || mcp_json.to_s.strip.empty? || mcp_json.to_s.strip == '[]'
|
|
397
|
+
|
|
398
|
+
if mcp_json.is_a?(String)
|
|
399
|
+
begin
|
|
400
|
+
parsed = JSON.parse(mcp_json)
|
|
401
|
+
parsed.is_a?(Array) ? parsed : []
|
|
402
|
+
rescue JSON::ParserError
|
|
403
|
+
[]
|
|
404
|
+
end
|
|
405
|
+
elsif mcp_json.is_a?(Array)
|
|
406
|
+
mcp_json
|
|
407
|
+
else
|
|
408
|
+
[]
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
private_class_method :parse_mcp_json
|
|
412
|
+
|
|
413
|
+
# Normalizes a tools value (could be array of strings, symbols, or JSON string).
|
|
414
|
+
def self.normalize_tools(tools)
|
|
415
|
+
if tools.is_a?(Array)
|
|
416
|
+
tools.map(&:to_sym)
|
|
417
|
+
elsif tools.is_a?(String)
|
|
418
|
+
begin
|
|
419
|
+
parsed = JSON.parse(tools)
|
|
420
|
+
parsed.is_a?(Array) ? parsed.map(&:to_sym) : []
|
|
421
|
+
rescue JSON::ParserError
|
|
422
|
+
tools.strip.empty? ? [] : [tools.to_sym]
|
|
423
|
+
end
|
|
424
|
+
else
|
|
425
|
+
[]
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
private_class_method :normalize_tools
|
|
429
|
+
|
|
430
|
+
# Checks if a key corresponds to a field on AgentDefinition.
|
|
431
|
+
DEFINITION_FIELDS = %i[
|
|
432
|
+
description instruction tool_names model_name temperature
|
|
433
|
+
fallback_mode mcp_servers webhook_enabled webhook_secret
|
|
434
|
+
agent_type planning_strategy sub_agent_names output_key
|
|
435
|
+
sequential_sub_agent_names parallel_sub_agent_names loop_sub_agent_names
|
|
436
|
+
delegation_targets loop_max_iterations loop_condition_state_key
|
|
437
|
+
loop_condition_expected_value auth_credential_names auth_url_mappings
|
|
438
|
+
auth_scheme_assignments auth_credential_assignments
|
|
439
|
+
].freeze
|
|
440
|
+
|
|
441
|
+
# Web UI uses different field names; map them to definition ivars.
|
|
442
|
+
WEB_TO_DEFINITION_MAP = {
|
|
443
|
+
tools: :tool_names,
|
|
444
|
+
model: :model_name,
|
|
445
|
+
mcp_servers_json: :mcp_servers
|
|
446
|
+
}.freeze
|
|
447
|
+
|
|
448
|
+
def self.definition_field?(key_sym)
|
|
449
|
+
DEFINITION_FIELDS.include?(key_sym) || WEB_TO_DEFINITION_MAP.key?(key_sym)
|
|
450
|
+
end
|
|
451
|
+
private_class_method :definition_field?
|
|
452
|
+
|
|
453
|
+
# Updates a field on an AgentDefinition instance via instance_variable_set.
|
|
454
|
+
def self.update_definition_field(definition, key_sym, value)
|
|
455
|
+
# Map web field names to definition ivar names
|
|
456
|
+
ivar_name = WEB_TO_DEFINITION_MAP[key_sym] || key_sym
|
|
457
|
+
|
|
458
|
+
case ivar_name
|
|
459
|
+
when :tool_names
|
|
460
|
+
tools = normalize_tools(value)
|
|
461
|
+
definition.instance_variable_set(:@tool_names, Set.new(tools))
|
|
462
|
+
when :model_name
|
|
463
|
+
definition.instance_variable_set(:@model_name, value&.to_sym)
|
|
464
|
+
when :mcp_servers
|
|
465
|
+
if value.is_a?(String)
|
|
466
|
+
definition.instance_variable_set(:@mcp_servers, parse_mcp_json(value))
|
|
467
|
+
elsif value.is_a?(Array)
|
|
468
|
+
definition.instance_variable_set(:@mcp_servers, value)
|
|
469
|
+
end
|
|
470
|
+
when :fallback_mode
|
|
471
|
+
val_sym = value&.to_sym
|
|
472
|
+
definition.instance_variable_set(:@fallback_mode, %i[error echo].include?(val_sym) ? val_sym : :error)
|
|
473
|
+
when :agent_type
|
|
474
|
+
val_sym = value&.to_sym
|
|
475
|
+
valid = %i[llm sequential parallel loop]
|
|
476
|
+
definition.instance_variable_set(:@agent_type, valid.include?(val_sym) ? val_sym : :llm)
|
|
477
|
+
when :planning_strategy
|
|
478
|
+
val_sym = value&.to_sym
|
|
479
|
+
definition.instance_variable_set(:@planning_strategy, %i[plan react].include?(val_sym) ? val_sym : :plan)
|
|
480
|
+
when :sub_agent_names, :sequential_sub_agent_names, :parallel_sub_agent_names,
|
|
481
|
+
:loop_sub_agent_names, :delegation_targets, :auth_credential_names
|
|
482
|
+
arr = value.is_a?(Array) ? value.map(&:to_sym) : []
|
|
483
|
+
definition.instance_variable_set(:"@#{ivar_name}", Set.new(arr))
|
|
484
|
+
when :output_key, :loop_condition_state_key
|
|
485
|
+
definition.instance_variable_set(:"@#{ivar_name}", value&.to_sym)
|
|
486
|
+
when :loop_max_iterations
|
|
487
|
+
definition.instance_variable_set(:@loop_max_iterations, value&.to_i)
|
|
488
|
+
when :temperature
|
|
489
|
+
definition.instance_variable_set(:@temperature, value&.to_f)
|
|
490
|
+
when :webhook_enabled
|
|
491
|
+
definition.instance_variable_set(:@webhook_enabled, !!value)
|
|
492
|
+
when :description, :instruction, :webhook_secret
|
|
493
|
+
definition.instance_variable_set(:"@#{ivar_name}", value&.to_s)
|
|
494
|
+
when :loop_condition_expected_value
|
|
495
|
+
definition.instance_variable_set(:@loop_condition_expected_value, value)
|
|
496
|
+
when :auth_url_mappings
|
|
497
|
+
definition.instance_variable_set(:@auth_url_mappings, value.is_a?(Array) ? value : [])
|
|
498
|
+
when :auth_scheme_assignments, :auth_credential_assignments
|
|
499
|
+
definition.instance_variable_set(:"@#{ivar_name}", value.is_a?(Hash) ? value : {})
|
|
500
|
+
end
|
|
501
|
+
rescue StandardError => e
|
|
502
|
+
Legate.logger.warn("GlobalDefinitionRegistry: Failed to update definition field :#{ivar_name}: #{e.message}")
|
|
503
|
+
end
|
|
504
|
+
private_class_method :update_definition_field
|
|
505
|
+
end
|
|
506
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# lib/legate/global_tool_manager.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'logger'
|
|
5
|
+
require_relative 'tool' # Need Tool class for instance checks/metadata
|
|
6
|
+
require_relative 'tool/metadata_dsl'
|
|
7
|
+
|
|
8
|
+
module Legate
|
|
9
|
+
# Manages the central registration and discovery of all defined Legate::Tool subclasses.
|
|
10
|
+
# This provides a way to list all available tools without needing a specific
|
|
11
|
+
# ToolRegistry instance (which is tied to an Agent).
|
|
12
|
+
module GlobalToolManager
|
|
13
|
+
# Store tool classes keyed by their symbolic name
|
|
14
|
+
@defined_tools = {} # { :tool_symbol => ToolClass }
|
|
15
|
+
# Guards @defined_tools: tools can be registered at runtime (e.g. custom-tool
|
|
16
|
+
# autoload) while requests read it concurrently under Puma.
|
|
17
|
+
@mutex = Mutex.new
|
|
18
|
+
|
|
19
|
+
# Register a tool class globally. Called automatically via Legate::Tool.inherited
|
|
20
|
+
# @param tool_class [Class] The tool class to register.
|
|
21
|
+
def self.register_tool(tool_class)
|
|
22
|
+
unless tool_class < Legate::Tool
|
|
23
|
+
Legate.logger.warn("GlobalToolManager: Attempted to register non-tool class: #{tool_class.inspect}")
|
|
24
|
+
return
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
metadata = tool_class.tool_metadata
|
|
28
|
+
tool_name = metadata[:name]&.to_sym
|
|
29
|
+
|
|
30
|
+
# --- Attempt name inference if not found in metadata ---
|
|
31
|
+
# This handles cases where the new DSL isn't used (e.g., old define_metadata)
|
|
32
|
+
# or if the DSL itself couldn't determine a name (e.g., anonymous class)
|
|
33
|
+
if tool_name.nil? || tool_name == :''
|
|
34
|
+
# First, check for the instance variable set by the DEPRECATED define_metadata
|
|
35
|
+
if tool_class.instance_variable_defined?(:@tool_name)
|
|
36
|
+
tool_name = tool_class.instance_variable_get(:@tool_name)
|
|
37
|
+
Legate.logger.debug("GlobalToolManager: Tool class #{tool_class} using name from deprecated @tool_name: #{tool_name.inspect}")
|
|
38
|
+
else
|
|
39
|
+
# If not found via deprecated method, try inference via DSL
|
|
40
|
+
begin
|
|
41
|
+
# Check if the class responds to inferred_name (from MetadataDsl)
|
|
42
|
+
if tool_class.respond_to?(:inferred_name)
|
|
43
|
+
inferred = tool_class.inferred_name
|
|
44
|
+
if inferred
|
|
45
|
+
Legate.logger.debug("GlobalToolManager: Tool class #{tool_class} had no explicit name, using inferred name: #{inferred.inspect}")
|
|
46
|
+
tool_name = inferred
|
|
47
|
+
else
|
|
48
|
+
Legate.logger.warn("GlobalToolManager: Tool class #{tool_class} has no explicit name and inference failed (maybe anonymous?). Skipping registration.")
|
|
49
|
+
return
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
# Fallback if MetadataDsl isn't included or something is wrong
|
|
53
|
+
Legate.logger.warn("GlobalToolManager: Tool class #{tool_class} has no name defined via tool_metadata or @tool_name, and does not support inferred_name. Skipping registration.")
|
|
54
|
+
return
|
|
55
|
+
end
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
Legate.logger.error("GlobalToolManager: Error during name inference for #{tool_class}: #{e.message}")
|
|
58
|
+
return # Don't register if inference itself fails
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
# --- End Name Inference Attempt ---
|
|
63
|
+
|
|
64
|
+
# Ensure tool_name is a symbol before proceeding
|
|
65
|
+
tool_name = tool_name&.to_sym
|
|
66
|
+
if tool_name.nil? || tool_name == :''
|
|
67
|
+
Legate.logger.error("GlobalToolManager: Could not determine a valid tool name for #{tool_class}. Skipping registration.")
|
|
68
|
+
return
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
@mutex.synchronize do
|
|
72
|
+
if @defined_tools.key?(tool_name) && @defined_tools[tool_name] != tool_class
|
|
73
|
+
Legate.logger.warn("GlobalToolManager: Tool name '#{tool_name}' is already registered with class #{@defined_tools[tool_name]}. Overwriting with #{tool_class}.")
|
|
74
|
+
elsif !@defined_tools.key?(tool_name)
|
|
75
|
+
Legate.logger.debug("GlobalToolManager: Registered tool '#{tool_name}' with class #{tool_class}.")
|
|
76
|
+
end
|
|
77
|
+
@defined_tools[tool_name] = tool_class
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Get a list of all globally registered tools with basic info.
|
|
82
|
+
# @return [Array<Hash>] An array of hashes, each with :name and :description.
|
|
83
|
+
def self.list_all_tools
|
|
84
|
+
snapshot = @mutex.synchronize { @defined_tools.dup }
|
|
85
|
+
snapshot.map do |name_sym, klass|
|
|
86
|
+
metadata = klass.tool_metadata
|
|
87
|
+
{
|
|
88
|
+
name: metadata[:name] || name_sym, # Fallback, though name should always be present if registered
|
|
89
|
+
description: metadata[:description] || '[No description provided]',
|
|
90
|
+
parameters: metadata[:parameters] || []
|
|
91
|
+
}
|
|
92
|
+
end.sort_by { |t| t[:name].to_s }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Find a registered tool class by its name symbol.
|
|
96
|
+
# @param name_symbol [Symbol] The symbolic name of the tool.
|
|
97
|
+
# @return [Class, nil] The tool class or nil if not found.
|
|
98
|
+
def self.find_class(name_symbol)
|
|
99
|
+
@mutex.synchronize { @defined_tools[name_symbol.to_sym] }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Get the names (symbols) of all registered tools.
|
|
103
|
+
# @return [Array<Symbol>] An array of tool name symbols.
|
|
104
|
+
def self.registered_tool_names
|
|
105
|
+
@mutex.synchronize { @defined_tools.keys }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Create an instance of a tool by its name symbol using the globally registered class.
|
|
109
|
+
# @param name_symbol [Symbol] The symbolic name of the tool.
|
|
110
|
+
# @return [Legate::Tool, nil] An instance of the tool or nil if instantiation fails or class not found.
|
|
111
|
+
def self.create_instance(name_symbol)
|
|
112
|
+
klass = find_class(name_symbol.to_sym)
|
|
113
|
+
|
|
114
|
+
if klass
|
|
115
|
+
begin
|
|
116
|
+
instance = klass.new
|
|
117
|
+
Legate.logger.debug("GlobalToolManager: Successfully instantiated tool '#{name_symbol}'.")
|
|
118
|
+
instance
|
|
119
|
+
rescue StandardError => e
|
|
120
|
+
Legate.logger.error("GlobalToolManager: Failed to instantiate tool '#{name_symbol}' (Class: #{klass}): #{e.class} - #{e.message}")
|
|
121
|
+
Legate.logger.error(e.backtrace.first(5).join("\n"))
|
|
122
|
+
nil
|
|
123
|
+
end
|
|
124
|
+
else
|
|
125
|
+
Legate.logger.warn("GlobalToolManager: Attempted to create instance of tool '#{name_symbol}' which is not globally registered.")
|
|
126
|
+
nil
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Clears all registered tools. Primarily for testing.
|
|
131
|
+
def self.reset!
|
|
132
|
+
@mutex.synchronize { @defined_tools = {} }
|
|
133
|
+
end
|
|
134
|
+
end # End GlobalToolManager module
|
|
135
|
+
end # End Legate module
|