activeagent 1.0.1 → 1.0.2
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/README.md +10 -4
- data/lib/active_agent/base.rb +3 -2
- data/lib/active_agent/concerns/provider.rb +6 -2
- data/lib/active_agent/concerns/rescue.rb +39 -0
- data/lib/active_agent/concerns/streaming.rb +2 -1
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/api/traces_controller.rb +117 -0
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/application_controller.rb +54 -0
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/dashboard_controller.rb +126 -0
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/traces_controller.rb +103 -0
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/agent_execution_job.rb +56 -0
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/application_job.rb +14 -0
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_cleanup_job.rb +49 -0
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_provision_job.rb +65 -0
- data/lib/active_agent/dashboard/app/jobs/active_agent/process_telemetry_traces_job.rb +77 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent.rb +256 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_run.rb +113 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_template.rb +208 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_version.rb +60 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/application_record.rb +46 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_action.rb +125 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_snapshot.rb +83 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_run.rb +52 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_session.rb +169 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/session_recording.rb +193 -0
- data/lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb +198 -0
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/_trace_detail.html.erb +105 -0
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/index.html.erb +135 -0
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/metrics.html.erb +143 -0
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/show.html.erb +36 -0
- data/lib/active_agent/dashboard/app/views/layouts/active_agent/dashboard/application.html.erb +94 -0
- data/lib/active_agent/dashboard/config/routes.rb +78 -0
- data/lib/active_agent/dashboard/engine.rb +39 -0
- data/lib/active_agent/dashboard.rb +151 -0
- data/lib/active_agent/providers/_base_provider.rb +2 -1
- data/lib/active_agent/providers/anthropic_provider.rb +14 -4
- data/lib/active_agent/providers/azure/_types.rb +5 -0
- data/lib/active_agent/providers/azure/options.rb +111 -0
- data/lib/active_agent/providers/azure_open_ai_provider.rb +2 -0
- data/lib/active_agent/providers/azure_openai_provider.rb +2 -0
- data/lib/active_agent/providers/azure_provider.rb +133 -0
- data/lib/active_agent/providers/azureopenai_provider.rb +2 -0
- data/lib/active_agent/providers/bedrock/_types.rb +8 -0
- data/lib/active_agent/providers/bedrock/bearer_client.rb +109 -0
- data/lib/active_agent/providers/bedrock/options.rb +77 -0
- data/lib/active_agent/providers/bedrock_provider.rb +84 -0
- data/lib/active_agent/providers/common/messages/_types.rb +6 -2
- data/lib/active_agent/providers/concerns/exception_handler.rb +1 -0
- data/lib/active_agent/providers/gemini/_types.rb +19 -0
- data/lib/active_agent/providers/gemini/options.rb +41 -0
- data/lib/active_agent/providers/gemini_provider.rb +94 -0
- data/lib/active_agent/providers/open_ai/chat/transforms.rb +37 -1
- data/lib/active_agent/providers/open_ai/chat_provider.rb +2 -0
- data/lib/active_agent/providers/ruby_llm/_types.rb +77 -0
- data/lib/active_agent/providers/ruby_llm/embedding_request.rb +16 -0
- data/lib/active_agent/providers/ruby_llm/messages/_types.rb +109 -0
- data/lib/active_agent/providers/ruby_llm/messages/assistant.rb +27 -0
- data/lib/active_agent/providers/ruby_llm/messages/base.rb +48 -0
- data/lib/active_agent/providers/ruby_llm/messages/system.rb +18 -0
- data/lib/active_agent/providers/ruby_llm/messages/tool.rb +24 -0
- data/lib/active_agent/providers/ruby_llm/messages/user.rb +18 -0
- data/lib/active_agent/providers/ruby_llm/options.rb +28 -0
- data/lib/active_agent/providers/ruby_llm/request.rb +30 -0
- data/lib/active_agent/providers/ruby_llm/tool_proxy.rb +45 -0
- data/lib/active_agent/providers/ruby_llm_provider.rb +407 -0
- data/lib/active_agent/railtie.rb +32 -1
- data/lib/active_agent/telemetry/configuration.rb +213 -0
- data/lib/active_agent/telemetry/instrumentation.rb +155 -0
- data/lib/active_agent/telemetry/reporter.rb +176 -0
- data/lib/active_agent/telemetry/span.rb +267 -0
- data/lib/active_agent/telemetry/tracer.rb +184 -0
- data/lib/active_agent/telemetry.rb +162 -0
- data/lib/active_agent/version.rb +1 -1
- data/lib/active_agent.rb +2 -0
- data/lib/generators/active_agent/dashboard/install/install_generator.rb +96 -0
- data/lib/generators/active_agent/dashboard/install/templates/initializer.rb +89 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_runs.rb +42 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_templates.rb +38 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_versions.rb +22 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agents.rb +53 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_sandbox_runs.rb +28 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_sandbox_sessions.rb +43 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_session_recordings.rb +44 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_telemetry_traces.rb +56 -0
- data/lib/generators/active_agent/dashboard/install_generator.rb +64 -0
- data/lib/generators/active_agent/dashboard/templates/active_agent_dashboard.rb.erb +30 -0
- data/lib/generators/active_agent/dashboard/templates/create_active_agent_telemetry_traces.rb.erb +30 -0
- metadata +99 -13
|
@@ -0,0 +1,65 @@
|
|
|
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
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
|
@@ -0,0 +1,256 @@
|
|
|
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].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
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAgent
|
|
4
|
+
module Dashboard
|
|
5
|
+
# Tracks individual agent execution runs.
|
|
6
|
+
#
|
|
7
|
+
# Each run captures input, output, timing, token usage, and any errors
|
|
8
|
+
# that occurred during execution.
|
|
9
|
+
#
|
|
10
|
+
# @example Creating a run
|
|
11
|
+
# run = agent.execute("Analyze this code", code: code)
|
|
12
|
+
# run.status # => "pending"
|
|
13
|
+
#
|
|
14
|
+
# @example Monitoring a run
|
|
15
|
+
# run.in_progress? # => true
|
|
16
|
+
# run.finished? # => false
|
|
17
|
+
#
|
|
18
|
+
class AgentRun < ApplicationRecord
|
|
19
|
+
belongs_to :agent, class_name: "ActiveAgent::Dashboard::Agent"
|
|
20
|
+
has_one :session_recording, class_name: "ActiveAgent::Dashboard::SessionRecording", dependent: :nullify
|
|
21
|
+
|
|
22
|
+
# Status enum
|
|
23
|
+
enum :status, { pending: 0, running: 1, complete: 2, failed: 3, cancelled: 4 }
|
|
24
|
+
|
|
25
|
+
# Validations
|
|
26
|
+
validates :trace_id, presence: true
|
|
27
|
+
|
|
28
|
+
# Scopes
|
|
29
|
+
scope :recent, -> { order(created_at: :desc) }
|
|
30
|
+
scope :successful, -> { where(status: :complete) }
|
|
31
|
+
scope :failed_runs, -> { where(status: :failed) }
|
|
32
|
+
scope :today, -> { where("created_at >= ?", Time.current.beginning_of_day) }
|
|
33
|
+
|
|
34
|
+
# Callbacks
|
|
35
|
+
before_validation :set_trace_id, on: :create
|
|
36
|
+
after_update_commit :broadcast_update, if: :saved_change_to_status?
|
|
37
|
+
|
|
38
|
+
# Add a log entry
|
|
39
|
+
def add_log(message, level: :info)
|
|
40
|
+
new_logs = logs || []
|
|
41
|
+
new_logs << {
|
|
42
|
+
timestamp: Time.current.iso8601,
|
|
43
|
+
level: level.to_s,
|
|
44
|
+
message: message
|
|
45
|
+
}
|
|
46
|
+
update!(logs: new_logs)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Calculate duration if not set
|
|
50
|
+
def calculated_duration_ms
|
|
51
|
+
return duration_ms if duration_ms.present?
|
|
52
|
+
return nil unless started_at && completed_at
|
|
53
|
+
|
|
54
|
+
((completed_at - started_at) * 1000).to_i
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Check if run is still in progress
|
|
58
|
+
def in_progress?
|
|
59
|
+
pending? || running?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Check if run is finished
|
|
63
|
+
def finished?
|
|
64
|
+
complete? || failed? || cancelled?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Get a summary for display
|
|
68
|
+
def summary
|
|
69
|
+
{
|
|
70
|
+
id: id,
|
|
71
|
+
status: status,
|
|
72
|
+
input_preview: input_prompt&.truncate(100),
|
|
73
|
+
output_preview: output&.truncate(200),
|
|
74
|
+
duration_ms: calculated_duration_ms,
|
|
75
|
+
tokens: total_tokens,
|
|
76
|
+
created_at: created_at,
|
|
77
|
+
error: error_message
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Stream output updates via ActionCable
|
|
82
|
+
def broadcast_update
|
|
83
|
+
return unless defined?(ActionCable)
|
|
84
|
+
|
|
85
|
+
ActionCable.server.broadcast(
|
|
86
|
+
"agent_run_#{id}",
|
|
87
|
+
{
|
|
88
|
+
type: "update",
|
|
89
|
+
run: summary
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Cancel a running execution
|
|
95
|
+
def cancel!
|
|
96
|
+
return unless in_progress?
|
|
97
|
+
|
|
98
|
+
update!(
|
|
99
|
+
status: :cancelled,
|
|
100
|
+
completed_at: Time.current,
|
|
101
|
+
error_message: "Cancelled by user"
|
|
102
|
+
)
|
|
103
|
+
broadcast_update
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def set_trace_id
|
|
109
|
+
self.trace_id ||= SecureRandom.uuid
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|