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,296 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # OAuth Callback Agent Example
5
+ #
6
+ # This example shows how to create an agent that handles OAuth 2.0 callbacks
7
+ # from various providers (GitHub, Google, etc.).
8
+ #
9
+ # Setup:
10
+ # 1. Set OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET environment variables
11
+ # 2. Configure OAuth provider redirect URI to https://<agent-url>/oauth/callback
12
+ # 3. Set OAUTH_STATE_SECRET for state parameter verification
13
+ #
14
+ # OAuth 2.0 flow:
15
+ # 1. User clicks "Login with Provider" -> redirected to provider
16
+ # 2. User authorizes -> provider redirects to /oauth/callback with code
17
+ # 3. Agent exchanges code for access token
18
+ # 4. Agent fetches user info and creates session
19
+
20
+ require 'bundler/setup'
21
+ require 'language_operator'
22
+ require 'net/http'
23
+ require 'uri'
24
+
25
+ LanguageOperator::Dsl.define_agents do
26
+ agent 'oauth-handler' do
27
+ description 'Handles OAuth callbacks and manages user authentication'
28
+
29
+ # Set to reactive mode to receive callbacks
30
+ mode :reactive
31
+
32
+ # GitHub OAuth callback
33
+ webhook '/oauth/github/callback' do
34
+ method :get
35
+
36
+ # Validate state parameter to prevent CSRF attacks
37
+ validate do |context|
38
+ state = context[:params]['state']
39
+ return 'Missing state parameter' unless state
40
+
41
+ # In production, verify state was generated by your app
42
+ # and hasn't been used before (store in Redis/database)
43
+ expected_state = ENV.fetch('OAUTH_STATE_SECRET', nil)
44
+ return 'Invalid state parameter' unless state == expected_state
45
+
46
+ true
47
+ end
48
+
49
+ on_request do |context|
50
+ code = context[:params]['code']
51
+ error = context[:params]['error']
52
+
53
+ # Handle OAuth errors
54
+ if error
55
+ return {
56
+ status: 'error',
57
+ error: error,
58
+ error_description: context[:params]['error_description'],
59
+ message: 'OAuth authorization failed'
60
+ }
61
+ end
62
+
63
+ # Exchange authorization code for access token
64
+ token_response = exchange_code_for_token(
65
+ code: code,
66
+ client_id: ENV.fetch('GITHUB_CLIENT_ID', nil),
67
+ client_secret: ENV.fetch('GITHUB_CLIENT_SECRET', nil),
68
+ token_url: 'https://github.com/login/oauth/access_token'
69
+ )
70
+
71
+ if token_response[:error]
72
+ return {
73
+ status: 'error',
74
+ error: token_response[:error],
75
+ message: 'Failed to exchange code for token'
76
+ }
77
+ end
78
+
79
+ access_token = token_response[:access_token]
80
+
81
+ # Fetch user info from GitHub
82
+ user_info = fetch_github_user(access_token)
83
+
84
+ {
85
+ status: 'success',
86
+ message: 'OAuth authentication successful',
87
+ user: {
88
+ id: user_info['id'],
89
+ username: user_info['login'],
90
+ email: user_info['email'],
91
+ name: user_info['name']
92
+ },
93
+ # In production, create session/JWT here
94
+ session_created: true
95
+ }
96
+ end
97
+ end
98
+
99
+ # Google OAuth callback
100
+ webhook '/oauth/google/callback' do
101
+ method :get
102
+
103
+ validate do |context|
104
+ state = context[:params]['state']
105
+ return 'Missing state parameter' unless state
106
+
107
+ expected_state = ENV.fetch('OAUTH_STATE_SECRET', nil)
108
+ return 'Invalid state parameter' unless state == expected_state
109
+
110
+ true
111
+ end
112
+
113
+ on_request do |context|
114
+ code = context[:params]['code']
115
+ error = context[:params]['error']
116
+
117
+ if error
118
+ return {
119
+ status: 'error',
120
+ error: error,
121
+ message: 'OAuth authorization failed'
122
+ }
123
+ end
124
+
125
+ # Exchange code for token
126
+ token_response = exchange_code_for_token(
127
+ code: code,
128
+ client_id: ENV.fetch('GOOGLE_CLIENT_ID', nil),
129
+ client_secret: ENV.fetch('GOOGLE_CLIENT_SECRET', nil),
130
+ token_url: 'https://oauth2.googleapis.com/token',
131
+ redirect_uri: ENV.fetch('GOOGLE_REDIRECT_URI', nil)
132
+ )
133
+
134
+ if token_response[:error]
135
+ return {
136
+ status: 'error',
137
+ error: token_response[:error],
138
+ message: 'Failed to exchange code for token'
139
+ }
140
+ end
141
+
142
+ access_token = token_response[:access_token]
143
+ token_response[:id_token]
144
+
145
+ # Fetch user info from Google
146
+ user_info = fetch_google_user(access_token)
147
+
148
+ {
149
+ status: 'success',
150
+ message: 'OAuth authentication successful',
151
+ user: {
152
+ id: user_info['id'],
153
+ email: user_info['email'],
154
+ name: user_info['name'],
155
+ picture: user_info['picture']
156
+ },
157
+ session_created: true
158
+ }
159
+ end
160
+ end
161
+
162
+ # Generic OAuth callback with bearer token auth
163
+ # (for testing or internal use)
164
+ webhook '/oauth/test/callback' do
165
+ method :post
166
+
167
+ # Require bearer token for security
168
+ authenticate do
169
+ verify_bearer_token(token: ENV.fetch('OAUTH_TEST_TOKEN', nil))
170
+ end
171
+
172
+ require_content_type 'application/json'
173
+
174
+ on_request do |context|
175
+ data = JSON.parse(context[:body])
176
+
177
+ {
178
+ status: 'success',
179
+ message: 'Test callback received',
180
+ data: data
181
+ }
182
+ end
183
+ end
184
+
185
+ # OAuth initiation endpoint (starts the flow)
186
+ webhook '/oauth/github/initiate' do
187
+ method :get
188
+
189
+ on_request do |_context|
190
+ # Generate state parameter for CSRF protection
191
+ SecureRandom.hex(32)
192
+
193
+ # In production, store state in Redis/database with expiration
194
+ # For demo purposes, we're just using a static value
195
+
196
+ # Build GitHub authorization URL
197
+ params = {
198
+ client_id: ENV.fetch('GITHUB_CLIENT_ID', nil),
199
+ redirect_uri: ENV.fetch('GITHUB_REDIRECT_URI', nil),
200
+ scope: 'user:email',
201
+ state: ENV.fetch('OAUTH_STATE_SECRET', nil) # Use proper state in production
202
+ }
203
+
204
+ auth_url = "https://github.com/login/oauth/authorize?#{URI.encode_www_form(params)}"
205
+
206
+ # Return redirect response
207
+ [
208
+ 302,
209
+ { 'Location' => auth_url },
210
+ ['Redirecting to GitHub...']
211
+ ]
212
+ end
213
+ end
214
+
215
+ # Session verification endpoint
216
+ webhook '/oauth/verify' do
217
+ method :get
218
+
219
+ # Verify bearer token
220
+ authenticate do
221
+ verify_bearer_token(token: ENV.fetch('SESSION_TOKEN', nil))
222
+ end
223
+
224
+ on_request do |_context|
225
+ {
226
+ status: 'valid',
227
+ message: 'Session is valid',
228
+ authenticated: true
229
+ }
230
+ end
231
+ end
232
+ end
233
+ end
234
+
235
+ # Helper methods
236
+
237
+ def exchange_code_for_token(code:, client_id:, client_secret:, token_url:, redirect_uri: nil)
238
+ uri = URI(token_url)
239
+
240
+ params = {
241
+ code: code,
242
+ client_id: client_id,
243
+ client_secret: client_secret,
244
+ grant_type: 'authorization_code'
245
+ }
246
+ params[:redirect_uri] = redirect_uri if redirect_uri
247
+
248
+ request = Net::HTTP::Post.new(uri)
249
+ request.set_form_data(params)
250
+ request['Accept'] = 'application/json'
251
+
252
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
253
+ http.request(request)
254
+ end
255
+
256
+ JSON.parse(response.body, symbolize_names: true)
257
+ rescue StandardError => e
258
+ { error: e.message }
259
+ end
260
+
261
+ def fetch_github_user(access_token)
262
+ uri = URI('https://api.github.com/user')
263
+
264
+ request = Net::HTTP::Get.new(uri)
265
+ request['Authorization'] = "Bearer #{access_token}"
266
+ request['Accept'] = 'application/vnd.github.v3+json'
267
+
268
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
269
+ http.request(request)
270
+ end
271
+
272
+ JSON.parse(response.body)
273
+ rescue StandardError => e
274
+ { error: e.message }
275
+ end
276
+
277
+ def fetch_google_user(access_token)
278
+ uri = URI('https://www.googleapis.com/oauth2/v2/userinfo')
279
+
280
+ request = Net::HTTP::Get.new(uri)
281
+ request['Authorization'] = "Bearer #{access_token}"
282
+
283
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
284
+ http.request(request)
285
+ end
286
+
287
+ JSON.parse(response.body)
288
+ rescue StandardError => e
289
+ { error: e.message }
290
+ end
291
+
292
+ # Run the agent if this file is executed directly
293
+ if __FILE__ == $PROGRAM_NAME
294
+ agent = LanguageOperator::Dsl.agent_registry.get('oauth-handler')
295
+ agent.run!
296
+ end
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Stripe Webhook Agent Example
5
+ #
6
+ # This example shows how to create an agent that receives Stripe webhook events
7
+ # with proper signature verification for security.
8
+ #
9
+ # Setup:
10
+ # 1. Set STRIPE_WEBHOOK_SECRET environment variable (from Stripe Dashboard)
11
+ # 2. Configure Stripe webhook to send events to https://<agent-url>/stripe/events
12
+ # 3. Select events you want to receive (e.g., payment_intent.succeeded, customer.created)
13
+ #
14
+ # Stripe webhook documentation:
15
+ # https://stripe.com/docs/webhooks
16
+
17
+ require 'bundler/setup'
18
+ require 'language_operator'
19
+
20
+ LanguageOperator::Dsl.define_agents do
21
+ agent 'stripe-payment-processor' do
22
+ description 'Processes Stripe payment events and triggers fulfillment'
23
+
24
+ # Set to reactive mode to receive webhooks
25
+ mode :reactive
26
+
27
+ # Stripe webhook endpoint
28
+ webhook '/stripe/events' do
29
+ method :post
30
+
31
+ # Verify Stripe webhook signature
32
+ # Stripe sends signature in Stripe-Signature header
33
+ # Format: "t=<timestamp>,v1=<signature>"
34
+ # Note: For simplicity, we're verifying the v1 signature
35
+ # Production should also check timestamp to prevent replay attacks
36
+ authenticate do
37
+ verify_custom do |context|
38
+ signature_header = context[:headers]['Stripe-Signature']
39
+ return false unless signature_header
40
+
41
+ # Parse signature header
42
+ # Format: t=1614556800,v1=abc123,v0=def456
43
+ sig_parts = {}
44
+ signature_header.split(',').each do |part|
45
+ key, value = part.split('=', 2)
46
+ sig_parts[key] = value
47
+ end
48
+
49
+ timestamp = sig_parts['t']
50
+ signature = sig_parts['v1']
51
+ return false unless timestamp && signature
52
+
53
+ # Construct signed payload
54
+ signed_payload = "#{timestamp}.#{context[:body]}"
55
+
56
+ # Compute expected signature
57
+ secret = ENV.fetch('STRIPE_WEBHOOK_SECRET', nil)
58
+ expected = OpenSSL::HMAC.hexdigest('sha256', secret, signed_payload)
59
+
60
+ # Compare signatures (constant-time)
61
+ signature == expected
62
+ end
63
+ end
64
+
65
+ # Validate request format
66
+ require_content_type 'application/json'
67
+
68
+ on_request do |context|
69
+ event = JSON.parse(context[:body])
70
+ event_type = event['type']
71
+ event_data = event['data']['object']
72
+
73
+ case event_type
74
+ when 'payment_intent.succeeded'
75
+ handle_payment_succeeded(event_data)
76
+ when 'payment_intent.payment_failed'
77
+ handle_payment_failed(event_data)
78
+ when 'customer.created'
79
+ handle_customer_created(event_data)
80
+ when 'customer.subscription.created'
81
+ handle_subscription_created(event_data)
82
+ when 'customer.subscription.deleted'
83
+ handle_subscription_deleted(event_data)
84
+ when 'invoice.payment_succeeded'
85
+ handle_invoice_paid(event_data)
86
+ when 'invoice.payment_failed'
87
+ handle_invoice_failed(event_data)
88
+ when 'charge.refunded'
89
+ handle_refund(event_data)
90
+ else
91
+ { status: 'ignored', event_type: event_type }
92
+ end
93
+ end
94
+ end
95
+
96
+ # Alternative endpoint with API key authentication
97
+ # Useful for testing or internal triggers
98
+ webhook '/stripe/manual-trigger' do
99
+ method :post
100
+
101
+ # Verify API key
102
+ authenticate do
103
+ verify_api_key(
104
+ header: 'X-API-Key',
105
+ key: ENV.fetch('STRIPE_INTERNAL_API_KEY', nil)
106
+ )
107
+ end
108
+
109
+ require_content_type 'application/json'
110
+
111
+ on_request do |context|
112
+ data = JSON.parse(context[:body])
113
+ action = data['action']
114
+
115
+ {
116
+ status: 'processed',
117
+ action: action,
118
+ message: "Manual trigger received: #{action}"
119
+ }
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ # Helper methods for event handling
126
+
127
+ def handle_payment_succeeded(payment_intent)
128
+ {
129
+ status: 'processed',
130
+ event: 'payment_succeeded',
131
+ amount: payment_intent['amount'],
132
+ currency: payment_intent['currency'],
133
+ customer: payment_intent['customer'],
134
+ payment_intent_id: payment_intent['id'],
135
+ message: "Payment of #{payment_intent['amount']} #{payment_intent['currency']} succeeded"
136
+ }
137
+ end
138
+
139
+ def handle_payment_failed(payment_intent)
140
+ {
141
+ status: 'processed',
142
+ event: 'payment_failed',
143
+ amount: payment_intent['amount'],
144
+ currency: payment_intent['currency'],
145
+ customer: payment_intent['customer'],
146
+ error: payment_intent['last_payment_error']&.dig('message'),
147
+ message: 'Payment failed'
148
+ }
149
+ end
150
+
151
+ def handle_customer_created(customer)
152
+ {
153
+ status: 'processed',
154
+ event: 'customer_created',
155
+ customer_id: customer['id'],
156
+ email: customer['email'],
157
+ message: "New customer created: #{customer['email']}"
158
+ }
159
+ end
160
+
161
+ def handle_subscription_created(subscription)
162
+ {
163
+ status: 'processed',
164
+ event: 'subscription_created',
165
+ subscription_id: subscription['id'],
166
+ customer: subscription['customer'],
167
+ plan: subscription['items']['data'].first['price']['id'],
168
+ message: "New subscription created for customer #{subscription['customer']}"
169
+ }
170
+ end
171
+
172
+ def handle_subscription_deleted(subscription)
173
+ {
174
+ status: 'processed',
175
+ event: 'subscription_deleted',
176
+ subscription_id: subscription['id'],
177
+ customer: subscription['customer'],
178
+ message: "Subscription deleted for customer #{subscription['customer']}"
179
+ }
180
+ end
181
+
182
+ def handle_invoice_paid(invoice)
183
+ {
184
+ status: 'processed',
185
+ event: 'invoice_paid',
186
+ invoice_id: invoice['id'],
187
+ amount: invoice['amount_paid'],
188
+ customer: invoice['customer'],
189
+ message: "Invoice #{invoice['number']} paid"
190
+ }
191
+ end
192
+
193
+ def handle_invoice_failed(invoice)
194
+ {
195
+ status: 'processed',
196
+ event: 'invoice_failed',
197
+ invoice_id: invoice['id'],
198
+ amount: invoice['amount_due'],
199
+ customer: invoice['customer'],
200
+ message: "Invoice #{invoice['number']} payment failed"
201
+ }
202
+ end
203
+
204
+ def handle_refund(charge)
205
+ {
206
+ status: 'processed',
207
+ event: 'refund',
208
+ charge_id: charge['id'],
209
+ amount_refunded: charge['amount_refunded'],
210
+ customer: charge['customer'],
211
+ message: "Charge #{charge['id']} refunded"
212
+ }
213
+ end
214
+
215
+ # Run the agent if this file is executed directly
216
+ if __FILE__ == $PROGRAM_NAME
217
+ agent = LanguageOperator::Dsl.agent_registry.get('stripe-payment-processor')
218
+ agent.run!
219
+ end
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example webhook agent that demonstrates the reactive/HTTP server capability
5
+ #
6
+ # Usage:
7
+ # PORT=8080 ruby examples/webhook_agent.rb
8
+ #
9
+ # Test with curl:
10
+ # curl -X POST http://localhost:8080/webhook -H "Content-Type: application/json" -d '{"event": "test"}'
11
+ # curl http://localhost:8080/health
12
+
13
+ require 'bundler/setup'
14
+ require 'language_operator'
15
+ require 'language_operator/dsl'
16
+
17
+ # Define a webhook agent using the DSL
18
+ # rubocop:disable Metrics/BlockLength
19
+ LanguageOperator::Dsl.define do
20
+ agent 'example-webhook-handler' do
21
+ description 'Example agent that handles HTTP webhooks'
22
+ mode :reactive
23
+
24
+ # Health check endpoint (already provided by default)
25
+ # GET /health
26
+
27
+ # Custom webhook endpoint
28
+ webhook '/webhook' do
29
+ method :post
30
+ on_request do |context|
31
+ puts "\n=== Received Webhook ==="
32
+ puts "Method: #{context[:method]}"
33
+ puts "Path: #{context[:path]}"
34
+ puts "Body: #{context[:body]}"
35
+ puts "Params: #{context[:params].inspect}"
36
+ puts "========================\n"
37
+
38
+ {
39
+ status: 'received',
40
+ message: 'Webhook processed successfully',
41
+ received_data: context[:params]
42
+ }
43
+ end
44
+ end
45
+
46
+ # GitHub-style webhook endpoint
47
+ webhook '/github/pr' do
48
+ method :post
49
+ on_request do |_context|
50
+ puts "\n=== GitHub PR Webhook ==="
51
+ puts 'Simulating PR review...'
52
+ puts "============================\n"
53
+
54
+ {
55
+ status: 'pr_reviewed',
56
+ message: 'Pull request review queued'
57
+ }
58
+ end
59
+ end
60
+ end
61
+ end
62
+ # rubocop:enable Metrics/BlockLength
63
+
64
+ # Get the agent definition and run it
65
+ agent_def = LanguageOperator::Dsl.agent_registry.get('example-webhook-handler')
66
+
67
+ if agent_def
68
+ puts "Starting webhook agent on port #{ENV.fetch('PORT', '8080')}"
69
+ puts 'Available endpoints:'
70
+ puts ' GET /health - Health check'
71
+ puts ' GET /ready - Readiness check'
72
+ puts ' POST /webhook - Generic webhook'
73
+ puts ' POST /github/pr - GitHub PR webhook'
74
+ puts "\nPress Ctrl+C to stop\n\n"
75
+
76
+ agent_def.run!
77
+ else
78
+ puts 'Error: Agent definition not found'
79
+ exit 1
80
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../client'
4
+ require_relative 'telemetry'
5
+ require_relative 'instrumentation'
6
+
7
+ module LanguageOperator
8
+ module Agent
9
+ # Base Agent Class
10
+ #
11
+ # Extends LanguageOperator::Client::Base with agent-specific functionality including:
12
+ # - Workspace integration
13
+ # - Goal-directed execution
14
+ # - Autonomous operation modes
15
+ #
16
+ # @example Basic agent
17
+ # agent = LanguageOperator::Agent::Base.new(config)
18
+ # agent.connect!
19
+ # agent.execute_goal("Complete the task")
20
+ class Base < LanguageOperator::Client::Base
21
+ include Instrumentation
22
+
23
+ attr_reader :workspace_path, :mode
24
+
25
+ # Initialize the agent
26
+ #
27
+ # @param config [Hash] Configuration hash
28
+ def initialize(config)
29
+ super
30
+
31
+ # Initialize OpenTelemetry
32
+ LanguageOperator::Agent::Telemetry.configure
33
+ otel_enabled = !ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT', nil).nil?
34
+ logger.info "OpenTelemetry #{otel_enabled ? 'enabled' : 'disabled'}"
35
+
36
+ @workspace_path = ENV.fetch('WORKSPACE_PATH', '/workspace')
37
+ @mode = ENV.fetch('AGENT_MODE', 'autonomous')
38
+ @executor = nil
39
+ @scheduler = nil
40
+ end
41
+
42
+ # Run the agent in its configured mode
43
+ #
44
+ # @return [void]
45
+ def run
46
+ with_span('agent.run', attributes: {
47
+ 'agent.name' => ENV.fetch('AGENT_NAME', nil),
48
+ 'agent.mode' => @mode,
49
+ 'agent.workspace_available' => workspace_available?
50
+ }) do
51
+ connect!
52
+
53
+ case @mode
54
+ when 'autonomous', 'interactive'
55
+ run_autonomous
56
+ when 'scheduled', 'event-driven'
57
+ run_scheduled
58
+ when 'reactive', 'http', 'webhook'
59
+ run_reactive
60
+ else
61
+ raise "Unknown agent mode: #{@mode}"
62
+ end
63
+ end
64
+ end
65
+
66
+ # Execute a single goal
67
+ #
68
+ # @param goal [String] The goal to achieve
69
+ # @return [String] The result
70
+ def execute_goal(goal)
71
+ @executor ||= Executor.new(self)
72
+ @executor.execute(goal)
73
+ end
74
+
75
+ # Check if workspace is available
76
+ #
77
+ # @return [Boolean]
78
+ def workspace_available?
79
+ File.directory?(@workspace_path) && File.writable?(@workspace_path)
80
+ end
81
+
82
+ private
83
+
84
+ # Run in autonomous mode
85
+ #
86
+ # @return [void]
87
+ def run_autonomous
88
+ @executor = Executor.new(self)
89
+ @executor.run_loop
90
+ end
91
+
92
+ # Run in scheduled mode
93
+ #
94
+ # @return [void]
95
+ def run_scheduled
96
+ @scheduler = Scheduler.new(self)
97
+ @scheduler.start
98
+ end
99
+
100
+ # Run in reactive mode (HTTP server)
101
+ #
102
+ # @return [void]
103
+ def run_reactive
104
+ require_relative 'web_server'
105
+ @web_server = WebServer.new(self)
106
+ @web_server.start
107
+ end
108
+ end
109
+ end
110
+ end