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
@@ -2,18 +2,17 @@
2
2
 
3
3
  module ActiveAgent
4
4
  module Telemetry
5
- # Manages trace creation and lifecycle.
5
+ # Manages trace creation and lifecycle on the activeagents-telemetry
6
+ # core: builds an ActiveAgents::Telemetry::Trace per unit of work, tracks
7
+ # the current span in thread-local storage, and hands finished traces to
8
+ # the Reporter.
6
9
  #
7
- # The Tracer creates traces, manages the current trace context,
8
- # and coordinates with the Reporter for async transmission.
9
- #
10
- # @example Basic usage
10
+ # @example
11
11
  # tracer = Tracer.new(configuration)
12
12
  # tracer.trace("MyAgent.greet") do |span|
13
13
  # span.set_attribute("user_id", 123)
14
14
  # span.add_span("llm.generate", span_type: :llm)
15
15
  # end
16
- #
17
16
  class Tracer
18
17
  # @return [Configuration] Telemetry configuration
19
18
  attr_reader :configuration
@@ -27,44 +26,47 @@ module ActiveAgent
27
26
  def initialize(configuration)
28
27
  @configuration = configuration
29
28
  @reporter = Reporter.new(configuration)
30
- @mutex = Mutex.new
31
29
  end
32
30
 
33
31
  # Creates and executes a new trace.
34
32
  #
33
+ # Callers may supply the trace id (instrumentation passes the
34
+ # generation's prompt_options[:trace_id]) so external records — e.g.
35
+ # solid_agent's persisted generations — can correlate with this trace;
36
+ # otherwise one is generated.
37
+ #
35
38
  # @param name [String] Trace name (typically "AgentClass.action")
36
- # @param attributes [Hash] Root span attributes
39
+ # @param attributes [Hash] Root span attributes; :trace_id and
40
+ # :span_type are extracted rather than recorded
37
41
  # @yield [span] Yields the root span for adding child spans
38
42
  # @return [Object] Result of the block
39
- #
40
- # @example
41
- # tracer.trace("WeatherAgent.forecast") do |span|
42
- # span.set_attribute("location", "Seattle")
43
- # result = do_llm_call
44
- # span.set_tokens(input: 100, output: 50)
45
- # result
46
- # end
47
- def trace(name, **attributes, &block)
43
+ def trace(name, **attributes)
48
44
  return yield(Telemetry::NullSpan.new) unless should_trace?
49
45
 
50
- trace_id = generate_trace_id
46
+ trace_id = attributes.delete(:trace_id) || SecureRandom.hex(16)
47
+ span_type = attributes.delete(:span_type) || :root
48
+
49
+ trace = build_trace(trace_id)
51
50
  root_span = Span.new(
52
51
  name,
53
52
  trace_id: trace_id,
54
- span_type: :root,
53
+ span_type: span_type,
55
54
  **default_attributes.merge(attributes)
56
55
  )
56
+ trace.add_span(root_span)
57
57
 
58
58
  with_span(root_span) do
59
59
  result = yield(root_span)
60
60
  root_span.finish
61
- report_trace(root_span)
61
+ reporter.report(redact_trace!(trace))
62
62
  result
63
63
  end
64
64
  rescue StandardError => e
65
- root_span&.record_error(e)
66
- root_span&.finish
67
- report_trace(root_span) if root_span
65
+ if root_span && !root_span.finished?
66
+ root_span.record_error(e)
67
+ root_span.finish
68
+ reporter.report(redact_trace!(trace))
69
+ end
68
70
  raise
69
71
  end
70
72
 
@@ -76,11 +78,12 @@ module ActiveAgent
76
78
  def span(name, **attributes)
77
79
  return Telemetry::NullSpan.new unless should_trace?
78
80
 
81
+ span_type = attributes.delete(:span_type) || :root
79
82
  current = current_span
80
83
  if current
81
- current.add_span(name, **attributes)
84
+ current.add_span(name, span_type: span_type, **attributes)
82
85
  else
83
- Span.new(name, trace_id: generate_trace_id, **default_attributes.merge(attributes))
86
+ Span.new(name, trace_id: SecureRandom.hex(16), span_type: span_type, **default_attributes.merge(attributes))
84
87
  end
