activeagent 1.0.3 → 1.2.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +132 -0
  3. data/README.md +39 -3
  4. data/lib/active_agent/base.rb +4 -0
  5. data/lib/active_agent/model_capabilities.rb +89 -0
  6. data/lib/active_agent/providers/_base_provider.rb +23 -2
  7. data/lib/active_agent/providers/concerns/exception_handler.rb +12 -1
  8. data/lib/active_agent/providers/errors.rb +140 -0
  9. data/lib/active_agent/providers/ollama/chat/transforms.rb +9 -3
  10. data/lib/active_agent/railtie.rb +5 -0
  11. data/lib/active_agent/telemetry/configuration.rb +115 -171
  12. data/lib/active_agent/telemetry/instrumentation.rb +141 -14
  13. data/lib/active_agent/telemetry/reporter.rb +12 -165
  14. data/lib/active_agent/telemetry/span.rb +18 -245
  15. data/lib/active_agent/telemetry/tool_origin.rb +90 -0
  16. data/lib/active_agent/telemetry/tracer.rb +67 -70
  17. data/lib/active_agent/telemetry.rb +2 -2
  18. data/lib/active_agent/version.rb +1 -1
  19. data/lib/active_agent.rb +1 -1
  20. metadata +38 -42
  21. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/api/traces_controller.rb +0 -117
  22. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/application_controller.rb +0 -54
  23. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/dashboard_controller.rb +0 -126
  24. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/traces_controller.rb +0 -103
  25. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/agent_execution_job.rb +0 -56
  26. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/application_job.rb +0 -14
  27. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_cleanup_job.rb +0 -49
  28. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_provision_job.rb +0 -65
  29. data/lib/active_agent/dashboard/app/jobs/active_agent/process_telemetry_traces_job.rb +0 -77
  30. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent.rb +0 -256
  31. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_run.rb +0 -113
  32. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_template.rb +0 -208
  33. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_version.rb +0 -60
  34. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/application_record.rb +0 -46
  35. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_action.rb +0 -125
  36. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_snapshot.rb +0 -83
  37. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_run.rb +0 -52
  38. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_session.rb +0 -169
  39. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/session_recording.rb +0 -193
  40. data/lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb +0 -198
  41. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/_trace_detail.html.erb +0 -105
  42. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/index.html.erb +0 -135
  43. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/metrics.html.erb +0 -143
  44. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/show.html.erb +0 -36
  45. data/lib/active_agent/dashboard/app/views/layouts/active_agent/dashboard/application.html.erb +0 -94
  46. data/lib/active_agent/dashboard/config/routes.rb +0 -78
  47. data/lib/active_agent/dashboard/engine.rb +0 -39
  48. data/lib/active_agent/dashboard.rb +0 -151
  49. data/lib/generators/active_agent/dashboard/install/install_generator.rb +0 -96
  50. data/lib/generators/active_agent/dashboard/install/templates/initializer.rb +0 -89
  51. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_runs.rb +0 -42
  52. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_templates.rb +0 -38
  53. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_versions.rb +0 -22
  54. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agents.rb +0 -53
  55. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_sandbox_runs.rb +0 -28
  56. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_sandbox_sessions.rb +0 -43
  57. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_session_recordings.rb +0 -44
  58. data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_telemetry_traces.rb +0 -56
  59. data/lib/generators/active_agent/dashboard/install_generator.rb +0 -64
  60. data/lib/generators/active_agent/dashboard/templates/active_agent_dashboard.rb.erb +0 -30
  61. data/lib/generators/active_agent/dashboard/templates/create_active_agent_telemetry_traces.rb.erb +0 -30
