language-operator 0.0.1 → 0.1.30
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 +53 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +284 -0
- data/LICENSE +229 -21
- data/Makefile +77 -0
- data/README.md +3 -11
- data/Rakefile +34 -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/agent-reference.md +591 -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/examples/README.md +569 -0
- data/examples/agent_example.rb +86 -0
- data/examples/chat_endpoint_agent.rb +118 -0
- data/examples/github_webhook_agent.rb +171 -0
- data/examples/mcp_agent.rb +158 -0
- data/examples/oauth_callback_agent.rb +296 -0
- data/examples/stripe_webhook_agent.rb +219 -0
- data/examples/webhook_agent.rb +80 -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 +115 -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 +1252 -0
- data/lib/language_operator/cli/commands/cluster.rb +335 -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 +396 -0
- data/lib/language_operator/cli/commands/quickstart.rb +22 -0
- data/lib/language_operator/cli/commands/status.rb +156 -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 +81 -0
- data/lib/language_operator/cli/formatters/log_formatter.rb +290 -0
- data/lib/language_operator/cli/formatters/progress_formatter.rb +53 -0
- data/lib/language_operator/cli/formatters/table_formatter.rb +179 -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/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 +232 -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/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 +160 -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/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 +503 -20
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module LanguageOperator
|
|
8
|
+
module Config
|
|
9
|
+
# Fetches and caches tool registry from remote URL
|
|
10
|
+
class ToolRegistry
|
|
11
|
+
REGISTRY_URL = 'https://raw.githubusercontent.com/language-operator/language-tools/main/index.yaml'
|
|
12
|
+
CACHE_TTL = 3600 # 1 hour
|
|
13
|
+
|
|
14
|
+
def initialize(registry_url: REGISTRY_URL, api_token: nil)
|
|
15
|
+
@registry_url = registry_url
|
|
16
|
+
@api_token = api_token || ENV.fetch('GITHUB_TOKEN', nil)
|
|
17
|
+
@cache = nil
|
|
18
|
+
@cache_time = nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Fetch tools from registry with caching
|
|
22
|
+
#
|
|
23
|
+
# @return [Hash] Tool configurations keyed by tool name
|
|
24
|
+
def fetch
|
|
25
|
+
# Return cached data if still valid
|
|
26
|
+
return @cache if @cache && @cache_time && (Time.now - @cache_time) < CACHE_TTL
|
|
27
|
+
|
|
28
|
+
# Fetch from remote
|
|
29
|
+
begin
|
|
30
|
+
tools = fetch_remote
|
|
31
|
+
@cache = tools
|
|
32
|
+
@cache_time = Time.now
|
|
33
|
+
tools
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
# Fall back to local file if remote fetch fails
|
|
36
|
+
warn "Failed to fetch remote registry: #{e.message}"
|
|
37
|
+
warn 'Falling back to local registry'
|
|
38
|
+
fetch_local
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Clear the cache to force a fresh fetch
|
|
43
|
+
def clear_cache
|
|
44
|
+
@cache = nil
|
|
45
|
+
@cache_time = nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def fetch_remote
|
|
51
|
+
uri = URI(@registry_url)
|
|
52
|
+
response = fetch_with_redirects(uri, limit: 5)
|
|
53
|
+
|
|
54
|
+
raise "HTTP #{response.code}: #{response.message}" unless response.is_a?(Net::HTTPSuccess)
|
|
55
|
+
|
|
56
|
+
# Parse YAML response
|
|
57
|
+
data = YAML.safe_load(response.body)
|
|
58
|
+
|
|
59
|
+
# Extract tools from nested structure
|
|
60
|
+
data['tools'] || {}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def fetch_with_redirects(uri, limit: 5)
|
|
64
|
+
raise 'Too many HTTP redirects' if limit.zero?
|
|
65
|
+
|
|
66
|
+
request = Net::HTTP::Get.new(uri)
|
|
67
|
+
request['Authorization'] = "token #{@api_token}" if @api_token
|
|
68
|
+
|
|
69
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
|
70
|
+
http.request(request)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
case response
|
|
74
|
+
when Net::HTTPSuccess
|
|
75
|
+
response
|
|
76
|
+
when Net::HTTPRedirection
|
|
77
|
+
location = response['location']
|
|
78
|
+
# Handle relative redirects by merging with current URI
|
|
79
|
+
new_uri = uri.merge(location)
|
|
80
|
+
# Preserve authorization for same host
|
|
81
|
+
fetch_with_redirects(new_uri, limit: limit - 1)
|
|
82
|
+
else
|
|
83
|
+
response
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def fetch_local
|
|
88
|
+
# Fall back to bundled local registry
|
|
89
|
+
patterns_path = File.join(__dir__, 'tool_patterns.yaml')
|
|
90
|
+
return {} unless File.exist?(patterns_path)
|
|
91
|
+
|
|
92
|
+
YAML.load_file(patterns_path)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LanguageOperator
|
|
4
|
+
# Configuration loading helpers for environment variables
|
|
5
|
+
#
|
|
6
|
+
# Provides utilities for loading, validating, and type-converting
|
|
7
|
+
# configuration from environment variables with support for:
|
|
8
|
+
# - Key-to-env-var mappings
|
|
9
|
+
# - Prefixes (e.g., SMTP_HOST from prefix: 'SMTP')
|
|
10
|
+
# - Default values
|
|
11
|
+
# - Type conversion (string, integer, boolean, float)
|
|
12
|
+
# - Required config validation
|
|
13
|
+
#
|
|
14
|
+
# @example Load SMTP configuration
|
|
15
|
+
# config = LanguageOperator::Config.load(
|
|
16
|
+
# { host: 'HOST', port: 'PORT', user: 'USER', password: 'PASSWORD' },
|
|
17
|
+
# prefix: 'SMTP',
|
|
18
|
+
# required: [:host, :user, :password],
|
|
19
|
+
# defaults: { port: '587', tls: 'true' },
|
|
20
|
+
# types: { port: :integer, tls: :boolean }
|
|
21
|
+
# )
|
|
22
|
+
# # => { host: "smtp.example.com", port: 587, user: "user@example.com",
|
|
23
|
+
# # password: "secret", tls: true }
|
|
24
|
+
module Config
|
|
25
|
+
# Load configuration from environment variables
|
|
26
|
+
#
|
|
27
|
+
# @param mappings [Hash{Symbol => String}] Map of config keys to env var names
|
|
28
|
+
# @param prefix [String, nil] Optional prefix to prepend to env var names
|
|
29
|
+
# @param defaults [Hash{Symbol => Object}] Default values for optional config
|
|
30
|
+
# @param types [Hash{Symbol => Symbol}] Type conversion (:string, :integer, :boolean, :float)
|
|
31
|
+
# @return [Hash{Symbol => Object}] Configuration hash with values from env vars or defaults
|
|
32
|
+
#
|
|
33
|
+
# @example Basic usage
|
|
34
|
+
# config = Config.from_env(
|
|
35
|
+
# { database_url: 'DATABASE_URL' },
|
|
36
|
+
# defaults: { database_url: 'sqlite://localhost/db.sqlite3' }
|
|
37
|
+
# )
|
|
38
|
+
#
|
|
39
|
+
# @example With prefix and types
|
|
40
|
+
# config = Config.from_env(
|
|
41
|
+
# { host: 'HOST', port: 'PORT' },
|
|
42
|
+
# prefix: 'REDIS',
|
|
43
|
+
# defaults: { port: '6379' },
|
|
44
|
+
# types: { port: :integer }
|
|
45
|
+
# )
|
|
46
|
+
# # Reads REDIS_HOST and REDIS_PORT env vars
|
|
47
|
+
def self.from_env(mappings, prefix: nil, defaults: {}, types: {})
|
|
48
|
+
config = {}
|
|
49
|
+
|
|
50
|
+
mappings.each do |key, env_var|
|
|
51
|
+
full_var = prefix ? "#{prefix}_#{env_var}" : env_var
|
|
52
|
+
raw_value = ENV[full_var] || defaults[key]
|
|
53
|
+
|
|
54
|
+
config[key] = convert_type(raw_value, types[key] || :string)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
config
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Validate that required configuration keys are present and non-empty
|
|
61
|
+
#
|
|
62
|
+
# @param config [Hash] Configuration hash
|
|
63
|
+
# @param required_keys [Array<Symbol>] Keys that must be present
|
|
64
|
+
# @raise [RuntimeError] If any required keys are missing or empty
|
|
65
|
+
#
|
|
66
|
+
# @example
|
|
67
|
+
# Config.validate_required!(config, [:host, :user, :password])
|
|
68
|
+
def self.validate_required!(config, required_keys)
|
|
69
|
+
missing = required_keys.select { |key| config[key].nil? || config[key].to_s.strip.empty? }
|
|
70
|
+
return if missing.empty?
|
|
71
|
+
|
|
72
|
+
raise LanguageOperator::Errors.missing_config(missing.map(&:to_s).map(&:upcase))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Convert a string value to the specified type
|
|
76
|
+
#
|
|
77
|
+
# @param value [String, nil] Raw string value from environment
|
|
78
|
+
# @param type [Symbol] Target type (:string, :integer, :boolean, :float)
|
|
79
|
+
# @return [Object] Converted value
|
|
80
|
+
#
|
|
81
|
+
# @example String conversion
|
|
82
|
+
# Config.convert_type('hello', :string) # => "hello"
|
|
83
|
+
#
|
|
84
|
+
# @example Integer conversion
|
|
85
|
+
# Config.convert_type('42', :integer) # => 42
|
|
86
|
+
#
|
|
87
|
+
# @example Boolean conversion
|
|
88
|
+
# Config.convert_type('true', :boolean) # => true
|
|
89
|
+
# Config.convert_type('1', :boolean) # => true
|
|
90
|
+
# Config.convert_type('yes', :boolean) # => true
|
|
91
|
+
# Config.convert_type('false', :boolean) # => false
|
|
92
|
+
#
|
|
93
|
+
# @example Float conversion
|
|
94
|
+
# Config.convert_type('3.14', :float) # => 3.14
|
|
95
|
+
def self.convert_type(value, type)
|
|
96
|
+
return nil if value.nil?
|
|
97
|
+
|
|
98
|
+
case type
|
|
99
|
+
when :string
|
|
100
|
+
value.to_s
|
|
101
|
+
when :integer
|
|
102
|
+
value.to_i
|
|
103
|
+
when :float
|
|
104
|
+
value.to_f
|
|
105
|
+
when :boolean
|
|
106
|
+
%w[true 1 yes on].include?(value.to_s.downcase)
|
|
107
|
+
else
|
|
108
|
+
value
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Load configuration with validation in one call
|
|
113
|
+
#
|
|
114
|
+
# Combines from_env and validate_required! for convenience.
|
|
115
|
+
#
|
|
116
|
+
# @param mappings [Hash] Config key to env var mappings
|
|
117
|
+
# @param required [Array<Symbol>] Required config keys
|
|
118
|
+
# @param defaults [Hash] Default values
|
|
119
|
+
# @param types [Hash] Type conversions
|
|
120
|
+
# @param prefix [String, nil] Env var prefix
|
|
121
|
+
# @return [Hash] Validated configuration
|
|
122
|
+
# @raise [RuntimeError] If required keys are missing
|
|
123
|
+
#
|
|
124
|
+
# @example Complete configuration loading
|
|
125
|
+
# config = Config.load(
|
|
126
|
+
# { host: 'HOST', port: 'PORT', user: 'USER', password: 'PASSWORD' },
|
|
127
|
+
# prefix: 'SMTP',
|
|
128
|
+
# required: [:host, :user, :password],
|
|
129
|
+
# defaults: { port: '587' },
|
|
130
|
+
# types: { port: :integer }
|
|
131
|
+
# )
|
|
132
|
+
def self.load(mappings, required: [], defaults: {}, types: {}, prefix: nil)
|
|
133
|
+
config = from_env(mappings, prefix: prefix, defaults: defaults, types: types)
|
|
134
|
+
validate_required!(config, required) unless required.empty?
|
|
135
|
+
config
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
|
|
5
|
+
module LanguageOperator
|
|
6
|
+
module Dsl
|
|
7
|
+
# Adapter to bridge our DSL to the official MCP Ruby SDK
|
|
8
|
+
#
|
|
9
|
+
# Converts LanguageOperator::Dsl::ToolDefinition objects into MCP::Tool classes
|
|
10
|
+
# that can be used with the official MCP Ruby SDK.
|
|
11
|
+
#
|
|
12
|
+
# @example Convert a tool definition
|
|
13
|
+
# tool_class = Adapter.tool_definition_to_mcp_tool(tool_def)
|
|
14
|
+
# server = MCP::Server.new(tools: [tool_class])
|
|
15
|
+
class Adapter
|
|
16
|
+
# Convert our ToolDefinition to an MCP::Tool class
|
|
17
|
+
def self.tool_definition_to_mcp_tool(tool_def)
|
|
18
|
+
# Build input schema from our parameter definitions
|
|
19
|
+
schema = build_input_schema(tool_def.parameters)
|
|
20
|
+
|
|
21
|
+
# Create a dynamic class that extends MCP::Tool
|
|
22
|
+
Class.new(MCP::Tool) do
|
|
23
|
+
# Set the tool name
|
|
24
|
+
define_singleton_method(:name) { tool_def.name }
|
|
25
|
+
|
|
26
|
+
# Set the description
|
|
27
|
+
description tool_def.description
|
|
28
|
+
|
|
29
|
+
# Set input schema
|
|
30
|
+
input_schema(schema)
|
|
31
|
+
|
|
32
|
+
# Define the call method
|
|
33
|
+
define_singleton_method(:call) do |server_context: {}, **args|
|
|
34
|
+
# Convert args to string keys to match our DSL expectations
|
|
35
|
+
params = args.transform_keys(&:to_s)
|
|
36
|
+
|
|
37
|
+
# Execute the tool using our DSL
|
|
38
|
+
result = tool_def.call(params)
|
|
39
|
+
|
|
40
|
+
# Convert result to MCP::Tool::Response
|
|
41
|
+
MCP::Tool::Response.new([
|
|
42
|
+
{ type: 'text', text: result.to_s }
|
|
43
|
+
])
|
|
44
|
+
rescue ArgumentError => e
|
|
45
|
+
# Return error as text response
|
|
46
|
+
MCP::Tool::Response.new([
|
|
47
|
+
{ type: 'text', text: "Error: #{e.message}" }
|
|
48
|
+
])
|
|
49
|
+
rescue StandardError => e
|
|
50
|
+
# Return error as text response
|
|
51
|
+
MCP::Tool::Response.new([
|
|
52
|
+
{ type: 'text', text: "Error: #{e.message}" }
|
|
53
|
+
])
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Build MCP input schema from our parameter definitions
|
|
59
|
+
def self.build_input_schema(parameters)
|
|
60
|
+
properties = {}
|
|
61
|
+
required = []
|
|
62
|
+
|
|
63
|
+
parameters.each do |name, param_def|
|
|
64
|
+
properties[name.to_sym] = build_parameter_schema(param_def)
|
|
65
|
+
required << name if param_def.required
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
schema = { properties: properties }
|
|
69
|
+
schema[:required] = required unless required.empty?
|
|
70
|
+
schema
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Build schema for a single parameter
|
|
74
|
+
def self.build_parameter_schema(param_def)
|
|
75
|
+
# Access instance variables directly since the DSL methods are setters
|
|
76
|
+
param_type = param_def.instance_variable_get(:@type)
|
|
77
|
+
param_desc = param_def.instance_variable_get(:@description)
|
|
78
|
+
param_enum = param_def.instance_variable_get(:@enum)
|
|
79
|
+
param_default = param_def.instance_variable_get(:@default)
|
|
80
|
+
|
|
81
|
+
schema = {
|
|
82
|
+
type: map_type(param_type),
|
|
83
|
+
description: param_desc || '' # MCP requires description to be a string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
schema[:enum] = param_enum if param_enum
|
|
87
|
+
schema[:default] = param_default if param_default
|
|
88
|
+
|
|
89
|
+
schema
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Map our type symbols to JSON schema types
|
|
93
|
+
def self.map_type(ruby_type)
|
|
94
|
+
case ruby_type
|
|
95
|
+
when :string then 'string'
|
|
96
|
+
when :number, :integer then 'number'
|
|
97
|
+
when :boolean then 'boolean'
|
|
98
|
+
when :array then 'array'
|
|
99
|
+
when :object then 'object'
|
|
100
|
+
else 'string'
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Convert a registry of tools to MCP::Tool classes
|
|
105
|
+
def self.registry_to_mcp_tools(registry)
|
|
106
|
+
registry.all.map do |tool_def|
|
|
107
|
+
tool_definition_to_mcp_tool(tool_def)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Create an MCP::Server from our registry
|
|
112
|
+
def self.create_mcp_server(registry, server_name: 'langop-mcp', server_context: {})
|
|
113
|
+
tools = registry_to_mcp_tools(registry)
|
|
114
|
+
|
|
115
|
+
MCP::Server.new(
|
|
116
|
+
name: server_name,
|
|
117
|
+
version: LanguageOperator::VERSION,
|
|
118
|
+
tools: tools,
|
|
119
|
+
server_context: server_context
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LanguageOperator
|
|
4
|
+
module Dsl
|
|
5
|
+
# DSL context for defining agents
|
|
6
|
+
#
|
|
7
|
+
# Provides the evaluation context for agent definition files. Agents are
|
|
8
|
+
# defined using the `agent` method within this context.
|
|
9
|
+
#
|
|
10
|
+
# @example Agent definition file
|
|
11
|
+
# agent "news-summarizer" do
|
|
12
|
+
# description "Daily news summarization"
|
|
13
|
+
#
|
|
14
|
+
# schedule "0 12 * * *"
|
|
15
|
+
#
|
|
16
|
+
# objectives [
|
|
17
|
+
# "Search for recent news",
|
|
18
|
+
# "Summarize findings"
|
|
19
|
+
# ]
|
|
20
|
+
#
|
|
21
|
+
# workflow do
|
|
22
|
+
# step :search, tool: "web_search"
|
|
23
|
+
# step :summarize, depends_on: :search
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
class AgentContext
|
|
27
|
+
# Initialize context with registry
|
|
28
|
+
#
|
|
29
|
+
# @param registry [LanguageOperator::Dsl::AgentRegistry] Agent registry
|
|
30
|
+
def initialize(registry)
|
|
31
|
+
@registry = registry
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Define an agent
|
|
35
|
+
#
|
|
36
|
+
# @param name [String] Agent name
|
|
37
|
+
# @yield Agent definition block
|
|
38
|
+
# @return [void]
|
|
39
|
+
def agent(name, &)
|
|
40
|
+
agent_def = AgentDefinition.new(name)
|
|
41
|
+
agent_def.instance_eval(&) if block_given?
|
|
42
|
+
@registry.register(agent_def)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Registry for agents (similar to tool registry)
|
|
47
|
+
class AgentRegistry
|
|
48
|
+
def initialize
|
|
49
|
+
@agents = {}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Register an agent
|
|
53
|
+
#
|
|
54
|
+
# @param agent_def [AgentDefinition] Agent definition
|
|
55
|
+
# @return [void]
|
|
56
|
+
def register(agent_def)
|
|
57
|
+
@agents[agent_def.name] = agent_def
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Get an agent by name
|
|
61
|
+
#
|
|
62
|
+
# @param name [String] Agent name
|
|
63
|
+
# @return [AgentDefinition, nil] Agent definition
|
|
64
|
+
def get(name)
|
|
65
|
+
@agents[name.to_s]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Get all agents
|
|
69
|
+
#
|
|
70
|
+
# @return [Array<AgentDefinition>] All registered agents
|
|
71
|
+
def all
|
|
72
|
+
@agents.values
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Clear all agents
|
|
76
|
+
#
|
|
77
|
+
# @return [void]
|
|
78
|
+
def clear
|
|
79
|
+
@agents.clear
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Get count of registered agents
|
|
83
|
+
#
|
|
84
|
+
# @return [Integer] Number of agents
|
|
85
|
+
def count
|
|
86
|
+
@agents.size
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|