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,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'base64'
5
+
6
+ module LanguageOperator
7
+ module Agent
8
+ # Executes webhook authentication checks
9
+ class WebhookAuthenticator
10
+ # Authenticate a webhook request
11
+ #
12
+ # @param authentication [LanguageOperator::Dsl::WebhookAuthentication] Authentication definition
13
+ # @param context [Hash] Request context with :headers, :body, etc.
14
+ # @return [Boolean] true if authenticated, false otherwise
15
+ def self.authenticate(authentication, context)
16
+ return true unless authentication # No authentication required
17
+
18
+ case authentication.type
19
+ when :signature
20
+ verify_signature(authentication.config, context)
21
+ when :api_key
22
+ verify_api_key(authentication.config, context)
23
+ when :bearer_token
24
+ verify_bearer_token(authentication.config, context)
25
+ when :basic_auth
26
+ verify_basic_auth(authentication.config, context)
27
+ when :custom
28
+ verify_custom(authentication.config, context)
29
+ when :any_of
30
+ verify_any_of(authentication.config, context)
31
+ when :all_of
32
+ verify_all_of(authentication.config, context)
33
+ else
34
+ false
35
+ end
36
+ end
37
+
38
+ # Verify HMAC signature
39
+ def self.verify_signature(config, context)
40
+ header = config[:header]
41
+ secret = config[:secret]
42
+ algorithm = config[:algorithm] || :sha256
43
+ prefix = config[:prefix]
44
+
45
+ signature = get_header(context, header)
46
+ return false unless signature
47
+
48
+ # Remove prefix if specified (e.g., "sha256=")
49
+ signature = signature.sub(/^#{Regexp.escape(prefix)}/, '') if prefix
50
+
51
+ # Compute expected signature
52
+ body = context[:body] || ''
53
+ expected = OpenSSL::HMAC.hexdigest(algorithm.to_s, secret, body)
54
+
55
+ # Constant-time comparison to prevent timing attacks
56
+ secure_compare(signature, expected)
57
+ end
58
+
59
+ # Verify API key from header
60
+ def self.verify_api_key(config, context)
61
+ header = config[:header]
62
+ expected_key = config[:key]
63
+
64
+ actual_key = get_header(context, header)
65
+ return false unless actual_key
66
+
67
+ secure_compare(actual_key, expected_key)
68
+ end
69
+
70
+ # Verify bearer token
71
+ def self.verify_bearer_token(config, context)
72
+ expected_token = config[:token]
73
+
74
+ auth_header = get_header(context, 'Authorization')
75
+ return false unless auth_header
76
+
77
+ # Extract token from "Bearer <token>"
78
+ match = auth_header.match(/^Bearer\s+(.+)$/i)
79
+ return false unless match
80
+
81
+ actual_token = match[1]
82
+ secure_compare(actual_token, expected_token)
83
+ end
84
+
85
+ # Verify basic auth credentials
86
+ def self.verify_basic_auth(config, context)
87
+ expected_username = config[:username]
88
+ expected_password = config[:password]
89
+
90
+ auth_header = get_header(context, 'Authorization')
91
+ return false unless auth_header
92
+
93
+ # Extract credentials from "Basic <base64>"
94
+ match = auth_header.match(/^Basic\s+(.+)$/i)
95
+ return false unless match
96
+
97
+ begin
98
+ credentials = Base64.decode64(match[1])
99
+ username, password = credentials.split(':', 2)
100
+
101
+ secure_compare(username, expected_username) &&
102
+ secure_compare(password, expected_password)
103
+ rescue StandardError
104
+ false
105
+ end
106
+ end
107
+
108
+ # Verify using custom callback
109
+ def self.verify_custom(config, context)
110
+ callback = config[:callback]
111
+ return false unless callback
112
+
113
+ begin
114
+ result = callback.call(context)
115
+ result == true # Explicit true check
116
+ rescue StandardError
117
+ # Silently fail on callback errors for security
118
+ false
119
+ end
120
+ end
121
+
122
+ # Verify any of multiple authentication methods
123
+ def self.verify_any_of(config, context)
124
+ methods = config[:methods] || []
125
+ return false if methods.empty?
126
+
127
+ methods.any? { |auth| authenticate(auth, context) }
128
+ end
129
+
130
+ # Verify all of multiple authentication methods
131
+ def self.verify_all_of(config, context)
132
+ methods = config[:methods] || []
133
+ return false if methods.empty?
134
+
135
+ methods.all? { |auth| authenticate(auth, context) }
136
+ end
137
+
138
+ # Execute validations
139
+ #
140
+ # @param validations [Array<Hash>] Validation definitions
141
+ # @param context [Hash] Request context
142
+ # @return [Array<String>] Validation errors (empty if valid)
143
+ def self.validate(validations, context)
144
+ errors = []
145
+
146
+ validations.each do |validation|
147
+ case validation[:type]
148
+ when :headers
149
+ errors.concat(validate_headers(validation[:config], context))
150
+ when :content_type
151
+ errors.concat(validate_content_type(validation[:config], context))
152
+ when :custom
153
+ result = validation[:config].call(context)
154
+ errors << result unless result == true
155
+ end
156
+ end
157
+
158
+ errors.compact
159
+ end
160
+
161
+ # Validate required headers
162
+ def self.validate_headers(required_headers, context)
163
+ errors = []
164
+ context[:headers] || {}
165
+
166
+ required_headers.each do |header_name, expected_value|
167
+ actual_value = get_header(context, header_name)
168
+
169
+ if actual_value.nil?
170
+ errors << "Missing required header: #{header_name}"
171
+ elsif expected_value && actual_value != expected_value
172
+ errors << "Invalid value for header #{header_name}"
173
+ end
174
+ end
175
+
176
+ errors
177
+ end
178
+
179
+ # Validate content type
180
+ def self.validate_content_type(allowed_types, context)
181
+ content_type = get_header(context, 'Content-Type')
182
+ return ['Missing Content-Type header'] unless content_type
183
+
184
+ # Extract media type (ignore charset, boundary, etc.)
185
+ media_type = content_type.split(';').first.strip.downcase
186
+
187
+ return [] if allowed_types.any? { |type| media_type == type.downcase }
188
+
189
+ ["Invalid Content-Type: expected #{allowed_types.join(' or ')}, got #{media_type}"]
190
+ end
191
+
192
+ # Get header value (case-insensitive)
193
+ def self.get_header(context, name)
194
+ headers = context[:headers] || {}
195
+ name_lower = name.downcase
196
+
197
+ # Try exact match first
198
+ return headers[name] if headers.key?(name)
199
+
200
+ # Try case-insensitive match
201
+ headers.each do |key, value|
202
+ return value if key.downcase == name_lower
203
+ end
204
+
205
+ nil
206
+ end
207
+
208
+ # Constant-time string comparison to prevent timing attacks
209
+ def self.secure_compare(a, b)
210
+ return false if a.nil? || b.nil?
211
+ return false unless a.bytesize == b.bytesize
212
+
213
+ l = a.unpack('C*')
214
+ r = b.unpack('C*')
215
+
216
+ res = 0
217
+ l.zip(r) { |x, y| res |= x ^ y }
218
+ res.zero?
219
+ end
220
+
221
+ private_class_method :verify_signature, :verify_api_key, :verify_bearer_token,
222
+ :verify_basic_auth, :verify_custom, :verify_any_of, :verify_all_of,
223
+ :validate_headers, :validate_content_type, :get_header, :secure_compare
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'agent/base'
4
+ require_relative 'agent/executor'
5
+ require_relative 'agent/scheduler'
6
+ require_relative 'agent/web_server'
7
+ require_relative 'dsl'
8
+ require_relative 'logger'
9
+
10
+ module LanguageOperator
11
+ # Agent Framework
12
+ #
13
+ # Provides autonomous execution capabilities for language agents.
14
+ # Extends LanguageOperator::Client with agent-specific features like scheduling,
15
+ # goal evaluation, and workspace integration.
16
+ #
17
+ # @example Running an agent
18
+ # config = LanguageOperator::Client::Config.from_env
19
+ # agent = LanguageOperator::Agent::Base.new(config)
20
+ # agent.run
21
+ #
22
+ # @example Creating a custom agent
23
+ # agent = LanguageOperator::Agent::Base.new(config)
24
+ # agent.execute_goal("Summarize daily news")
25
+ module Agent
26
+ # Run the default agent based on environment configuration
27
+ #
28
+ # @param config_path [String] Path to configuration file
29
+ # @return [void]
30
+ def self.run(config_path: nil)
31
+ # Disable stdout buffering for real-time logging in containers
32
+ $stdout.sync = true
33
+ $stderr.sync = true
34
+
35
+ config_path ||= ENV.fetch('CONFIG_PATH', 'config.yaml')
36
+ config = LanguageOperator::Client::Config.load_with_fallback(config_path)
37
+
38
+ # Create agent instance
39
+ agent = LanguageOperator::Agent::Base.new(config)
40
+
41
+ # Load and run with synthesized code if available
42
+ load_and_run(agent)
43
+ end
44
+
45
+ # Load synthesized agent code and run with definition if available
46
+ #
47
+ # @param agent [LanguageOperator::Agent::Base] The agent instance
48
+ # @return [void]
49
+ def self.load_and_run(agent)
50
+ agent_code_path = ENV.fetch('AGENT_CODE_PATH', nil)
51
+ agent_name = ENV.fetch('AGENT_NAME', nil)
52
+
53
+ if agent_code_path && File.exist?(agent_code_path)
54
+ load_synthesized_agent(agent, agent_code_path, agent_name)
55
+ else
56
+ LanguageOperator::Logger.info('No synthesized code found, running in standard mode',
57
+ component: 'Agent',
58
+ agent_code_path: agent_code_path)
59
+ agent.run
60
+ end
61
+ end
62
+
63
+ # Load synthesized agent code and execute
64
+ #
65
+ # @param agent [LanguageOperator::Agent::Base] The agent instance
66
+ # @param code_path [String] Path to synthesized code
67
+ # @param agent_name [String] Name of agent definition
68
+ # @return [void]
69
+ def self.load_synthesized_agent(agent, code_path, agent_name)
70
+ LanguageOperator::Logger.info('DSL code loading',
71
+ component: 'Agent',
72
+ path: code_path,
73
+ agent_name: agent_name)
74
+
75
+ # Load synthesized DSL code
76
+ LanguageOperator::Dsl.load_agent_file(code_path)
77
+
78
+ # Get agent definition from registry
79
+ agent_def = LanguageOperator::Dsl.agent_registry.get(agent_name) if agent_name
80
+
81
+ if agent_def
82
+ LanguageOperator::Logger.info('Agent definition loaded',
83
+ component: 'Agent',
84
+ agent_name: agent_name,
85
+ has_workflow: !agent_def.workflow.nil?)
86
+ run_with_definition(agent, agent_def)
87
+ else
88
+ log_definition_not_found(agent_name)
89
+ agent.run
90
+ end
91
+ rescue StandardError => e
92
+ log_load_error(e)
93
+ agent.run
94
+ end
95
+
96
+ # Log when agent definition is not found
97
+ #
98
+ # @param agent_name [String] Name of agent
99
+ # @return [void]
100
+ def self.log_definition_not_found(agent_name)
101
+ LanguageOperator::Logger.warn('Agent definition not found in registry',
102
+ component: 'Agent',
103
+ agent_name: agent_name,
104
+ available: LanguageOperator::Dsl.agent_registry.all.map(&:name))
105
+ LanguageOperator::Logger.info('Falling back to autonomous mode', component: 'Agent')
106
+ end
107
+
108
+ # Log agent code loading error
109
+ #
110
+ # @param error [StandardError] The error
111
+ # @return [void]
112
+ def self.log_load_error(error)
113
+ LanguageOperator::Logger.error('Failed to load agent code',
114
+ component: 'Agent',
115
+ error: error.message,
116
+ backtrace: error.backtrace[0..3])
117
+ LanguageOperator::Logger.info('Falling back to autonomous mode', component: 'Agent')
118
+ end
119
+
120
+ # Run agent with a loaded definition
121
+ #
122
+ # @param agent [LanguageOperator::Agent::Base] The agent instance
123
+ # @param agent_def [LanguageOperator::Dsl::AgentDefinition] The agent definition
124
+ # @return [void]
125
+ def self.run_with_definition(agent, agent_def)
126
+ agent.connect!
127
+
128
+ case agent.mode
129
+ when 'autonomous', 'interactive'
130
+ # Execute workflow in autonomous mode
131
+ executor = LanguageOperator::Agent::Executor.new(agent)
132
+ executor.execute_workflow(agent_def)
133
+ when 'scheduled', 'event-driven'
134
+ # Schedule workflow execution
135
+ scheduler = LanguageOperator::Agent::Scheduler.new(agent)
136
+ scheduler.start_with_workflow(agent_def)
137
+ when 'reactive', 'http', 'webhook'
138
+ # Start web server with webhooks, MCP tools, and chat endpoint
139
+ web_server = LanguageOperator::Agent::WebServer.new(agent)
140
+ agent_def.webhooks.each { |webhook_def| webhook_def.register(web_server) }
141
+ web_server.register_mcp_tools(agent_def.mcp_server) if agent_def.mcp_server&.tools?
142
+ web_server.register_chat_endpoint(agent_def.chat_endpoint, agent) if agent_def.chat_endpoint
143
+ web_server.start
144
+ else
145
+ raise "Unknown agent mode: #{agent.mode}"
146
+ end
147
+ end
148
+ end
149
+ end