Class: RailsConsoleAi::Agent

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/rails_console_ai/agent.rb

Constant Summary collapse

STATUS_PROPOSED =
'proposed'.freeze
STATUS_APPROVED =
'approved'.freeze
STATUSES =
[STATUS_PROPOSED, STATUS_APPROVED].freeze
CONTENT_ATTRIBUTES =

Attributes that, if changed, invalidate the current approval and revert the agent back to “proposed”. Status / approver columns are excluded so that an explicit approve! call doesn’t reset its own approval.

%w[name description body max_rounds model tools].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.connectionObject



29
30
31
32
33
34
35
36
37
# File 'app/models/rails_console_ai/agent.rb', line 29

def self.connection
  klass = RailsConsoleAi.configuration.connection_class
  if klass
    klass = Object.const_get(klass) if klass.is_a?(String)
    klass.connection
  else
    super
  end
end

.decode_json_array(raw) ⇒ Object



109
110
111
112
113
114
115
# File 'app/models/rails_console_ai/agent.rb', line 109

def self.decode_json_array(raw)
  return [] if raw.nil? || (raw.respond_to?(:empty?) && raw.empty?)
  return raw if raw.is_a?(Array)
  JSON.parse(raw)
rescue JSON::ParserError
  []
end

.encode_json_array(value) ⇒ Object



117
118
119
# File 'app/models/rails_console_ai/agent.rb', line 117

def self.encode_json_array(value)
  JSON.dump(Array(value))
end

.record_use!(id) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
# File 'app/models/rails_console_ai/agent.rb', line 74

def self.record_use!(id)
  return false unless connection.column_exists?(table_name, :use_count)
  where(id: id).update_all([
    'use_count = COALESCE(use_count, 0) + 1, last_used_at = ?',
    Time.now.utc
  ])
  true
rescue ::ActiveRecord::ActiveRecordError => e
  RailsConsoleAi.logger.warn("RailsConsoleAi::Agent.record_use!(#{id.inspect}) failed: #{e.message}")
  false
end

Instance Method Details

#approve!(approved_by:) ⇒ Object

Raises:

  • (ArgumentError)


154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'app/models/rails_console_ai/agent.rb', line 154

def approve!(approved_by:)
  raise ArgumentError, 'approved_by is required' if approved_by.to_s.strip.empty?

  update_with_version!(
    {
      status:      STATUS_APPROVED,
      approved_by: approved_by,
      approved_at: Time.now.utc
    },
    edited_by: approved_by,
    change_note: "Approved by #{approved_by}",
    preserve_approval: true
  )
end

#approved?Boolean

Returns:

  • (Boolean)


64
# File 'app/models/rails_console_ai/agent.rb', line 64

def approved?; status.to_s == STATUS_APPROVED; end

#approved_atObject



59
60
61
# File 'app/models/rails_console_ai/agent.rb', line 59

def approved_at
  has_attribute?(:approved_at) ? read_attribute(:approved_at) : nil
end

#approved_byObject



55
56
57
# File 'app/models/rails_console_ai/agent.rb', line 55

def approved_by
  has_attribute?(:approved_by) ? read_attribute(:approved_by) : nil
end

#decode_json_array(raw) ⇒ Object



121
# File 'app/models/rails_console_ai/agent.rb', line 121

def decode_json_array(raw); self.class.decode_json_array(raw); end

#encode_json_array(value) ⇒ Object



122
# File 'app/models/rails_console_ai/agent.rb', line 122

def encode_json_array(value); self.class.encode_json_array(value); end

#has_attribute_status?Boolean

Returns:

  • (Boolean)


105
106
107
# File 'app/models/rails_console_ai/agent.rb', line 105

def has_attribute_status?
  has_attribute?(:status)
end

#last_used_atObject



70
71
72
# File 'app/models/rails_console_ai/agent.rb', line 70

def last_used_at
  has_attribute?(:last_used_at) ? read_attribute(:last_used_at) : nil
end

#proposed?Boolean

Returns:

  • (Boolean)


63
# File 'app/models/rails_console_ai/agent.rb', line 63

def proposed?; status.to_s == STATUS_PROPOSED; end

#statusObject

Defensive accessors — if ‘ai_db_migrate` hasn’t been run yet, the status / approval columns may be missing on an older table.



51
52
53
# File 'app/models/rails_console_ai/agent.rb', line 51

def status
  has_attribute_status? ? read_attribute(:status) : STATUS_PROPOSED
end

#to_hashObject



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'app/models/rails_console_ai/agent.rb', line 86

def to_hash
  {
    'id'           => id,
    'name'         => name,
    'description'  => description,
    'body'         => body,
    'max_rounds'   => max_rounds,
    'model'        => model,
    'tools'        => tools,
    'status'       => status,
    'approved_by'  => approved_by,
    'approved_at'  => approved_at,
    'use_count'    => use_count,
    'last_used_at' => last_used_at,
    'source'       => :db,
    'updated_at'   => updated_at
  }
end

#toolsObject

Manual JSON accessor for ‘tools` — same approach we use for skill tags, avoids Rails-version-specific `serialize` API.



41
42
43
# File 'app/models/rails_console_ai/agent.rb', line 41

def tools
  decode_json_array(read_attribute(:tools))
end

#tools=(value) ⇒ Object



45
46
47
# File 'app/models/rails_console_ai/agent.rb', line 45

def tools=(value)
  write_attribute(:tools, encode_json_array(value))
end

#update_with_version!(attrs, edited_by: nil, change_note: nil, preserve_approval: false) ⇒ Object

Assigns attrs, saves, and records one AgentVersion snapshot of the post-save state. If ‘preserve_approval` is false (the default), any change to a content attribute reverts the agent back to “proposed” and clears the approver.



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'app/models/rails_console_ai/agent.rb', line 127

def update_with_version!(attrs, edited_by: nil, change_note: nil, preserve_approval: false)
  transaction do
    assign_attributes(attrs)

    if !preserve_approval && approved? && content_dirty?
      self.status      = STATUS_PROPOSED
      self.approved_by = nil
      self.approved_at = nil
    end

    save!
    RailsConsoleAi::AgentVersion.create!(
      agent_id:    id,
      name:        name,
      description: description,
      body:        body,
      max_rounds:  max_rounds,
      model:       model,
      tools:       tools,
      status:      status,
      edited_by:   edited_by,
      change_note: change_note
    )
  end
  self
end

#use_countObject



66
67
68
# File 'app/models/rails_console_ai/agent.rb', line 66

def use_count
  has_attribute?(:use_count) ? (read_attribute(:use_count) || 0) : 0
end