85
88
  end
86
89
 
@@ -91,7 +94,9 @@ module ActiveAgent
91
94
  Thread.current[CURRENT_SPAN_KEY]
92
95
  end
93
96
 
94
- # Flushes buffered traces immediately.
97
+ # Flushes buffered traces and waits for delivery to complete, so
98
+ # callers (tests, rails runner, job shutdown) can rely on the traces
99
+ # having been delivered/stored when this returns.
95
100
  #
96
101
  # @return [void]
97
102
  def flush
@@ -107,11 +112,16 @@ module ActiveAgent
107
112
 
108
113
  private
109
114
 
115
+ def build_trace(trace_id)
116
+ ActiveAgents::Telemetry::Trace.new(
117
+ trace_id: trace_id,
118
+ service_name: configuration.resolved_service_name,
119
+ environment: configuration.resolved_environment,
120
+ resource_attributes: configuration.resource_attributes
121
+ )
122
+ end
123
+
110
124
  # Executes block with span as current context.
111
- #
112
- # @param span [Span] Span to set as current
113
- # @yield Block to execute
114
- # @return [Object] Result of block
115
125
  def with_span(span)
116
126
  previous = Thread.current[CURRENT_SPAN_KEY]
117
127
  Thread.current[CURRENT_SPAN_KEY] = span
@@ -120,61 +130,48 @@ module ActiveAgent
120
130
  Thread.current[CURRENT_SPAN_KEY] = previous
121
131
  end
122
132
 
123
- # Reports a completed trace to the reporter.
124
- #
125
- # @param span [Span] Root span of the trace
126
- def report_trace(span)
127
- reporter.report(build_trace_payload(span))
133
+ # Redacts span (and span-event) attributes whose keys match any
134
+ # configured redact_attributes entry, before the trace is handed to
135
+ # the reporter. Matching is case-insensitive substring deliberately
136
+ # over-broad: better to redact a harmless "max_tokens" than to ship
137
+ # an "api_key". (Ported from the pre-shared-core payload builder,
138
+ # which no longer exists — the gem's reporter ships the trace as-is.)
139
+ REDACTED = "[REDACTED]"
140
+
141
+ def redact_trace!(trace)
142
+ patterns = Array(configuration.redact_attributes).map(&:to_s).reject(&:empty?)
143
+ return trace if patterns.empty?
144
+
145
+ matcher = Regexp.union(patterns.map { |pattern| Regexp.new(Regexp.escape(pattern), Regexp::IGNORECASE) })
146
+ Array(trace.spans).each { |span| redact_span!(span, matcher) }
147
+ trace
128
148
  end
129
149
 
130
- # Builds the trace payload for transmission.
131
- #
132
- # @param root_span [Span] Root span
133
- # @return [Hash] Trace payload
134
- def build_trace_payload(root_span)
135
- {
136
- trace_id: root_span.trace_id,
137
- service_name: configuration.resolved_service_name,
138
- environment: configuration.environment,
139
- timestamp: Time.current.iso8601(6),
140
- resource_attributes: configuration.resource_attributes,
141
- spans: flatten_spans(root_span)
142
- }
150
+ def redact_span!(span, matcher)
151
+ redact_hash!(span.attributes, matcher) if span.attributes.is_a?(Hash)
152
+ Array(span.events).each do |event|
153
+ attributes = event.is_a?(Hash) ? (event["attributes"] || event[:attributes]) : nil
154
+ redact_hash!(attributes, matcher) if attributes.is_a?(Hash)
155
+ end
156
+ Array(span.children).each { |child| redact_span!(child, matcher) }
143
157
  end
144
158
 
145
- # Flattens span hierarchy into array.
146
- #
147
- # @param span [Span] Root span
148
- # @return [Array<Hash>] Flattened span data
149
- def flatten_spans(span)
150
- result = [ span.to_h.except(:children) ]
151
- span.children.each do |child|
152
- result.concat(flatten_spans(child))
159
+ def redact_hash!(attributes, matcher)
160
+ attributes.each_key do |key|
161
+ attributes[key] = REDACTED if key.to_s.match?(matcher)
153
162
  end
