Class: Crystil::Sentinel

Inherits:
Object
  • Object
show all
Defined in:
lib/crystil/sentinel.rb,
sig/crystil/sentinel.rbs

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Sentinel

Returns a new instance of Sentinel.

Parameters:



5
6
7
# File 'lib/crystil/sentinel.rb', line 5

def initialize(config)
  @config = config
end

Instance Method Details

#make_relevance_intercept_request(title:, request:, provider: nil, version: nil) ⇒ [bool, String?]

Parameters:

  • title: (String, nil)
  • request: (Hash[Symbol, untyped])
  • provider: (String, nil) (defaults to: nil)
  • version: (String, nil) (defaults to: nil)

Returns:

  • ([bool, String?])


46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/crystil/sentinel.rb', line 46

def make_relevance_intercept_request(title:, request:, provider: nil, version: nil)
  payload = {
    attribution: @config.attribution&.to_h,
    conversation: {
      client: {
        provider: provider,
        title: title,
        version: version
      },
      request: normalize_request(request)
    },
    meta: {
      sdk: {
        client: "ruby",
        version: @config.version
      }
    }
  }

  api = API::Sentinel.new(@config.api_url, @config.api_key, @config.secs_irrelevant_request_timeout)
  response = api.relevance_intercept(payload) || {}

  relevant = response.key?("relevant") ? response["relevant"] : true
  reason = relevant ? nil : (response["reason"] || "Irrelevant request blocked.")

  [relevant, reason]
end

#normalize_request(request) ⇒ Object

Parameters:

  • request (Hash[Symbol, untyped])

Returns:

  • (Object)


74
75
76
77
78
79
80
81
82
83
84
# File 'lib/crystil/sentinel.rb', line 74

def normalize_request(request)
  # Remove Procs before JSON creation since the
  # procs or methods will fail for normalization.
  # We know that OpenAI sends a proc on their
  # request, so this is mostly for that.
  cleaned = remove_procs(request)
  json_obj = JSON.generate(cleaned)
  JSON.parse(json_obj)
rescue StandardError
  request
end

#raise_if_irrelevant(enabled: true) ⇒ self

Parameters:

  • enabled: (Boolean) (defaults to: true)

Returns:

  • (self)

Raises:

  • (TypeError)


9
10
11
12
13
14
# File 'lib/crystil/sentinel.rb', line 9

def raise_if_irrelevant(enabled: true)
  raise TypeError, "enabled must be a bool" unless [true, false].include?(enabled)

  @config.raise_if_irrelevant = enabled
  self
end

#raise_if_irrelevant!(title:, request:, provider: nil, version: nil) ⇒ void

This method returns an undefined value.

Parameters:

  • title: (String, nil)
  • request: (Hash[Symbol, untyped])
  • provider: (String, nil) (defaults to: nil)
  • version: (String, nil) (defaults to: nil)

Raises:



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/crystil/sentinel.rb', line 27

def raise_if_irrelevant!(title:, request:, provider: nil, version: nil)
  return unless @config.raise_if_irrelevant

  begin
    relevant, reason = make_relevance_intercept_request(
      title: title,
      request: request,
      provider: provider,
      version: version
    )
  rescue StandardError
    return nil
  end

  raise CrystilRequestInterceptedError, (reason || "Irrelevant request blocked.") unless relevant
end

#remove_procs(obj) ⇒ Object

Parameters:

  • obj (Object)

Returns:

  • (Object)


86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/crystil/sentinel.rb', line 86

def remove_procs(obj)
  case obj
  when Hash
    obj.transform_values { |v| remove_procs(v) }
  when Array
    obj.map { |v| remove_procs(v) }
  when Proc, Method
    true
  else
    # Return true object for any callable object; otherwise return obj.
    obj.respond_to?(:call) || obj
  end
end

#set_secs_irrelevant_request_timeout(timeout) ⇒ self

Intentional explicit set_* verb prefix — matches JS/Python SDK naming so callers porting code between SDKs see the same surface. See CLAUDE.md "Python SDK is the source of truth" / known divergences.

Parameters:

  • timeout (Numeric)

Returns:

  • (self)

Raises:

  • (TypeError)


19
20
21
22
23
24
25
# File 'lib/crystil/sentinel.rb', line 19

def set_secs_irrelevant_request_timeout(timeout) # rubocop:disable Naming/AccessorMethodName
  raise TypeError, "timeout must be a Numeric" unless timeout.is_a?(Numeric)
  raise ArgumentError, "timeout must be greater than 0" unless timeout.positive?

  @config.secs_irrelevant_request_timeout = timeout
  self
end