activeagent 1.1.0 → 1.3.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 +185 -0
- data/README.md +58 -14
- data/lib/active_agent/base.rb +11 -2
- data/lib/active_agent/concerns/delegation.rb +385 -0
- data/lib/active_agent/delegation/backend.rb +109 -0
- data/lib/active_agent/delegation/budget.rb +138 -0
- data/lib/active_agent/delegation/contract.rb +117 -0
- data/lib/active_agent/delegation/definition.rb +95 -0
- data/lib/active_agent/delegation/ledger.rb +56 -0
- data/lib/active_agent/delegation/pricing.rb +106 -0
- data/lib/active_agent/delegation/runner.rb +283 -0
- data/lib/active_agent/delegation/schema.rb +220 -0
- data/lib/active_agent/providers/_base_provider.rb +97 -1
- data/lib/active_agent/providers/open_ai/chat_provider.rb +21 -2
- data/lib/active_agent/telemetry/configuration.rb +13 -9
- data/lib/active_agent/telemetry/instrumentation.rb +38 -1
- 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 +1 -5
- metadata +31 -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
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activeagent
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Bowen
|
|
@@ -109,6 +109,26 @@ dependencies:
|
|
|
109
109
|
- - "<="
|
|
110
110
|
- !ruby/object:Gem::Version
|
|
111
111
|
version: '9.0'
|
|
112
|
+
- !ruby/object:Gem::Dependency
|
|
113
|
+
name: railties
|
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
|
115
|
+
requirements:
|
|
116
|
+
- - ">="
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
version: '7.2'
|
|
119
|
+
- - "<="
|
|
120
|
+
- !ruby/object:Gem::Version
|
|
121
|
+
version: '9.0'
|
|
122
|
+
type: :runtime
|
|
123
|
+
prerelease: false
|
|
124
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
125
|
+
requirements:
|
|
126
|
+
- - ">="
|
|
127
|
+
- !ruby/object:Gem::Version
|
|
128
|
+
version: '7.2'
|
|
129
|
+
- - "<="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '9.0'
|
|
112
132
|
- !ruby/object:Gem::Dependency
|
|
113
133
|
name: activeagents-telemetry
|
|
114
134
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -390,6 +410,7 @@ files:
|
|
|
390
410
|
- lib/active_agent/base.rb
|
|
391
411
|
- lib/active_agent/collector.rb
|
|
392
412
|
- lib/active_agent/concerns/callbacks.rb
|
|
413
|
+
- lib/active_agent/concerns/delegation.rb
|
|
393
414
|
- lib/active_agent/concerns/observers.rb
|
|
394
415
|
- lib/active_agent/concerns/parameterized.rb
|
|
395
416
|
- lib/active_agent/concerns/preview.rb
|
|
@@ -400,34 +421,14 @@ files:
|
|
|
400
421
|
- lib/active_agent/concerns/tooling.rb
|
|
401
422
|
- lib/active_agent/concerns/view.rb
|
|
402
423
|
- lib/active_agent/configuration.rb
|
|
403
|
-
- lib/active_agent/
|
|
404
|
-
- lib/active_agent/
|
|
405
|
-
- lib/active_agent/
|
|
406
|
-
- lib/active_agent/
|
|
407
|
-
- lib/active_agent/
|
|
408
|
-
- lib/active_agent/
|
|
409
|
-
- lib/active_agent/
|
|
410
|
-
- lib/active_agent/
|
|
411
|
-
- lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_provision_job.rb
|
|
412
|
-
- lib/active_agent/dashboard/app/jobs/active_agent/process_telemetry_traces_job.rb
|
|
413
|
-
- lib/active_agent/dashboard/app/models/active_agent/dashboard/agent.rb
|
|
414
|
-
- lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_run.rb
|
|
415
|
-
- lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_template.rb
|
|
416
|
-
- lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_version.rb
|
|
417
|
-
- lib/active_agent/dashboard/app/models/active_agent/dashboard/application_record.rb
|
|
418
|
-
- lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_action.rb
|
|
419
|
-
- lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_snapshot.rb
|
|
420
|
-
- lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_run.rb
|
|
421
|
-
- lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_session.rb
|
|
422
|
-
- lib/active_agent/dashboard/app/models/active_agent/dashboard/session_recording.rb
|
|
423
|
-
- lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb
|
|
424
|
-
- lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/_trace_detail.html.erb
|
|
425
|
-
- lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/index.html.erb
|
|
426
|
-
- lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/metrics.html.erb
|
|
427
|
-
- lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/show.html.erb
|
|
428
|
-
- lib/active_agent/dashboard/app/views/layouts/active_agent/dashboard/application.html.erb
|
|
429
|
-
- lib/active_agent/dashboard/config/routes.rb
|
|
430
|
-
- lib/active_agent/dashboard/engine.rb
|
|
424
|
+
- lib/active_agent/delegation/backend.rb
|
|
425
|
+
- lib/active_agent/delegation/budget.rb
|
|
426
|
+
- lib/active_agent/delegation/contract.rb
|
|
427
|
+
- lib/active_agent/delegation/definition.rb
|
|
428
|
+
- lib/active_agent/delegation/ledger.rb
|
|
429
|
+
- lib/active_agent/delegation/pricing.rb
|
|
430
|
+
- lib/active_agent/delegation/runner.rb
|
|
431
|
+
- lib/active_agent/delegation/schema.rb
|
|
431
432
|
- lib/active_agent/deprecator.rb
|
|
432
433
|
- lib/active_agent/generation.rb
|
|
433
434
|
- lib/active_agent/generation_job.rb
|
|
@@ -551,14 +552,12 @@ files:
|
|
|
551
552
|
- lib/active_agent/telemetry/instrumentation.rb
|
|
552
553
|
- lib/active_agent/telemetry/reporter.rb
|
|
553
554
|
- lib/active_agent/telemetry/span.rb
|
|
555
|
+
- lib/active_agent/telemetry/tool_origin.rb
|
|
554
556
|
- lib/active_agent/telemetry/tracer.rb
|
|
555
557
|
- lib/active_agent/version.rb
|
|
556
558
|
- lib/activeagent.rb
|
|
557
559
|
- lib/generators/active_agent/agent/USAGE
|
|
558
560
|
- lib/generators/active_agent/agent/agent_generator.rb
|
|
559
|
-
- lib/generators/active_agent/dashboard/install_generator.rb
|
|
560
|
-
- lib/generators/active_agent/dashboard/templates/active_agent_dashboard.rb.erb
|
|
561
|
-
- lib/generators/active_agent/dashboard/templates/create_active_agent_telemetry_traces.rb.erb
|
|
562
561
|
- lib/generators/active_agent/install/USAGE
|
|
563
562
|
- lib/generators/active_agent/install/install_generator.rb
|
|
564
563
|
- lib/generators/active_agent/templates/active_agent.yml
|
data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/api/traces_controller.rb
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module ActiveAgent
|
|
4
|
-
module Dashboard
|
|
5
|
-
module Api
|
|
6
|
-
# Telemetry ingestion endpoint.
|
|
7
|
-
#
|
|
8
|
-
# Receives traces from ActiveAgent::Telemetry::Reporter and stores them
|
|
9
|
-
# for analysis and visualization in the dashboard.
|
|
10
|
-
#
|
|
11
|
-
# Supports two modes:
|
|
12
|
-
# - Local mode: synchronous processing; unauthenticated unless
|
|
13
|
-
# ActiveAgent::Dashboard.ingest_api_key is set (set it whenever the
|
|
14
|
-
# mount is reachable beyond your own machine)
|
|
15
|
-
# - Multi-tenant mode: per-account Bearer token auth, async processing
|
|
16
|
-
# via job
|
|
17
|
-
#
|
|
18
|
-
# @example Local mode request
|
|
19
|
-
# POST <mount>/api/traces (e.g. /activeagents/api/traces)
|
|
20
|
-
# Content-Type: application/json
|
|
21
|
-
#
|
|
22
|
-
# {
|
|
23
|
-
# "traces": [...],
|
|
24
|
-
# "sdk": { "name": "activeagent", "version": "0.5.0" }
|
|
25
|
-
# }
|
|
26
|
-
#
|
|
27
|
-
# @example Multi-tenant mode request
|
|
28
|
-
# POST <mount>/api/traces (e.g. /activeagents/api/traces)
|
|
29
|
-
# Authorization: Bearer <api_key>
|
|
30
|
-
# Content-Type: application/json
|
|
31
|
-
#
|
|
32
|
-
# {
|
|
33
|
-
# "traces": [...],
|
|
34
|
-
# "sdk": { "name": "activeagent", "version": "0.5.0" }
|
|
35
|
-
# }
|
|
36
|
-
#
|
|
37
|
-
class TracesController < ActionController::API
|
|
38
|
-
before_action :authenticate_api_key!, if: -> { ActiveAgent::Dashboard.multi_tenant? }
|
|
39
|
-
before_action :authenticate_ingest_key!, unless: -> { ActiveAgent::Dashboard.multi_tenant? }
|
|
40
|
-
|
|
41
|
-
# Maximum traces accepted per request (mirrors
|
|
42
|
-
# ProcessTelemetryTracesJob::MAX_TRACES_PER_JOB).
|
|
43
|
-
MAX_TRACES_PER_REQUEST = 100
|
|
44
|
-
|
|
45
|
-
# POST <mount>/api/traces (e.g. /activeagents/api/traces)
|
|
46
|
-
def create
|
|
47
|
-
traces = Array(params[:traces]).take(MAX_TRACES_PER_REQUEST)
|
|
48
|
-
sdk_info = params[:sdk] || {}
|
|
49
|
-
|
|
50
|
-
return head :accepted if traces.empty?
|
|
51
|
-
|
|
52
|
-
if ActiveAgent::Dashboard.multi_tenant?
|
|
53
|
-
# Multi-tenant mode: process in background
|
|
54
|
-
ActiveAgent::ProcessTelemetryTracesJob.perform_later(
|
|
55
|
-
account_id: @account&.id,
|
|
56
|
-
traces: traces.as_json,
|
|
57
|
-
sdk_info: sdk_info.as_json,
|
|
58
|
-
received_at: Time.current.iso8601(6)
|
|
59
|
-
)
|
|
60
|
-
else
|
|
61
|
-
# Local mode: process synchronously
|
|
62
|
-
process_traces_synchronously(traces, sdk_info)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
head :accepted
|
|
66
|
-
rescue ActionController::ParameterMissing => e
|
|
67
|
-
render json: { error: e.message }, status: :bad_request
|
|
68
|
-
rescue StandardError => e
|
|
69
|
-
Rails.logger.error("[ActiveAgent::Dashboard] Trace ingestion error: #{e.message}")
|
|
70
|
-
render json: { error: "Internal server error" }, status: :internal_server_error
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
private
|
|
74
|
-
|
|
75
|
-
# Authenticates the request using Bearer token from Authorization header.
|
|
76
|
-
# Only used in multi-tenant mode.
|
|
77
|
-
def authenticate_api_key!
|
|
78
|
-
token = extract_bearer_token
|
|
79
|
-
|
|
80
|
-
if token.blank?
|
|
81
|
-
render json: { error: "Missing Authorization header" }, status: :unauthorized
|
|
82
|
-
return
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
account_class = ActiveAgent::Dashboard.account_class.constantize
|
|
86
|
-
@account = account_class.find_by(telemetry_api_key: token)
|
|
87
|
-
|
|
88
|
-
if @account.nil?
|
|
89
|
-
render json: { error: "Invalid API key" }, status: :unauthorized
|
|
90
|
-
return
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Track usage for rate limiting (if the account responds to it)
|
|
94
|
-
@account.increment_telemetry_usage! if @account.respond_to?(:increment_telemetry_usage!)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Requires the configured single-tenant ingest key when one is set.
|
|
98
|
-
# The telemetry reporter and ruby_llm_telemetry both send their
|
|
99
|
-
# api_key as a Bearer header, so remote apps work unchanged.
|
|
100
|
-
def authenticate_ingest_key!
|
|
101
|
-
expected = ActiveAgent::Dashboard.ingest_api_key
|
|
102
|
-
return if expected.blank?
|
|
103
|
-
|
|
104
|
-
token = extract_bearer_token
|
|
105
|
-
return if token.present? && ActiveSupport::SecurityUtils.secure_compare(token, expected)
|
|
106
|
-
|
|
107
|
-
render json: { error: "Invalid API key" }, status: :unauthorized
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Extracts Bearer token from Authorization header.
|
|
111
|
-
def extract_bearer_token
|
|
112
|
-
auth_header = request.headers["Authorization"]
|
|
113
|
-
return nil if auth_header.blank?
|
|
114
|
-
|
|
115
|
-
match = auth_header.match(/^Bearer\s+(.+)$/i)
|
|
116
|
-
match[1] if match
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Process traces synchronously for local development.
|
|
120
|
-
def process_traces_synchronously(traces, sdk_info)
|
|
121
|
-
model = ActiveAgent::Dashboard.trace_model
|
|
122
|
-
|
|
123
|
-
traces.each do |trace|
|
|
124
|
-
# Skip if trace already exists (idempotency)
|
|
125
|
-
next if model.exists?(trace_id: trace["trace_id"])
|
|
126
|
-
|
|
127
|
-
model.create_from_payload(trace, sdk_info)
|
|
128
|
-
rescue StandardError => e
|
|
129
|
-
Rails.logger.error(
|
|
130
|
-
"[ActiveAgent::Dashboard] Failed to process trace #{trace['trace_id']}: " \
|
|
131
|
-
"#{e.class} - #{e.message}"
|
|
132
|
-
)
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
end
|
data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/application_controller.rb
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module ActiveAgent
|
|
4
|
-
module Dashboard
|
|
5
|
-
# Base controller for the ActiveAgent Dashboard.
|
|
6
|
-
#
|
|
7
|
-
# Handles authentication and provides helper methods for multi-tenant mode.
|
|
8
|
-
class ApplicationController < ActionController::Base
|
|
9
|
-
protect_from_forgery with: :exception
|
|
10
|
-
|
|
11
|
-
before_action :authenticate_dashboard!
|
|
12
|
-
|
|
13
|
-
# Add the engine's view path
|
|
14
|
-
prepend_view_path ActiveAgent::Dashboard::Engine.dashboard_root.join("app", "views").to_s
|
|
15
|
-
|
|
16
|
-
# Use custom layout if configured, otherwise use engine layout
|
|
17
|
-
layout -> { ActiveAgent::Dashboard.layout || "active_agent/dashboard/application" }
|
|
18
|
-
|
|
19
|
-
helper_method :current_user, :current_owner
|
|
20
|
-
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
def authenticate_dashboard!
|
|
24
|
-
if ActiveAgent::Dashboard.authentication_method.nil?
|
|
25
|
-
# Traces contain prompts, outputs, and error backtraces. Refuse to
|
|
26
|
-
# serve them unauthenticated in production rather than exposing
|
|
27
|
-
# them to the internet by default.
|
|
28
|
-
if Rails.env.production?
|
|
29
|
-
render plain: "ActiveAgent Dashboard: set ActiveAgent::Dashboard.authentication_method " \
|
|
30
|
-
"(see docs/framework/dashboard.md) to enable access in production.",
|
|
31
|
-
status: :forbidden
|
|
32
|
-
end
|
|
33
|
-
return
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
result = ActiveAgent::Dashboard.authentication_method.call(self)
|
|
37
|
-
head :unauthorized unless result
|
|
38
|
-
rescue StandardError => e
|
|
39
|
-
Rails.logger.error("[ActiveAgent::Dashboard] Authentication error: #{e.message}")
|
|
40
|
-
head :unauthorized
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Returns the current user from the host application.
|
|
44
|
-
def current_user
|
|
45
|
-
return nil unless ActiveAgent::Dashboard.current_user_method
|
|
46
|
-
|
|
47
|
-
send(ActiveAgent::Dashboard.current_user_method)
|
|
48
|
-
rescue NoMethodError
|
|
49
|
-
nil
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Returns the current owner (account in multi-tenant, user otherwise).
|
|
53
|
-
def current_owner
|
|
54
|
-
if ActiveAgent::Dashboard.multi_tenant? && ActiveAgent::Dashboard.current_account_method
|
|
55
|
-
send(ActiveAgent::Dashboard.current_account_method)
|
|
56
|
-
else
|
|
57
|
-
current_user
|
|
58
|
-
end
|
|
59
|
-
rescue NoMethodError
|
|
60
|
-
nil
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
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
|