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.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +125 -0
  3. data/CHANGELOG.md +88 -0
  4. data/Gemfile +8 -0
  5. data/Gemfile.lock +284 -0
  6. data/LICENSE +229 -21
  7. data/Makefile +82 -0
  8. data/README.md +3 -11
  9. data/Rakefile +63 -0
  10. data/bin/aictl +7 -0
  11. data/completions/_aictl +232 -0
  12. data/completions/aictl.bash +121 -0
  13. data/completions/aictl.fish +114 -0
  14. data/docs/architecture/agent-runtime.md +585 -0
  15. data/docs/dsl/SCHEMA_VERSION.md +250 -0
  16. data/docs/dsl/agent-reference.md +604 -0
  17. data/docs/dsl/best-practices.md +1078 -0
  18. data/docs/dsl/chat-endpoints.md +895 -0
  19. data/docs/dsl/constraints.md +671 -0
  20. data/docs/dsl/mcp-integration.md +1177 -0
  21. data/docs/dsl/webhooks.md +932 -0
  22. data/docs/dsl/workflows.md +744 -0
  23. data/lib/language_operator/agent/base.rb +110 -0
  24. data/lib/language_operator/agent/executor.rb +440 -0
  25. data/lib/language_operator/agent/instrumentation.rb +54 -0
  26. data/lib/language_operator/agent/metrics_tracker.rb +183 -0
  27. data/lib/language_operator/agent/safety/ast_validator.rb +272 -0
  28. data/lib/language_operator/agent/safety/audit_logger.rb +104 -0
  29. data/lib/language_operator/agent/safety/budget_tracker.rb +175 -0
  30. data/lib/language_operator/agent/safety/content_filter.rb +93 -0
  31. data/lib/language_operator/agent/safety/manager.rb +207 -0
  32. data/lib/language_operator/agent/safety/rate_limiter.rb +150 -0
  33. data/lib/language_operator/agent/safety/safe_executor.rb +127 -0
  34. data/lib/language_operator/agent/scheduler.rb +183 -0
  35. data/lib/language_operator/agent/telemetry.rb +116 -0
  36. data/lib/language_operator/agent/web_server.rb +610 -0
  37. data/lib/language_operator/agent/webhook_authenticator.rb +226 -0
  38. data/lib/language_operator/agent.rb +149 -0
  39. data/lib/language_operator/cli/commands/agent.rb +1205 -0
  40. data/lib/language_operator/cli/commands/cluster.rb +371 -0
  41. data/lib/language_operator/cli/commands/install.rb +404 -0
  42. data/lib/language_operator/cli/commands/model.rb +266 -0
  43. data/lib/language_operator/cli/commands/persona.rb +393 -0
  44. data/lib/language_operator/cli/commands/quickstart.rb +22 -0
  45. data/lib/language_operator/cli/commands/status.rb +143 -0
  46. data/lib/language_operator/cli/commands/system.rb +772 -0
  47. data/lib/language_operator/cli/commands/tool.rb +537 -0
  48. data/lib/language_operator/cli/commands/use.rb +47 -0
  49. data/lib/language_operator/cli/errors/handler.rb +180 -0
  50. data/lib/language_operator/cli/errors/suggestions.rb +176 -0
  51. data/lib/language_operator/cli/formatters/code_formatter.rb +77 -0
  52. data/lib/language_operator/cli/formatters/log_formatter.rb +288 -0
  53. data/lib/language_operator/cli/formatters/progress_formatter.rb +49 -0
  54. data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
  55. data/lib/language_operator/cli/formatters/table_formatter.rb +163 -0
  56. data/lib/language_operator/cli/formatters/value_formatter.rb +113 -0
  57. data/lib/language_operator/cli/helpers/cluster_context.rb +62 -0
  58. data/lib/language_operator/cli/helpers/cluster_validator.rb +101 -0
  59. data/lib/language_operator/cli/helpers/editor_helper.rb +58 -0
  60. data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +167 -0
  61. data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
  62. data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +74 -0
  63. data/lib/language_operator/cli/helpers/schedule_builder.rb +108 -0
  64. data/lib/language_operator/cli/helpers/user_prompts.rb +69 -0
  65. data/lib/language_operator/cli/main.rb +236 -0
  66. data/lib/language_operator/cli/templates/tools/generic.yaml +66 -0
  67. data/lib/language_operator/cli/wizards/agent_wizard.rb +246 -0
  68. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +588 -0
  69. data/lib/language_operator/client/base.rb +214 -0
  70. data/lib/language_operator/client/config.rb +136 -0
  71. data/lib/language_operator/client/cost_calculator.rb +37 -0
  72. data/lib/language_operator/client/mcp_connector.rb +123 -0
  73. data/lib/language_operator/client.rb +19 -0
  74. data/lib/language_operator/config/cluster_config.rb +101 -0
  75. data/lib/language_operator/config/tool_patterns.yaml +57 -0
  76. data/lib/language_operator/config/tool_registry.rb +96 -0
  77. data/lib/language_operator/config.rb +138 -0
  78. data/lib/language_operator/dsl/adapter.rb +124 -0
  79. data/lib/language_operator/dsl/agent_context.rb +90 -0
  80. data/lib/language_operator/dsl/agent_definition.rb +427 -0
  81. data/lib/language_operator/dsl/chat_endpoint_definition.rb +115 -0
  82. data/lib/language_operator/dsl/config.rb +119 -0
  83. data/lib/language_operator/dsl/context.rb +50 -0
  84. data/lib/language_operator/dsl/execution_context.rb +47 -0
  85. data/lib/language_operator/dsl/helpers.rb +109 -0
  86. data/lib/language_operator/dsl/http.rb +184 -0
  87. data/lib/language_operator/dsl/mcp_server_definition.rb +73 -0
  88. data/lib/language_operator/dsl/parameter_definition.rb +124 -0
  89. data/lib/language_operator/dsl/registry.rb +36 -0
  90. data/lib/language_operator/dsl/schema.rb +1102 -0
  91. data/lib/language_operator/dsl/shell.rb +125 -0
  92. data/lib/language_operator/dsl/tool_definition.rb +112 -0
  93. data/lib/language_operator/dsl/webhook_authentication.rb +114 -0
  94. data/lib/language_operator/dsl/webhook_definition.rb +106 -0
  95. data/lib/language_operator/dsl/workflow_definition.rb +259 -0
  96. data/lib/language_operator/dsl.rb +161 -0
  97. data/lib/language_operator/errors.rb +60 -0
  98. data/lib/language_operator/kubernetes/client.rb +279 -0
  99. data/lib/language_operator/kubernetes/resource_builder.rb +194 -0
  100. data/lib/language_operator/loggable.rb +47 -0
  101. data/lib/language_operator/logger.rb +141 -0
  102. data/lib/language_operator/retry.rb +123 -0
  103. data/lib/language_operator/retryable.rb +132 -0
  104. data/lib/language_operator/templates/README.md +23 -0
  105. data/lib/language_operator/templates/examples/agent_synthesis.tmpl +115 -0
  106. data/lib/language_operator/templates/examples/persona_distillation.tmpl +19 -0
  107. data/lib/language_operator/templates/schema/.gitkeep +0 -0
  108. data/lib/language_operator/templates/schema/CHANGELOG.md +93 -0
  109. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
  110. data/lib/language_operator/templates/schema/agent_dsl_schema.json +452 -0
  111. data/lib/language_operator/tool_loader.rb +242 -0
  112. data/lib/language_operator/validators.rb +170 -0
  113. data/lib/language_operator/version.rb +1 -1
  114. data/lib/language_operator.rb +65 -3
  115. data/requirements/tasks/challenge.md +9 -0
  116. data/requirements/tasks/iterate.md +36 -0
  117. data/requirements/tasks/optimize.md +21 -0
  118. data/requirements/tasks/tag.md +5 -0
  119. data/test_agent_dsl.rb +108 -0
  120. metadata +507 -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