coolhand 0.1.5 → 0.3.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +14 -1
- data/CHANGELOG.md +99 -1
- data/CLAUDE.md +34 -0
- data/README.md +165 -46
- data/coolhand-ruby.gemspec +10 -6
- data/docs/anthropic.md +518 -0
- data/docs/elevenlabs.md +6 -4
- data/lib/coolhand/api_service.rb +264 -0
- data/lib/coolhand/base_interceptor.rb +213 -0
- data/lib/coolhand/collector.rb +19 -0
- data/lib/coolhand/configuration.rb +84 -0
- data/lib/coolhand/default_exclude_api_patterns.yml +9 -0
- data/lib/coolhand/default_intercept_addresses.yml +15 -0
- data/lib/coolhand/feedback_service.rb +15 -0
- data/lib/coolhand/logger_service.rb +112 -0
- data/lib/coolhand/net_http_interceptor.rb +163 -0
- data/lib/coolhand/open_ai/batch_result_processor.rb +139 -0
- data/lib/coolhand/open_ai/webhook_validator.rb +127 -0
- data/lib/coolhand/{ruby/version.rb → version.rb} +1 -3
- data/lib/coolhand/vertex/batch_result_processor.rb +84 -0
- data/lib/coolhand/webhook_interceptor.rb +39 -0
- data/lib/coolhand.rb +109 -2
- metadata +41 -19
- data/lib/coolhand/ruby/api_service.rb +0 -211
- data/lib/coolhand/ruby/collector.rb +0 -21
- data/lib/coolhand/ruby/configuration.rb +0 -38
- data/lib/coolhand/ruby/feedback_service.rb +0 -17
- data/lib/coolhand/ruby/interceptor.rb +0 -119
- data/lib/coolhand/ruby/logger_service.rb +0 -114
- data/lib/coolhand/ruby.rb +0 -90
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "api_service"
|
|
4
|
-
|
|
5
|
-
module Coolhand
|
|
6
|
-
module Ruby
|
|
7
|
-
class LoggerService < ApiService
|
|
8
|
-
def initialize
|
|
9
|
-
super("v2/llm_request_logs")
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def log_to_api(captured_data)
|
|
13
|
-
create_log(captured_data, "auto-monitor")
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Helper method for forwarding webhook data to Coolhand
|
|
17
|
-
def forward_webhook(webhook_body:, source:, event_type: nil, headers: {}, **options)
|
|
18
|
-
# Validate required parameters
|
|
19
|
-
unless Coolhand.required_field?(webhook_body)
|
|
20
|
-
error_msg = "webhook_body is required and cannot be nil or empty"
|
|
21
|
-
if Coolhand.configuration.silent
|
|
22
|
-
puts "COOLHAND WARNING: #{error_msg}"
|
|
23
|
-
return false
|
|
24
|
-
else
|
|
25
|
-
raise ArgumentError, error_msg
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
unless Coolhand.required_field?(source)
|
|
30
|
-
error_msg = "source is required and cannot be nil or empty"
|
|
31
|
-
if Coolhand.configuration.silent
|
|
32
|
-
puts "COOLHAND WARNING: #{error_msg}"
|
|
33
|
-
return false
|
|
34
|
-
else
|
|
35
|
-
raise ArgumentError, error_msg
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Auto-generate required fields unless provided
|
|
40
|
-
webhook_data = {
|
|
41
|
-
id: options[:id] || SecureRandom.uuid,
|
|
42
|
-
timestamp: options[:timestamp] || Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%6NZ"),
|
|
43
|
-
method: options[:method] || "POST",
|
|
44
|
-
url: options[:url] || build_webhook_url(source, event_type),
|
|
45
|
-
headers: sanitize_headers(headers),
|
|
46
|
-
request_body: clean_webhook_body(webhook_body, source),
|
|
47
|
-
response_body: options[:response_body],
|
|
48
|
-
response_headers: options[:response_headers],
|
|
49
|
-
status_code: options[:status_code] || 200,
|
|
50
|
-
source: "#{source}_webhook"
|
|
51
|
-
}.merge(options.slice(:metadata, :conversation_id, :agent_id))
|
|
52
|
-
|
|
53
|
-
# Send to API asynchronously
|
|
54
|
-
log_to_api(webhook_data)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
private
|
|
58
|
-
|
|
59
|
-
def build_webhook_url(source, event_type)
|
|
60
|
-
base = "webhook://#{source}"
|
|
61
|
-
event_type ? "#{base}/#{event_type}" : base
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def sanitize_headers(headers)
|
|
65
|
-
return {} if headers.nil?
|
|
66
|
-
return {} if headers.respond_to?(:empty?) && headers.empty?
|
|
67
|
-
return {} unless headers.respond_to?(:each)
|
|
68
|
-
|
|
69
|
-
# Handle Rails request headers or plain hash
|
|
70
|
-
clean_headers = {}
|
|
71
|
-
headers.each do |key, value|
|
|
72
|
-
next unless key && value
|
|
73
|
-
|
|
74
|
-
# Convert Rails HTTP_ prefix headers
|
|
75
|
-
clean_key = key.to_s.gsub(/^HTTP_/, "").tr("_", "-").downcase
|
|
76
|
-
|
|
77
|
-
# Redact sensitive headers
|
|
78
|
-
clean_value = clean_key.match?(/key|token|secret|authorization|signature/i) ? "[REDACTED]" : value.to_s
|
|
79
|
-
clean_headers[clean_key] = clean_value
|
|
80
|
-
end
|
|
81
|
-
clean_headers
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def clean_webhook_body(body, source)
|
|
85
|
-
# Service-specific binary field filters
|
|
86
|
-
binary_fields = case source.to_s.downcase
|
|
87
|
-
when "elevenlabs"
|
|
88
|
-
%w[full_audio audio audio_data raw_audio audio_base64 voice_sample audio_url]
|
|
89
|
-
when "twilio"
|
|
90
|
-
%w[recording_url media_url]
|
|
91
|
-
else
|
|
92
|
-
%w[audio_data image_data file_content binary_content]
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
remove_binary_fields(body, binary_fields)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def remove_binary_fields(obj, fields_to_remove)
|
|
99
|
-
case obj
|
|
100
|
-
when Hash
|
|
101
|
-
obj.each_with_object({}) do |(key, value), cleaned|
|
|
102
|
-
next if fields_to_remove.any? { |field| key.to_s.downcase.include?(field) }
|
|
103
|
-
|
|
104
|
-
cleaned[key] = remove_binary_fields(value, fields_to_remove)
|
|
105
|
-
end
|
|
106
|
-
when Array
|
|
107
|
-
obj.map { |item| remove_binary_fields(item, fields_to_remove) }
|
|
108
|
-
else
|
|
109
|
-
obj
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|
data/lib/coolhand/ruby.rb
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "net/http"
|
|
4
|
-
require "uri"
|
|
5
|
-
require "faraday"
|
|
6
|
-
require "securerandom"
|
|
7
|
-
|
|
8
|
-
require_relative "ruby/version"
|
|
9
|
-
require_relative "ruby/configuration"
|
|
10
|
-
require_relative "ruby/collector"
|
|
11
|
-
require_relative "ruby/interceptor"
|
|
12
|
-
require_relative "ruby/api_service"
|
|
13
|
-
require_relative "ruby/logger_service"
|
|
14
|
-
require_relative "ruby/feedback_service"
|
|
15
|
-
|
|
16
|
-
# The main module for the Coolhand gem.
|
|
17
|
-
# It provides the configuration interface and initializes the patching.
|
|
18
|
-
module Coolhand
|
|
19
|
-
class Error < StandardError; end
|
|
20
|
-
|
|
21
|
-
# Class-level instance variables to hold the configuration
|
|
22
|
-
@configuration = Configuration.new
|
|
23
|
-
|
|
24
|
-
class << self
|
|
25
|
-
attr_reader :configuration
|
|
26
|
-
|
|
27
|
-
# Reset configuration to defaults (mainly for testing)
|
|
28
|
-
def reset_configuration!
|
|
29
|
-
@configuration = Configuration.new
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Provides a block to configure the gem.
|
|
33
|
-
#
|
|
34
|
-
# Example:
|
|
35
|
-
# Coolhand.configure do |config|
|
|
36
|
-
# config.environment = 'development'
|
|
37
|
-
# config.silent = false
|
|
38
|
-
# config.api_key = "xxx-yyy-zzz"
|
|
39
|
-
# config.intercept_addresses = ["openai.com"]
|
|
40
|
-
# end
|
|
41
|
-
def configure
|
|
42
|
-
yield(configuration)
|
|
43
|
-
|
|
44
|
-
configuration.validate!
|
|
45
|
-
|
|
46
|
-
# Apply the patch after configuration is set
|
|
47
|
-
Interceptor.patch!
|
|
48
|
-
|
|
49
|
-
log "✅ Coolhand ready - will log OpenAI calls"
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def capture
|
|
53
|
-
unless block_given?
|
|
54
|
-
log "❌ Coolhand Error: Method .capture requires block."
|
|
55
|
-
return
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
Interceptor.patch!
|
|
59
|
-
|
|
60
|
-
yield
|
|
61
|
-
ensure
|
|
62
|
-
Interceptor.unpatch!
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# A simple logger that respects the 'silent' configuration option.
|
|
66
|
-
def log(message)
|
|
67
|
-
return if configuration.silent
|
|
68
|
-
|
|
69
|
-
puts "COOLHAND: #{message}"
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Creates a new FeedbackService instance
|
|
73
|
-
def feedback_service
|
|
74
|
-
Ruby::FeedbackService.new
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Creates a new LoggerService instance
|
|
78
|
-
def logger_service
|
|
79
|
-
Ruby::LoggerService.new
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def required_field?(value)
|
|
83
|
-
return false if value.nil?
|
|
84
|
-
return false if value.respond_to?(:empty?) && value.empty?
|
|
85
|
-
return false if value.to_s.strip.empty?
|
|
86
|
-
|
|
87
|
-
true
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|