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.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +125 -0
  3. data/CHANGELOG.md +53 -0
  4. data/Gemfile +8 -0
  5. data/Gemfile.lock +284 -0
  6. data/LICENSE +229 -21
  7. data/Makefile +77 -0
  8. data/README.md +3 -11
  9. data/Rakefile +34 -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/agent-reference.md +591 -0
  16. data/docs/dsl/best-practices.md +1078 -0
  17. data/docs/dsl/chat-endpoints.md +895 -0
  18. data/docs/dsl/constraints.md +671 -0
  19. data/docs/dsl/mcp-integration.md +1177 -0
  20. data/docs/dsl/webhooks.md +932 -0
  21. data/docs/dsl/workflows.md +744 -0
  22. data/examples/README.md +569 -0
  23. data/examples/agent_example.rb +86 -0
  24. data/examples/chat_endpoint_agent.rb +118 -0
  25. data/examples/github_webhook_agent.rb +171 -0
  26. data/examples/mcp_agent.rb +158 -0
  27. data/examples/oauth_callback_agent.rb +296 -0
  28. data/examples/stripe_webhook_agent.rb +219 -0
  29. data/examples/webhook_agent.rb +80 -0
  30. data/lib/language_operator/agent/base.rb +110 -0
  31. data/lib/language_operator/agent/executor.rb +440 -0
  32. data/lib/language_operator/agent/instrumentation.rb +54 -0
  33. data/lib/language_operator/agent/metrics_tracker.rb +183 -0
  34. data/lib/language_operator/agent/safety/ast_validator.rb +272 -0
  35. data/lib/language_operator/agent/safety/audit_logger.rb +104 -0
  36. data/lib/language_operator/agent/safety/budget_tracker.rb +175 -0
  37. data/lib/language_operator/agent/safety/content_filter.rb +93 -0
  38. data/lib/language_operator/agent/safety/manager.rb +207 -0
  39. data/lib/language_operator/agent/safety/rate_limiter.rb +150 -0
  40. data/lib/language_operator/agent/safety/safe_executor.rb +115 -0
  41. data/lib/language_operator/agent/scheduler.rb +183 -0
  42. data/lib/language_operator/agent/telemetry.rb +116 -0
  43. data/lib/language_operator/agent/web_server.rb +610 -0
  44. data/lib/language_operator/agent/webhook_authenticator.rb +226 -0
  45. data/lib/language_operator/agent.rb +149 -0
  46. data/lib/language_operator/cli/commands/agent.rb +1252 -0
  47. data/lib/language_operator/cli/commands/cluster.rb +335 -0
  48. data/lib/language_operator/cli/commands/install.rb +404 -0
  49. data/lib/language_operator/cli/commands/model.rb +266 -0
  50. data/lib/language_operator/cli/commands/persona.rb +396 -0
  51. data/lib/language_operator/cli/commands/quickstart.rb +22 -0
  52. data/lib/language_operator/cli/commands/status.rb +156 -0
  53. data/lib/language_operator/cli/commands/tool.rb +537 -0
  54. data/lib/language_operator/cli/commands/use.rb +47 -0
  55. data/lib/language_operator/cli/errors/handler.rb +180 -0
  56. data/lib/language_operator/cli/errors/suggestions.rb +176 -0
  57. data/lib/language_operator/cli/formatters/code_formatter.rb +81 -0
  58. data/lib/language_operator/cli/formatters/log_formatter.rb +290 -0
  59. data/lib/language_operator/cli/formatters/progress_formatter.rb +53 -0
  60. data/lib/language_operator/cli/formatters/table_formatter.rb +179 -0
  61. data/lib/language_operator/cli/formatters/value_formatter.rb +113 -0
  62. data/lib/language_operator/cli/helpers/cluster_context.rb +62 -0
  63. data/lib/language_operator/cli/helpers/cluster_validator.rb +101 -0
  64. data/lib/language_operator/cli/helpers/editor_helper.rb +58 -0
  65. data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +167 -0
  66. data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +74 -0
  67. data/lib/language_operator/cli/helpers/schedule_builder.rb +108 -0
  68. data/lib/language_operator/cli/helpers/user_prompts.rb +69 -0
  69. data/lib/language_operator/cli/main.rb +232 -0
  70. data/lib/language_operator/cli/templates/tools/generic.yaml +66 -0
  71. data/lib/language_operator/cli/wizards/agent_wizard.rb +246 -0
  72. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +588 -0
  73. data/lib/language_operator/client/base.rb +214 -0
  74. data/lib/language_operator/client/config.rb +136 -0
  75. data/lib/language_operator/client/cost_calculator.rb +37 -0
  76. data/lib/language_operator/client/mcp_connector.rb +123 -0
  77. data/lib/language_operator/client.rb +19 -0
  78. data/lib/language_operator/config/cluster_config.rb +101 -0
  79. data/lib/language_operator/config/tool_patterns.yaml +57 -0
  80. data/lib/language_operator/config/tool_registry.rb +96 -0
  81. data/lib/language_operator/config.rb +138 -0
  82. data/lib/language_operator/dsl/adapter.rb +124 -0
  83. data/lib/language_operator/dsl/agent_context.rb +90 -0
  84. data/lib/language_operator/dsl/agent_definition.rb +427 -0
  85. data/lib/language_operator/dsl/chat_endpoint_definition.rb +115 -0
  86. data/lib/language_operator/dsl/config.rb +119 -0
  87. data/lib/language_operator/dsl/context.rb +50 -0
  88. data/lib/language_operator/dsl/execution_context.rb +47 -0
  89. data/lib/language_operator/dsl/helpers.rb +109 -0
  90. data/lib/language_operator/dsl/http.rb +184 -0
  91. data/lib/language_operator/dsl/mcp_server_definition.rb +73 -0
  92. data/lib/language_operator/dsl/parameter_definition.rb +124 -0
  93. data/lib/language_operator/dsl/registry.rb +36 -0
  94. data/lib/language_operator/dsl/shell.rb +125 -0
  95. data/lib/language_operator/dsl/tool_definition.rb +112 -0
  96. data/lib/language_operator/dsl/webhook_authentication.rb +114 -0
  97. data/lib/language_operator/dsl/webhook_definition.rb +106 -0
  98. data/lib/language_operator/dsl/workflow_definition.rb +259 -0
  99. data/lib/language_operator/dsl.rb +160 -0
  100. data/lib/language_operator/errors.rb +60 -0
  101. data/lib/language_operator/kubernetes/client.rb +279 -0
  102. data/lib/language_operator/kubernetes/resource_builder.rb +194 -0
  103. data/lib/language_operator/loggable.rb +47 -0
  104. data/lib/language_operator/logger.rb +141 -0
  105. data/lib/language_operator/retry.rb +123 -0
  106. data/lib/language_operator/retryable.rb +132 -0
  107. data/lib/language_operator/tool_loader.rb +242 -0
  108. data/lib/language_operator/validators.rb +170 -0
  109. data/lib/language_operator/version.rb +1 -1
  110. data/lib/language_operator.rb +65 -3
  111. data/requirements/tasks/challenge.md +9 -0
  112. data/requirements/tasks/iterate.md +36 -0
  113. data/requirements/tasks/optimize.md +21 -0
  114. data/requirements/tasks/tag.md +5 -0
  115. data/test_agent_dsl.rb +108 -0
  116. metadata +503 -20
