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,111 @@
|
|
|
1
|
+
# File: lib/legate/web/routes/core_routes.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Legate
|
|
5
|
+
module Web
|
|
6
|
+
module CoreRoutes
|
|
7
|
+
def self.registered(app)
|
|
8
|
+
# GET / - Main welcome page with dashboard metrics.
|
|
9
|
+
app.get '/' do
|
|
10
|
+
logger.debug('GET / route handler entered (from CoreRoutes)')
|
|
11
|
+
|
|
12
|
+
# Compute dashboard metrics
|
|
13
|
+
definition_store = instance_variable_get(:@definition_store)
|
|
14
|
+
tool_manager = instance_variable_get(:@tool_manager)
|
|
15
|
+
|
|
16
|
+
@agent_count = 0
|
|
17
|
+
@running_count = 0
|
|
18
|
+
@tool_count = 0
|
|
19
|
+
@auth_scheme_count = 0
|
|
20
|
+
|
|
21
|
+
if definition_store
|
|
22
|
+
definitions = begin
|
|
23
|
+
definition_store.list_definitions
|
|
24
|
+
rescue StandardError
|
|
25
|
+
[]
|
|
26
|
+
end
|
|
27
|
+
@agent_count = definitions.size
|
|
28
|
+
end
|
|
29
|
+
# Running count is based on in-memory @agents hash
|
|
30
|
+
active_agents_hash = instance_variable_get(:@agents)
|
|
31
|
+
@running_count = active_agents_hash&.size || 0
|
|
32
|
+
|
|
33
|
+
if tool_manager
|
|
34
|
+
@tool_count = begin
|
|
35
|
+
tool_manager.tools.size
|
|
36
|
+
rescue StandardError
|
|
37
|
+
0
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Count auth schemes (best-effort; the dashboard must never break).
|
|
42
|
+
# Was checking the wrong constant (Legate::Authentication::Manager),
|
|
43
|
+
# so this count never displayed.
|
|
44
|
+
@auth_scheme_count = begin
|
|
45
|
+
Legate::Auth::Manager.instance.schemes.size
|
|
46
|
+
rescue StandardError
|
|
47
|
+
0
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Fetch recent activity
|
|
51
|
+
@recent_activity = if defined?(Legate::ActivityLog)
|
|
52
|
+
begin
|
|
53
|
+
Legate::ActivityLog.recent(8)
|
|
54
|
+
rescue StandardError
|
|
55
|
+
[]
|
|
56
|
+
end
|
|
57
|
+
else
|
|
58
|
+
[]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
slim :index
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# GET /activity/recent - Returns recent activity HTML partial
|
|
65
|
+
app.get '/activity/recent' do
|
|
66
|
+
@recent_activity = if defined?(Legate::ActivityLog)
|
|
67
|
+
begin
|
|
68
|
+
Legate::ActivityLog.recent(8)
|
|
69
|
+
rescue StandardError
|
|
70
|
+
[]
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
[]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
slim :_activity_list, layout: false
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# GET /healthz - Standard health check endpoint.
|
|
80
|
+
app.get '/healthz' do
|
|
81
|
+
current_app_instance = self
|
|
82
|
+
definition_store = current_app_instance.instance_variable_get(:@definition_store)
|
|
83
|
+
|
|
84
|
+
store_ok = if definition_store
|
|
85
|
+
definition_store.check_connection
|
|
86
|
+
else
|
|
87
|
+
true # No persistence configured (in-memory mode)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
unless store_ok
|
|
91
|
+
logger.error('Health check failed: Definition Store unavailable or connection failed (from CoreRoutes).')
|
|
92
|
+
status 503
|
|
93
|
+
body 'Service Unavailable (Persistence)'
|
|
94
|
+
return
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
status 200
|
|
98
|
+
body 'OK'
|
|
99
|
+
rescue Legate::DefinitionStore::StoreError => e
|
|
100
|
+
logger.error("Health check failed (from CoreRoutes): Store error - #{e.message}")
|
|
101
|
+
status 503
|
|
102
|
+
body 'Service Unavailable (Persistence Error)'
|
|
103
|
+
rescue StandardError => e # Catch other unexpected errors
|
|
104
|
+
logger.error("Health check failed (from CoreRoutes): Unexpected error - #{e.class}: #{e.message}")
|
|
105
|
+
status 503
|
|
106
|
+
body 'Service Unavailable (Internal)'
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# File: lib/legate/web/routes/documentation_routes.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'kramdown' # Required for markdown processing
|
|
5
|
+
require 'kramdown-parser-gfm' # Required for GitHub Flavored Markdown
|
|
6
|
+
require 'pathname' # For robust path operations
|
|
7
|
+
|
|
8
|
+
module Legate
|
|
9
|
+
module Web
|
|
10
|
+
module DocumentationRoutes
|
|
11
|
+
module Helpers
|
|
12
|
+
def render_markdown(file_path)
|
|
13
|
+
public_docs_pathname = Pathname.new(File.expand_path(File.join(settings.root, 'public', 'docs')))
|
|
14
|
+
target_file_pathname = Pathname.new(File.expand_path(file_path))
|
|
15
|
+
|
|
16
|
+
# Check if the target file is within the public_docs_pathname directory
|
|
17
|
+
# and is an actual file that exists.
|
|
18
|
+
# The `ascend` method iterates upwards from the file to its parent directories.
|
|
19
|
+
# We check if any of these parent paths match our intended docs root.
|
|
20
|
+
is_within_docs_dir = false
|
|
21
|
+
target_file_pathname.ascend do |p|
|
|
22
|
+
if p == public_docs_pathname
|
|
23
|
+
is_within_docs_dir = true
|
|
24
|
+
break
|
|
25
|
+
end
|
|
26
|
+
break if p.root? # Stop if we reach the filesystem root
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
unless is_within_docs_dir && target_file_pathname.file? && target_file_pathname.exist?
|
|
30
|
+
logger.warn "Markdown render: Path not valid or file does not exist. Target: '#{target_file_pathname}', Expected base: '#{public_docs_pathname}'"
|
|
31
|
+
return nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
markdown_content = File.read(target_file_pathname.to_s, encoding: 'UTF-8')
|
|
35
|
+
|
|
36
|
+
# Configure Kramdown with enhanced rendering options - now using GFM
|
|
37
|
+
html = Kramdown::Document.new(
|
|
38
|
+
markdown_content,
|
|
39
|
+
input: 'GFM', # GitHub Flavored Markdown
|
|
40
|
+
syntax_highlighter: nil, # We'll apply our custom highlighting via CSS
|
|
41
|
+
hard_wrap: false # Don't convert newlines to <br>
|
|
42
|
+
).to_html
|
|
43
|
+
|
|
44
|
+
# Post-process HTML to add language tags to code blocks
|
|
45
|
+
process_code_blocks(html)
|
|
46
|
+
rescue Errno::ENOENT # Should be caught by File.exist? check now, but good fallback
|
|
47
|
+
logger.warn "Markdown file not found (render_markdown): #{target_file_pathname}"
|
|
48
|
+
nil
|
|
49
|
+
rescue StandardError => e
|
|
50
|
+
logger.error "Error rendering markdown for #{target_file_pathname}: #{e.message}"
|
|
51
|
+
logger.error e.backtrace.join("\n") # Add backtrace to help debugging
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Process code blocks to add language classes and data attributes
|
|
56
|
+
def process_code_blocks(html)
|
|
57
|
+
# Find fenced code blocks with language specifications
|
|
58
|
+
html.gsub(%r{<pre><code\s+class="language-(\w+)">(.*?)</code></pre>}m) do |match|
|
|
59
|
+
language = ::Regexp.last_match(1)
|
|
60
|
+
code_content = ::Regexp.last_match(2)
|
|
61
|
+
# Replace with our enhanced version that adds data-lang attribute
|
|
62
|
+
%(<pre data-lang="#{language}"><code class="language-#{language}">#{code_content}</code></pre>)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def generate_summary(markdown_content, max_lines = 5)
|
|
67
|
+
return '' if markdown_content.nil? || markdown_content.empty?
|
|
68
|
+
|
|
69
|
+
lines = markdown_content.lines
|
|
70
|
+
summary_lines = []
|
|
71
|
+
non_empty_lines_count = 0
|
|
72
|
+
lines.each do |line|
|
|
73
|
+
stripped_line = line.strip
|
|
74
|
+
if !stripped_line.empty? && !stripped_line.start_with?('#') # Ignore headers for summary start
|
|
75
|
+
summary_lines << stripped_line
|
|
76
|
+
non_empty_lines_count += 1
|
|
77
|
+
break if non_empty_lines_count >= max_lines
|
|
78
|
+
elsif !summary_lines.empty? && stripped_line.empty? # Break on first blank line after content started
|
|
79
|
+
break
|
|
80
|
+
elsif summary_lines.empty? && stripped_line.start_with?('#') # Skip leading headers
|
|
81
|
+
next
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
summary_lines.join(' ').gsub(/\*\*|\*|_|`/, '') # Basic stripping of markdown
|
|
85
|
+
end
|
|
86
|
+
end # module Helpers
|
|
87
|
+
|
|
88
|
+
def self.registered(app)
|
|
89
|
+
app.helpers Helpers # Register the helpers for use in routes
|
|
90
|
+
|
|
91
|
+
# Route for Documentation Index (GET /docs)
|
|
92
|
+
app.get '/docs' do
|
|
93
|
+
logger.debug('--- GET /docs --- (DocumentationRoutes)')
|
|
94
|
+
docs_root_path = Pathname.new(File.join(settings.root, 'public', 'docs'))
|
|
95
|
+
logger.debug("Docs root resolved to: #{docs_root_path}")
|
|
96
|
+
|
|
97
|
+
categorized_documents = {}
|
|
98
|
+
|
|
99
|
+
begin
|
|
100
|
+
# Process files directly in docs_root_path first (General category)
|
|
101
|
+
general_docs = []
|
|
102
|
+
Dir.glob(File.join(docs_root_path, '*.md')).sort.each do |md_file_path|
|
|
103
|
+
pathname = Pathname.new(md_file_path)
|
|
104
|
+
next if pathname.directory? # Skip directories if any found by glob
|
|
105
|
+
|
|
106
|
+
filename_md = pathname.basename.to_s
|
|
107
|
+
filename_no_ext = pathname.basename('.md').to_s
|
|
108
|
+
title = filename_no_ext.gsub(/[-_]/, ' ').split.map(&:capitalize).join(' ')
|
|
109
|
+
file_content = File.read(md_file_path, encoding: 'UTF-8')
|
|
110
|
+
first_h1_match = file_content.match(/^#\s+(.+)$/)
|
|
111
|
+
title = first_h1_match[1].strip if first_h1_match
|
|
112
|
+
summary = generate_summary(file_content)
|
|
113
|
+
|
|
114
|
+
general_docs << {
|
|
115
|
+
title: title,
|
|
116
|
+
path: filename_no_ext, # Path relative to docs_root for linking
|
|
117
|
+
summary: summary
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
categorized_documents['General'] = general_docs if general_docs.any?
|
|
121
|
+
|
|
122
|
+
# Process subdirectories for categories
|
|
123
|
+
Dir.glob(File.join(docs_root_path, '*/')).sort.each do |dir_path|
|
|
124
|
+
category_pathname = Pathname.new(dir_path)
|
|
125
|
+
category_name = category_pathname.basename.to_s.gsub(/[-_]/, ' ').split.map(&:capitalize).join(' ')
|
|
126
|
+
category_docs = []
|
|
127
|
+
|
|
128
|
+
Dir.glob(File.join(category_pathname, '*.md')).sort.each do |md_file_path|
|
|
129
|
+
pathname = Pathname.new(md_file_path)
|
|
130
|
+
filename_md = pathname.basename.to_s
|
|
131
|
+
filename_no_ext = pathname.basename('.md').to_s
|
|
132
|
+
full_relative_path = "#{category_pathname.basename}/#{filename_no_ext}"
|
|
133
|
+
|
|
134
|
+
title = filename_no_ext.gsub(/[-_]/, ' ').split.map(&:capitalize).join(' ')
|
|
135
|
+
file_content = File.read(md_file_path, encoding: 'UTF-8')
|
|
136
|
+
first_h1_match = file_content.match(/^#\s+(.+)$/)
|
|
137
|
+
title = first_h1_match[1].strip if first_h1_match
|
|
138
|
+
summary = generate_summary(file_content)
|
|
139
|
+
|
|
140
|
+
category_docs << {
|
|
141
|
+
title: title,
|
|
142
|
+
path: full_relative_path, # Path includes category dir
|
|
143
|
+
summary: summary
|
|
144
|
+
}
|
|
145
|
+
end
|
|
146
|
+
categorized_documents[category_name] = category_docs if category_docs.any?
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
logger.warn("No markdown files or categories found in #{docs_root_path}") if categorized_documents.empty?
|
|
150
|
+
rescue StandardError => e
|
|
151
|
+
logger.error("Error scanning documentation directory: #{e.message}")
|
|
152
|
+
logger.error(e.backtrace.first(5).join("\n"))
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
instance_variable_set(:@categorized_documents, categorized_documents)
|
|
156
|
+
logger.debug("Setting @categorized_documents with #{categorized_documents.keys.count} categories.")
|
|
157
|
+
logger.debug("Content of @categorized_documents: #{@categorized_documents.inspect}")
|
|
158
|
+
|
|
159
|
+
slim :docs_index
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Route for Displaying a Single Document (GET /docs/*)
|
|
163
|
+
# The splat parameter will capture the category/filename path
|
|
164
|
+
app.get '/docs/*' do |path_splat|
|
|
165
|
+
logger.debug("GET /docs/#{path_splat} - Entered (from DocumentationRoutes)")
|
|
166
|
+
|
|
167
|
+
# Sanitize path_splat: allow alphanumeric, underscore, hyphen, and forward slash for path
|
|
168
|
+
# Remove leading/trailing slashes and protect against directory traversal
|
|
169
|
+
sane_path = path_splat.gsub(%r{^/+|/+$}, '').gsub(%r{\.{2}/}, '')
|
|
170
|
+
sane_path.gsub!(%r{[^0-9a-zA-Z_\-/]}, '') # Allow alphanumeric, _, -, /
|
|
171
|
+
|
|
172
|
+
if sane_path.empty?
|
|
173
|
+
logger.warn("Attempt to access doc with invalid path: '#{path_splat}'")
|
|
174
|
+
halt 404, slim(:error_404, locals: { title: 'Document Not Found', message: 'Invalid document path.' })
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Construct the full file path
|
|
178
|
+
# Ensure it's .md, even if not explicitly in splat, for direct access attempts
|
|
179
|
+
file_path_to_check = sane_path.end_with?('.md') ? sane_path : "#{sane_path}.md"
|
|
180
|
+
full_file_path = File.join(settings.root, 'public', 'docs', file_path_to_check)
|
|
181
|
+
logger.debug("Attempting to access document at: #{full_file_path}")
|
|
182
|
+
|
|
183
|
+
# The render_markdown helper already performs security checks to ensure the file is within 'public/docs'
|
|
184
|
+
# and is a .md file.
|
|
185
|
+
|
|
186
|
+
begin
|
|
187
|
+
markdown_html = render_markdown(full_file_path) # render_markdown expects absolute path
|
|
188
|
+
|
|
189
|
+
if markdown_html.nil?
|
|
190
|
+
logger.warn("Documentation file not found or rendering failed: #{full_file_path}")
|
|
191
|
+
halt 404,
|
|
192
|
+
slim(:error_404,
|
|
193
|
+
locals: { title: 'Document Not Found',
|
|
194
|
+
message: "Document '#{sane_path}' not found or could not be rendered." })
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
instance_variable_set(:@doc_html_content, markdown_html)
|
|
198
|
+
|
|
199
|
+
# Try to extract title from H1 in markdown content
|
|
200
|
+
# This requires reading the file again, or passing content from render_markdown if it returned it
|
|
201
|
+
# For now, let's re-read for title extraction consistency.
|
|
202
|
+
doc_title_for_view = sane_path.split('/').last.gsub(/[-_]/, ' ').split.map(&:capitalize).join(' ') # Default title
|
|
203
|
+
if File.exist?(full_file_path)
|
|
204
|
+
markdown_content_for_title = File.read(full_file_path, encoding: 'UTF-8')
|
|
205
|
+
first_h1_match = markdown_content_for_title.match(/^#\s+(.+)$/)
|
|
206
|
+
doc_title_for_view = first_h1_match[1].strip if first_h1_match
|
|
207
|
+
end
|
|
208
|
+
instance_variable_set(:@doc_title, doc_title_for_view)
|
|
209
|
+
|
|
210
|
+
slim :docs_show
|
|
211
|
+
rescue StandardError => e
|
|
212
|
+
logger.error("Error processing document #{sane_path}: #{e.message}")
|
|
213
|
+
logger.error(e.backtrace.join("\n"))
|
|
214
|
+
halt 500, 'Error displaying document.'
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# File: lib/legate/web/routes/tool_generator_routes.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../../generators/tool_generator'
|
|
5
|
+
require_relative '../../generators/runtime_tool_loader'
|
|
6
|
+
|
|
7
|
+
module Legate
|
|
8
|
+
module Web
|
|
9
|
+
# Routes for AI-powered tool code generation. Delegates to
|
|
10
|
+
# Legate::Generators::ToolGenerator so the web path shares the exact
|
|
11
|
+
# generation logic AND the CodeValidator safety check used by the CLI
|
|
12
|
+
# (the route must not reimplement either).
|
|
13
|
+
module ToolGeneratorRoutes
|
|
14
|
+
def self.registered(app)
|
|
15
|
+
# POST /tools/generate - Generate tool class code from natural language
|
|
16
|
+
app.post '/tools/generate' do
|
|
17
|
+
content_type :json
|
|
18
|
+
|
|
19
|
+
begin
|
|
20
|
+
request.body.rewind
|
|
21
|
+
body = JSON.parse(request.body.read)
|
|
22
|
+
rescue JSON::ParserError => e
|
|
23
|
+
halt 400, json(error: "Invalid JSON: #{e.message}")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
begin
|
|
27
|
+
result = Legate::Generators::ToolGenerator.generate(description: body['description'].to_s.strip)
|
|
28
|
+
logger.info("Successfully generated tool code (name: #{result[:suggested_name]}, type: #{result[:tool_type]})")
|
|
29
|
+
json(result)
|
|
30
|
+
rescue Legate::Generators::ToolGenerator::ApiKeyMissingError => e
|
|
31
|
+
halt 503, json(error: e.message)
|
|
32
|
+
rescue Legate::Generators::ToolGenerator::ApiError => e
|
|
33
|
+
logger.error("Gemini API error during tool generation: #{e.message}")
|
|
34
|
+
halt 503, json(error: 'AI service communication error. Please try again.')
|
|
35
|
+
rescue Legate::Generators::ToolGenerator::GenerationError => e
|
|
36
|
+
# Covers validation failures, empty responses, and unsafe generated
|
|
37
|
+
# code rejected by CodeValidator.
|
|
38
|
+
halt 400, json(error: e.message)
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
logger.error("Unexpected error during tool generation: #{e.class} - #{e.message}")
|
|
41
|
+
halt 500, json(error: 'Generation failed. Please try again.')
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# POST /tools/install - Load a generated custom tool into the RUNNING process.
|
|
46
|
+
# SECURITY: executes LLM-generated Ruby. Gated by config + explicit confirm +
|
|
47
|
+
# server-side re-validation (see RuntimeToolLoader). Writes tools/<name>.rb.
|
|
48
|
+
app.post '/tools/install' do
|
|
49
|
+
content_type :json
|
|
50
|
+
|
|
51
|
+
halt 403, json(error: 'Runtime tool loading is disabled in this environment. Use Download instead, place the file in tools/, and restart.') unless Legate::Generators::RuntimeToolLoader.enabled?
|
|
52
|
+
|
|
53
|
+
begin
|
|
54
|
+
request.body.rewind
|
|
55
|
+
body = JSON.parse(request.body.read)
|
|
56
|
+
rescue JSON::ParserError => e
|
|
57
|
+
halt 400, json(error: "Invalid JSON: #{e.message}")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Require an explicit confirmation that the user accepts running this code.
|
|
61
|
+
halt 400, json(error: 'Confirmation required to install a tool.') unless body['confirm'] == true
|
|
62
|
+
|
|
63
|
+
source = body['code'].to_s
|
|
64
|
+
halt 400, json(error: 'No tool code provided.') if source.strip.empty?
|
|
65
|
+
|
|
66
|
+
result = Legate::Generators::RuntimeToolLoader.load_source!(
|
|
67
|
+
source, suggested_name: body['suggested_name'].to_s
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if result[:ok]
|
|
71
|
+
logger.info("Runtime-loaded custom tool '#{result[:tool_name]}' -> #{result[:path]}")
|
|
72
|
+
json(ok: true, tool_name: result[:tool_name])
|
|
73
|
+
else
|
|
74
|
+
logger.warn("Runtime tool install rejected: #{result[:error]}")
|
|
75
|
+
halt 422, json(error: result[:error])
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# File: lib/legate/web/routes/tools_ui_routes.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Legate
|
|
5
|
+
module Web
|
|
6
|
+
module ToolsUIRoutes
|
|
7
|
+
def self.registered(app)
|
|
8
|
+
# GET /tools - Display available native and MCP tools
|
|
9
|
+
app.get '/tools' do
|
|
10
|
+
logger.info('GET /tools route handler entered (from ToolsUIRoutes)')
|
|
11
|
+
|
|
12
|
+
current_app_instance = self
|
|
13
|
+
definition_store = current_app_instance.instance_variable_get(:@definition_store)
|
|
14
|
+
|
|
15
|
+
# 1. Get Native Tools (already formatted)
|
|
16
|
+
native_tools_metadata = Legate::GlobalToolManager.list_all_tools.map do |tool_meta|
|
|
17
|
+
parameters_array = []
|
|
18
|
+
if tool_meta[:parameters].is_a?(Hash) && !tool_meta[:parameters].empty?
|
|
19
|
+
tool_meta[:parameters].each do |param_name, details|
|
|
20
|
+
parameters_array << {
|
|
21
|
+
name: param_name,
|
|
22
|
+
type: details[:type],
|
|
23
|
+
description: details[:description],
|
|
24
|
+
required: details[:required]
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
tool_meta.merge(parameters: parameters_array, source: :native, source_detail: 'Native')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# 2. Get MCP Tools
|
|
32
|
+
all_mcp_configs = []
|
|
33
|
+
if definition_store
|
|
34
|
+
begin
|
|
35
|
+
agent_summaries = definition_store.list_definitions
|
|
36
|
+
all_mcp_configs = agent_summaries.flat_map do |summary|
|
|
37
|
+
mcp_json = summary[:mcp_servers_json]
|
|
38
|
+
if mcp_json && !mcp_json.empty? && mcp_json != '[]'
|
|
39
|
+
JSON.parse(mcp_json)
|
|
40
|
+
else
|
|
41
|
+
[]
|
|
42
|
+
end
|
|
43
|
+
end.uniq
|
|
44
|
+
rescue JSON::ParserError => e
|
|
45
|
+
logger.error("Error parsing MCP JSON for /tools (from ToolsUIRoutes): #{e.message}")
|
|
46
|
+
rescue Legate::DefinitionStore::StoreError => e
|
|
47
|
+
logger.error("Store error fetching agent definitions for /tools (from ToolsUIRoutes): #{e.message}")
|
|
48
|
+
end
|
|
49
|
+
else
|
|
50
|
+
logger.warn('Definition store not available for MCP tool discovery in /tools (from ToolsUIRoutes)')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
mcp_tool_fetch_results = fetch_mcp_tools(all_mcp_configs || [])
|
|
54
|
+
|
|
55
|
+
processed_mcp_tools_metadata = []
|
|
56
|
+
mcp_tool_fetch_results.each do |result|
|
|
57
|
+
next unless result[:status] == :success && result[:tools]
|
|
58
|
+
|
|
59
|
+
result[:tools].each do |mcp_tool_schema|
|
|
60
|
+
parameters = []
|
|
61
|
+
begin
|
|
62
|
+
input_schema = mcp_tool_schema[:inputSchema]
|
|
63
|
+
if input_schema&.is_a?(Hash)
|
|
64
|
+
properties = input_schema['properties'] || {}
|
|
65
|
+
required_props = input_schema['required'] || []
|
|
66
|
+
parameters = Legate::Mcp::Util::SchemaConverter.json_to_legate(properties, required_props)
|
|
67
|
+
end
|
|
68
|
+
rescue StandardError => e
|
|
69
|
+
logger.error("Error converting MCP schema for tool '#{mcp_tool_schema[:name]}' in /tools (from ToolsUIRoutes): #{e.message}")
|
|
70
|
+
end
|
|
71
|
+
processed_mcp_tools_metadata << {
|
|
72
|
+
name: mcp_tool_schema[:name].to_sym,
|
|
73
|
+
description: mcp_tool_schema[:description] || '',
|
|
74
|
+
parameters: parameters,
|
|
75
|
+
source: :mcp,
|
|
76
|
+
source_detail: "MCP (#{result[:server]})"
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# 3. Set @native_tools for the current view requirements
|
|
82
|
+
instance_variable_set(:@native_tools, native_tools_metadata.sort_by { |t| t[:name].to_s })
|
|
83
|
+
|
|
84
|
+
# For future enhancement of tools.slim to show all tools:
|
|
85
|
+
combined_tools_map = {}
|
|
86
|
+
native_tools_metadata.each { |tool| combined_tools_map[tool[:name]] = tool }
|
|
87
|
+
processed_mcp_tools_metadata.each { |tool| combined_tools_map[tool[:name]] ||= tool }
|
|
88
|
+
instance_variable_set(:@all_tools_list, combined_tools_map.values.sort_by { |t| t[:name].to_s })
|
|
89
|
+
instance_variable_set(:@mcp_tool_results_for_view, mcp_tool_fetch_results) # For displaying fetch errors
|
|
90
|
+
|
|
91
|
+
slim :tools
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# GET /tools/:name - Display tool detail page (native or MCP)
|
|
95
|
+
app.get '/tools/:name' do |name|
|
|
96
|
+
logger.info("GET /tools/#{name} route handler entered (from ToolsUIRoutes)")
|
|
97
|
+
tool_name_sym = name.to_sym
|
|
98
|
+
|
|
99
|
+
current_app_instance = self
|
|
100
|
+
definition_store = current_app_instance.instance_variable_get(:@definition_store)
|
|
101
|
+
|
|
102
|
+
# 1. Try to find in native tools
|
|
103
|
+
native_tool_metadata = Legate::GlobalToolManager.list_all_tools.find { |t| t[:name] == tool_name_sym }
|
|
104
|
+
tool_to_display = nil
|
|
105
|
+
|
|
106
|
+
if native_tool_metadata
|
|
107
|
+
parameters_array = []
|
|
108
|
+
if native_tool_metadata[:parameters].is_a?(Hash) && !native_tool_metadata[:parameters].empty?
|
|
109
|
+
native_tool_metadata[:parameters].each do |param_name, details|
|
|
110
|
+
parameters_array << {
|
|
111
|
+
name: param_name,
|
|
112
|
+
type: details[:type],
|
|
113
|
+
description: details[:description],
|
|
114
|
+
required: details[:required]
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
tool_to_display = native_tool_metadata.merge(parameters: parameters_array, source: :native,
|
|
119
|
+
source_detail: 'Native')
|
|
120
|
+
else
|
|
121
|
+
# 2. Not native, try to find in MCP tools
|
|
122
|
+
all_mcp_configs = []
|
|
123
|
+
if definition_store
|
|
124
|
+
begin
|
|
125
|
+
agent_summaries = definition_store.list_definitions
|
|
126
|
+
all_mcp_configs = agent_summaries.flat_map do |summary|
|
|
127
|
+
mcp_json = summary[:mcp_servers_json]
|
|
128
|
+
if mcp_json && !mcp_json.empty? && mcp_json != '[]'
|
|
129
|
+
JSON.parse(mcp_json)
|
|
130
|
+
else
|
|
131
|
+
[]
|
|
132
|
+
end
|
|
133
|
+
end.uniq
|
|
134
|
+
rescue JSON::ParserError => e
|
|
135
|
+
logger.error("Error parsing MCP JSON for /tools/#{name} (from ToolsUIRoutes): #{e.message}")
|
|
136
|
+
rescue Legate::DefinitionStore::StoreError => e
|
|
137
|
+
logger.error("Store error fetching agent definitions for /tools/#{name} (from ToolsUIRoutes): #{e.message}")
|
|
138
|
+
end
|
|
139
|
+
else
|
|
140
|
+
logger.warn("Definition store not available for MCP tool discovery in /tools/#{name} (from ToolsUIRoutes)")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
mcp_tool_fetch_results = fetch_mcp_tools(all_mcp_configs || [])
|
|
144
|
+
|
|
145
|
+
mcp_tool_fetch_results.each do |result|
|
|
146
|
+
next unless result[:status] == :success && result[:tools]
|
|
147
|
+
|
|
148
|
+
tool_data = result[:tools].find { |t| t[:name].to_s == name || t[:name].to_sym == tool_name_sym }
|
|
149
|
+
next unless tool_data
|
|
150
|
+
|
|
151
|
+
parameters = []
|
|
152
|
+
begin
|
|
153
|
+
input_schema = tool_data[:inputSchema]
|
|
154
|
+
if input_schema&.is_a?(Hash)
|
|
155
|
+
properties = input_schema['properties'] || {}
|
|
156
|
+
required_props = input_schema['required'] || []
|
|
157
|
+
parameters = Legate::Mcp::Util::SchemaConverter.json_to_legate(properties, required_props)
|
|
158
|
+
end
|
|
159
|
+
rescue StandardError => e
|
|
160
|
+
logger.error("Error converting MCP schema for tool '#{tool_data[:name]}' in /tools/#{name} (from ToolsUIRoutes): #{e.message}")
|
|
161
|
+
end
|
|
162
|
+
tool_to_display = {
|
|
163
|
+
name: tool_data[:name].to_sym,
|
|
164
|
+
description: tool_data[:description] || '',
|
|
165
|
+
parameters: parameters,
|
|
166
|
+
source: :mcp,
|
|
167
|
+
source_detail: "MCP (#{result[:server]})"
|
|
168
|
+
}
|
|
169
|
+
break
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
if tool_to_display
|
|
174
|
+
instance_variable_set(:@tool, tool_to_display)
|
|
175
|
+
logger.debug("Found tool metadata for '#{name}': #{tool_to_display.inspect}")
|
|
176
|
+
slim :tool_detail
|
|
177
|
+
else
|
|
178
|
+
logger.warn("Tool '#{name}' not found anywhere (from ToolsUIRoutes).")
|
|
179
|
+
status 404
|
|
180
|
+
slim(:error_404,
|
|
181
|
+
locals: { title: 'Tool Not Found', message: "Tool definition for '#{name}' not found." })
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# GET /tools/:name/download - Download native tool as Ruby file
|
|
186
|
+
app.get '/tools/:name/download' do |name|
|
|
187
|
+
logger.info("Received request to download tool '#{name}' as Ruby file")
|
|
188
|
+
tool_name_sym = name.to_sym
|
|
189
|
+
|
|
190
|
+
# Check if it's a native tool
|
|
191
|
+
tool_class = Legate::GlobalToolManager.find_class(tool_name_sym)
|
|
192
|
+
halt 400, 'Only native tools can be downloaded as Ruby files. MCP tools cannot be exported.' unless tool_class
|
|
193
|
+
|
|
194
|
+
# Generate Ruby code
|
|
195
|
+
require 'legate/tool_code_generator'
|
|
196
|
+
ruby_code = Legate::ToolCodeGenerator.generate(tool_name_sym)
|
|
197
|
+
|
|
198
|
+
halt 404, 'Tool not found or could not generate code.' unless ruby_code
|
|
199
|
+
|
|
200
|
+
content_type 'application/x-ruby'
|
|
201
|
+
attachment "#{name.to_s.gsub(/[^a-zA-Z0-9_-]/, '_')}.rb"
|
|
202
|
+
ruby_code
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|