154
- result
155
163
  end
156
164
 
157
- # Returns whether this trace should be sampled.
158
- #
159
- # @return [Boolean]
165
+ # Returns whether this trace should be collected at all.
160
166
  def should_trace?
161
167
  configuration.enabled? && configuration.configured? && configuration.should_sample?
162
168
  end
163
169
 
164
- # Generates a unique trace ID.
165
- #
166
- # @return [String] 32-character hex trace ID
167
- def generate_trace_id
168
- SecureRandom.hex(16)
169
- end
170
-
171
170
  # Returns default attributes for all spans.
172
- #
173
- # @return [Hash] Default attributes
174
171
  def default_attributes
175
172
  {
176
173
  "service.name" => configuration.resolved_service_name,
177
- "service.environment" => configuration.environment,
174
+ "service.environment" => configuration.resolved_environment,
178
175
  "telemetry.sdk.name" => "activeagent",
179
176
  "telemetry.sdk.version" => ActiveAgent::VERSION
180
177
  }
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "net/http"
4
- require "json"
5
3
  require "securerandom"
4
+ require "activeagents/telemetry"
6
5
 
7
6
  module ActiveAgent
8
7
  # Telemetry module for collecting and reporting agent traces.
@@ -55,6 +54,7 @@ module ActiveAgent
55
54
  autoload :Span
56
55
  autoload :Reporter
57
56
  autoload :Instrumentation
57
+ autoload :ToolOrigin
58
58
 
59
59
  class << self
60
60
  # Returns the telemetry configuration instance.
@@ -1,3 +1,3 @@
1
1
  module ActiveAgent
2
- VERSION = "1.0.3"
2
+ VERSION = "1.2.0"
3
3
  end
data/lib/active_agent.rb CHANGED
@@ -101,13 +101,13 @@ module ActiveAgent
101
101
  autoload :Preview, "active_agent/concerns/preview"
102
102
  autoload :Previews, "active_agent/concerns/preview"
103
103
  autoload :GenerationJob
104
+ autoload :ModelCapabilities
104
105
  autoload :Observers, "active_agent/concerns/observers"
105
106
  autoload :Provider, "active_agent/concerns/provider"
106
107
  autoload :Rescue, "active_agent/concerns/rescue"
107
108
  autoload :Tooling, "active_agent/concerns/tooling"
108
109
  autoload :View, "active_agent/concerns/view"
109
110
  autoload :Telemetry
110
- autoload :Dashboard
111
111
 
112
112
  class << self
113
113
  # Eagerly loads all ActiveAgent components and descendant agent classes.
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.0.3
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Bowen
@@ -109,6 +109,40 @@ 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'
132
+ - !ruby/object:Gem::Dependency
133
+ name: activeagents-telemetry
134
+ requirement: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.1'
139
+ type: :runtime
140
+ prerelease: false
141
+ version_requirements: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0.1'
112
146
  - !ruby/object:Gem::Dependency
113
147
  name: jbuilder
114
148
  requirement: !ruby/object:Gem::Requirement
@@ -386,38 +420,11 @@ files:
386
420
  - lib/active_agent/concerns/tooling.rb
387
421
  - lib/active_agent/concerns/view.rb
388
422
  - lib/active_agent/configuration.rb
389
- - lib/active_agent/dashboard.rb
390
- - lib/active_agent/dashboard/app/controllers/active_agent/dashboard/api/traces_controller.rb
391
- - lib/active_agent/dashboard/app/controllers/active_agent/dashboard/application_controller.rb
392
- - lib/active_agent/dashboard/app/controllers/active_agent/dashboard/dashboard_controller.rb
393
- - lib/active_agent/dashboard/app/controllers/active_agent/dashboard/traces_controller.rb
394
- - lib/active_agent/dashboard/app/jobs/active_agent/dashboard/agent_execution_job.rb
395
- - lib/active_agent/dashboard/app/jobs/active_agent/dashboard/application_job.rb
396
- - lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_cleanup_job.rb
397
- - lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_provision_job.rb
398
- - lib/active_agent/dashboard/app/jobs/active_agent/process_telemetry_traces_job.rb
399
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/agent.rb
400
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_run.rb
401
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_template.rb
402
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_version.rb
403
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/application_record.rb
404
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_action.rb
405
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_snapshot.rb
406
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_run.rb
407
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_session.rb
408
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/session_recording.rb
409
- - lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb
410
- - lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/_trace_detail.html.erb
411
- - lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/index.html.erb
412
- - lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/metrics.html.erb
413
- - lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/show.html.erb
414
- - lib/active_agent/dashboard/app/views/layouts/active_agent/dashboard/application.html.erb
415
- - lib/active_agent/dashboard/config/routes.rb
416
- - lib/active_agent/dashboard/engine.rb
417
423
  - lib/active_agent/deprecator.rb
