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.
- checksums.yaml +4 -4
- data/.rubocop.yml +125 -0
- data/CHANGELOG.md +88 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +284 -0
- data/LICENSE +229 -21
- data/Makefile +82 -0
- data/README.md +3 -11
- data/Rakefile +63 -0
- data/bin/aictl +7 -0
- data/completions/_aictl +232 -0
- data/completions/aictl.bash +121 -0
- data/completions/aictl.fish +114 -0
- data/docs/architecture/agent-runtime.md +585 -0
- data/docs/dsl/SCHEMA_VERSION.md +250 -0
- data/docs/dsl/agent-reference.md +604 -0
- data/docs/dsl/best-practices.md +1078 -0
- data/docs/dsl/chat-endpoints.md +895 -0
- data/docs/dsl/constraints.md +671 -0
- data/docs/dsl/mcp-integration.md +1177 -0
- data/docs/dsl/webhooks.md +932 -0
- data/docs/dsl/workflows.md +744 -0
- data/lib/language_operator/agent/base.rb +110 -0
- data/lib/language_operator/agent/executor.rb +440 -0
- data/lib/language_operator/agent/instrumentation.rb +54 -0
- data/lib/language_operator/agent/metrics_tracker.rb +183 -0
- data/lib/language_operator/agent/safety/ast_validator.rb +272 -0
- data/lib/language_operator/agent/safety/audit_logger.rb +104 -0
- data/lib/language_operator/agent/safety/budget_tracker.rb +175 -0
- data/lib/language_operator/agent/safety/content_filter.rb +93 -0
- data/lib/language_operator/agent/safety/manager.rb +207 -0
- data/lib/language_operator/agent/safety/rate_limiter.rb +150 -0
- data/lib/language_operator/agent/safety/safe_executor.rb +127 -0
- data/lib/language_operator/agent/scheduler.rb +183 -0
- data/lib/language_operator/agent/telemetry.rb +116 -0
- data/lib/language_operator/agent/web_server.rb +610 -0
- data/lib/language_operator/agent/webhook_authenticator.rb +226 -0
- data/lib/language_operator/agent.rb +149 -0
- data/lib/language_operator/cli/commands/agent.rb +1205 -0
- data/lib/language_operator/cli/commands/cluster.rb +371 -0
- data/lib/language_operator/cli/commands/install.rb +404 -0
- data/lib/language_operator/cli/commands/model.rb +266 -0
- data/lib/language_operator/cli/commands/persona.rb +393 -0
- data/lib/language_operator/cli/commands/quickstart.rb +22 -0
- data/lib/language_operator/cli/commands/status.rb +143 -0
- data/lib/language_operator/cli/commands/system.rb +772 -0
- data/lib/language_operator/cli/commands/tool.rb +537 -0
- data/lib/language_operator/cli/commands/use.rb +47 -0
- data/lib/language_operator/cli/errors/handler.rb +180 -0
- data/lib/language_operator/cli/errors/suggestions.rb +176 -0
- data/lib/language_operator/cli/formatters/code_formatter.rb +77 -0
- data/lib/language_operator/cli/formatters/log_formatter.rb +288 -0
- data/lib/language_operator/cli/formatters/progress_formatter.rb +49 -0
- data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
- data/lib/language_operator/cli/formatters/table_formatter.rb +163 -0
- data/lib/language_operator/cli/formatters/value_formatter.rb +113 -0
- data/lib/language_operator/cli/helpers/cluster_context.rb +62 -0
- data/lib/language_operator/cli/helpers/cluster_validator.rb +101 -0
- data/lib/language_operator/cli/helpers/editor_helper.rb +58 -0
- data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +167 -0
- data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
- data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +74 -0
- data/lib/language_operator/cli/helpers/schedule_builder.rb +108 -0
- data/lib/language_operator/cli/helpers/user_prompts.rb +69 -0
- data/lib/language_operator/cli/main.rb +236 -0
- data/lib/language_operator/cli/templates/tools/generic.yaml +66 -0
- data/lib/language_operator/cli/wizards/agent_wizard.rb +246 -0
- data/lib/language_operator/cli/wizards/quickstart_wizard.rb +588 -0
- data/lib/language_operator/client/base.rb +214 -0
- data/lib/language_operator/client/config.rb +136 -0
- data/lib/language_operator/client/cost_calculator.rb +37 -0
- data/lib/language_operator/client/mcp_connector.rb +123 -0
- data/lib/language_operator/client.rb +19 -0
- data/lib/language_operator/config/cluster_config.rb +101 -0
- data/lib/language_operator/config/tool_patterns.yaml +57 -0
- data/lib/language_operator/config/tool_registry.rb +96 -0
- data/lib/language_operator/config.rb +138 -0
- data/lib/language_operator/dsl/adapter.rb +124 -0
- data/lib/language_operator/dsl/agent_context.rb +90 -0
- data/lib/language_operator/dsl/agent_definition.rb +427 -0
- data/lib/language_operator/dsl/chat_endpoint_definition.rb +115 -0
- data/lib/language_operator/dsl/config.rb +119 -0
- data/lib/language_operator/dsl/context.rb +50 -0
- data/lib/language_operator/dsl/execution_context.rb +47 -0
- data/lib/language_operator/dsl/helpers.rb +109 -0
- data/lib/language_operator/dsl/http.rb +184 -0
- data/lib/language_operator/dsl/mcp_server_definition.rb +73 -0
- data/lib/language_operator/dsl/parameter_definition.rb +124 -0
- data/lib/language_operator/dsl/registry.rb +36 -0
- data/lib/language_operator/dsl/schema.rb +1102 -0
- data/lib/language_operator/dsl/shell.rb +125 -0
- data/lib/language_operator/dsl/tool_definition.rb +112 -0
- data/lib/language_operator/dsl/webhook_authentication.rb +114 -0
- data/lib/language_operator/dsl/webhook_definition.rb +106 -0
- data/lib/language_operator/dsl/workflow_definition.rb +259 -0
- data/lib/language_operator/dsl.rb +161 -0
- data/lib/language_operator/errors.rb +60 -0
- data/lib/language_operator/kubernetes/client.rb +279 -0
- data/lib/language_operator/kubernetes/resource_builder.rb +194 -0
- data/lib/language_operator/loggable.rb +47 -0
- data/lib/language_operator/logger.rb +141 -0
- data/lib/language_operator/retry.rb +123 -0
- data/lib/language_operator/retryable.rb +132 -0
- data/lib/language_operator/templates/README.md +23 -0
- data/lib/language_operator/templates/examples/agent_synthesis.tmpl +115 -0
- data/lib/language_operator/templates/examples/persona_distillation.tmpl +19 -0
- data/lib/language_operator/templates/schema/.gitkeep +0 -0
- data/lib/language_operator/templates/schema/CHANGELOG.md +93 -0
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +452 -0
- data/lib/language_operator/tool_loader.rb +242 -0
- data/lib/language_operator/validators.rb +170 -0
- data/lib/language_operator/version.rb +1 -1
- data/lib/language_operator.rb +65 -3
- data/requirements/tasks/challenge.md +9 -0
- data/requirements/tasks/iterate.md +36 -0
- data/requirements/tasks/optimize.md +21 -0
- data/requirements/tasks/tag.md +5 -0
- data/test_agent_dsl.rb +108 -0
- metadata +507 -20
|
@@ -0,0 +1,932 @@
|
|
|
1
|
+
# Webhook Guide
|
|
2
|
+
|
|
3
|
+
Complete guide to configuring webhook endpoints for reactive agents.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Basic Webhook Configuration](#basic-webhook-configuration)
|
|
9
|
+
- [HTTP Methods](#http-methods)
|
|
10
|
+
- [Authentication](#authentication)
|
|
11
|
+
- [Request Validation](#request-validation)
|
|
12
|
+
- [Event Handling](#event-handling)
|
|
13
|
+
- [Complete Examples](#complete-examples)
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
Webhooks enable agents to respond to external events from services like GitHub, Stripe, Slack, and custom applications.
|
|
18
|
+
|
|
19
|
+
**Key Features:**
|
|
20
|
+
- Multiple authentication methods (HMAC, API key, Bearer token, Basic auth)
|
|
21
|
+
- Request validation (headers, content-type, custom rules)
|
|
22
|
+
- Automatic routing via UUID-based subdomains
|
|
23
|
+
- Integration with workflow execution
|
|
24
|
+
|
|
25
|
+
## Basic Webhook Configuration
|
|
26
|
+
|
|
27
|
+
Define a webhook endpoint in a reactive agent:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
agent "webhook-handler" do
|
|
31
|
+
description "Handle webhook events"
|
|
32
|
+
|
|
33
|
+
mode :reactive
|
|
34
|
+
|
|
35
|
+
webhook "/events" do
|
|
36
|
+
method :post
|
|
37
|
+
# Additional configuration...
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
on_webhook_event do |event|
|
|
41
|
+
# Process the event
|
|
42
|
+
puts "Received: #{event.inspect}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**URL Structure:**
|
|
48
|
+
Each agent gets a unique subdomain based on its UUID:
|
|
49
|
+
```
|
|
50
|
+
https://<agent-uuid>.webhooks.your-domain.com/events
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The operator automatically creates routing for this subdomain.
|
|
54
|
+
|
|
55
|
+
## HTTP Methods
|
|
56
|
+
|
|
57
|
+
Specify which HTTP methods the webhook accepts:
|
|
58
|
+
|
|
59
|
+
### Single Method
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
webhook "/github/pr" do
|
|
63
|
+
method :post # Only POST requests
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Multiple Methods
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
webhook "/api/data" do
|
|
71
|
+
methods [:get, :post, :put] # Accept GET, POST, or PUT
|
|
72
|
+
end
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Supported methods:**
|
|
76
|
+
- `:get`
|
|
77
|
+
- `:post`
|
|
78
|
+
- `:put`
|
|
79
|
+
- `:delete`
|
|
80
|
+
- `:patch`
|
|
81
|
+
- `:head`
|
|
82
|
+
- `:options`
|
|
83
|
+
|
|
84
|
+
## Authentication
|
|
85
|
+
|
|
86
|
+
Webhooks support multiple authentication methods to verify request origin.
|
|
87
|
+
|
|
88
|
+
### HMAC Signature Verification
|
|
89
|
+
|
|
90
|
+
Verify requests using HMAC signatures (GitHub, Stripe style):
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
webhook "/github/events" do
|
|
94
|
+
method :post
|
|
95
|
+
|
|
96
|
+
authenticate do
|
|
97
|
+
verify_signature(
|
|
98
|
+
header: 'X-Hub-Signature-256',
|
|
99
|
+
secret: ENV['GITHUB_WEBHOOK_SECRET'],
|
|
100
|
+
algorithm: :sha256
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Parameters:**
|
|
107
|
+
- `header` (String): HTTP header containing the signature
|
|
108
|
+
- `secret` (String): Shared secret for verification
|
|
109
|
+
- `algorithm` (Symbol): Hash algorithm (`:sha1`, `:sha256`, `:sha512`)
|
|
110
|
+
|
|
111
|
+
**How it works:**
|
|
112
|
+
1. Receiver computes HMAC of request body using secret
|
|
113
|
+
2. Compares computed signature with header value
|
|
114
|
+
3. Rejects request if signatures don't match
|
|
115
|
+
|
|
116
|
+
**Common Patterns:**
|
|
117
|
+
|
|
118
|
+
GitHub webhooks:
|
|
119
|
+
```ruby
|
|
120
|
+
verify_signature(
|
|
121
|
+
header: 'X-Hub-Signature-256',
|
|
122
|
+
secret: ENV['GITHUB_WEBHOOK_SECRET'],
|
|
123
|
+
algorithm: :sha256
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Stripe webhooks:
|
|
128
|
+
```ruby
|
|
129
|
+
verify_signature(
|
|
130
|
+
header: 'Stripe-Signature',
|
|
131
|
+
secret: ENV['STRIPE_WEBHOOK_SECRET'],
|
|
132
|
+
algorithm: :sha256
|
|
133
|
+
)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### API Key Authentication
|
|
137
|
+
|
|
138
|
+
Verify requests using API keys in headers or query parameters:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
webhook "/api/events" do
|
|
142
|
+
method :post
|
|
143
|
+
|
|
144
|
+
authenticate do
|
|
145
|
+
verify_api_key(
|
|
146
|
+
header: 'X-API-Key',
|
|
147
|
+
secret: ENV['WEBHOOK_API_KEY']
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Header-based:**
|
|
154
|
+
```ruby
|
|
155
|
+
verify_api_key(
|
|
156
|
+
header: 'X-API-Key',
|
|
157
|
+
secret: ENV['API_KEY']
|
|
158
|
+
)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Query parameter:**
|
|
162
|
+
```ruby
|
|
163
|
+
verify_api_key(
|
|
164
|
+
param: 'api_key',
|
|
165
|
+
secret: ENV['API_KEY']
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Bearer Token Authentication
|
|
170
|
+
|
|
171
|
+
OAuth-style bearer token authentication:
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
webhook "/api/events" do
|
|
175
|
+
method :post
|
|
176
|
+
|
|
177
|
+
authenticate do
|
|
178
|
+
verify_bearer_token(
|
|
179
|
+
token: ENV['BEARER_TOKEN']
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Expected header:**
|
|
186
|
+
```
|
|
187
|
+
Authorization: Bearer <token>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Basic Auth
|
|
191
|
+
|
|
192
|
+
HTTP Basic Authentication:
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
webhook "/secure/endpoint" do
|
|
196
|
+
method :post
|
|
197
|
+
|
|
198
|
+
authenticate do
|
|
199
|
+
basic_auth(
|
|
200
|
+
username: ENV['WEBHOOK_USERNAME'],
|
|
201
|
+
password: ENV['WEBHOOK_PASSWORD']
|
|
202
|
+
)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Expected header:**
|
|
208
|
+
```
|
|
209
|
+
Authorization: Basic <base64(username:password)>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Custom Authentication
|
|
213
|
+
|
|
214
|
+
Implement custom authentication logic:
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
webhook "/custom/auth" do
|
|
218
|
+
method :post
|
|
219
|
+
|
|
220
|
+
authenticate do
|
|
221
|
+
custom do |request|
|
|
222
|
+
# Custom validation logic
|
|
223
|
+
api_key = request.headers['X-Custom-Auth']
|
|
224
|
+
valid_keys = ENV['VALID_API_KEYS'].split(',')
|
|
225
|
+
|
|
226
|
+
if valid_keys.include?(api_key)
|
|
227
|
+
true # Authentication succeeded
|
|
228
|
+
else
|
|
229
|
+
false # Authentication failed
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Composite Authentication
|
|
237
|
+
|
|
238
|
+
Require multiple authentication methods (all must pass):
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
webhook "/highly-secure" do
|
|
242
|
+
method :post
|
|
243
|
+
|
|
244
|
+
authenticate do
|
|
245
|
+
all_of do
|
|
246
|
+
verify_api_key header: 'X-API-Key', secret: ENV['API_KEY']
|
|
247
|
+
verify_signature header: 'X-Signature', secret: ENV['HMAC_SECRET'], algorithm: :sha256
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Accept any of multiple authentication methods (any one passes):
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
webhook "/flexible-auth" do
|
|
257
|
+
method :post
|
|
258
|
+
|
|
259
|
+
authenticate do
|
|
260
|
+
any_of do
|
|
261
|
+
verify_api_key header: 'X-API-Key', secret: ENV['API_KEY']
|
|
262
|
+
verify_bearer_token token: ENV['BEARER_TOKEN']
|
|
263
|
+
basic_auth username: ENV['USERNAME'], password: ENV['PASSWORD']
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Request Validation
|
|
270
|
+
|
|
271
|
+
Validate incoming requests beyond authentication.
|
|
272
|
+
|
|
273
|
+
### Content-Type Validation
|
|
274
|
+
|
|
275
|
+
Require specific content types:
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
webhook "/json-only" do
|
|
279
|
+
method :post
|
|
280
|
+
|
|
281
|
+
validate do
|
|
282
|
+
content_type 'application/json'
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Multiple acceptable types:**
|
|
288
|
+
```ruby
|
|
289
|
+
validate do
|
|
290
|
+
content_type ['application/json', 'application/x-www-form-urlencoded']
|
|
291
|
+
end
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Header Validation
|
|
295
|
+
|
|
296
|
+
Require specific headers to be present:
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
webhook "/strict" do
|
|
300
|
+
method :post
|
|
301
|
+
|
|
302
|
+
validate do
|
|
303
|
+
require_headers ['X-Event-Type', 'X-Request-ID']
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Header value validation:**
|
|
309
|
+
```ruby
|
|
310
|
+
validate do
|
|
311
|
+
header_matches 'X-Event-Type', /^(push|pull_request|issues)$/
|
|
312
|
+
end
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Request Size Limits
|
|
316
|
+
|
|
317
|
+
Limit request body size:
|
|
318
|
+
|
|
319
|
+
```ruby
|
|
320
|
+
webhook "/limited" do
|
|
321
|
+
method :post
|
|
322
|
+
|
|
323
|
+
validate do
|
|
324
|
+
max_body_size '1MB' # Reject requests larger than 1MB
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Supported formats:**
|
|
330
|
+
- `'1KB'` - 1 Kilobyte
|
|
331
|
+
- `'500KB'` - 500 Kilobytes
|
|
332
|
+
- `'1MB'` - 1 Megabyte
|
|
333
|
+
- `'10MB'` - 10 Megabytes
|
|
334
|
+
|
|
335
|
+
### Custom Validation
|
|
336
|
+
|
|
337
|
+
Implement custom validation rules:
|
|
338
|
+
|
|
339
|
+
```ruby
|
|
340
|
+
webhook "/custom-validation" do
|
|
341
|
+
method :post
|
|
342
|
+
|
|
343
|
+
validate do
|
|
344
|
+
custom do |request|
|
|
345
|
+
# Parse and validate request body
|
|
346
|
+
begin
|
|
347
|
+
body = JSON.parse(request.body.read)
|
|
348
|
+
body.key?('event_type') && body.key?('data')
|
|
349
|
+
rescue JSON::ParserError
|
|
350
|
+
false
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Combined Validation
|
|
358
|
+
|
|
359
|
+
```ruby
|
|
360
|
+
webhook "/comprehensive-validation" do
|
|
361
|
+
method :post
|
|
362
|
+
|
|
363
|
+
validate do
|
|
364
|
+
content_type 'application/json'
|
|
365
|
+
max_body_size '5MB'
|
|
366
|
+
require_headers ['X-Event-Type', 'X-Request-ID']
|
|
367
|
+
|
|
368
|
+
custom do |request|
|
|
369
|
+
# Additional custom checks
|
|
370
|
+
event_type = request.headers['X-Event-Type']
|
|
371
|
+
['user.created', 'user.updated', 'user.deleted'].include?(event_type)
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Event Handling
|
|
378
|
+
|
|
379
|
+
Process webhook events in your agent.
|
|
380
|
+
|
|
381
|
+
### Basic Event Handler
|
|
382
|
+
|
|
383
|
+
```ruby
|
|
384
|
+
agent "event-processor" do
|
|
385
|
+
mode :reactive
|
|
386
|
+
|
|
387
|
+
webhook "/events" do
|
|
388
|
+
method :post
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
on_webhook_event do |event|
|
|
392
|
+
puts "Event received: #{event['type']}"
|
|
393
|
+
|
|
394
|
+
# Access event data
|
|
395
|
+
user_id = event.dig('data', 'user_id')
|
|
396
|
+
action = event['action']
|
|
397
|
+
|
|
398
|
+
# Perform actions...
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Event Handler with Workflow
|
|
404
|
+
|
|
405
|
+
Trigger workflows from webhook events:
|
|
406
|
+
|
|
407
|
+
```ruby
|
|
408
|
+
agent "github-pr-handler" do
|
|
409
|
+
mode :reactive
|
|
410
|
+
|
|
411
|
+
webhook "/github/pr" do
|
|
412
|
+
method :post
|
|
413
|
+
authenticate do
|
|
414
|
+
verify_signature(
|
|
415
|
+
header: 'X-Hub-Signature-256',
|
|
416
|
+
secret: ENV['GITHUB_WEBHOOK_SECRET'],
|
|
417
|
+
algorithm: :sha256
|
|
418
|
+
)
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
on_webhook_event do |event|
|
|
423
|
+
# Event data available in workflow context
|
|
424
|
+
@event_data = event
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
workflow do
|
|
428
|
+
step :extract_pr_info do
|
|
429
|
+
execute do |context|
|
|
430
|
+
pr = context[:event].dig('pull_request')
|
|
431
|
+
{
|
|
432
|
+
number: pr['number'],
|
|
433
|
+
title: pr['title'],
|
|
434
|
+
author: pr.dig('user', 'login'),
|
|
435
|
+
diff_url: pr['diff_url']
|
|
436
|
+
}
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
step :fetch_diff do
|
|
441
|
+
depends_on :extract_pr_info
|
|
442
|
+
tool 'http_get'
|
|
443
|
+
params url: '{extract_pr_info.diff_url}'
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
step :review do
|
|
447
|
+
depends_on :fetch_diff
|
|
448
|
+
prompt "Review this pull request code: {fetch_diff.output}"
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
step :post_comment do
|
|
452
|
+
depends_on :review
|
|
453
|
+
tool 'github_api'
|
|
454
|
+
params(
|
|
455
|
+
action: 'create_comment',
|
|
456
|
+
issue_number: '{extract_pr_info.number}',
|
|
457
|
+
body: '{review.output}'
|
|
458
|
+
)
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Event Filtering
|
|
465
|
+
|
|
466
|
+
Process only specific event types:
|
|
467
|
+
|
|
468
|
+
```ruby
|
|
469
|
+
on_webhook_event do |event|
|
|
470
|
+
event_type = event['type']
|
|
471
|
+
|
|
472
|
+
case event_type
|
|
473
|
+
when 'pull_request.opened'
|
|
474
|
+
# Handle new PR
|
|
475
|
+
handle_new_pr(event)
|
|
476
|
+
when 'pull_request.closed'
|
|
477
|
+
# Handle closed PR
|
|
478
|
+
handle_closed_pr(event)
|
|
479
|
+
else
|
|
480
|
+
# Ignore other events
|
|
481
|
+
puts "Ignoring event type: #{event_type}"
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
## Complete Examples
|
|
487
|
+
|
|
488
|
+
### GitHub Pull Request Reviewer
|
|
489
|
+
|
|
490
|
+
```ruby
|
|
491
|
+
agent "github-pr-reviewer" do
|
|
492
|
+
description "Automatically review pull requests"
|
|
493
|
+
|
|
494
|
+
mode :reactive
|
|
495
|
+
|
|
496
|
+
persona <<~PERSONA
|
|
497
|
+
You are a senior software engineer conducting code reviews.
|
|
498
|
+
Focus on correctness, security, performance, and maintainability.
|
|
499
|
+
Provide constructive, specific feedback.
|
|
500
|
+
PERSONA
|
|
501
|
+
|
|
502
|
+
webhook "/github/pull-request" do
|
|
503
|
+
method :post
|
|
504
|
+
|
|
505
|
+
# GitHub webhook authentication
|
|
506
|
+
authenticate do
|
|
507
|
+
verify_signature(
|
|
508
|
+
header: 'X-Hub-Signature-256',
|
|
509
|
+
secret: ENV['GITHUB_WEBHOOK_SECRET'],
|
|
510
|
+
algorithm: :sha256
|
|
511
|
+
)
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
# Validate GitHub webhook format
|
|
515
|
+
validate do
|
|
516
|
+
content_type 'application/json'
|
|
517
|
+
require_headers ['X-GitHub-Event', 'X-GitHub-Delivery']
|
|
518
|
+
max_body_size '10MB'
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
on_webhook_event do |event|
|
|
523
|
+
# Filter to only PR open/sync events
|
|
524
|
+
gh_event = event.headers['X-GitHub-Event']
|
|
525
|
+
return unless gh_event == 'pull_request'
|
|
526
|
+
|
|
527
|
+
action = event.dig('body', 'action')
|
|
528
|
+
return unless ['opened', 'synchronize'].include?(action)
|
|
529
|
+
|
|
530
|
+
# Event passes filters, workflow will execute
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
workflow do
|
|
534
|
+
step :extract_pr do
|
|
535
|
+
execute do |context|
|
|
536
|
+
pr = context[:event].dig('body', 'pull_request')
|
|
537
|
+
{
|
|
538
|
+
number: pr['number'],
|
|
539
|
+
title: pr['title'],
|
|
540
|
+
body: pr['body'],
|
|
541
|
+
diff_url: pr['diff_url'],
|
|
542
|
+
repo_full_name: context[:event].dig('body', 'repository', 'full_name')
|
|
543
|
+
}
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
step :fetch_diff do
|
|
548
|
+
depends_on :extract_pr
|
|
549
|
+
tool 'github_api'
|
|
550
|
+
params(
|
|
551
|
+
action: 'get_pr_diff',
|
|
552
|
+
repo: '{extract_pr.repo_full_name}',
|
|
553
|
+
pr_number: '{extract_pr.number}'
|
|
554
|
+
)
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
step :review_code do
|
|
558
|
+
depends_on [:extract_pr, :fetch_diff]
|
|
559
|
+
prompt <<~PROMPT
|
|
560
|
+
Review this pull request:
|
|
561
|
+
|
|
562
|
+
Title: {extract_pr.title}
|
|
563
|
+
Description: {extract_pr.body}
|
|
564
|
+
|
|
565
|
+
Code changes:
|
|
566
|
+
{fetch_diff.output}
|
|
567
|
+
|
|
568
|
+
Provide a detailed code review covering:
|
|
569
|
+
1. Code correctness
|
|
570
|
+
2. Security issues
|
|
571
|
+
3. Performance concerns
|
|
572
|
+
4. Best practices
|
|
573
|
+
5. Suggestions for improvement
|
|
574
|
+
|
|
575
|
+
Be specific and constructive.
|
|
576
|
+
PROMPT
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
step :post_review do
|
|
580
|
+
depends_on [:extract_pr, :review_code]
|
|
581
|
+
tool 'github_api'
|
|
582
|
+
params(
|
|
583
|
+
action: 'create_review_comment',
|
|
584
|
+
repo: '{extract_pr.repo_full_name}',
|
|
585
|
+
pr_number: '{extract_pr.number}',
|
|
586
|
+
body: '{review_code.output}'
|
|
587
|
+
)
|
|
588
|
+
end
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
constraints do
|
|
592
|
+
timeout '10m'
|
|
593
|
+
requests_per_hour 100
|
|
594
|
+
daily_budget 1000 # $10/day
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### Stripe Payment Processor
|
|
600
|
+
|
|
601
|
+
```ruby
|
|
602
|
+
agent "stripe-payment-handler" do
|
|
603
|
+
description "Process Stripe payment events"
|
|
604
|
+
|
|
605
|
+
mode :reactive
|
|
606
|
+
|
|
607
|
+
webhook "/stripe/events" do
|
|
608
|
+
method :post
|
|
609
|
+
|
|
610
|
+
# Stripe webhook signature verification
|
|
611
|
+
authenticate do
|
|
612
|
+
verify_signature(
|
|
613
|
+
header: 'Stripe-Signature',
|
|
614
|
+
secret: ENV['STRIPE_WEBHOOK_SECRET'],
|
|
615
|
+
algorithm: :sha256
|
|
616
|
+
)
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
validate do
|
|
620
|
+
content_type 'application/json'
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
on_webhook_event do |event|
|
|
625
|
+
event_type = event.dig('body', 'type')
|
|
626
|
+
|
|
627
|
+
case event_type
|
|
628
|
+
when 'payment_intent.succeeded'
|
|
629
|
+
handle_successful_payment(event)
|
|
630
|
+
when 'payment_intent.payment_failed'
|
|
631
|
+
handle_failed_payment(event)
|
|
632
|
+
when 'customer.subscription.created'
|
|
633
|
+
handle_new_subscription(event)
|
|
634
|
+
else
|
|
635
|
+
puts "Unhandled event: #{event_type}"
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
workflow do
|
|
640
|
+
step :extract_payment do
|
|
641
|
+
execute do |context|
|
|
642
|
+
event_data = context[:event].dig('body', 'data', 'object')
|
|
643
|
+
{
|
|
644
|
+
payment_id: event_data['id'],
|
|
645
|
+
amount: event_data['amount'],
|
|
646
|
+
currency: event_data['currency'],
|
|
647
|
+
customer_id: event_data['customer'],
|
|
648
|
+
status: event_data['status']
|
|
649
|
+
}
|
|
650
|
+
end
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
step :update_database do
|
|
654
|
+
depends_on :extract_payment
|
|
655
|
+
tool 'database_update'
|
|
656
|
+
params(
|
|
657
|
+
table: 'payments',
|
|
658
|
+
where: { stripe_payment_id: '{extract_payment.payment_id}' },
|
|
659
|
+
data: {
|
|
660
|
+
status: '{extract_payment.status}',
|
|
661
|
+
updated_at: 'NOW()'
|
|
662
|
+
}
|
|
663
|
+
)
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
step :send_confirmation do
|
|
667
|
+
depends_on :extract_payment
|
|
668
|
+
tool 'send_email'
|
|
669
|
+
params(
|
|
670
|
+
to: '{extract_payment.customer_email}',
|
|
671
|
+
subject: 'Payment Confirmation',
|
|
672
|
+
body: 'Your payment of {extract_payment.amount} {extract_payment.currency} was successful'
|
|
673
|
+
)
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
constraints do
|
|
678
|
+
timeout '30s' # Process quickly
|
|
679
|
+
requests_per_minute 100 # High throughput
|
|
680
|
+
hourly_budget 100 # $1/hour
|
|
681
|
+
end
|
|
682
|
+
end
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### Custom Application Webhook
|
|
686
|
+
|
|
687
|
+
```ruby
|
|
688
|
+
agent "custom-app-handler" do
|
|
689
|
+
description "Handle events from custom application"
|
|
690
|
+
|
|
691
|
+
mode :reactive
|
|
692
|
+
|
|
693
|
+
webhook "/app/events" do
|
|
694
|
+
methods [:post, :put]
|
|
695
|
+
|
|
696
|
+
# Flexible authentication
|
|
697
|
+
authenticate do
|
|
698
|
+
any_of do
|
|
699
|
+
verify_api_key header: 'X-API-Key', secret: ENV['APP_API_KEY']
|
|
700
|
+
verify_bearer_token token: ENV['APP_BEARER_TOKEN']
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
validate do
|
|
705
|
+
content_type 'application/json'
|
|
706
|
+
max_body_size '5MB'
|
|
707
|
+
|
|
708
|
+
custom do |request|
|
|
709
|
+
# Ensure required fields present
|
|
710
|
+
body = JSON.parse(request.body.read)
|
|
711
|
+
body['event_type'] && body['timestamp'] && body['data']
|
|
712
|
+
rescue JSON::ParserError
|
|
713
|
+
false
|
|
714
|
+
end
|
|
715
|
+
end
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
on_webhook_event do |event|
|
|
719
|
+
# Process event...
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
constraints do
|
|
723
|
+
timeout '1m'
|
|
724
|
+
requests_per_minute 50
|
|
725
|
+
daily_budget 500
|
|
726
|
+
end
|
|
727
|
+
end
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### Slack Command Handler
|
|
731
|
+
|
|
732
|
+
```ruby
|
|
733
|
+
agent "slack-bot" do
|
|
734
|
+
description "Respond to Slack slash commands"
|
|
735
|
+
|
|
736
|
+
mode :reactive
|
|
737
|
+
|
|
738
|
+
webhook "/slack/commands" do
|
|
739
|
+
method :post
|
|
740
|
+
|
|
741
|
+
# Slack uses token verification
|
|
742
|
+
authenticate do
|
|
743
|
+
custom do |request|
|
|
744
|
+
token = request.params['token']
|
|
745
|
+
token == ENV['SLACK_VERIFICATION_TOKEN']
|
|
746
|
+
end
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
validate do
|
|
750
|
+
content_type 'application/x-www-form-urlencoded'
|
|
751
|
+
end
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
on_webhook_event do |event|
|
|
755
|
+
command = event['command'] # e.g., '/deploy'
|
|
756
|
+
text = event['text'] # command arguments
|
|
757
|
+
user = event['user_name']
|
|
758
|
+
|
|
759
|
+
# Process command...
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
workflow do
|
|
763
|
+
step :parse_command do
|
|
764
|
+
execute do |context|
|
|
765
|
+
command = context[:event]['command']
|
|
766
|
+
text = context[:event]['text']
|
|
767
|
+
|
|
768
|
+
{
|
|
769
|
+
command: command,
|
|
770
|
+
args: text.split(' '),
|
|
771
|
+
user: context[:event]['user_name']
|
|
772
|
+
}
|
|
773
|
+
end
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
step :execute_command do
|
|
777
|
+
depends_on :parse_command
|
|
778
|
+
prompt "Execute this Slack command: {parse_command.command} {parse_command.args}"
|
|
779
|
+
end
|
|
780
|
+
|
|
781
|
+
step :respond_to_slack do
|
|
782
|
+
depends_on :execute_command
|
|
783
|
+
tool 'slack_respond'
|
|
784
|
+
params(
|
|
785
|
+
response_url: '{event.response_url}',
|
|
786
|
+
text: '{execute_command.output}'
|
|
787
|
+
)
|
|
788
|
+
end
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
constraints do
|
|
792
|
+
timeout '3s' # Slack requires fast responses
|
|
793
|
+
requests_per_minute 30
|
|
794
|
+
end
|
|
795
|
+
end
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
## Security Best Practices
|
|
799
|
+
|
|
800
|
+
### Always Use Authentication
|
|
801
|
+
|
|
802
|
+
Never expose webhooks without authentication:
|
|
803
|
+
|
|
804
|
+
```ruby
|
|
805
|
+
# INSECURE - Don't do this
|
|
806
|
+
webhook "/insecure" do
|
|
807
|
+
method :post
|
|
808
|
+
# No authentication!
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
# SECURE - Always authenticate
|
|
812
|
+
webhook "/secure" do
|
|
813
|
+
method :post
|
|
814
|
+
authenticate do
|
|
815
|
+
verify_signature(...)
|
|
816
|
+
end
|
|
817
|
+
end
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
### Use HTTPS
|
|
821
|
+
|
|
822
|
+
Webhooks are automatically served over HTTPS via the Gateway. Ensure:
|
|
823
|
+
- TLS certificates are properly configured
|
|
824
|
+
- No HTTP fallback is enabled
|
|
825
|
+
|
|
826
|
+
### Validate Request Size
|
|
827
|
+
|
|
828
|
+
Always limit request size to prevent DoS:
|
|
829
|
+
|
|
830
|
+
```ruby
|
|
831
|
+
validate do
|
|
832
|
+
max_body_size '10MB'
|
|
833
|
+
end
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
### Store Secrets Securely
|
|
837
|
+
|
|
838
|
+
Use Kubernetes Secrets for webhook secrets:
|
|
839
|
+
|
|
840
|
+
```yaml
|
|
841
|
+
apiVersion: v1
|
|
842
|
+
kind: Secret
|
|
843
|
+
metadata:
|
|
844
|
+
name: webhook-secrets
|
|
845
|
+
data:
|
|
846
|
+
github-webhook-secret: <base64-encoded-secret>
|
|
847
|
+
stripe-webhook-secret: <base64-encoded-secret>
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
Reference in agent environment:
|
|
851
|
+
|
|
852
|
+
```ruby
|
|
853
|
+
authenticate do
|
|
854
|
+
verify_signature(
|
|
855
|
+
secret: ENV['GITHUB_WEBHOOK_SECRET'] # From K8s Secret
|
|
856
|
+
)
|
|
857
|
+
end
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
### Implement Rate Limiting
|
|
861
|
+
|
|
862
|
+
Protect against abuse:
|
|
863
|
+
|
|
864
|
+
```ruby
|
|
865
|
+
constraints do
|
|
866
|
+
requests_per_minute 30
|
|
867
|
+
requests_per_hour 500
|
|
868
|
+
end
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
### Log Webhook Activity
|
|
872
|
+
|
|
873
|
+
Enable audit logging for security monitoring:
|
|
874
|
+
|
|
875
|
+
```ruby
|
|
876
|
+
on_webhook_event do |event|
|
|
877
|
+
# Log webhook receipt
|
|
878
|
+
AuditLog.record(
|
|
879
|
+
event: 'webhook_received',
|
|
880
|
+
source_ip: event.request.ip,
|
|
881
|
+
user_agent: event.request.user_agent,
|
|
882
|
+
payload_size: event.request.content_length
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
# Process event...
|
|
886
|
+
end
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
## Troubleshooting
|
|
890
|
+
|
|
891
|
+
### Webhook Not Receiving Events
|
|
892
|
+
|
|
893
|
+
1. **Check DNS**: Verify subdomain resolves correctly
|
|
894
|
+
```bash
|
|
895
|
+
dig <agent-uuid>.webhooks.your-domain.com
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
2. **Check Gateway routing**: Verify HTTPRoute is created
|
|
899
|
+
```bash
|
|
900
|
+
kubectl get httproute -n language-operator-system
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
3. **Check agent status**: Verify agent is running
|
|
904
|
+
```bash
|
|
905
|
+
kubectl get languageagent <agent-name> -o yaml
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
4. **Check webhook URL**: Get the correct URL from agent status
|
|
909
|
+
```bash
|
|
910
|
+
kubectl get languageagent <agent-name> -o jsonpath='{.status.webhookURLs}'
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
### Authentication Failures
|
|
914
|
+
|
|
915
|
+
1. **Verify secret**: Ensure correct secret is configured
|
|
916
|
+
2. **Check signature algorithm**: Ensure it matches the sender's algorithm
|
|
917
|
+
3. **Inspect headers**: Log incoming headers to verify format
|
|
918
|
+
4. **Test with curl**: Send a test request with correct authentication
|
|
919
|
+
|
|
920
|
+
### Event Not Processing
|
|
921
|
+
|
|
922
|
+
1. **Check filters**: Ensure `on_webhook_event` logic isn't filtering out events
|
|
923
|
+
2. **Check logs**: Review agent logs for errors
|
|
924
|
+
3. **Verify workflow**: Test workflow execution separately
|
|
925
|
+
4. **Check constraints**: Ensure rate limits aren't being exceeded
|
|
926
|
+
|
|
927
|
+
## See Also
|
|
928
|
+
|
|
929
|
+
- [Agent Reference](agent-reference.md) - Complete agent DSL reference
|
|
930
|
+
- [Workflows](workflows.md) - Workflow definition guide
|
|
931
|
+
- [Constraints](constraints.md) - Resource and behavior limits
|
|
932
|
+
- [Best Practices](best-practices.md) - Production deployment patterns
|