@@ -0,0 +1,427 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'workflow_definition'
4
+ require_relative 'webhook_definition'
5
+ require_relative 'mcp_server_definition'
6
+ require_relative 'chat_endpoint_definition'
7
+ require_relative '../logger'
8
+ require_relative '../loggable'
9
+
10
+ module LanguageOperator
11
+ module Dsl
12
+ # Agent definition for autonomous agents
13
+ #
14
+ # Defines an agent with objectives, workflow, schedule, and constraints.
15
+ # Used within the DSL to create agents that can be executed standalone
16
+ # or deployed to Kubernetes.
17
+ #
18
+ # @example Define a simple scheduled agent
19
+ # agent "news-summarizer" do
20
+ # description "Daily news summarization agent"
21
+ #
22
+ # schedule "0 12 * * *"
23
+ #
24
+ # objectives [
25
+ # "Search for recent news",
26
+ # "Summarize findings"
27
+ # ]
28
+ #
29
+ # workflow do
30
+ # step :search, tool: "web_search", params: {query: "latest news"}
31
+ # step :summarize, depends_on: :search
32
+ # end
33
+ # end
34
+ #
35
+ # @example Define a webhook agent
36
+ # agent "github-webhook" do
37
+ # description "Handle GitHub webhooks"
38
+ # mode :reactive
39
+ #
40
+ # webhook "/github/pr-opened" do
41
+ # method :post
42
+ # on_request do |context|
43
+ # # Process webhook
44
+ # end
45
+ # end
46
+ # end
47
+ class AgentDefinition
48
+ include LanguageOperator::Loggable
49
+
50
+ attr_reader :name, :description, :persona, :schedule, :objectives, :workflow,
51
+ :constraints, :output_config, :execution_mode, :webhooks, :mcp_server, :chat_endpoint
52
+
53
+ def initialize(name)
54
+ @name = name
55
+ @description = nil
56
+ @persona = nil
57
+ @schedule = nil
58
+ @objectives = []
59
+ @workflow = nil
60
+ @constraints = {}
61
+ @output_config = {}
62
+ @execution_mode = :autonomous
63
+ @webhooks = []
64
+ @mcp_server = nil
65
+ @chat_endpoint = nil
66
+
67
+ logger.debug('Agent definition initialized',
68
+ name: name,
69
+ mode: @execution_mode)
70
+ end
71
+
72
+ # Set or get description
73
+ #
74
+ # @param val [String, nil] Description text
75
+ # @return [String] Current description
76
+ def description(val = nil)
77
+ return @description if val.nil?
78
+
79
+ @description = val
80
+ end
81
+
82
+ # Set persona/system prompt
83
+ #
84
+ # @param text [String] Persona text or system prompt
85
+ # @return [String] Current persona
86
+ def persona(text = nil)
87
+ return @persona if text.nil?
88
+
89
+ @persona = text
90
+ end
91
+
92
+ # Set schedule (cron expression)
93
+ #
94
+ # @param cron [String] Cron expression
95
+ # @return [String] Current schedule
96
+ def schedule(cron = nil)
97
+ return @schedule if cron.nil?
98
+
99
+ @schedule = cron
100
+ @execution_mode = :scheduled
101
+ end
102
+
103
+ # Set objectives (list of goals)
104
+ #
105
+ # @param list [Array<String>] List of objectives
106
+ # @return [Array<String>] Current objectives
107
+ def objectives(list = nil)
108
+ return @objectives if list.nil?
109
+
110
+ @objectives = list
111
+ end
112
+
113
+ # Define a single objective
114
+ #
115
+ # @param text [String] Objective text
116
+ # @return [void]
117
+ def objective(text)
118
+ @objectives << text
119
+ end
120
+
121
+ # Define workflow with steps
122
+ #
123
+ # @yield Workflow definition block
124
+ # @return [WorkflowDefinition] Current workflow
125
+ def workflow(&block)
126
+ return @workflow if block.nil?
127
+
128
+ @workflow = WorkflowDefinition.new
129
+ @workflow.instance_eval(&block) if block
130
+ @workflow
131
+ end
132
+
133
+ # Define constraints (max_iterations, timeout, etc.)
134
+ #
135
+ # @yield Constraints block
136
+ # @return [Hash] Current constraints
137
+ def constraints(&block)
138
+ return @constraints if block.nil?
139
+
140
+ constraint_builder = ConstraintBuilder.new
141
+ constraint_builder.instance_eval(&block) if block
142
+ @constraints = constraint_builder.to_h
143
+ end
144
+
145
+ # Define output configuration
146
+ #
147
+ # @yield Output configuration block
148
+ # @return [Hash] Current output config
149
+ def output(&block)
150
+ return @output_config if block.nil?
151
+
152
+ output_builder = OutputBuilder.new
153
+ output_builder.instance_eval(&block) if block
154
+ @output_config = output_builder.to_h
155
+ end
156
+
157
+ # Set execution mode
158
+ #
159
+ # @param mode [Symbol] Execution mode (:autonomous, :scheduled, :reactive)
160
+ # @return [Symbol] Current execution mode
161
+ def mode(mode = nil)
162
+ return @execution_mode if mode.nil?
163
+
164
+ @execution_mode = mode
165
+ end
166
+
167
+ # Define a webhook endpoint
168
+ #
169
+ # @param path [String] URL path for the webhook
170
+ # @yield Webhook configuration block
171
+ # @return [WebhookDefinition] The webhook definition
172
+ def webhook(path, &block)
173
+ webhook_def = WebhookDefinition.new(path)
174
+ webhook_def.instance_eval(&block) if block
175
+ @webhooks << webhook_def
176
+ @execution_mode = :reactive if @execution_mode == :autonomous
177
+ webhook_def
178
+ end
179
+
180
+ # Define MCP server capabilities
181
+ #
182
+ # Allows this agent to expose tools via MCP protocol.
183
+ # Other agents or MCP clients can discover and call these tools.
184
+ #
185
+ # @yield MCP server configuration block
186
+ # @return [McpServerDefinition] The MCP server definition
187
+ def as_mcp_server(&block)
188
+ @mcp_server = McpServerDefinition.new(@name)
189
+ @mcp_server.instance_eval(&block) if block
190
+ @execution_mode = :reactive if @execution_mode == :autonomous
191
+ @mcp_server
192
+ end
193
+
194
+ # Define chat endpoint capabilities
195
+ #
196
+ # Allows this agent to respond to OpenAI-compatible chat completion requests.
197
+ # Other systems can treat this agent as a language model.
198
+ #
199
+ # @yield Chat endpoint configuration block
200
+ # @return [ChatEndpointDefinition] The chat endpoint definition
201
+ def as_chat_endpoint(&block)
202
+ @chat_endpoint ||= ChatEndpointDefinition.new(@name)
203
+ @chat_endpoint.instance_eval(&block) if block
204
+ @execution_mode = :reactive if @execution_mode == :autonomous
205
+ @chat_endpoint
206
+ end
207
+
208
+ # Execute the agent
209
+ #
210
+ # @return [void]
211
+ def run!
212
+ logger.info('Starting agent',
213
+ name: @name,
214
+ mode: @execution_mode,
215
+ objectives_count: @objectives.size,
216
+ has_workflow: !@workflow.nil?)
217
+
218
+ case @execution_mode
219
+ when :scheduled
220
+ run_scheduled
221
+ when :autonomous
222
+ run_autonomous
223
+ when :reactive
224
+ run_reactive
225
+ else
226
+ logger.error('Unknown execution mode', mode: @execution_mode)
227
+ raise "Unknown execution mode: #{@execution_mode}"
228
+ end
229
+ end
230
+
231
+ private
232
+
233
+ def logger_component
234
+ "Agent:#{@name}"
235
+ end
236
+
237
+ def run_scheduled
238
+ require 'rufus-scheduler'
239
+
240
+ scheduler = Rufus::Scheduler.new
241
+
242
+ logger.info('Scheduling agent',
243
+ name: @name,
244
+ cron: @schedule)
245
+
246
+ scheduler.cron(@schedule) do
247
+ logger.timed('Scheduled execution') do
248
+ execute_objectives
249
+ end
250
+ end
251
+
252
+ scheduler.join
253
+ end
254
+
255
+ def run_autonomous
256
+ logger.info('Running agent in autonomous mode', name: @name)
257
+ execute_objectives
258
+ end
259
+
260
+ def run_reactive
261
+ logger.info('Running agent in reactive mode',
262
+ name: @name,
263
+ webhooks: @webhooks.size,
264
+ mcp_tools: @mcp_server&.tools&.size || 0,
265
+ chat_endpoint: !@chat_endpoint.nil?)
266
+
267
+ # Create an Agent::Base instance with this definition
268
+ require_relative '../agent/base'
269
+ require_relative '../agent/web_server'
270
+
271
+ # Build agent config
272
+ agent_config = build_agent_config
273
+
274
+ # Create agent instance
275
+ agent = LanguageOperator::Agent::Base.new(agent_config)
276
+ agent.instance_variable_set(:@mode, 'reactive')
277
+
278
+ # Create web server
279
+ web_server = LanguageOperator::Agent::WebServer.new(agent)
280
+
281
+ # Register webhooks
282
+ @webhooks.each do |webhook_def|
283
+ webhook_def.register(web_server)
284
+ end
285
+
286
+ # Register MCP tools
287
+ web_server.register_mcp_tools(@mcp_server) if @mcp_server&.tools?
288
+
289
+ # Register chat endpoint
290
+ web_server.register_chat_endpoint(@chat_endpoint, agent) if @chat_endpoint
291
+
292
+ # Start the server
293
+ web_server.start
294
+ end
295
+
296
+ # Build agent configuration hash
297
+ #
298
+ # @return [Hash] Agent configuration
299
+ def build_agent_config
300
+ {
301
+ 'agent' => {
302
+ 'name' => @name,
303
+ 'instructions' => @description || "Process incoming requests for #{@name}",
304
+ 'persona' => @persona
305
+ },
306
+ 'llm' => {
307
+ 'provider' => ENV['LLM_PROVIDER'] || 'anthropic',
308
+ 'model' => ENV['LLM_MODEL'] || 'claude-3-5-sonnet-20241022',
309
+ 'api_key' => ENV.fetch('ANTHROPIC_API_KEY', nil)
310
+ },
311
+ 'mcp' => {
312
+ 'servers' => {}
313
+ }
314
+ }
315
+ end
316
+
317
+ def execute_objectives
318
+ logger.info('Executing objectives',
319
+ total: @objectives.size,
320
+ has_workflow: !@workflow.nil?)
321
+
322
+ @objectives.each_with_index do |objective, index|
323
+ logger.info('Executing objective',
324
+ index: index + 1,
325
+ total: @objectives.size,
326
+ objective: objective[0..100])
327
+
328
+ # If workflow defined, execute it; otherwise just log
329
+ if @workflow
330
+ logger.timed('Objective workflow execution') do
331
+ @workflow.execute(objective)
332
+ end
333
+ else
334
+ logger.warn('No workflow defined, skipping execution')
335
+ end
336
+ end
337
+
338
+ logger.info('All objectives completed', total: @objectives.size)
339
+ end
340
+ end
341
+
342
+ # Helper class for building constraints
343
+ class ConstraintBuilder
344
+ def initialize
345
+ @constraints = {}
346
+ end
347
+
348
+ def max_iterations(value)
349
+ @constraints[:max_iterations] = value
350
+ end
351
+
352
+ def timeout(value)
353
+ @constraints[:timeout] = value
354
+ end
355
+
356
+ def memory(value)
357
+ @constraints[:memory] = value
358
+ end
359
+
360
+ def rate_limit(value)
361
+ @constraints[:rate_limit] = value
362
+ end
363
+
364
+ # Budget constraints
365
+ def daily_budget(value)
366
+ @constraints[:daily_budget] = value
367
+ end
368
+
369
+ def hourly_budget(value)
370
+ @constraints[:hourly_budget] = value
371
+ end
372
+
373
+ def token_budget(value)
374
+ @constraints[:token_budget] = value
375
+ end
376
+
377
+ # Rate limiting
378
+ def requests_per_minute(value)
379
+ @constraints[:requests_per_minute] = value
380
+ end
381
+
382
+ def requests_per_hour(value)
383
+ @constraints[:requests_per_hour] = value
384
+ end
385
+
386
+ def requests_per_day(value)
387
+ @constraints[:requests_per_day] = value
388
+ end
389
+
390
+ # Content filtering
391
+ def blocked_patterns(patterns)
392
+ @constraints[:blocked_patterns] = patterns
393
+ end
394
+
395
+ def blocked_topics(topics)
396
+ @constraints[:blocked_topics] = topics
397
+ end
398
+
399
+ def to_h
400
+ @constraints
401
+ end
402
+ end
403
+
404
+ # Helper class for building output configuration
405
+ class OutputBuilder
406
+ def initialize
407
+ @config = {}
408
+ end
409
+
410
+ def workspace(path)
411
+ @config[:workspace] = path
412
+ end
413
+
414
+ def slack(channel:)
415
+ @config[:slack] = { channel: channel }
416
+ end
417
+
418
+ def email(to:)
419
+ @config[:email] = { to: to }
420
+ end
421
+
422
+ def to_h
423
+ @config
424
+ end
425
+ end
426
+ end
427
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LanguageOperator
4
+ module Dsl
5
+ # Chat endpoint definition for agents
6
+ #
7
+ # Allows agents to expose an OpenAI-compatible chat completion endpoint.
8
+ # Other systems can treat the agent as a language model.
9
+ #
10
+ # @example Define chat endpoint in an agent
11
+ # agent "github-expert" do
12
+ # as_chat_endpoint do
13
+ # system_prompt "You are a GitHub API expert"
14
+ # temperature 0.7
15
+ # max_tokens 2000
16
+ # end
17
+ # end
18
+ class ChatEndpointDefinition
19
+ attr_reader :system_prompt, :temperature, :max_tokens, :model_name,
20
+ :top_p, :frequency_penalty, :presence_penalty, :stop_sequences
21
+
22
+ def initialize(agent_name)
23
+ @agent_name = agent_name
24
+ @system_prompt = nil
25
+ @temperature = 0.7
26
+ @max_tokens = 2000
27
+ @model_name = agent_name
28
+ @top_p = 1.0
29
+ @frequency_penalty = 0.0
30
+ @presence_penalty = 0.0
31
+ @stop_sequences = nil
32
+ end
33
+
34
+ # Set system prompt for chat mode
35
+ #
36
+ # @param prompt [String] System prompt
37
+ # @return [String] Current system prompt
38
+ def system_prompt(prompt = nil)
39
+ return @system_prompt if prompt.nil?
40
+
41
+ @system_prompt = prompt
42
+ end
43
+
44
+ # Set temperature parameter
45
+ #
46
+ # @param value [Float] Temperature (0.0-2.0)
47
+ # @return [Float] Current temperature
48
+ def temperature(value = nil)
49
+ return @temperature if value.nil?
50
+
51
+ @temperature = value
52
+ end
53
+
54
+ # Set maximum tokens
55
+ #
56
+ # @param value [Integer] Max tokens
57
+ # @return [Integer] Current max tokens
58
+ def max_tokens(value = nil)
59
+ return @max_tokens if value.nil?
60
+
61
+ @max_tokens = value
62
+ end
63
+
64
+ # Set model name exposed in API
65
+ #
66
+ # @param name [String] Model name
67
+ # @return [String] Current model name
68
+ def model(name = nil)
69
+ return @model_name if name.nil?
70
+
71
+ @model_name = name
72
+ end
73
+
74
+ # Set top_p parameter
75
+ #
76
+ # @param value [Float] Top-p (0.0-1.0)
77
+ # @return [Float] Current top_p
78
+ def top_p(value = nil)
79
+ return @top_p if value.nil?
80
+
81
+ @top_p = value
82
+ end
83
+
84
+ # Set frequency penalty
85
+ #
86
+ # @param value [Float] Frequency penalty (-2.0 to 2.0)
87
+ # @return [Float] Current frequency penalty
88
+ def frequency_penalty(value = nil)
89
+ return @frequency_penalty if value.nil?
90
+
91
+ @frequency_penalty = value
92
+ end
93
+
94
+ # Set presence penalty
95
+ #
96
+ # @param value [Float] Presence penalty (-2.0 to 2.0)
97
+ # @return [Float] Current presence penalty
98
+ def presence_penalty(value = nil)
99
+ return @presence_penalty if value.nil?
100
+
101
+ @presence_penalty = value
102
+ end
103
+
104
+ # Set stop sequences
105
+ #
106
+ # @param sequences [Array<String>] Stop sequences
107
+ # @return [Array<String>] Current stop sequences
108
+ def stop(sequences = nil)
109
+ return @stop_sequences if sequences.nil?
110
+
111
+ @stop_sequences = sequences
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LanguageOperator
4
+ module Dsl
5
+ # Configuration helper for managing environment variables
6
+ #
7
+ # Provides utilities for reading and managing environment variables with fallback support,
8
+ # type conversion, and validation. All methods are class methods.
9
+ #
10
+ # @example Basic usage
11
+ # Config.get('SMTP_HOST', 'MAIL_HOST', default: 'localhost')
12
+ # Config.require('DATABASE_URL')
13
+ # Config.get_bool('USE_TLS', default: true)
14
+ class Config
15
+ # Get environment variable with multiple fallback keys
16
+ #
17
+ # @param keys [Array<String>] Environment variable names to try
18
+ # @param default [Object, nil] Default value if none found
19
+ # @return [String, nil] The first non-nil value or default
20
+ def self.get(*keys, default: nil)
21
+ keys.each do |key|
22
+ value = ENV.fetch(key.to_s, nil)
23
+ return value if value
24
+ end
25
+ default
26
+ end
27
+
28
+ # Get required environment variable with fallback keys
29
+ #
30
+ # @param keys [Array<String>] Environment variable names to try
31
+ # @return [String] The first non-nil value
32
+ # @raise [ArgumentError] If none of the keys are set
33
+ def self.require(*keys)
34
+ value = get(*keys)
35
+ raise ArgumentError, "Missing required configuration: #{keys.join(' or ')}" unless value
36
+
37
+ value
38
+ end
39
+
40
+ # Get environment variable as integer
41
+ #
42
+ # @param keys [Array<String>] Environment variable names to try
43
+ # @param default [Integer, nil] Default value if none found
44
+ # @return [Integer, nil] The value converted to integer, or default
45
+ def self.get_int(*keys, default: nil)
46
+ value = get(*keys)
47
+ return default if value.nil?
48
+
49
+ value.to_i
50
+ end
51
+
52
+ # Get environment variable as boolean
53
+ #
54
+ # Treats 'true', '1', 'yes', 'on' as true (case insensitive).
55
+ #
56
+ # @param keys [Array<String>] Environment variable names to try
57
+ # @param default [Boolean] Default value if none found
58
+ # @return [Boolean] The value as boolean
59
+ def self.get_bool(*keys, default: false)
60
+ value = get(*keys)
61
+ return default if value.nil?
62
+
63
+ value.to_s.downcase.match?(/^(true|1|yes|on)$/)
64
+ end
65
+
66
+ # Get environment variable as array (split by separator)
67
+ #
68
+ # @param keys [Array<String>] Environment variable names to try
69
+ # @param default [Array] Default value if none found
70
+ # @param separator [String] Character to split on (default: ',')
71
+ # @return [Array<String>] The value split into array
72
+ def self.get_array(*keys, default: [], separator: ',')
73
+ value = get(*keys)
74
+ return default if value.nil? || value.empty?
75
+
76
+ value.split(separator).map(&:strip).reject(&:empty?)
77
+ end
78
+
79
+ # Check if all required keys are present
80
+ #
81
+ # @param keys [Array<String>] Environment variable names to check
82
+ # @return [Array<String>] Array of missing keys (empty if all present)
83
+ def self.check_required(*keys)
84
+ keys.reject { |key| ENV.fetch(key.to_s, nil) }
85
+ end
86
+
87
+ # Check if environment variable is set (even if empty string)
88
+ #
89
+ # @param keys [Array<String>] Environment variable names to check
90
+ # @return [Boolean] True if any key is set
91
+ def self.set?(*keys)
92
+ keys.any? { |key| ENV.key?(key.to_s) }
93
+ end
94
+
95
+ # Get all environment variables matching a prefix
96
+ #
97
+ # @param prefix [String] Prefix to match
98
+ # @return [Hash<String, String>] Hash with prefix removed from keys
99
+ def self.with_prefix(prefix)
100
+ ENV.select { |key, _| key.start_with?(prefix) }
101
+ .transform_keys { |key| key.sub(prefix, '') }
102
+ end
103
+
104
+ # Build a configuration hash from environment variables
105
+ #
106
+ # @param mappings [Hash{Symbol => Array<String>, String}] Config key to env var(s) mapping
107
+ # @return [Hash{Symbol => String}] Configuration hash with values found
108
+ def self.build(mappings)
109
+ config = {}
110
+ mappings.each do |config_key, env_keys|
111
+ env_keys = [env_keys] unless env_keys.is_a?(Array)
112
+ value = get(*env_keys)
113
+ config[config_key] = value if value
114
+ end
115
+ config
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LanguageOperator
4
+ module Dsl
5
+ # DSL context for defining tools
6
+ #
7
+ # Provides the evaluation context for tool definition files. Tools are
8
+ # defined using the `tool` method within this context.
9
+ #
10
+ # @example Tool definition file
11
+ # tool "search" do
12
+ # description "Search the web"
13
+ #
14
+ # parameter :query do
15
+ # description "Search query"
16
+ # type :string
17
+ # required true
18
+ # end
19
+ #
20
+ # execute do |params|
21
+ # http_get("https://api.search.com?q=#{params[:query]}")
22
+ # end
23
+ # end
24
+ class Context
25
+ include LanguageOperator::Dsl::Helpers
26
+
27
+ # Provide access to HTTP and Shell helper classes as constants
28
+ HTTP = LanguageOperator::Dsl::HTTP
29
+ Shell = LanguageOperator::Dsl::Shell
30
+
31
+ # Initialize context with registry
32
+ #
33
+ # @param registry [LanguageOperator::Dsl::Registry] Tool registry
34
+ def initialize(registry)
35
+ @registry = registry
36
+ end
37
+
38
+ # Define a tool
39
+ #
40
+ # @param name [String] Tool name
41
+ # @yield Tool definition block
42
+ # @return [void]
43
+ def tool(name, &)
44
+ tool_def = ToolDefinition.new(name)
45
+ tool_def.instance_eval(&) if block_given?
46
+ @registry.register(tool_def)
47
+ end
48
+ end
49
+ end
50
+ end