418
424
  - lib/active_agent/generation.rb
419
425
  - lib/active_agent/generation_job.rb
420
426
  - lib/active_agent/inline_preview_interceptor.rb
427
+ - lib/active_agent/model_capabilities.rb
421
428
  - lib/active_agent/providers/_base_provider.rb
422
429
  - lib/active_agent/providers/anthropic/_types.rb
423
430
  - lib/active_agent/providers/anthropic/options.rb
@@ -453,6 +460,7 @@ files:
453
460
  - lib/active_agent/providers/concerns/instrumentation.rb
454
461
  - lib/active_agent/providers/concerns/previewable.rb
455
462
  - lib/active_agent/providers/concerns/tool_choice_clearing.rb
463
+ - lib/active_agent/providers/errors.rb
456
464
  - lib/active_agent/providers/gemini/_types.rb
457
465
  - lib/active_agent/providers/gemini/options.rb
458
466
  - lib/active_agent/providers/gemini_provider.rb
@@ -535,24 +543,12 @@ files:
535
543
  - lib/active_agent/telemetry/instrumentation.rb
536
544
  - lib/active_agent/telemetry/reporter.rb
537
545
  - lib/active_agent/telemetry/span.rb
546
+ - lib/active_agent/telemetry/tool_origin.rb
538
547
  - lib/active_agent/telemetry/tracer.rb
539
548
  - lib/active_agent/version.rb
540
549
  - lib/activeagent.rb
541
550
  - lib/generators/active_agent/agent/USAGE
542
551
  - lib/generators/active_agent/agent/agent_generator.rb
543
- - lib/generators/active_agent/dashboard/install/install_generator.rb
544
- - lib/generators/active_agent/dashboard/install/templates/initializer.rb
545
- - lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_runs.rb
546
- - lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_templates.rb
547
- - lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_versions.rb
548
- - lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agents.rb
549
- - lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_sandbox_runs.rb
550
- - lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_sandbox_sessions.rb
551
- - lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_session_recordings.rb
552
- - lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_telemetry_traces.rb
553
- - lib/generators/active_agent/dashboard/install_generator.rb
554
- - lib/generators/active_agent/dashboard/templates/active_agent_dashboard.rb.erb
555
- - lib/generators/active_agent/dashboard/templates/create_active_agent_telemetry_traces.rb.erb
556
552
  - lib/generators/active_agent/install/USAGE
557
553
  - lib/generators/active_agent/install/install_generator.rb
558
554
  - lib/generators/active_agent/templates/active_agent.yml
