rack-ai 0.1.0

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.
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ module AI
5
+ module Features
6
+ class Logging
7
+ attr_reader :name, :provider, :config
8
+
9
+ def initialize(provider, config)
10
+ @name = :logging
11
+ @provider = provider
12
+ @config = config
13
+ end
14
+
15
+ def enabled?
16
+ @config.feature_enabled?(:logging)
17
+ end
18
+
19
+ def process_response?
20
+ true
21
+ end
22
+
23
+ def process_request(env)
24
+ return { processed: false, reason: "disabled" } unless enabled?
25
+
26
+ # Collect request metadata for AI analysis
27
+ request_summary = build_request_summary(env)
28
+
29
+ # Generate AI insights about the request
30
+ insights = generate_request_insights(request_summary, env)
31
+
32
+ {
33
+ request_summary: request_summary,
34
+ ai_insights: insights,
35
+ feature: @name,
36
+ timestamp: Time.now.iso8601
37
+ }
38
+ end
39
+
40
+ def process_response(env, status, headers, body)
41
+ return { processed: false, reason: "disabled" } unless enabled?
42
+
43
+ # Collect response metadata
44
+ response_summary = build_response_summary(status, headers, body)
45
+
46
+ # Generate AI insights about the complete request-response cycle
47
+ cycle_insights = generate_cycle_insights(env, response_summary)
48
+
49
+ # Log structured data for AI analysis
50
+ log_structured_data(env, response_summary, cycle_insights)
51
+
52
+ {
53
+ response_summary: response_summary,
54
+ cycle_insights: cycle_insights,
55
+ feature: "#{@name}_response",
56
+ timestamp: Time.now.iso8601
57
+ }
58
+ end
59
+
60
+ def generate_traffic_summary(time_window = 3600)
61
+ # This would be called periodically to generate AI-powered traffic summaries
62
+ return { processed: false, reason: "disabled" } unless enabled?
63
+
64
+ # In a real implementation, this would analyze stored log data
65
+ sample_data = build_sample_traffic_data(time_window)
66
+
67
+ begin
68
+ summary = @provider.analyze_patterns(sample_data)
69
+
70
+ {
71
+ time_window: time_window,
72
+ summary: summary,
73
+ generated_at: Time.now.iso8601
74
+ }
75
+ rescue => e
76
+ Utils::Logger.error("Failed to generate traffic summary", error: e.message)
77
+ { error: e.message, generated_at: Time.now.iso8601 }
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def build_request_summary(env)
84
+ {
85
+ method: env["REQUEST_METHOD"],
86
+ path: env["PATH_INFO"],
87
+ query_params_count: env["QUERY_STRING"]&.split("&")&.size || 0,
88
+ user_agent: env["HTTP_USER_AGENT"],
89
+ remote_ip: env["REMOTE_ADDR"],
90
+ content_length: env["CONTENT_LENGTH"]&.to_i || 0,
91
+ content_type: env["CONTENT_TYPE"],
92
+ timestamp: Time.now.iso8601,
93
+ has_auth: !!(env["HTTP_AUTHORIZATION"] || env["HTTP_X_API_KEY"]),
94
+ is_ajax: env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest",
95
+ referer: env["HTTP_REFERER"]
96
+ }
97
+ end
98
+
99
+ def build_response_summary(status, headers, body)
100
+ body_size = 0
101
+ if body.respond_to?(:each)
102
+ body.each { |chunk| body_size += chunk.bytesize }
103
+ end
104
+
105
+ {
106
+ status: status,
107
+ content_type: headers["Content-Type"],
108
+ content_length: headers["Content-Length"]&.to_i || body_size,
109
+ cache_control: headers["Cache-Control"],
110
+ has_cookies: !!headers["Set-Cookie"],
111
+ timestamp: Time.now.iso8601
112
+ }
113
+ end
114
+
115
+ def generate_request_insights(request_summary, env)
116
+ # Simple rule-based insights (in production, could use AI provider)
117
+ insights = []
118
+
119
+ # Analyze request patterns
120
+ if request_summary[:query_params_count] > 10
121
+ insights << { type: "performance", message: "High number of query parameters" }
122
+ end
123
+
124
+ if request_summary[:content_length] > 1_000_000
125
+ insights << { type: "performance", message: "Large request body" }
126
+ end
127
+
128
+ if request_summary[:user_agent].nil? || request_summary[:user_agent].empty?
129
+ insights << { type: "security", message: "Missing User-Agent header" }
130
+ end
131
+
132
+ if request_summary[:path].include?("..")
133
+ insights << { type: "security", message: "Potential path traversal attempt" }
134
+ end
135
+
136
+ # Check for API usage patterns
137
+ if request_summary[:path].start_with?("/api/")
138
+ insights << { type: "api", message: "API endpoint access" }
139
+ end
140
+
141
+ insights
142
+ end
143
+
144
+ def generate_cycle_insights(env, response_summary)
145
+ insights = []
146
+ ai_results = env["rack.ai"][:results] || {}
147
+
148
+ # Analyze response patterns
149
+ if response_summary[:status] >= 400
150
+ insights << {
151
+ type: "error",
152
+ message: "Error response: #{response_summary[:status]}",
153
+ severity: response_summary[:status] >= 500 ? "high" : "medium"
154
+ }
155
+ end
156
+
157
+ if response_summary[:content_length] > 5_000_000
158
+ insights << { type: "performance", message: "Large response body" }
159
+ end
160
+
161
+ # Correlate with AI analysis results
162
+ if ai_results[:classification]
163
+ classification = ai_results[:classification][:classification]
164
+ insights << {
165
+ type: "classification",
166
+ message: "Request classified as: #{classification}",
167
+ confidence: ai_results[:classification][:confidence]
168
+ }
169
+ end
170
+
171
+ if ai_results[:security] && ai_results[:security][:threat_level] != :low
172
+ insights << {
173
+ type: "security",
174
+ message: "Security threat detected: #{ai_results[:security][:threat_level]}",
175
+ threats: ai_results[:security][:injection_detection][:threats]
176
+ }
177
+ end
178
+
179
+ insights
180
+ end
181
+
182
+ def log_structured_data(env, response_summary, insights)
183
+ log_entry = {
184
+ timestamp: Time.now.iso8601,
185
+ request: {
186
+ method: env["REQUEST_METHOD"],
187
+ path: env["PATH_INFO"],
188
+ remote_ip: env["REMOTE_ADDR"],
189
+ user_agent: env["HTTP_USER_AGENT"]
190
+ },
191
+ response: {
192
+ status: response_summary[:status],
193
+ content_type: response_summary[:content_type],
194
+ content_length: response_summary[:content_length]
195
+ },
196
+ ai_results: env["rack.ai"][:results],
197
+ insights: insights,
198
+ processing_time: Time.now - env["rack.ai"][:start_time]
199
+ }
200
+
201
+ # In production, this would go to structured logging system
202
+ Utils::Logger.info("AI-Enhanced Request Log", log_entry)
203
+ end
204
+
205
+ def build_sample_traffic_data(time_window)
206
+ # In a real implementation, this would query actual log storage
207
+ # For demo purposes, return sample data structure
208
+ {
209
+ time_window: time_window,
210
+ total_requests: 1500,
211
+ unique_ips: 245,
212
+ status_codes: {
213
+ "200" => 1200,
214
+ "404" => 150,
215
+ "500" => 50,
216
+ "403" => 100
217
+ },
218
+ top_paths: [
219
+ { path: "/api/users", count: 300 },
220
+ { path: "/", count: 250 },
221
+ { path: "/api/posts", count: 200 }
222
+ ],
223
+ user_agents: {
224
+ "browser" => 1000,
225
+ "bot" => 300,
226
+ "api_client" => 200
227
+ },
228
+ geographic_distribution: {
229
+ "US" => 800,
230
+ "EU" => 400,
231
+ "ASIA" => 300
232
+ }
233
+ }
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ module AI
5
+ module Features
6
+ class Moderation
7
+ attr_reader :name, :provider, :config
8
+
9
+ def initialize(provider, config)
10
+ @name = :moderation
11
+ @provider = provider
12
+ @config = config
13
+ end
14
+
15
+ def enabled?
16
+ @config.feature_enabled?(:moderation)
17
+ end
18
+
19
+ def process_response?
20
+ @config.moderation.check_response
21
+ end
22
+
23
+ def process_request(env)
24
+ content = extract_content_from_request(env)
25
+ return { processed: false, reason: "no_content" } if content.empty?
26
+
27
+ result = @provider.moderate_content(content.join(" "))
28
+
29
+ # Apply toxicity threshold
30
+ threshold = @config.moderation.toxicity_threshold
31
+ max_score = result[:category_scores]&.values&.max || 0.0
32
+
33
+ result[:action] = if result[:flagged] && max_score > threshold
34
+ @config.moderation.block_on_violation ? :block : :flag
35
+ else
36
+ :allow
37
+ end
38
+
39
+ # Add metadata
40
+ result[:feature] = @name
41
+ result[:timestamp] = Time.now.iso8601
42
+ result[:content_analyzed] = content.size
43
+ result[:threshold] = threshold
44
+
45
+ result
46
+ end
47
+
48
+ def process_response(env, status, headers, body)
49
+ return { processed: false, reason: "disabled" } unless process_response?
50
+
51
+ content = extract_content_from_response(body)
52
+ return { processed: false, reason: "no_content" } if content.empty?
53
+
54
+ result = @provider.moderate_content(content)
55
+ result[:feature] = "#{@name}_response"
56
+ result[:timestamp] = Time.now.iso8601
57
+
58
+ result
59
+ end
60
+
61
+ private
62
+
63
+ def extract_content_from_request(env)
64
+ content = []
65
+
66
+ # Extract from query parameters
67
+ if env["QUERY_STRING"] && !env["QUERY_STRING"].empty?
68
+ content << env["QUERY_STRING"]
69
+ end
70
+
71
+ # Extract from form data (if POST/PUT)
72
+ if %w[POST PUT PATCH].include?(env["REQUEST_METHOD"])
73
+ input = env["rack.input"]
74
+ if input && input.respond_to?(:read)
75
+ body_content = input.read
76
+ input.rewind if input.respond_to?(:rewind)
77
+ content << body_content unless body_content.empty?
78
+ end
79
+ end
80
+
81
+ # Extract from specific headers that might contain user content
82
+ user_content_headers = %w[HTTP_X_COMMENT HTTP_X_MESSAGE HTTP_X_DESCRIPTION]
83
+ user_content_headers.each do |header|
84
+ content << env[header] if env[header]
85
+ end
86
+
87
+ content.compact.reject(&:empty?)
88
+ end
89
+
90
+ def extract_content_from_response(body)
91
+ return "" unless body.respond_to?(:each)
92
+
93
+ content = ""
94
+ body.each { |chunk| content += chunk.to_s }
95
+
96
+ # Only moderate text content
97
+ return "" unless content.match?(/\A[\s\p{Print}]*\z/)
98
+
99
+ content
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ module AI
5
+ module Features
6
+ class Routing
7
+ attr_reader :name, :provider, :config
8
+
9
+ def initialize(provider, config)
10
+ @name = :routing
11
+ @provider = provider
12
+ @config = config
13
+ end
14
+
15
+ def enabled?
16
+ @config.feature_enabled?(:routing) && @config.config.routing.smart_routing_enabled
17
+ end
18
+
19
+ def process_response?
20
+ false
21
+ end
22
+
23
+ def process_request(env)
24
+ return { processed: false, reason: "disabled" } unless enabled?
25
+
26
+ # Get classification and security results from other features
27
+ ai_results = env["rack.ai"][:results] || {}
28
+ classification = ai_results[:classification]
29
+ security = ai_results[:security]
30
+
31
+ routing_decision = make_routing_decision(classification, security, env)
32
+
33
+ if routing_decision[:should_route]
34
+ # Modify the request path for routing
35
+ original_path = env["PATH_INFO"]
36
+ new_path = routing_decision[:target_path]
37
+
38
+ env["PATH_INFO"] = new_path
39
+ env["rack.ai.original_path"] = original_path
40
+ end
41
+
42
+ {
43
+ routing_decision: routing_decision,
44
+ original_path: env["rack.ai.original_path"],
45
+ new_path: env["PATH_INFO"],
46
+ feature: @name,
47
+ timestamp: Time.now.iso8601
48
+ }
49
+ end
50
+
51
+ private
52
+
53
+ def make_routing_decision(classification, security, env)
54
+ # Default: no routing
55
+ decision = {
56
+ should_route: false,
57
+ target_path: env["PATH_INFO"],
58
+ reason: "no_routing_needed",
59
+ confidence: 1.0
60
+ }
61
+
62
+ # Route based on classification
63
+ if classification
64
+ case classification[:classification]
65
+ when :suspicious
66
+ if classification[:confidence] > 0.8
67
+ decision = {
68
+ should_route: true,
69
+ target_path: @config.config.routing.suspicious_route,
70
+ reason: "suspicious_classification",
71
+ confidence: classification[:confidence]
72
+ }
73
+ end
74
+ when :bot
75
+ if classification[:confidence] > 0.9
76
+ decision = {
77
+ should_route: true,
78
+ target_path: @config.config.routing.bot_route,
79
+ reason: "bot_classification",
80
+ confidence: classification[:confidence]
81
+ }
82
+ end
83
+ when :spam
84
+ decision = {
85
+ should_route: true,
86
+ target_path: "/blocked",
87
+ reason: "spam_classification",
88
+ confidence: classification[:confidence]
89
+ }
90
+ end
91
+ end
92
+
93
+ # Override with security-based routing
94
+ if security && security[:threat_level] == :high
95
+ decision = {
96
+ should_route: true,
97
+ target_path: "/security/blocked",
98
+ reason: "high_security_threat",
99
+ confidence: 1.0
100
+ }
101
+ elsif security && security[:threat_level] == :medium
102
+ decision = {
103
+ should_route: true,
104
+ target_path: "/security/verify",
105
+ reason: "medium_security_threat",
106
+ confidence: 0.8
107
+ }
108
+ end
109
+
110
+ # Add load balancing logic
111
+ if should_load_balance?(env)
112
+ decision = apply_load_balancing(decision, env)
113
+ end
114
+
115
+ decision
116
+ end
117
+
118
+ def should_load_balance?(env)
119
+ # Simple load balancing based on request patterns
120
+ path = env["PATH_INFO"]
121
+ method = env["REQUEST_METHOD"]
122
+
123
+ # Load balance API endpoints
124
+ path.start_with?("/api/") && method == "GET"
125
+ end
126
+
127
+ def apply_load_balancing(decision, env)
128
+ # Simple round-robin or least-connections logic
129
+ # In production, this would integrate with actual load balancer
130
+
131
+ servers = ["server1", "server2", "server3"]
132
+ selected_server = servers.sample # Random for demo
133
+
134
+ decision.merge({
135
+ load_balanced: true,
136
+ target_server: selected_server,
137
+ reason: "#{decision[:reason]}_with_load_balancing"
138
+ })
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end