@@ -1,103 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Controller for viewing telemetry traces.
6
- #
7
- # Provides:
8
- # - List view with filtering and pagination
9
- # - Detail view with span timeline
10
- # - Metrics overview
11
- # - Live updates via Turbo Streams
12
- class TracesController < ApplicationController
13
- def index
14
- @traces = fetch_traces
15
- @metrics = calculate_metrics
16
-
17
- respond_to do |format|
18
- format.html
19
- format.turbo_stream
20
- end
21
- end
22
-
23
- def show
24
- @trace = ActiveAgent::TelemetryTrace.find(params[:id])
25
- end
26
-
27
- def metrics
28
- @metrics = calculate_metrics
29
- @agent_stats = agent_statistics
30
- @time_series = time_series_data
31
-
32
- respond_to do |format|
33
- format.html
34
- format.turbo_stream
35
- end
36
- end
37
-
38
- private
39
-
40
- def fetch_traces
41
- traces = ActiveAgent::TelemetryTrace.recent
42
-
43
- traces = traces.for_agent(params[:agent]) if params[:agent].present?
44
- traces = traces.with_errors if params[:status] == "error"
45
- traces = traces.for_service(params[:service]) if params[:service].present?
46
-
47
- if params[:start_date].present? && params[:end_date].present?
48
- traces = traces.for_date_range(
49
- Time.parse(params[:start_date]),
50
- Time.parse(params[:end_date])
51
- )
52
- end
53
-
54
- traces.limit(params[:limit] || 50)
55
- end
56
-
57
- def calculate_metrics
58
- traces = ActiveAgent::TelemetryTrace.where(
59
- "created_at > ?", 24.hours.ago
60
- )
61
-
62
- {
63
- total_traces: traces.count,
64
- total_tokens: traces.sum(:total_input_tokens) + traces.sum(:total_output_tokens),
65
- avg_duration_ms: traces.average(:total_duration_ms)&.round(2) || 0,
66
- error_rate: calculate_error_rate(traces),
67
- unique_agents: traces.distinct.count(:agent_class)
68
- }
69
- end
70
-
71
- def calculate_error_rate(traces)
72
- total = traces.count
73
- return 0.0 if total.zero?
74
-
75
- errors = traces.with_errors.count
76
- ((errors.to_f / total) * 100).round(2)
77
- end
78
-
79
- def agent_statistics
80
- ActiveAgent::TelemetryTrace
81
- .where("created_at > ?", 24.hours.ago)
82
- .group(:agent_class)
83
- .select(
84
- "agent_class",
85
- "COUNT(*) as trace_count",
86
- "SUM(total_input_tokens + total_output_tokens) as total_tokens",
87
- "AVG(total_duration_ms) as avg_duration",
88
- "SUM(CASE WHEN status = 'ERROR' THEN 1 ELSE 0 END) as error_count"
89
- )
90
- end
91
-
92
- def time_series_data
93
- ActiveAgent::TelemetryTrace
94
- .where("created_at > ?", 1.hour.ago)
95
- .group_by_minute(:created_at)
96
- .count
97
- rescue NoMethodError
98
- # Fallback if groupdate gem not available
99
- {}
100
- end
101
- end
102
- end
103
- end
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Background job for executing agent runs.
6
- #
7
- # Handles the actual execution of an agent with the given input,
8
- # updating the AgentRun record with results.
9
- #
10
- class AgentExecutionJob < ApplicationJob
11
- queue_as :default
12
-
13
- def perform(agent_run_id)
14
- run = AgentRun.find(agent_run_id)
15
- return if run.finished?
16
-
17
- run.update!(status: :running, started_at: Time.current)
18
-
19
- begin
20
- agent = run.agent
21
- result = execute_agent(agent, run.input_prompt, **(run.input_params || {}))
22
-
23
- run.update!(
24
- output: result[:output],
25
- output_metadata: result[:metadata],
26
- status: :complete,
27
- completed_at: Time.current,
28
- duration_ms: ((Time.current - run.started_at) * 1000).to_i,
29
- input_tokens: result.dig(:usage, :input_tokens),
30
- output_tokens: result.dig(:usage, :output_tokens),
31
- total_tokens: result.dig(:usage, :total_tokens)
32
- )
33
- rescue => e
34
- run.update!(
35
- status: :failed,
36
- completed_at: Time.current,
37
- error_message: e.message,
38
- error_backtrace: e.backtrace&.first(10)&.join("\n")
39
- )
40
- end
41
- end
42
-
43
- private
44
-
45
- def execute_agent(agent, input_prompt, **params)
46
- # TODO: Build and execute actual ActiveAgent::Base subclass
47
- # For now, return mock data
48
- {
49
- output: "Executed: #{input_prompt}",
50
- metadata: { provider: agent.provider, model: agent.model },
51
- usage: { input_tokens: 100, output_tokens: 200, total_tokens: 300 }
52
- }
53
- end
54
- end
55
- end
56
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Base class for all Dashboard engine jobs.
6
- class ApplicationJob < ActiveJob::Base
7
- # Retry failed jobs
8
- retry_on StandardError, wait: :polynomially_longer, attempts: 3
9
-
10
- # Discard jobs for records that no longer exist
11
- discard_on ActiveRecord::RecordNotFound
12
- end
13
- end
14
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Background job for cleaning up sandbox environments.
6
- #
7
- # Removes containers, Cloud Run jobs, or other resources
8
- # when a sandbox session expires.
9
- #
10
- class SandboxCleanupJob < ApplicationJob
11
- queue_as :default
12
-
13
- def perform(sandbox_session_id)
14
- session = SandboxSession.find_by(id: sandbox_session_id)
15
- return unless session
16
-
17
- cleanup_sandbox(session)
18
- end
19
-
20
- private
21
-
22
- def cleanup_sandbox(session)
23
- case ActiveAgent::Dashboard.sandbox_service
24
- when :cloud_run
25
- cleanup_cloud_run(session)
26
- when :kubernetes
27
- cleanup_kubernetes(session)
28
- else
29
- cleanup_local(session)
30
- end
31
- end
32
-
33
- def cleanup_local(session)
34
- # Local mode: Nothing to clean up
35
- Rails.logger.info "[ActiveAgent::Dashboard] Cleaned up local sandbox: #{session.session_id}"
36
- end
37
-
38
- def cleanup_cloud_run(session)
39
- # TODO: Implement Cloud Run cleanup
40
- Rails.logger.info "[ActiveAgent::Dashboard] Would clean up Cloud Run job: #{session.cloud_run_job_id}"
41
- end
42
-
43
- def cleanup_kubernetes(session)
44
- # TODO: Implement Kubernetes cleanup
45
- Rails.logger.info "[ActiveAgent::Dashboard] Would clean up Kubernetes pod: #{session.cloud_run_job_id}"
46
- end
47
- end
48
- end
49
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Background job for provisioning sandbox environments.
6
- #
7
- # Creates isolated execution environments (Docker, Cloud Run, etc.)
8
- # for running agents with tools.
9
- #
10
- class SandboxProvisionJob < ApplicationJob
11
- queue_as :default
12
-
13
- def perform(sandbox_session_id)
14
- session = SandboxSession.find(sandbox_session_id)
15
- return unless session.provisioning?
16
-
17
- begin
18
- result = provision_sandbox(session)
19
-
20
- session.mark_ready!(
21
- sandbox_url: result[:url],
22
- sandbox_job_id: result[:job_id]
23
- )
24
- rescue => e
25
- session.update!(
26
- status: :failed,
27
- error_message: e.message
28
- )
29
- end
30
- end
31
-
32
- private
33
-
34
- def provision_sandbox(session)
35
- case ActiveAgent::Dashboard.sandbox_service
36
- when :cloud_run
37
- provision_cloud_run(session)
38
- when :kubernetes
39
- provision_kubernetes(session)
40
- else
41
- provision_local(session)
42
- end
43
- end
44
-
45
- def provision_local(session)
46
- # Local mode: No actual provisioning needed
47
- # The sandbox runs in the same process or via Docker
48
- {
49
- url: "http://localhost:#{3000 + session.id}",
50
- job_id: "local-#{session.session_id}"
51
- }
52
- end
53
-
54
- def provision_cloud_run(session)
55
- # TODO: Implement Cloud Run provisioning
56
- raise NotImplementedError, "Cloud Run provisioning not yet implemented in engine"
57
- end
58
-
59
- def provision_kubernetes(session)
60
- # TODO: Implement Kubernetes provisioning
61
- raise NotImplementedError, "Kubernetes provisioning not yet implemented in engine"
62
- end
63
- end
64
- end
65
- end
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- # Processes telemetry traces received from ActiveAgent clients.
5
- #
6
- # This job handles the asynchronous processing of trace data to avoid
7
- # blocking the ingestion endpoint. It:
8
- # - Creates TelemetryTrace records for each trace
9
- # - Updates aggregate statistics
10
- # - Handles any errors gracefully
11
- #
12
- # @example Local mode
13
- # ActiveAgent::ProcessTelemetryTracesJob.perform_later(
14
- # traces: [...],
15
- # sdk_info: { name: "activeagent", version: "0.5.0" },
16
- # received_at: "2024-01-15T10:30:00Z"
17
- # )
18
- #
19
- # @example Multi-tenant mode
20
- # ActiveAgent::ProcessTelemetryTracesJob.perform_later(
21
- # account_id: 1,
22
- # traces: [...],
23
- # sdk_info: { name: "activeagent", version: "0.5.0" },
24
- # received_at: "2024-01-15T10:30:00Z"
25
- # )
26
- #
27
- class ProcessTelemetryTracesJob < ::ActiveJob::Base
28
- queue_as :default
29
-
30
- # Maximum traces to process in a single job to avoid memory issues
31
- MAX_TRACES_PER_JOB = 100
32
-
33
- def perform(account_id: nil, traces:, sdk_info:, received_at:)
34
- account = resolve_account(account_id)
35
-
36
- # In multi-tenant mode, require an account
37
- if ActiveAgent::Dashboard.multi_tenant? && account.nil?
38
- Rails.logger.warn("[ProcessTelemetryTracesJob] Skipping traces - no valid account")
39
- return
40
- end
41
-
42
- traces = traces.take(MAX_TRACES_PER_JOB)
43
-
44
- traces.each do |trace|
45
- process_trace(trace, sdk_info, account)
46
- rescue StandardError => e
47
- Rails.logger.error(
48
- "[ProcessTelemetryTracesJob] Failed to process trace #{trace['trace_id']}: " \
49
- "#{e.class} - #{e.message}"
50
- )
51
- end
52
- end
53
-
54
- private
55
-
56
- def resolve_account(account_id)
57
- return nil unless ActiveAgent::Dashboard.multi_tenant?
58
- return nil unless account_id
59
-
60
- account_class = ActiveAgent::Dashboard.account_class.constantize
61
- account_class.find_by(id: account_id)
62
- end
63
-
64
- def process_trace(trace, sdk_info, account)
65
- model = ActiveAgent::Dashboard.trace_model
66
-
67
- # Build uniqueness scope
68
- scope = model.where(trace_id: trace["trace_id"])
69
- scope = scope.where(account: account) if ActiveAgent::Dashboard.multi_tenant? && account
70
-
71
- # Skip if trace already exists (idempotency)
72
- return if scope.exists?
73
-
74
- model.create_from_payload(trace, sdk_info, account: account)
75
- end
76
- end
77
- end
@@ -1,256 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Represents an AI agent configuration.
6
- #
7
- # Agents are the core entity in the dashboard, storing all configuration
8
- # needed to execute AI interactions including provider settings, instructions,
9
- # tools, and appearance.
10
- #
11
- # Supports both local (single-user) and multi-tenant (account-scoped) modes.
12
- #
13
- # @example Creating an agent
14
- # agent = ActiveAgent::Dashboard::Agent.create!(
15
- # name: "Code Assistant",
16
- # provider: "openai",
17
- # model: "gpt-4o"
18
- # )
19
- #
20
- # @example Executing an agent
21
- # run = agent.execute("Explain this code", code: "def foo; end")
22
- #
23
- class Agent < ApplicationRecord
24
- # Associations - owner is optional to support both modes
25
- belongs_to :user, class_name: ActiveAgent::Dashboard.user_class, optional: true if ActiveAgent::Dashboard.user_class
26
- belongs_to :account, class_name: ActiveAgent::Dashboard.account_class, optional: true if ActiveAgent::Dashboard.multi_tenant?
27
-
28
- has_many :agent_versions, class_name: "ActiveAgent::Dashboard::AgentVersion", dependent: :destroy
29
- has_many :agent_runs, class_name: "ActiveAgent::Dashboard::AgentRun", dependent: :destroy
30
-
31
- # Validations
32
- validates :name, presence: true, length: { minimum: 2, maximum: 100 }
33
- validates :slug, presence: true, format: { with: /\A[a-z0-9\-_]+\z/ }
34
- validates :provider, presence: true
35
- validates :model, presence: true
36
-
37
- # Ensure slug uniqueness within scope
38
- if ActiveAgent::Dashboard.multi_tenant?
39
- validates :slug, uniqueness: { scope: :account_id }
40
- else
41
- validates :slug, uniqueness: { scope: :user_id }
42
- end
43
-
44
- # Status enum
45
- enum :status, { draft: 0, active: 1, archived: 2 }
46
-
47
- # Callbacks
48
- before_validation :generate_slug, on: :create
49
- after_create :create_initial_version
50
- after_update :create_version_on_config_change, if: :configuration_changed?
51
-
52
- # Scopes
53
- scope :active_agents, -> { where(status: :active) }
54
- scope :by_provider, ->(provider) { where(provider: provider) }
55
- scope :with_tool, ->(tool) { where("tools @> ?", [ tool ].to_json) }
56
-
57
- # Available presets matching AgentAvatar component
58
- PRESET_TYPES = %w[
59
- terminal webDeveloper documentAnalysis writing translation
60
- playwright research imageAnalysis computerUse productDesign
61
- ].freeze
62
-
63
- # Available instruction sets
64
- INSTRUCTION_SETS = %w[
65
- github ruby rails aws gcp python typescript docker kubernetes
66
- ].freeze
67
-
68
- # Available tools/MCPs
69
- AVAILABLE_TOOLS = %w[
70
- terminal playwright filesystem code database slack fetch search edit translate memory
71
- ].freeze
72
-
73
- # Available providers
74
- PROVIDERS = %w[openai anthropic ollama openrouter requesty].freeze
75
-
76
- # Returns the configuration as a hash for versioning
77
- def configuration_snapshot
78
- {
79
- name: name,
80
- description: description,
81
- provider: provider,
82
- model: model,
83
- instructions: instructions,
84
- preset_type: preset_type,
85
- appearance: appearance,
86
- instruction_sets: instruction_sets,
87
- tools: tools,
88
- mcp_servers: mcp_servers,
89
- model_config: model_config,
90
- response_format: response_format
91
- }
92
- end
93
-
94
- # Restore from a version
95
- def restore_from_version!(version)
96
- config = version.configuration_snapshot
97
- update!(
98
- instructions: config["instructions"],
99
- preset_type: config["preset_type"],
100
- appearance: config["appearance"],
101
- instruction_sets: config["instruction_sets"],
102
- tools: config["tools"],
103
- mcp_servers: config["mcp_servers"],
104
- model_config: config["model_config"],
105
- response_format: config["response_format"]
106
- )
107
- end
108
-
109
- # Get the latest version
110
- def latest_version
111
- agent_versions.order(version_number: :desc).first
112
- end
113
-
114
- # Get version count
115
- def version_count
116
- agent_versions.count
117
- end
118
-
119
- # Generate Ruby agent class code
120
- def to_agent_class_code
121
- <<~RUBY
122
- class #{agent_class_name || name.camelize}Agent < ApplicationAgent
123
- generate_with :#{provider}, model: "#{model}"#{model_config_code}
124
-
125
- def perform
126
- prompt#{instructions_code}
127
- end
128
- end
129
- RUBY
130
- end
131
-
132
- # Execute a run with this agent
133
- def execute(input_prompt, **params)
134
- run = agent_runs.create!(
135
- input_prompt: input_prompt,
136
- input_params: params,
137
- status: :pending,
138
- trace_id: SecureRandom.uuid
139
- )
140
-
141
- # Queue the execution job
142
- ActiveAgent::Dashboard::AgentExecutionJob.perform_later(run.id)
143
-
144
- run
145
- end
146
-
147
- # Quick test execution (synchronous)
148
- def test_execute(input_prompt, **params)
149
- run = agent_runs.create!(
150
- input_prompt: input_prompt,
151
- input_params: params,
152
- status: :running,
153
- trace_id: SecureRandom.uuid,
154
- started_at: Time.current
155
- )
156
-
157
- begin
158
- result = build_and_execute_agent(input_prompt, **params)
159
-
160
- run.update!(
161
- output: result[:output],
162
- output_metadata: result[:metadata],
163
- status: :complete,
164
- completed_at: Time.current,
165
- duration_ms: ((Time.current - run.started_at) * 1000).to_i,
166
- input_tokens: result.dig(:usage, :input_tokens),
167
- output_tokens: result.dig(:usage, :output_tokens),
168
- total_tokens: result.dig(:usage, :total_tokens)
169
- )
170
- rescue => e
171
- run.update!(
172
- status: :failed,
173
- completed_at: Time.current,
174
- error_message: e.message,
175
- error_backtrace: e.backtrace&.first(10)&.join("\n")
176
- )
177
- end
178
-
179
- run
180
- end
181
-
182
- private
183
-
184
- def generate_slug
185
- return if slug.present?
186
-
187
- base_slug = name.to_s.parameterize
188
- self.slug = base_slug
189
-
190
- # Ensure uniqueness within scope
191
- counter = 1
192
- scope = self.class.where(slug: slug)
193
- scope = scope.where(account_id: account_id) if respond_to?(:account_id) && account_id
194
- scope = scope.where(user_id: user_id) if respond_to?(:user_id) && user_id
195
-
196
- while scope.exists?
197
- self.slug = "#{base_slug}-#{counter}"
198
- scope = self.class.where(slug: slug)
199
- scope = scope.where(account_id: account_id) if respond_to?(:account_id) && account_id
200
- scope = scope.where(user_id: user_id) if respond_to?(:user_id) && user_id
201
- counter += 1
202
- end
203
- end
204
-
205
- def create_initial_version
206
- agent_versions.create!(
207
- version_number: 1,
208
- change_summary: "Initial creation",
209
- configuration_snapshot: configuration_snapshot
210
- )
211
- end
212
-
213
- def configuration_changed?
214
- saved_changes.keys.any? do |key|
215
- %w[instructions preset_type appearance instruction_sets tools mcp_servers model_config response_format].include?(key)
216
- end
217
- end
218
-
219
- def create_version_on_config_change
220
- next_version = (latest_version&.version_number || 0) + 1
221
- changed_fields = saved_changes.keys.select do |key|
222
- %w[instructions preset_type appearance instruction_sets tools mcp_servers model_config response_format].include?(key)
223
- end
224
-
225
- agent_versions.create!(
226
- version_number: next_version,
227
- change_summary: "Updated: #{changed_fields.join(', ')}",
228
- configuration_snapshot: configuration_snapshot
229
- )
230
- end
231
-
232
- def model_config_code
233
- return "" if model_config.blank?
234
-
235
- configs = model_config.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")
236
- ", #{configs}"
237
- end
238
-
239
- def instructions_code
240
- return "" if instructions.blank?
241
-
242
- "\n prompt instructions: <<~INSTRUCTIONS\n #{instructions.gsub("\n", "\n ")}\n INSTRUCTIONS"
243
- end
244
-
245
- def build_and_execute_agent(input_prompt, **params)
246
- # TODO: Implement actual ActiveAgent execution
247
- # This will create a dynamic agent class and execute it
248
- {
249
- output: "Mock response for: #{input_prompt}",
250
- metadata: { provider: provider, model: model },
251
- usage: { input_tokens: 10, output_tokens: 20, total_tokens: 30 }
252
- }
253
- end
254
- end
255
- end
256
- end