@@ -1,117 +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: No authentication, synchronous processing
13
- # - Multi-tenant mode: Bearer token auth, async processing via job
14
- #
15
- # @example Local mode request
16
- # POST /active_agent/api/traces
17
- # Content-Type: application/json
18
- #
19
- # {
20
- # "traces": [...],
21
- # "sdk": { "name": "activeagent", "version": "0.5.0" }
22
- # }
23
- #
24
- # @example Multi-tenant mode request
25
- # POST /active_agent/api/traces
26
- # Authorization: Bearer <api_key>
27
- # Content-Type: application/json
28
- #
29
- # {
30
- # "traces": [...],
31
- # "sdk": { "name": "activeagent", "version": "0.5.0" }
32
- # }
33
- #
34
- class TracesController < ActionController::API
35
- before_action :authenticate_api_key!, if: -> { ActiveAgent::Dashboard.multi_tenant? }
36
-
37
- # POST /active_agent/api/traces
38
- def create
39
- traces = params[:traces] || []
40
- sdk_info = params[:sdk] || {}
41
-
42
- return head :accepted if traces.empty?
43
-
44
- if ActiveAgent::Dashboard.multi_tenant?
45
- # Multi-tenant mode: process in background
46
- ActiveAgent::ProcessTelemetryTracesJob.perform_later(
47
- account_id: @account&.id,
48
- traces: traces.as_json,
49
- sdk_info: sdk_info.as_json,
50
- received_at: Time.current.iso8601(6)
51
- )
52
- else
53
- # Local mode: process synchronously
54
- process_traces_synchronously(traces, sdk_info)
55
- end
56
-
57
- head :accepted
58
- rescue ActionController::ParameterMissing => e
59
- render json: { error: e.message }, status: :bad_request
60
- rescue StandardError => e
61
- Rails.logger.error("[ActiveAgent::Dashboard] Trace ingestion error: #{e.message}")
62
- render json: { error: "Internal server error" }, status: :internal_server_error
63
- end
64
-
65
- private
66
-
67
- # Authenticates the request using Bearer token from Authorization header.
68
- # Only used in multi-tenant mode.
69
- def authenticate_api_key!
70
- token = extract_bearer_token
71
-
72
- if token.blank?
73
- render json: { error: "Missing Authorization header" }, status: :unauthorized
74
- return
75
- end
76
-
77
- account_class = ActiveAgent::Dashboard.account_class.constantize
78
- @account = account_class.find_by(telemetry_api_key: token)
79
-
80
- if @account.nil?
81
- render json: { error: "Invalid API key" }, status: :unauthorized
82
- return
83
- end
84
-
85
- # Track usage for rate limiting (if the account responds to it)
86
- @account.increment_telemetry_usage! if @account.respond_to?(:increment_telemetry_usage!)
87
- end
88
-
89
- # Extracts Bearer token from Authorization header.
90
- def extract_bearer_token
91
- auth_header = request.headers["Authorization"]
92
- return nil if auth_header.blank?
93
-
94
- match = auth_header.match(/^Bearer\s+(.+)$/i)
95
- match[1] if match
96
- end
97
-
98
- # Process traces synchronously for local development.
99
- def process_traces_synchronously(traces, sdk_info)
100
- model = ActiveAgent::Dashboard.trace_model
101
-
102
- traces.each do |trace|
103
- # Skip if trace already exists (idempotency)
104
- next if model.exists?(trace_id: trace["trace_id"])
105
-
106
- model.create_from_payload(trace, sdk_info)
107
- rescue StandardError => e
108
- Rails.logger.error(
109
- "[ActiveAgent::Dashboard] Failed to process trace #{trace['trace_id']}: " \
110
- "#{e.class} - #{e.message}"
111
- )
112
- end
113
- end
114
- end
115
- end
116
- end
117
- end
@@ -1,54 +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
- return if ActiveAgent::Dashboard.authentication_method.nil?
25
-
26
- result = ActiveAgent::Dashboard.authentication_method.call(self)
27
- head :unauthorized unless result
28
- rescue StandardError => e
29
- Rails.logger.error("[ActiveAgent::Dashboard] Authentication error: #{e.message}")
30
- head :unauthorized
31
- end
32
-
33
- # Returns the current user from the host application.
34
- def current_user
35
- return nil unless ActiveAgent::Dashboard.current_user_method
36
-
37
- send(ActiveAgent::Dashboard.current_user_method)
38
- rescue NoMethodError
39
- nil
40
- end
41
-
42
- # Returns the current owner (account in multi-tenant, user otherwise).
43
- def current_owner
44
- if ActiveAgent::Dashboard.multi_tenant? && ActiveAgent::Dashboard.current_account_method
45
- send(ActiveAgent::Dashboard.current_account_method)
46
- else
47
- current_user
48
- end
49
- rescue NoMethodError
50
- nil
51
- end
52
- end
53
- end
54
- end
@@ -1,126 +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
- @agents = fetch_agents.limit(10)
10
- @recent_runs = fetch_recent_runs.limit(10)
11
- @recent_traces = fetch_recent_traces.limit(10)
12
- @metrics = calculate_metrics
13
-
14
- if ActiveAgent::Dashboard.use_inertia && defined?(InertiaRails)
15
- render inertia: "Dashboard", props: {
16
- agents: serialize_agents(@agents),
17
- recentRuns: serialize_runs(@recent_runs),
18
- recentTraces: serialize_traces(@recent_traces),
19
- metrics: @metrics,
20
- user: current_user_props,
21
- account: current_account_props
22
- }
23
- else
24
- render :index
25
- end
26
- end
27
-
28
- private
29
-
30
- def fetch_agents
31
- agents = Agent.order(updated_at: :desc)
32
- agents = agents.for_owner(current_owner) if current_owner
33
- agents
34
- end
35
-
36
- def fetch_recent_runs
37
- runs = AgentRun.includes(:agent).recent
38
- if current_owner && ActiveAgent::Dashboard.multi_tenant?
39
- runs = runs.joins(:agent).where(agents: { account_id: current_owner.id })
40
- end
41
- runs
42
- end
43
-
44
- def fetch_recent_traces
45
- traces = ActiveAgent::Dashboard.trace_model.recent
46
- traces = traces.for_account(current_owner) if current_owner && ActiveAgent::Dashboard.multi_tenant?
47
- traces
48
- end
49
-
50
- def calculate_metrics
51
- traces_24h = ActiveAgent::Dashboard.trace_model.where("created_at > ?", 24.hours.ago)
52
- runs_24h = AgentRun.where("created_at > ?", 24.hours.ago)
53
-
54
- if current_owner && ActiveAgent::Dashboard.multi_tenant?
55
- traces_24h = traces_24h.for_account(current_owner)
56
- runs_24h = runs_24h.joins(:agent).where(agents: { account_id: current_owner.id })
57
- end
58
-
59
- {
60
- total_agents: fetch_agents.count,
61
- active_agents: fetch_agents.active_agents.count,
62
- total_runs_24h: runs_24h.count,
63
- successful_runs_24h: runs_24h.successful.count,
64
- failed_runs_24h: runs_24h.failed_runs.count,
65
- total_traces_24h: traces_24h.count,
66
- total_tokens_24h: traces_24h.sum(:total_input_tokens).to_i + traces_24h.sum(:total_output_tokens).to_i,
67
- avg_duration_ms: runs_24h.where.not(duration_ms: nil).average(:duration_ms)&.round || 0
68
- }
69
- end
70
-
71
- def serialize_agents(agents)
72
- agents.map do |agent|
73
- {
74
- id: agent.id,
75
- name: agent.name,
76
- slug: agent.slug,
77
- description: agent.description,
78
- provider: agent.provider,
79
- model: agent.model,
80
- status: agent.status,
81
- presetType: agent.preset_type,
82
- versionCount: agent.version_count,
83
- updatedAt: agent.updated_at.iso8601
84
- }
85
- end
86
- end
87
-
88
- def serialize_runs(runs)
89
- runs.map(&:summary)
90
- end
91
-
92
- def serialize_traces(traces)
93
- traces.map do |trace|
94
- {
95
- id: trace.id,
96
- traceId: trace.trace_id,
97
- displayName: trace.display_name,
98
- status: trace.status,
99
- duration: trace.formatted_duration,
100
- tokens: trace.formatted_tokens,
101
- timestamp: trace.timestamp&.iso8601
102
- }
103
- end
104
- end
105
-
106
- def current_user_props
107
- return nil unless current_user
108
-
109
- {
110
- id: current_user.id,
111
- name: current_user.try(:display_name) || current_user.try(:name) || current_user.try(:email),
112
- email: current_user.try(:email)
113
- }
114
- end
115
-
116
- def current_account_props
117
- return nil unless current_owner && ActiveAgent::Dashboard.multi_tenant?
118
-
119
- {
120
- id: current_owner.id,
121
- name: current_owner.try(:name)
122
- }
123
- end
124
- end
125
- end
126
- end