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.
@@ -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