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,408 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'code_validator'
|
|
4
|
+
require_relative '../llm'
|
|
5
|
+
|
|
6
|
+
module Legate
|
|
7
|
+
module Generators
|
|
8
|
+
# AI-powered tool code generator. Uses the configured LLM adapter (Gemini by
|
|
9
|
+
# default) via Legate::LLM.
|
|
10
|
+
class ToolGenerator
|
|
11
|
+
class GenerationError < StandardError; end
|
|
12
|
+
class ApiKeyMissingError < GenerationError; end
|
|
13
|
+
class ApiError < GenerationError; end
|
|
14
|
+
|
|
15
|
+
# Model used for code generation (a capable model is worth it here). Passed
|
|
16
|
+
# to Legate::LLM.build_adapter; a configured non-Gemini factory may remap it.
|
|
17
|
+
GENERATION_MODEL = 'gemini-2.5-pro'
|
|
18
|
+
|
|
19
|
+
# Generate tool class code from a natural language description
|
|
20
|
+
# @param description [String] Natural language description of the tool to generate
|
|
21
|
+
# @return [Hash] { code: String, suggested_name: String, tool_type: String }
|
|
22
|
+
# @raise [ApiKeyMissingError] if GOOGLE_API_KEY is not set
|
|
23
|
+
# @raise [ApiError] if Gemini API fails
|
|
24
|
+
# @raise [GenerationError] for other generation failures
|
|
25
|
+
def self.generate(description:)
|
|
26
|
+
new.generate(description: description)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def generate(description:)
|
|
30
|
+
validate_description!(description)
|
|
31
|
+
adapter = Legate::LLM.build_adapter(model: GENERATION_MODEL)
|
|
32
|
+
raise ApiKeyMissingError, 'GOOGLE_API_KEY not configured. AI generation requires a Gemini API key.' unless adapter.available?
|
|
33
|
+
|
|
34
|
+
system_prompt = build_prompt
|
|
35
|
+
user_prompt = build_user_prompt(description)
|
|
36
|
+
|
|
37
|
+
generated_code = call_llm(adapter, system_prompt, user_prompt)
|
|
38
|
+
clean_code = clean_generated_code(generated_code)
|
|
39
|
+
CodeValidator.validate!(clean_code)
|
|
40
|
+
suggested_name = extract_tool_name(clean_code)
|
|
41
|
+
tool_type = detect_tool_type(clean_code)
|
|
42
|
+
|
|
43
|
+
{ code: clean_code, suggested_name: suggested_name, tool_type: tool_type }
|
|
44
|
+
rescue CodeValidator::UnsafeCodeError => e
|
|
45
|
+
raise GenerationError, e.message
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def validate_description!(description)
|
|
51
|
+
raise GenerationError, 'Description is required' if description.nil? || description.strip.empty?
|
|
52
|
+
raise GenerationError, 'Description too long. Maximum 5000 characters.' if description.length > 5000
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def call_llm(adapter, system_prompt, user_prompt)
|
|
56
|
+
text = begin
|
|
57
|
+
adapter.generate("#{system_prompt}\n\n#{user_prompt}")
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
raise ApiError, "AI service communication error: #{e.message}"
|
|
60
|
+
end
|
|
61
|
+
raise GenerationError, 'AI service returned empty response. Please try again.' unless text && !text.strip.empty?
|
|
62
|
+
|
|
63
|
+
text
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def build_user_prompt(description)
|
|
67
|
+
<<~PROMPT
|
|
68
|
+
Generate a Ruby tool class based on this description:
|
|
69
|
+
|
|
70
|
+
#{description}
|
|
71
|
+
|
|
72
|
+
Remember to output ONLY the Ruby code, no explanations or markdown formatting.
|
|
73
|
+
Determine the appropriate tool type (simple, HTTP API, or async) based on the description.
|
|
74
|
+
PROMPT
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def clean_generated_code(code)
|
|
78
|
+
clean = code.strip
|
|
79
|
+
clean = clean.gsub(/\A```ruby\n?/, '').gsub(/\A```\n?/, '')
|
|
80
|
+
clean = clean.gsub(/\n?```\z/, '')
|
|
81
|
+
clean.strip
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def extract_tool_name(code)
|
|
85
|
+
# Try to find class definition
|
|
86
|
+
if code =~ /class\s+(\w+)\s*<\s*(?:Legate::Tool|Legate::Tools::BaseAsyncJobTool)/
|
|
87
|
+
class_name = Regexp.last_match(1)
|
|
88
|
+
# Convert PascalCase to snake_case for filename
|
|
89
|
+
return class_name.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
90
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
91
|
+
.downcase
|
|
92
|
+
end
|
|
93
|
+
'generated_tool'
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def detect_tool_type(code)
|
|
97
|
+
if code.include?('BaseAsyncJobTool')
|
|
98
|
+
'async'
|
|
99
|
+
elsif code.include?('HttpClient') || code.include?('http_get') || code.include?('http_post') ||
|
|
100
|
+
code.include?('http_head') || code.include?('http_put') || code.include?('http_delete')
|
|
101
|
+
'http'
|
|
102
|
+
else
|
|
103
|
+
'simple'
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def build_prompt
|
|
108
|
+
<<~PROMPT
|
|
109
|
+
You are an expert Ruby developer specializing in Legate — AI Agent Framework for Ruby.
|
|
110
|
+
Your task is to generate complete, production-ready Ruby tool class code based on user descriptions.
|
|
111
|
+
|
|
112
|
+
## Tool Types
|
|
113
|
+
|
|
114
|
+
Based on the user's description, determine which type of tool to generate:
|
|
115
|
+
|
|
116
|
+
1. **Simple Tool** - For local computation, data transformation, no external calls
|
|
117
|
+
2. **HTTP API Tool** - For calling external REST APIs, checking URLs/websites, or any HTTP requests
|
|
118
|
+
3. **Async Tool** - For long-running background jobs (using threads)
|
|
119
|
+
|
|
120
|
+
## Simple Tool Template
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
# frozen_string_literal: true
|
|
124
|
+
|
|
125
|
+
require 'legate/tool'
|
|
126
|
+
|
|
127
|
+
class MyTool < Legate::Tool
|
|
128
|
+
tool_description 'Brief description of what this tool does'
|
|
129
|
+
|
|
130
|
+
parameter :param_name,
|
|
131
|
+
type: :string, # :string, :integer, :number, :boolean, :array, :object
|
|
132
|
+
description: 'What this parameter is for',
|
|
133
|
+
required: true # or false for optional
|
|
134
|
+
|
|
135
|
+
parameter :optional_param,
|
|
136
|
+
type: :integer,
|
|
137
|
+
description: 'An optional parameter',
|
|
138
|
+
required: false
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
def perform_execution(params, context)
|
|
143
|
+
# params is a Hash with symbol keys
|
|
144
|
+
# context is Legate::ToolContext with session access
|
|
145
|
+
|
|
146
|
+
input = params[:param_name]
|
|
147
|
+
|
|
148
|
+
# Your logic here
|
|
149
|
+
result = process(input)
|
|
150
|
+
|
|
151
|
+
# Return success
|
|
152
|
+
{ status: :success, result: result }
|
|
153
|
+
|
|
154
|
+
rescue StandardError => e
|
|
155
|
+
{ status: :error, error_message: e.message }
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Register the tool so agents can use it
|
|
160
|
+
Legate::GlobalToolManager.register_tool(MyTool)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## HTTP API Tool Template
|
|
164
|
+
|
|
165
|
+
For tools that call external APIs, check URLs/websites, or make any HTTP requests.
|
|
166
|
+
ALWAYS use HttpClient for ANY HTTP operations - never use Net::HTTP directly.
|
|
167
|
+
|
|
168
|
+
Available HTTP methods:
|
|
169
|
+
- `http_get(path, query: {}, headers: {})` - GET request
|
|
170
|
+
- `http_head(path, query: {}, headers: {})` - HEAD request (headers only, no body - efficient for status checks)
|
|
171
|
+
- `http_post(path, body: {}, query: {}, headers: {})` - POST request
|
|
172
|
+
- `http_put(path, body: {}, query: {}, headers: {})` - PUT request
|
|
173
|
+
- `http_delete(path, query: {}, headers: {})` - DELETE request
|
|
174
|
+
|
|
175
|
+
### Fixed Base URL Pattern (for single API)
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
# frozen_string_literal: true
|
|
179
|
+
|
|
180
|
+
require 'legate/tool'
|
|
181
|
+
require 'legate/tools/base/http_client'
|
|
182
|
+
|
|
183
|
+
class MyApiTool < Legate::Tool
|
|
184
|
+
include Legate::Tools::Base::HttpClient
|
|
185
|
+
|
|
186
|
+
tool_description 'Fetches data from External API'
|
|
187
|
+
|
|
188
|
+
parameter :query,
|
|
189
|
+
type: :string,
|
|
190
|
+
description: 'Search query',
|
|
191
|
+
required: true
|
|
192
|
+
|
|
193
|
+
def initialize(**options)
|
|
194
|
+
super
|
|
195
|
+
setup_http_client(
|
|
196
|
+
base_url: 'https://api.example.com/v1/',
|
|
197
|
+
headers: {
|
|
198
|
+
'Accept' => 'application/json',
|
|
199
|
+
'Authorization' => "Bearer \#{ENV['API_KEY']}"
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
private
|
|
205
|
+
|
|
206
|
+
def perform_execution(params, context)
|
|
207
|
+
query = params[:query]
|
|
208
|
+
|
|
209
|
+
# GET request (path is relative to base_url)
|
|
210
|
+
response = http_get('search', query: { q: query })
|
|
211
|
+
|
|
212
|
+
# POST request example:
|
|
213
|
+
# response = http_post('endpoint', body: { data: params[:data] })
|
|
214
|
+
|
|
215
|
+
data = JSON.parse(response.body)
|
|
216
|
+
{ status: :success, result: data }
|
|
217
|
+
|
|
218
|
+
rescue Legate::ToolHttpError => e
|
|
219
|
+
{ status: :error, error_message: "API error: \#{e.message}" }
|
|
220
|
+
rescue JSON::ParserError => e
|
|
221
|
+
{ status: :error, error_message: "Invalid response: \#{e.message}" }
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
Legate::GlobalToolManager.register_tool(MyApiTool)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Dynamic/Arbitrary URL Pattern (for URL checkers, webhooks, etc.)
|
|
229
|
+
|
|
230
|
+
For tools that need to call user-provided URLs (not a fixed API), use a placeholder
|
|
231
|
+
base_url and pass absolute URLs to the http_* methods:
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
# frozen_string_literal: true
|
|
235
|
+
|
|
236
|
+
require 'legate/tool'
|
|
237
|
+
require 'legate/tools/base/http_client'
|
|
238
|
+
require 'uri'
|
|
239
|
+
|
|
240
|
+
class UrlStatusChecker < Legate::Tool
|
|
241
|
+
include Legate::Tools::Base::HttpClient
|
|
242
|
+
|
|
243
|
+
tool_description 'Checks if a URL is reachable and returns its HTTP status code'
|
|
244
|
+
|
|
245
|
+
parameter :url,
|
|
246
|
+
type: :string,
|
|
247
|
+
description: 'The full URL to check (e.g., https://example.com)',
|
|
248
|
+
required: true
|
|
249
|
+
|
|
250
|
+
parameter :expected_status,
|
|
251
|
+
type: :integer,
|
|
252
|
+
description: 'Optional expected status code to validate against',
|
|
253
|
+
required: false
|
|
254
|
+
|
|
255
|
+
def initialize(**options)
|
|
256
|
+
super
|
|
257
|
+
# Use placeholder base_url - actual URLs will be absolute
|
|
258
|
+
setup_http_client(base_url: 'https://placeholder.invalid')
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
private
|
|
262
|
+
|
|
263
|
+
def perform_execution(params, context)
|
|
264
|
+
url = params[:url]
|
|
265
|
+
expected = params[:expected_status]
|
|
266
|
+
|
|
267
|
+
# Validate URL format
|
|
268
|
+
validate_url!(url)
|
|
269
|
+
|
|
270
|
+
# HEAD request is efficient - fetches headers only, no body
|
|
271
|
+
response = http_head(url)
|
|
272
|
+
|
|
273
|
+
result = {
|
|
274
|
+
url: url,
|
|
275
|
+
status_code: response.status,
|
|
276
|
+
reachable: (200..399).cover?(response.status)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if expected
|
|
280
|
+
result[:expected_status] = expected
|
|
281
|
+
result[:matches] = (response.status == expected)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
{ status: :success, result: result }
|
|
285
|
+
|
|
286
|
+
rescue URI::InvalidURIError => e
|
|
287
|
+
{ status: :error, error_message: "Invalid URL: \#{e.message}" }
|
|
288
|
+
rescue Legate::ToolHttpError => e
|
|
289
|
+
# Non-2xx responses are caught here
|
|
290
|
+
{ status: :error, error_message: "HTTP error: \#{e.message}" }
|
|
291
|
+
rescue Legate::ToolNetworkError => e
|
|
292
|
+
{ status: :error, error_message: "Network error: \#{e.message}" }
|
|
293
|
+
rescue Legate::ToolTimeoutError => e
|
|
294
|
+
{ status: :error, error_message: "Request timed out: \#{e.message}" }
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def validate_url!(url)
|
|
298
|
+
uri = URI.parse(url)
|
|
299
|
+
unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
300
|
+
raise URI::InvalidURIError, 'URL must use http:// or https://'
|
|
301
|
+
end
|
|
302
|
+
raise URI::InvalidURIError, 'URL must have a host' if uri.host.nil? || uri.host.empty?
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
Legate::GlobalToolManager.register_tool(UrlStatusChecker)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Async Tool Template
|
|
310
|
+
|
|
311
|
+
For long-running operations that should run in background:
|
|
312
|
+
|
|
313
|
+
```ruby
|
|
314
|
+
# frozen_string_literal: true
|
|
315
|
+
|
|
316
|
+
require 'legate/tools/base_async_job_tool'
|
|
317
|
+
|
|
318
|
+
# The worker that does the actual work (runs in a background thread)
|
|
319
|
+
class MyWorker
|
|
320
|
+
def perform(jid, session_id, input_data)
|
|
321
|
+
# Mark job as started
|
|
322
|
+
Legate::Tools::BaseAsyncJobTool.store_job_pending(jid)
|
|
323
|
+
|
|
324
|
+
# Do the long-running work
|
|
325
|
+
result = process_data(input_data)
|
|
326
|
+
|
|
327
|
+
# Store the result
|
|
328
|
+
Legate::Tools::BaseAsyncJobTool.store_job_result(jid, result)
|
|
329
|
+
|
|
330
|
+
rescue StandardError => e
|
|
331
|
+
Legate::Tools::BaseAsyncJobTool.store_job_error(jid, e.message, e.class.name)
|
|
332
|
+
raise
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
private
|
|
336
|
+
|
|
337
|
+
def process_data(data)
|
|
338
|
+
# Your long-running logic here
|
|
339
|
+
sleep(5) # Simulating work
|
|
340
|
+
{ processed: true, data: data }
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# The Legate tool that enqueues the job
|
|
345
|
+
class MyAsyncTool < Legate::Tools::BaseAsyncJobTool
|
|
346
|
+
tool_description 'Starts a background job to process data'
|
|
347
|
+
|
|
348
|
+
parameter :data,
|
|
349
|
+
type: :string,
|
|
350
|
+
description: 'Data to process',
|
|
351
|
+
required: true
|
|
352
|
+
|
|
353
|
+
def worker_class
|
|
354
|
+
MyWorker
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def prepare_job_arguments(params, context)
|
|
358
|
+
[context.session_id, params[:data]]
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
Legate::GlobalToolManager.register_tool(MyAsyncTool)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## ToolContext Methods
|
|
366
|
+
|
|
367
|
+
The `context` parameter provides access to:
|
|
368
|
+
- `context.state_get(:key)` - Read from session state
|
|
369
|
+
- `context.state_set(:key, value)` - Write to session state (applied after execution)
|
|
370
|
+
- `context.session_id` - Current session ID
|
|
371
|
+
- `context.user_id` - Current user ID
|
|
372
|
+
- `context.app_name` - Agent name
|
|
373
|
+
- `context.invocation_id` - Unique invocation ID
|
|
374
|
+
|
|
375
|
+
## Parameter Types
|
|
376
|
+
|
|
377
|
+
- `:string` - Text values
|
|
378
|
+
- `:integer` - Whole numbers
|
|
379
|
+
- `:number` - Decimal numbers (float)
|
|
380
|
+
- `:boolean` - true/false
|
|
381
|
+
- `:array` - List of values
|
|
382
|
+
- `:object` - Nested hash/object
|
|
383
|
+
|
|
384
|
+
## Output Requirements
|
|
385
|
+
|
|
386
|
+
1. Output ONLY valid Ruby code - no markdown fences, no explanations
|
|
387
|
+
2. Include appropriate requires at the top
|
|
388
|
+
3. Include helpful comments explaining the code
|
|
389
|
+
4. Use ENV variables for API keys and secrets (never hardcode)
|
|
390
|
+
5. End with `Legate::GlobalToolManager.register_tool(ToolClass)`
|
|
391
|
+
6. Use descriptive class names in PascalCase
|
|
392
|
+
7. Include proper error handling
|
|
393
|
+
|
|
394
|
+
## Determining Tool Type
|
|
395
|
+
|
|
396
|
+
- If description mentions: API, HTTP, fetch, external service, REST, URL, website,#{' '}
|
|
397
|
+
check site, status code, HEAD request, ping, request, web, endpoint, webhook,
|
|
398
|
+
download, upload, GET, POST, online, reachable → Use HTTP API Tool
|
|
399
|
+
- If description mentions: background, async, queue, long-running, process files → Use Async Tool
|
|
400
|
+
- Otherwise → Use Simple Tool
|
|
401
|
+
|
|
402
|
+
IMPORTANT: Any tool that makes network requests to URLs or APIs MUST use the HTTP API Tool pattern.
|
|
403
|
+
Never use Net::HTTP, Faraday, or other HTTP libraries directly - always use HttpClient.
|
|
404
|
+
PROMPT
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Entry point for Legate generators module
|
|
4
|
+
require_relative 'generators/agent_generator'
|
|
5
|
+
require_relative 'generators/tool_generator'
|
|
6
|
+
|
|
7
|
+
module Legate
|
|
8
|
+
# Generators for AI-powered code generation
|
|
9
|
+
module Generators
|
|
10
|
+
end
|
|
11
|
+
end
|