language-operator 0.0.1 → 0.1.31
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 +4 -4
- data/.rubocop.yml +125 -0
- data/CHANGELOG.md +88 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +284 -0
- data/LICENSE +229 -21
- data/Makefile +82 -0
- data/README.md +3 -11
- data/Rakefile +63 -0
- data/bin/aictl +7 -0
- data/completions/_aictl +232 -0
- data/completions/aictl.bash +121 -0
- data/completions/aictl.fish +114 -0
- data/docs/architecture/agent-runtime.md +585 -0
- data/docs/dsl/SCHEMA_VERSION.md +250 -0
- data/docs/dsl/agent-reference.md +604 -0
- data/docs/dsl/best-practices.md +1078 -0
- data/docs/dsl/chat-endpoints.md +895 -0
- data/docs/dsl/constraints.md +671 -0
- data/docs/dsl/mcp-integration.md +1177 -0
- data/docs/dsl/webhooks.md +932 -0
- data/docs/dsl/workflows.md +744 -0
- data/lib/language_operator/agent/base.rb +110 -0
- data/lib/language_operator/agent/executor.rb +440 -0
- data/lib/language_operator/agent/instrumentation.rb +54 -0
- data/lib/language_operator/agent/metrics_tracker.rb +183 -0
- data/lib/language_operator/agent/safety/ast_validator.rb +272 -0
- data/lib/language_operator/agent/safety/audit_logger.rb +104 -0
- data/lib/language_operator/agent/safety/budget_tracker.rb +175 -0
- data/lib/language_operator/agent/safety/content_filter.rb +93 -0
- data/lib/language_operator/agent/safety/manager.rb +207 -0
- data/lib/language_operator/agent/safety/rate_limiter.rb +150 -0
- data/lib/language_operator/agent/safety/safe_executor.rb +127 -0
- data/lib/language_operator/agent/scheduler.rb +183 -0
- data/lib/language_operator/agent/telemetry.rb +116 -0
- data/lib/language_operator/agent/web_server.rb +610 -0
- data/lib/language_operator/agent/webhook_authenticator.rb +226 -0
- data/lib/language_operator/agent.rb +149 -0
- data/lib/language_operator/cli/commands/agent.rb +1205 -0
- data/lib/language_operator/cli/commands/cluster.rb +371 -0
- data/lib/language_operator/cli/commands/install.rb +404 -0
- data/lib/language_operator/cli/commands/model.rb +266 -0
- data/lib/language_operator/cli/commands/persona.rb +393 -0
- data/lib/language_operator/cli/commands/quickstart.rb +22 -0
- data/lib/language_operator/cli/commands/status.rb +143 -0
- data/lib/language_operator/cli/commands/system.rb +772 -0
- data/lib/language_operator/cli/commands/tool.rb +537 -0
- data/lib/language_operator/cli/commands/use.rb +47 -0
- data/lib/language_operator/cli/errors/handler.rb +180 -0
- data/lib/language_operator/cli/errors/suggestions.rb +176 -0
- data/lib/language_operator/cli/formatters/code_formatter.rb +77 -0
- data/lib/language_operator/cli/formatters/log_formatter.rb +288 -0
- data/lib/language_operator/cli/formatters/progress_formatter.rb +49 -0
- data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
- data/lib/language_operator/cli/formatters/table_formatter.rb +163 -0
- data/lib/language_operator/cli/formatters/value_formatter.rb +113 -0
- data/lib/language_operator/cli/helpers/cluster_context.rb +62 -0
- data/lib/language_operator/cli/helpers/cluster_validator.rb +101 -0
- data/lib/language_operator/cli/helpers/editor_helper.rb +58 -0
- data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +167 -0
- data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
- data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +74 -0
- data/lib/language_operator/cli/helpers/schedule_builder.rb +108 -0
- data/lib/language_operator/cli/helpers/user_prompts.rb +69 -0
- data/lib/language_operator/cli/main.rb +236 -0
- data/lib/language_operator/cli/templates/tools/generic.yaml +66 -0
- data/lib/language_operator/cli/wizards/agent_wizard.rb +246 -0
- data/lib/language_operator/cli/wizards/quickstart_wizard.rb +588 -0
- data/lib/language_operator/client/base.rb +214 -0
- data/lib/language_operator/client/config.rb +136 -0
- data/lib/language_operator/client/cost_calculator.rb +37 -0
- data/lib/language_operator/client/mcp_connector.rb +123 -0
- data/lib/language_operator/client.rb +19 -0
- data/lib/language_operator/config/cluster_config.rb +101 -0
- data/lib/language_operator/config/tool_patterns.yaml +57 -0
- data/lib/language_operator/config/tool_registry.rb +96 -0
- data/lib/language_operator/config.rb +138 -0
- data/lib/language_operator/dsl/adapter.rb +124 -0
- data/lib/language_operator/dsl/agent_context.rb +90 -0
- data/lib/language_operator/dsl/agent_definition.rb +427 -0
- data/lib/language_operator/dsl/chat_endpoint_definition.rb +115 -0
- data/lib/language_operator/dsl/config.rb +119 -0
- data/lib/language_operator/dsl/context.rb +50 -0
- data/lib/language_operator/dsl/execution_context.rb +47 -0
- data/lib/language_operator/dsl/helpers.rb +109 -0
- data/lib/language_operator/dsl/http.rb +184 -0
- data/lib/language_operator/dsl/mcp_server_definition.rb +73 -0
- data/lib/language_operator/dsl/parameter_definition.rb +124 -0
- data/lib/language_operator/dsl/registry.rb +36 -0
- data/lib/language_operator/dsl/schema.rb +1102 -0
- data/lib/language_operator/dsl/shell.rb +125 -0
- data/lib/language_operator/dsl/tool_definition.rb +112 -0
- data/lib/language_operator/dsl/webhook_authentication.rb +114 -0
- data/lib/language_operator/dsl/webhook_definition.rb +106 -0
- data/lib/language_operator/dsl/workflow_definition.rb +259 -0
- data/lib/language_operator/dsl.rb +161 -0
- data/lib/language_operator/errors.rb +60 -0
- data/lib/language_operator/kubernetes/client.rb +279 -0
- data/lib/language_operator/kubernetes/resource_builder.rb +194 -0
- data/lib/language_operator/loggable.rb +47 -0
- data/lib/language_operator/logger.rb +141 -0
- data/lib/language_operator/retry.rb +123 -0
- data/lib/language_operator/retryable.rb +132 -0
- data/lib/language_operator/templates/README.md +23 -0
- data/lib/language_operator/templates/examples/agent_synthesis.tmpl +115 -0
- data/lib/language_operator/templates/examples/persona_distillation.tmpl +19 -0
- data/lib/language_operator/templates/schema/.gitkeep +0 -0
- data/lib/language_operator/templates/schema/CHANGELOG.md +93 -0
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +452 -0
- data/lib/language_operator/tool_loader.rb +242 -0
- data/lib/language_operator/validators.rb +170 -0
- data/lib/language_operator/version.rb +1 -1
- data/lib/language_operator.rb +65 -3
- data/requirements/tasks/challenge.md +9 -0
- data/requirements/tasks/iterate.md +36 -0
- data/requirements/tasks/optimize.md +21 -0
- data/requirements/tasks/tag.md +5 -0
- data/test_agent_dsl.rb +108 -0
- metadata +507 -20
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LanguageOperator
|
|
4
|
+
module Dsl
|
|
5
|
+
# Execution context that includes helpers for tool execution
|
|
6
|
+
#
|
|
7
|
+
# Provides helper methods during tool execution, allowing tools to access
|
|
8
|
+
# HTTP, Shell, Config and other utilities directly.
|
|
9
|
+
#
|
|
10
|
+
# @example Using in tool execution
|
|
11
|
+
# context = LanguageOperator::Dsl::ExecutionContext.new(params)
|
|
12
|
+
# context.http_get('https://example.com')
|
|
13
|
+
# context.shell('ls -la')
|
|
14
|
+
class ExecutionContext
|
|
15
|
+
include LanguageOperator::Dsl::Helpers
|
|
16
|
+
|
|
17
|
+
# Provide access to HTTP and Shell helper classes as constants
|
|
18
|
+
HTTP = LanguageOperator::Dsl::HTTP
|
|
19
|
+
Shell = LanguageOperator::Dsl::Shell
|
|
20
|
+
|
|
21
|
+
# Initialize execution context with parameters
|
|
22
|
+
#
|
|
23
|
+
# @param params [Hash] Tool execution parameters
|
|
24
|
+
def initialize(params)
|
|
25
|
+
@params = params
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Forward missing methods to helpers
|
|
29
|
+
#
|
|
30
|
+
# @param method [Symbol] Method name
|
|
31
|
+
# @param args [Array] Method arguments
|
|
32
|
+
def method_missing(method, *args)
|
|
33
|
+
# Allow helper methods to be called directly
|
|
34
|
+
super
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Check if method is available
|
|
38
|
+
#
|
|
39
|
+
# @param method [Symbol] Method name
|
|
40
|
+
# @param include_private [Boolean] Include private methods
|
|
41
|
+
# @return [Boolean]
|
|
42
|
+
def respond_to_missing?(method, include_private = false)
|
|
43
|
+
LanguageOperator::Dsl::Helpers.instance_methods.include?(method) || super
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
4
|
+
require 'shellwords'
|
|
5
|
+
|
|
6
|
+
module LanguageOperator
|
|
7
|
+
module Dsl
|
|
8
|
+
# Common helper methods for MCP tools
|
|
9
|
+
#
|
|
10
|
+
# Provides validation, formatting, and utility methods that can be used
|
|
11
|
+
# within tool execute blocks. All methods are instance methods that get
|
|
12
|
+
# mixed into the tool execution context.
|
|
13
|
+
#
|
|
14
|
+
# @example Using helpers in a tool
|
|
15
|
+
# tool "send_email" do
|
|
16
|
+
# execute do |params|
|
|
17
|
+
# error = validate_email(params["email"])
|
|
18
|
+
# return error if error
|
|
19
|
+
# # email is valid, proceed...
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
module Helpers
|
|
23
|
+
# Validate URL format
|
|
24
|
+
#
|
|
25
|
+
# @param url [String] URL to validate
|
|
26
|
+
# @return [String, nil] Error message if invalid, nil if valid
|
|
27
|
+
def validate_url(url)
|
|
28
|
+
return 'Error: Invalid URL. Must start with http:// or https://' unless url =~ %r{^https?://}
|
|
29
|
+
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Validate phone number in E.164 format
|
|
34
|
+
#
|
|
35
|
+
# @param number [String] Phone number to validate
|
|
36
|
+
# @return [String, nil] Error message if invalid, nil if valid
|
|
37
|
+
def validate_phone(number)
|
|
38
|
+
return 'Error: Invalid phone number format. Use E.164 format (e.g., +1234567890)' unless number =~ /^\+\d{10,15}$/
|
|
39
|
+
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Validate email address format
|
|
44
|
+
#
|
|
45
|
+
# @param email [String] Email address to validate
|
|
46
|
+
# @return [String, nil] Error message if invalid, nil if valid
|
|
47
|
+
def validate_email(email)
|
|
48
|
+
return 'Error: Invalid email format' unless email =~ /\A[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+\z/i
|
|
49
|
+
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Shell escape for backtick execution (deprecated - use Shell.run instead)
|
|
54
|
+
# @deprecated This method is deprecated and will be removed for security reasons
|
|
55
|
+
def shell_escape(str)
|
|
56
|
+
warn 'DEPRECATION WARNING: shell_escape is deprecated and should not be used'
|
|
57
|
+
Shellwords.escape(str.to_s)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Run a command and return structured result
|
|
61
|
+
# @deprecated This method has been removed for security reasons. Use Shell.run instead.
|
|
62
|
+
# @raise [SecurityError] Always raises an error
|
|
63
|
+
def run_command(_cmd)
|
|
64
|
+
raise SecurityError, 'run_command has been removed for security reasons. Use Shell.run instead.'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Check required environment variables
|
|
68
|
+
def env_required(*vars)
|
|
69
|
+
missing = vars.reject { |v| ENV.fetch(v, nil) }
|
|
70
|
+
return "Error: Missing required environment variables: #{missing.join(', ')}" unless missing.empty?
|
|
71
|
+
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Get environment variable with fallbacks
|
|
76
|
+
def env_get(*keys, default: nil)
|
|
77
|
+
keys.each do |key|
|
|
78
|
+
value = ENV.fetch(key, nil)
|
|
79
|
+
return value if value
|
|
80
|
+
end
|
|
81
|
+
default
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Truncate text to a maximum length
|
|
85
|
+
def truncate(text, max_length: 2000, suffix: '...')
|
|
86
|
+
return text if text.length <= max_length
|
|
87
|
+
|
|
88
|
+
text[0...max_length] + suffix
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Parse comma-separated values
|
|
92
|
+
def parse_csv(str)
|
|
93
|
+
return [] if str.nil? || str.empty?
|
|
94
|
+
|
|
95
|
+
str.split(',').map(&:strip).reject(&:empty?)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Format error message
|
|
99
|
+
def error(message)
|
|
100
|
+
"Error: #{message}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Format success message
|
|
104
|
+
def success(message)
|
|
105
|
+
message
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'uri'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
module LanguageOperator
|
|
9
|
+
module Dsl
|
|
10
|
+
# HTTP client helper for MCP tools
|
|
11
|
+
#
|
|
12
|
+
# Provides convenient methods for making HTTP requests from within tools.
|
|
13
|
+
# Supports GET, POST, PUT, DELETE, HEAD with automatic JSON parsing.
|
|
14
|
+
#
|
|
15
|
+
# @example Making a GET request
|
|
16
|
+
# result = HTTP.get('https://api.example.com/users')
|
|
17
|
+
# if result[:success]
|
|
18
|
+
# users = result[:json]
|
|
19
|
+
# end
|
|
20
|
+
class HTTP
|
|
21
|
+
# Perform a GET request
|
|
22
|
+
def self.get(url, headers: {}, follow_redirects: true, timeout: 30)
|
|
23
|
+
uri = parse_uri(url)
|
|
24
|
+
return { error: "Invalid URL: #{url}" } unless uri
|
|
25
|
+
|
|
26
|
+
http = build_http(uri, timeout: timeout)
|
|
27
|
+
request = Net::HTTP::Get.new(uri)
|
|
28
|
+
add_headers(request, headers)
|
|
29
|
+
|
|
30
|
+
execute_request(http, request, follow_redirects: follow_redirects)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Perform a POST request
|
|
34
|
+
def self.post(url, body: nil, json: nil, headers: {}, auth: nil, timeout: 30)
|
|
35
|
+
uri = parse_uri(url)
|
|
36
|
+
return { error: "Invalid URL: #{url}" } unless uri
|
|
37
|
+
|
|
38
|
+
http = build_http(uri, timeout: timeout)
|
|
39
|
+
request = Net::HTTP::Post.new(uri)
|
|
40
|
+
|
|
41
|
+
# Set body
|
|
42
|
+
if json
|
|
43
|
+
request.body = json.to_json
|
|
44
|
+
request['Content-Type'] = 'application/json'
|
|
45
|
+
elsif body
|
|
46
|
+
request.body = body
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
add_headers(request, headers)
|
|
50
|
+
add_auth(request, auth) if auth
|
|
51
|
+
|
|
52
|
+
execute_request(http, request)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Perform a PUT request
|
|
56
|
+
def self.put(url, body: nil, json: nil, headers: {}, auth: nil, timeout: 30)
|
|
57
|
+
uri = parse_uri(url)
|
|
58
|
+
return { error: "Invalid URL: #{url}" } unless uri
|
|
59
|
+
|
|
60
|
+
http = build_http(uri, timeout: timeout)
|
|
61
|
+
request = Net::HTTP::Put.new(uri)
|
|
62
|
+
|
|
63
|
+
if json
|
|
64
|
+
request.body = json.to_json
|
|
65
|
+
request['Content-Type'] = 'application/json'
|
|
66
|
+
elsif body
|
|
67
|
+
request.body = body
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
add_headers(request, headers)
|
|
71
|
+
add_auth(request, auth) if auth
|
|
72
|
+
|
|
73
|
+
execute_request(http, request)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Perform a DELETE request
|
|
77
|
+
def self.delete(url, headers: {}, auth: nil, timeout: 30)
|
|
78
|
+
uri = parse_uri(url)
|
|
79
|
+
return { error: "Invalid URL: #{url}" } unless uri
|
|
80
|
+
|
|
81
|
+
http = build_http(uri, timeout: timeout)
|
|
82
|
+
request = Net::HTTP::Delete.new(uri)
|
|
83
|
+
|
|
84
|
+
add_headers(request, headers)
|
|
85
|
+
add_auth(request, auth) if auth
|
|
86
|
+
|
|
87
|
+
execute_request(http, request)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Get just the headers from a URL
|
|
91
|
+
def self.head(url, headers: {}, timeout: 30)
|
|
92
|
+
uri = parse_uri(url)
|
|
93
|
+
return { error: "Invalid URL: #{url}" } unless uri
|
|
94
|
+
|
|
95
|
+
http = build_http(uri, timeout: timeout)
|
|
96
|
+
request = Net::HTTP::Head.new(uri)
|
|
97
|
+
add_headers(request, headers)
|
|
98
|
+
|
|
99
|
+
execute_request(http, request)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Wrapper for curl commands (REMOVED FOR SECURITY)
|
|
103
|
+
# This method has been removed as it executes shell commands via backticks
|
|
104
|
+
# which is a security risk in synthesized code.
|
|
105
|
+
# @deprecated This method has been removed for security reasons
|
|
106
|
+
# @raise [SecurityError] Always raises an error
|
|
107
|
+
def self.curl(_url, options: [])
|
|
108
|
+
raise SecurityError, 'HTTP.curl has been removed for security reasons. Use HTTP.get, HTTP.post, etc. instead.'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class << self
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def parse_uri(url)
|
|
115
|
+
URI.parse(url)
|
|
116
|
+
rescue URI::InvalidURIError
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def build_http(uri, timeout: 30)
|
|
121
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
122
|
+
http.use_ssl = (uri.scheme == 'https')
|
|
123
|
+
http.open_timeout = timeout
|
|
124
|
+
http.read_timeout = timeout
|
|
125
|
+
http
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def add_headers(request, headers)
|
|
129
|
+
# Add default user agent if not provided
|
|
130
|
+
request['User-Agent'] ||= 'Langop-SDK/1.0'
|
|
131
|
+
|
|
132
|
+
headers.each do |key, value|
|
|
133
|
+
request[key] = value
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def add_auth(request, auth)
|
|
138
|
+
case auth[:type]
|
|
139
|
+
when :basic
|
|
140
|
+
request.basic_auth(auth[:username], auth[:password])
|
|
141
|
+
when :bearer
|
|
142
|
+
request['Authorization'] = "Bearer #{auth[:token]}"
|
|
143
|
+
when :token
|
|
144
|
+
request['Authorization'] = "Token #{auth[:token]}"
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def execute_request(http, request, follow_redirects: false)
|
|
149
|
+
response = http.request(request)
|
|
150
|
+
|
|
151
|
+
# Handle redirects
|
|
152
|
+
if follow_redirects && response.is_a?(Net::HTTPRedirection)
|
|
153
|
+
location = response['location']
|
|
154
|
+
return get(location, headers: request.to_hash, follow_redirects: true)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Parse response
|
|
158
|
+
result = {
|
|
159
|
+
status: response.code.to_i,
|
|
160
|
+
headers: response.to_hash,
|
|
161
|
+
body: response.body,
|
|
162
|
+
success: response.is_a?(Net::HTTPSuccess)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# Try to parse JSON if content-type indicates JSON
|
|
166
|
+
if response['content-type']&.include?('application/json')
|
|
167
|
+
begin
|
|
168
|
+
result[:json] = JSON.parse(response.body)
|
|
169
|
+
rescue JSON::ParserError
|
|
170
|
+
# Body is not valid JSON, leave as string
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
result
|
|
175
|
+
rescue StandardError => e
|
|
176
|
+
{
|
|
177
|
+
error: e.message,
|
|
178
|
+
success: false
|
|
179
|
+
}
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'tool_definition'
|
|
4
|
+
|
|
5
|
+
module LanguageOperator
|
|
6
|
+
module Dsl
|
|
7
|
+
# MCP server definition for agents
|
|
8
|
+
#
|
|
9
|
+
# Allows agents to expose their own tools via the MCP protocol.
|
|
10
|
+
# Tools defined here can be called by other agents or MCP clients.
|
|
11
|
+
#
|
|
12
|
+
# @example Define tools in an agent
|
|
13
|
+
# agent "data-processor" do
|
|
14
|
+
# as_mcp_server do
|
|
15
|
+
# tool "process_csv" do
|
|
16
|
+
# description "Process CSV data"
|
|
17
|
+
# parameter :url do
|
|
18
|
+
# type :string
|
|
19
|
+
# required true
|
|
20
|
+
# end
|
|
21
|
+
# execute do |params|
|
|
22
|
+
# # Processing logic
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
class McpServerDefinition
|
|
28
|
+
attr_reader :tools, :server_name
|
|
29
|
+
|
|
30
|
+
def initialize(agent_name)
|
|
31
|
+
@agent_name = agent_name
|
|
32
|
+
@server_name = "#{agent_name}-mcp"
|
|
33
|
+
@tools = {}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Define a tool that this agent exposes
|
|
37
|
+
#
|
|
38
|
+
# @param name [String] Tool name
|
|
39
|
+
# @yield Tool definition block
|
|
40
|
+
# @return [ToolDefinition] The tool definition
|
|
41
|
+
def tool(name, &block)
|
|
42
|
+
tool_def = ToolDefinition.new(name)
|
|
43
|
+
tool_def.instance_eval(&block) if block
|
|
44
|
+
@tools[name] = tool_def
|
|
45
|
+
tool_def
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Set custom server name
|
|
49
|
+
#
|
|
50
|
+
# @param name [String] Server name
|
|
51
|
+
# @return [String] Current server name
|
|
52
|
+
def name(name = nil)
|
|
53
|
+
return @server_name if name.nil?
|
|
54
|
+
|
|
55
|
+
@server_name = name
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Get all tool definitions
|
|
59
|
+
#
|
|
60
|
+
# @return [Array<ToolDefinition>] Array of tool definitions
|
|
61
|
+
def all_tools
|
|
62
|
+
@tools.values
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Check if any tools are defined
|
|
66
|
+
#
|
|
67
|
+
# @return [Boolean] True if tools are defined
|
|
68
|
+
def tools?
|
|
69
|
+
!@tools.empty?
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LanguageOperator
|
|
4
|
+
module Dsl
|
|
5
|
+
# Parameter definition for tool parameters
|
|
6
|
+
#
|
|
7
|
+
# Defines parameter schema, validation, and metadata for MCP tools.
|
|
8
|
+
# Supports type checking, required validation, enums, defaults, and custom validators.
|
|
9
|
+
#
|
|
10
|
+
# @example Define a required string parameter
|
|
11
|
+
# parameter :name do
|
|
12
|
+
# type :string
|
|
13
|
+
# required true
|
|
14
|
+
# description "User's name"
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# @example Parameter with enum
|
|
18
|
+
# parameter :status do
|
|
19
|
+
# type :string
|
|
20
|
+
# enum ["active", "inactive", "pending"]
|
|
21
|
+
# default "pending"
|
|
22
|
+
# end
|
|
23
|
+
class ParameterDefinition
|
|
24
|
+
attr_reader :name, :validator
|
|
25
|
+
|
|
26
|
+
def initialize(name)
|
|
27
|
+
@name = name
|
|
28
|
+
@required = false
|
|
29
|
+
@validator = nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def type(val = nil)
|
|
33
|
+
return @type if val.nil?
|
|
34
|
+
|
|
35
|
+
@type = val
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Setter method for required flag
|
|
39
|
+
def required(val = true)
|
|
40
|
+
@required = val
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Getter method for required flag (explicit to avoid collision with setter)
|
|
44
|
+
def required?
|
|
45
|
+
@required
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def description(val = nil)
|
|
49
|
+
return @description if val.nil?
|
|
50
|
+
|
|
51
|
+
@description = val
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def enum(val = nil)
|
|
55
|
+
return @enum if val.nil?
|
|
56
|
+
|
|
57
|
+
@enum = val
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def default(val = nil)
|
|
61
|
+
return @default if val.nil?
|
|
62
|
+
|
|
63
|
+
@default = val
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Custom validation with proc or regex
|
|
67
|
+
def validate(proc_or_regex)
|
|
68
|
+
@validator = proc_or_regex
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Built-in validators
|
|
72
|
+
def url_format
|
|
73
|
+
@validator = %r{^https?://}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def email_format
|
|
77
|
+
@validator = /\A[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+\z/i
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def phone_format
|
|
81
|
+
@validator = /^\+\d{10,15}$/
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Validate a value against this parameter's validator
|
|
85
|
+
def validate_value(value)
|
|
86
|
+
return nil unless @validator
|
|
87
|
+
|
|
88
|
+
case @validator
|
|
89
|
+
when Regexp
|
|
90
|
+
return "Parameter '#{@name}' has invalid format" unless value.to_s =~ @validator
|
|
91
|
+
when Proc
|
|
92
|
+
result = @validator.call(value)
|
|
93
|
+
return result if result.is_a?(String) # Error message
|
|
94
|
+
return "Parameter '#{@name}' validation failed" unless result
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
nil # No error
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def to_schema
|
|
101
|
+
schema = {
|
|
102
|
+
'type' => map_type(@type),
|
|
103
|
+
'description' => @description
|
|
104
|
+
}
|
|
105
|
+
schema['enum'] = @enum if @enum
|
|
106
|
+
schema['default'] = @default if @default
|
|
107
|
+
schema
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def map_type(ruby_type)
|
|
113
|
+
case ruby_type
|
|
114
|
+
when :string then 'string'
|
|
115
|
+
when :number, :integer then 'number'
|
|
116
|
+
when :boolean then 'boolean'
|
|
117
|
+
when :array then 'array'
|
|
118
|
+
when :object then 'object'
|
|
119
|
+
else 'string'
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LanguageOperator
|
|
4
|
+
module Dsl
|
|
5
|
+
# Registry for storing and retrieving tool definitions
|
|
6
|
+
#
|
|
7
|
+
# Manages a collection of tools defined using the DSL.
|
|
8
|
+
# Tools can be registered, retrieved by name, or accessed as a collection.
|
|
9
|
+
#
|
|
10
|
+
# @example Using the registry
|
|
11
|
+
# registry = Registry.new
|
|
12
|
+
# registry.register(tool_definition)
|
|
13
|
+
# all_tools = registry.all
|
|
14
|
+
class Registry
|
|
15
|
+
def initialize
|
|
16
|
+
@tools = {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def register(tool)
|
|
20
|
+
@tools[tool.name] = tool
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def get(name)
|
|
24
|
+
@tools[name]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def all
|
|
28
|
+
@tools.values
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def clear
|
|
32
|
+
@tools.clear
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|