activeagent 1.1.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +61 -0
- data/README.md +24 -14
- data/lib/active_agent/telemetry/configuration.rb +13 -9
- data/lib/active_agent/telemetry/instrumentation.rb +4 -0
- data/lib/active_agent/telemetry/tool_origin.rb +90 -0
- data/lib/active_agent/telemetry.rb +1 -0
- data/lib/active_agent/version.rb +1 -1
- data/lib/active_agent.rb +0 -5
- metadata +22 -32
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/api/traces_controller.rb +0 -138
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/application_controller.rb +0 -64
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/dashboard_controller.rb +0 -129
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/traces_controller.rb +0 -123
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/agent_execution_job.rb +0 -56
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/application_job.rb +0 -14
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_cleanup_job.rb +0 -49
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_provision_job.rb +0 -65
- data/lib/active_agent/dashboard/app/jobs/active_agent/process_telemetry_traces_job.rb +0 -86
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent.rb +0 -256
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_run.rb +0 -113
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_template.rb +0 -208
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_version.rb +0 -60
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/application_record.rb +0 -46
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_action.rb +0 -125
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_snapshot.rb +0 -83
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_run.rb +0 -52
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_session.rb +0 -169
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/session_recording.rb +0 -193
- data/lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb +0 -214
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/_trace_detail.html.erb +0 -117
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/index.html.erb +0 -135
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/metrics.html.erb +0 -145
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/show.html.erb +0 -36
- data/lib/active_agent/dashboard/app/views/layouts/active_agent/dashboard/application.html.erb +0 -94
- data/lib/active_agent/dashboard/config/routes.rb +0 -19
- data/lib/active_agent/dashboard/engine.rb +0 -43
- data/lib/active_agent/dashboard.rb +0 -161
- data/lib/generators/active_agent/dashboard/install_generator.rb +0 -92
- data/lib/generators/active_agent/dashboard/templates/active_agent_dashboard.rb.erb +0 -67
- data/lib/generators/active_agent/dashboard/templates/create_active_agent_telemetry_traces.rb.erb +0 -46
data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/dashboard_controller.rb
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module ActiveAgent
|
|
4
|
-
module Dashboard
|
|
5
|
-
# Main dashboard controller showing overview metrics and recent activity.
|
|
6
|
-
#
|
|
7
|
-
class DashboardController < ApplicationController
|
|
8
|
-
def index
|
|
9
|
-
unless ActiveAgent::Dashboard.use_inertia && defined?(InertiaRails)
|
|
10
|
-
# No ERB overview ships yet — the traces list is the dashboard.
|
|
11
|
-
# Loading agent/run data here would also require the full
|
|
12
|
-
# dashboard tables, which telemetry-only installs don't have.
|
|
13
|
-
return redirect_to(traces_path)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
@agents = fetch_agents.limit(10)
|
|
17
|
-
@recent_runs = fetch_recent_runs.limit(10)
|
|
18
|
-
@recent_traces = fetch_recent_traces.limit(10)
|
|
19
|
-
@metrics = calculate_metrics
|
|
20
|
-
|
|
21
|
-
render inertia: "Dashboard", props: {
|
|
22
|
-
agents: serialize_agents(@agents),
|
|
23
|
-
recentRuns: serialize_runs(@recent_runs),
|
|
24
|
-
recentTraces: serialize_traces(@recent_traces),
|
|
25
|
-
metrics: @metrics,
|
|
26
|
-
user: current_user_props,
|
|
27
|
-
account: current_account_props
|
|
28
|
-
}
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def fetch_agents
|
|
34
|
-
agents = Agent.order(updated_at: :desc)
|
|
35
|
-
agents = agents.for_owner(current_owner) if current_owner
|
|
36
|
-
agents
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def fetch_recent_runs
|
|
40
|
-
runs = AgentRun.includes(:agent).recent
|
|
41
|
-
if current_owner && ActiveAgent::Dashboard.multi_tenant?
|
|
42
|
-
runs = runs.joins(:agent).where(agents: { account_id: current_owner.id })
|
|
43
|
-
end
|
|
44
|
-
runs
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def fetch_recent_traces
|
|
48
|
-
traces = ActiveAgent::Dashboard.trace_model.recent
|
|
49
|
-
traces = traces.for_account(current_owner) if current_owner && ActiveAgent::Dashboard.multi_tenant?
|
|
50
|
-
traces
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def calculate_metrics
|
|
54
|
-
traces_24h = ActiveAgent::Dashboard.trace_model.where("created_at > ?", 24.hours.ago)
|
|
55
|
-
runs_24h = AgentRun.where("created_at > ?", 24.hours.ago)
|
|
56
|
-
|
|
57
|
-
if current_owner && ActiveAgent::Dashboard.multi_tenant?
|
|
58
|
-
traces_24h = traces_24h.for_account(current_owner)
|
|
59
|
-
runs_24h = runs_24h.joins(:agent).where(agents: { account_id: current_owner.id })
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
{
|
|
63
|
-
total_agents: fetch_agents.count,
|
|
64
|
-
active_agents: fetch_agents.active_agents.count,
|
|
65
|
-
total_runs_24h: runs_24h.count,
|
|
66
|
-
successful_runs_24h: runs_24h.successful.count,
|
|
67
|
-
failed_runs_24h: runs_24h.failed_runs.count,
|
|
68
|
-
total_traces_24h: traces_24h.count,
|
|
69
|
-
total_tokens_24h: traces_24h.sum(:total_input_tokens).to_i + traces_24h.sum(:total_output_tokens).to_i,
|
|
70
|
-
avg_duration_ms: runs_24h.where.not(duration_ms: nil).average(:duration_ms)&.round || 0
|
|
71
|
-
}
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def serialize_agents(agents)
|
|
75
|
-
agents.map do |agent|
|
|
76
|
-
{
|
|
77
|
-
id: agent.id,
|
|
78
|
-
name: agent.name,
|
|
79
|
-
slug: agent.slug,
|
|
80
|
-
description: agent.description,
|
|
81
|
-
provider: agent.provider,
|
|
82
|
-
model: agent.model,
|
|
83
|
-
status: agent.status,
|
|
84
|
-
presetType: agent.preset_type,
|
|
85
|
-
versionCount: agent.version_count,
|
|
86
|
-
updatedAt: agent.updated_at.iso8601
|
|
87
|
-
}
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def serialize_runs(runs)
|
|
92
|
-
runs.map(&:summary)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def serialize_traces(traces)
|
|
96
|
-
traces.map do |trace|
|
|
97
|
-
{
|
|
98
|
-
id: trace.id,
|
|
99
|
-
traceId: trace.trace_id,
|
|
100
|
-
displayName: trace.display_name,
|
|
101
|
-
status: trace.status,
|
|
102
|
-
duration: trace.formatted_duration,
|
|
103
|
-
tokens: trace.formatted_tokens,
|
|
104
|
-
timestamp: trace.timestamp&.iso8601
|
|
105
|
-
}
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def current_user_props
|
|
110
|
-
return nil unless current_user
|
|
111
|
-
|
|
112
|
-
{
|
|
113
|
-
id: current_user.id,
|
|
114
|
-
name: current_user.try(:display_name) || current_user.try(:name) || current_user.try(:email),
|
|
115
|
-
email: current_user.try(:email)
|
|
116
|
-
}
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def current_account_props
|
|
120
|
-
return nil unless current_owner && ActiveAgent::Dashboard.multi_tenant?
|
|
121
|
-
|
|
122
|
-
{
|
|
123
|
-
id: current_owner.id,
|
|
124
|
-
name: current_owner.try(:name)
|
|
125
|
-
}
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
end
|
|
@@ -1,123 +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
|
-
# turbo-rails is optional; responding to an unregistered MIME
|
|
20
|
-
# type raises in host apps without it.
|
|
21
|
-
format.turbo_stream if turbo_stream_available?
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def show
|
|
26
|
-
@trace = scoped_traces.find(params[:id])
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def metrics
|
|
30
|
-
@metrics = calculate_metrics
|
|
31
|
-
@agent_stats = agent_statistics
|
|
32
|
-
@time_series = time_series_data
|
|
33
|
-
|
|
34
|
-
respond_to do |format|
|
|
35
|
-
format.html
|
|
36
|
-
format.turbo_stream if turbo_stream_available?
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
private
|
|
41
|
-
|
|
42
|
-
def turbo_stream_available?
|
|
43
|
-
Mime::Type.lookup_by_extension(:turbo_stream).present?
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# All queries honor the configured trace model and, in multi-tenant
|
|
47
|
-
# mode, the current owner's account (for_account no-ops otherwise).
|
|
48
|
-
#
|
|
49
|
-
# In multi-tenant mode an unresolvable owner returns nothing rather
|
|
50
|
-
# than falling through: current_owner degrades to current_user when
|
|
51
|
-
# current_account_method is unset, which would scope by a user id
|
|
52
|
-
# against account_id — another tenant's traces.
|
|
53
|
-
def scoped_traces
|
|
54
|
-
model = ActiveAgent::Dashboard.trace_model
|
|
55
|
-
return model.none if ActiveAgent::Dashboard.multi_tenant? && current_owner.nil?
|
|
56
|
-
|
|
57
|
-
model.for_account(current_owner)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def fetch_traces
|
|
61
|
-
traces = scoped_traces.recent
|
|
62
|
-
|
|
63
|
-
traces = traces.for_agent(params[:agent]) if params[:agent].present?
|
|
64
|
-
traces = traces.with_errors if params[:status] == "error"
|
|
65
|
-
traces = traces.for_service(params[:service]) if params[:service].present?
|
|
66
|
-
|
|
67
|
-
if params[:start_date].present? && params[:end_date].present?
|
|
68
|
-
traces = traces.for_date_range(
|
|
69
|
-
Time.parse(params[:start_date]),
|
|
70
|
-
Time.parse(params[:end_date])
|
|
71
|
-
)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
traces.limit(params[:limit] || 50)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def calculate_metrics
|
|
78
|
-
traces = scoped_traces.where(
|
|
79
|
-
"created_at > ?", 24.hours.ago
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
{
|
|
83
|
-
total_traces: traces.count,
|
|
84
|
-
total_tokens: traces.sum(:total_input_tokens) + traces.sum(:total_output_tokens),
|
|
85
|
-
avg_duration_ms: traces.average(:total_duration_ms)&.round(2) || 0,
|
|
86
|
-
error_rate: calculate_error_rate(traces),
|
|
87
|
-
unique_agents: traces.distinct.count(:agent_class)
|
|
88
|
-
}
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def calculate_error_rate(traces)
|
|
92
|
-
total = traces.count
|
|
93
|
-
return 0.0 if total.zero?
|
|
94
|
-
|
|
95
|
-
errors = traces.with_errors.count
|
|
96
|
-
((errors.to_f / total) * 100).round(2)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def agent_statistics
|
|
100
|
-
scoped_traces
|
|
101
|
-
.where("created_at > ?", 24.hours.ago)
|
|
102
|
-
.group(:agent_class)
|
|
103
|
-
.select(
|
|
104
|
-
"agent_class",
|
|
105
|
-
"COUNT(*) as trace_count",
|
|
106
|
-
"SUM(total_input_tokens + total_output_tokens) as total_tokens",
|
|
107
|
-
"AVG(total_duration_ms) as avg_duration",
|
|
108
|
-
"SUM(CASE WHEN status = 'ERROR' THEN 1 ELSE 0 END) as error_count"
|
|
109
|
-
)
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def time_series_data
|
|
113
|
-
scoped_traces
|
|
114
|
-
.where("created_at > ?", 1.hour.ago)
|
|
115
|
-
.group_by_minute(:created_at)
|
|
116
|
-
.count
|
|
117
|
-
rescue NoMethodError
|
|
118
|
-
# Fallback if groupdate gem not available
|
|
119
|
-
{}
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
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,86 +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
|
-
# Process in bounded slices; anything beyond the cap is re-enqueued
|
|
43
|
-
# rather than silently dropped (the client already received 202).
|
|
44
|
-
remainder = traces.drop(MAX_TRACES_PER_JOB)
|
|
45
|
-
traces = traces.take(MAX_TRACES_PER_JOB)
|
|
46
|
-
if remainder.any?
|
|
47
|
-
self.class.perform_later(
|
|
48
|
-
account_id: account_id, traces: remainder,
|
|
49
|
-
sdk_info: sdk_info, received_at: received_at
|
|
50
|
-
)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
traces.each do |trace|
|
|
54
|
-
process_trace(trace, sdk_info, account)
|
|
55
|
-
rescue StandardError => e
|
|
56
|
-
Rails.logger.error(
|
|
57
|
-
"[ProcessTelemetryTracesJob] Failed to process trace #{trace['trace_id']}: " \
|
|
58
|
-
"#{e.class} - #{e.message}"
|
|
59
|
-
)
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
private
|
|
64
|
-
|
|
65
|
-
def resolve_account(account_id)
|
|
66
|
-
return nil unless ActiveAgent::Dashboard.multi_tenant?
|
|
67
|
-
return nil unless account_id
|
|
68
|
-
|
|
69
|
-
account_class = ActiveAgent::Dashboard.account_class.constantize
|
|
70
|
-
account_class.find_by(id: account_id)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def process_trace(trace, sdk_info, account)
|
|
74
|
-
model = ActiveAgent::Dashboard.trace_model
|
|
75
|
-
|
|
76
|
-
# Build uniqueness scope
|
|
77
|
-
scope = model.where(trace_id: trace["trace_id"])
|
|
78
|
-
scope = scope.where(account: account) if ActiveAgent::Dashboard.multi_tenant? && account
|
|
79
|
-
|
|
80
|
-
# Skip if trace already exists (idempotency)
|
|
81
|
-
return if scope.exists?
|
|
82
|
-
|
|
83
|
-
model.create_from_payload(trace, sdk_info, account: account)
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|