actionagent 0.0.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/LICENSE +21 -0
- data/README.md +103 -0
- data/app/assets/builds/action_agent.css +2 -0
- data/app/assets/builds/action_agent.js +163 -0
- data/app/controllers/action_agent/api/agent_runs_controller.rb +128 -0
- data/app/controllers/action_agent/api/agents_controller.rb +411 -0
- data/app/controllers/action_agent/api/analytics_controller.rb +94 -0
- data/app/controllers/action_agent/api/api_keys_controller.rb +45 -0
- data/app/controllers/action_agent/api/base_controller.rb +112 -0
- data/app/controllers/action_agent/api/evaluations_controller.rb +148 -0
- data/app/controllers/action_agent/api/instance_tiers_controller.rb +106 -0
- data/app/controllers/action_agent/api/interactions_controller.rb +197 -0
- data/app/controllers/action_agent/api/mcp_controller.rb +218 -0
- data/app/controllers/action_agent/api/mcp_servers_controller.rb +156 -0
- data/app/controllers/action_agent/api/metrics_controller.rb +178 -0
- data/app/controllers/action_agent/api/provider_keys_controller.rb +52 -0
- data/app/controllers/action_agent/api/provider_models_controller.rb +119 -0
- data/app/controllers/action_agent/api/sandboxes_controller.rb +224 -0
- data/app/controllers/action_agent/api/session_recordings_controller.rb +372 -0
- data/app/controllers/action_agent/api/templates_controller.rb +94 -0
- data/app/controllers/action_agent/api/tools_controller.rb +58 -0
- data/app/controllers/action_agent/api/trace_reports_controller.rb +68 -0
- data/app/controllers/action_agent/api/traces_controller.rb +136 -0
- data/app/controllers/action_agent/application_controller.rb +105 -0
- data/app/controllers/action_agent/dashboard_controller.rb +104 -0
- data/app/controllers/action_agent/traces_controller.rb +121 -0
- data/app/jobs/action_agent/agent_execution_job.rb +52 -0
- data/app/jobs/action_agent/application_job.rb +12 -0
- data/app/jobs/action_agent/process_telemetry_traces_job.rb +86 -0
- data/app/jobs/action_agent/sandbox_cleanup_job.rb +42 -0
- data/app/jobs/action_agent/sandbox_provision_job.rb +56 -0
- data/app/jobs/action_agent/sandbox_run_job.rb +285 -0
- data/app/jobs/action_agent/trace_retention_job.rb +57 -0
- data/app/models/action_agent/agent.rb +343 -0
- data/app/models/action_agent/agent_context.rb +129 -0
- data/app/models/action_agent/agent_generation.rb +48 -0
- data/app/models/action_agent/agent_memory.rb +50 -0
- data/app/models/action_agent/agent_memory_entry.rb +14 -0
- data/app/models/action_agent/agent_message.rb +43 -0
- data/app/models/action_agent/agent_run.rb +151 -0
- data/app/models/action_agent/agent_template.rb +182 -0
- data/app/models/action_agent/agent_version.rb +48 -0
- data/app/models/action_agent/api_key.rb +53 -0
- data/app/models/action_agent/application_record.rb +27 -0
- data/app/models/action_agent/evaluation.rb +80 -0
- data/app/models/action_agent/evaluation_run.rb +20 -0
- data/app/models/action_agent/model_pricing.rb +80 -0
- data/app/models/action_agent/provider_key.rb +60 -0
- data/app/models/action_agent/recording_action.rb +119 -0
- data/app/models/action_agent/recording_snapshot.rb +88 -0
- data/app/models/action_agent/sandbox_instance_tier.rb +368 -0
- data/app/models/action_agent/sandbox_run.rb +45 -0
- data/app/models/action_agent/sandbox_session.rb +160 -0
- data/app/models/action_agent/session_recording.rb +178 -0
- data/app/models/action_agent/telemetry_trace.rb +357 -0
- data/app/models/concerns/action_agent/adapter_aware.rb +50 -0
- data/app/models/concerns/action_agent/ownable.rb +86 -0
- data/app/models/concerns/action_agent/session_recordable.rb +91 -0
- data/app/queries/action_agent/agent_executions.rb +201 -0
- data/app/serializers/action_agent/agent_message_serializer.rb +23 -0
- data/app/serializers/action_agent/interaction_preview.rb +22 -0
- data/app/serializers/action_agent/telemetry_trace_serializer.rb +122 -0
- data/app/serializers/action_agent/trace_interaction_serializer.rb +246 -0
- data/app/services/action_agent/agent_execution_service.rb +572 -0
- data/app/services/action_agent/agent_registrar.rb +197 -0
- data/app/services/action_agent/agent_scorecard.rb +191 -0
- data/app/services/action_agent/agent_toolbox.rb +504 -0
- data/app/services/action_agent/evaluation_runner_service.rb +481 -0
- data/app/services/action_agent/mcp_catalog.rb +247 -0
- data/app/services/action_agent/mcp_recording_middleware.rb +241 -0
- data/app/services/action_agent/mock_sandbox_backend.rb +52 -0
- data/app/services/action_agent/playwright_mcp_client.rb +148 -0
- data/app/services/action_agent/sandbox_orchestrator.rb +242 -0
- data/app/services/action_agent/session_recording_service.rb +228 -0
- data/app/services/action_agent/tool_discovery.rb +617 -0
- data/app/views/action_agent/dashboard/index.html.erb +5 -0
- data/app/views/action_agent/traces/_trace_detail.html.erb +117 -0
- data/app/views/action_agent/traces/index.html.erb +135 -0
- data/app/views/action_agent/traces/metrics.html.erb +145 -0
- data/app/views/action_agent/traces/show.html.erb +36 -0
- data/app/views/layouts/action_agent/application.html.erb +94 -0
- data/app/views/layouts/action_agent/react.html.erb +19 -0
- data/config/routes.rb +144 -0
- data/lib/action_agent/compatibility.rb +49 -0
- data/lib/action_agent/engine.rb +51 -0
- data/lib/action_agent/version.rb +5 -0
- data/lib/action_agent.rb +388 -0
- data/lib/actionagent.rb +6 -0
- data/lib/generators/action_agent/install_generator.rb +137 -0
- data/lib/generators/action_agent/templates/action_agent.rb.erb +82 -0
- data/lib/generators/action_agent/templates/add_agent_id_to_active_agent_telemetry_traces.rb.erb +24 -0
- data/lib/generators/action_agent/templates/create_active_agent_dashboard_tables.rb.erb +319 -0
- data/lib/generators/action_agent/templates/create_active_agent_telemetry_traces.rb.erb +58 -0
- metadata +209 -12
data/config/routes.rb
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
ActionAgent::Engine.routes.draw do
|
|
4
|
+
# The React dashboard's entry point. Its own paths (/traces, /metrics,
|
|
5
|
+
# /agents/1/edit, ...) are matched by the catch-all at the bottom of this
|
|
6
|
+
# file, so deep links and the browser's back button both work wherever the
|
|
7
|
+
# engine is mounted.
|
|
8
|
+
root to: "dashboard#index"
|
|
9
|
+
|
|
10
|
+
# The server-rendered console. Same data, no JavaScript — useful when the
|
|
11
|
+
# bundle can't run, and the surface the dashboard shipped with before the
|
|
12
|
+
# React app moved into the engine.
|
|
13
|
+
scope :console do
|
|
14
|
+
resources :traces, only: [ :index, :show ], controller: "traces" do
|
|
15
|
+
collection do
|
|
16
|
+
get :metrics
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# The dashboard's own JSON API, read and written by the React app.
|
|
22
|
+
namespace :api do
|
|
23
|
+
# Telemetry ingestion, relative to wherever the engine is mounted:
|
|
24
|
+
# <mount>/api/traces (e.g. /activeagents/api/traces at the default mount).
|
|
25
|
+
# Authenticated with a bearer token, not a session.
|
|
26
|
+
resources :traces, only: [ :create ]
|
|
27
|
+
|
|
28
|
+
# A JSON API has no :new or :edit forms to serve.
|
|
29
|
+
resources :agents, except: [ :new, :edit ] do
|
|
30
|
+
member do
|
|
31
|
+
get :versions
|
|
32
|
+
post :restore
|
|
33
|
+
get :runs
|
|
34
|
+
post :execute
|
|
35
|
+
post :test
|
|
36
|
+
post :duplicate
|
|
37
|
+
get :export
|
|
38
|
+
get :analytics
|
|
39
|
+
end
|
|
40
|
+
collection do
|
|
41
|
+
get :presets
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
resources :templates, only: [ :index, :show ] do
|
|
46
|
+
member do
|
|
47
|
+
post :use
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
resources :runs, controller: "agent_runs", only: [ :index, :show ] do
|
|
52
|
+
member do
|
|
53
|
+
post :cancel
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Sandboxes. The engine ships the in-memory backend; an operator registers
|
|
58
|
+
# real ones (see ActionAgent.sandbox_backends).
|
|
59
|
+
resources :sandboxes, param: :id, only: [ :index, :create, :show, :destroy ] do
|
|
60
|
+
collection do
|
|
61
|
+
post :compare
|
|
62
|
+
end
|
|
63
|
+
member do
|
|
64
|
+
post :run
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Tool inventory — auto-detected from the tool roster each generation
|
|
69
|
+
# request offered, telemetry tool spans, and solid_agent records.
|
|
70
|
+
resources :tools, only: [ :index ]
|
|
71
|
+
|
|
72
|
+
# MCP services — detected servers unioned with the default catalog
|
|
73
|
+
# (McpCatalog), plus on-demand sandbox provisioning. Keys are catalog
|
|
74
|
+
# slugs like "sequential-thinking", so the id segment allows dashes.
|
|
75
|
+
resources :mcp_servers, only: [ :index, :show ], id: /[^\/]+/ do
|
|
76
|
+
member do
|
|
77
|
+
post :launch
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
resources :instance_tiers, only: [ :index, :show ] do
|
|
82
|
+
collection do
|
|
83
|
+
get :recommend
|
|
84
|
+
get :pricing
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
resources :session_recordings, only: [ :index, :show, :destroy ] do
|
|
89
|
+
member do
|
|
90
|
+
get :actions
|
|
91
|
+
get "snapshot/:action_id", action: :snapshot, as: :snapshot
|
|
92
|
+
post :export
|
|
93
|
+
post :handoff
|
|
94
|
+
post :record_action
|
|
95
|
+
post :complete, action: :complete_session
|
|
96
|
+
end
|
|
97
|
+
collection do
|
|
98
|
+
get :recent
|
|
99
|
+
post :start_user_session
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
resource :analytics, only: [], controller: "analytics" do
|
|
104
|
+
get "/", action: :index
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Reading traces is the same path as ingesting them, separated by verb:
|
|
108
|
+
# POST is the SDK's authenticated-by-token ingest above, GET is the
|
|
109
|
+
# dashboard's session-authenticated read.
|
|
110
|
+
resources :traces, only: [ :index, :show ], controller: "trace_reports", as: :trace_reports
|
|
111
|
+
resource :metrics, only: [ :show ], controller: "metrics"
|
|
112
|
+
|
|
113
|
+
# Conversations (contexts, messages, generations) behind Interactions.
|
|
114
|
+
resources :interactions, only: [ :index, :show ]
|
|
115
|
+
|
|
116
|
+
# Agent output evaluations.
|
|
117
|
+
resources :evaluations, only: [ :index, :show, :create, :destroy ] do
|
|
118
|
+
member do
|
|
119
|
+
post :run
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Credentials: dashboard API keys (token shown once on create) and the
|
|
124
|
+
# owner's own LLM provider credentials, both encrypted at rest.
|
|
125
|
+
resources :api_keys, only: [ :index, :create, :destroy ]
|
|
126
|
+
resources :provider_keys, only: [ :index, :create, :destroy ], param: :provider
|
|
127
|
+
|
|
128
|
+
# Model catalogs for the agent builder (Ollama queried live from the
|
|
129
|
+
# configured host; hosted providers curated).
|
|
130
|
+
resources :provider_models, only: [ :index ]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The account's agents presented as an authenticated MCP server (tools +
|
|
134
|
+
# agent:// resources) over Streamable HTTP JSON-RPC. Authenticated with a
|
|
135
|
+
# dashboard API key rather than a session, so it sits outside the api
|
|
136
|
+
# namespace's session-authenticated controllers.
|
|
137
|
+
post "mcp", to: "api/mcp#create"
|
|
138
|
+
|
|
139
|
+
# Everything else under the mount is a client-side route: render the
|
|
140
|
+
# dashboard and let the browser resolve it. Anchored last so it can only
|
|
141
|
+
# ever catch what the routes above did not, and refuses /api paths so a
|
|
142
|
+
# mistyped endpoint answers as an API would rather than returning a page.
|
|
143
|
+
get "*path", to: "dashboard#index", constraints: ->(request) { !request.path.include?("/api/") }
|
|
144
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Transitional aliases for the names this engine shipped under when it lived
|
|
4
|
+
# inside the activeagent gem (<= 1.1.0).
|
|
5
|
+
#
|
|
6
|
+
# Three of these matter for more than tidiness:
|
|
7
|
+
#
|
|
8
|
+
# * ActiveAgent::ProcessTelemetryTracesJob — Active Job serializes the class
|
|
9
|
+
# name into the queue payload, so a job enqueued before the upgrade is
|
|
10
|
+
# dequeued after it and must still resolve to something.
|
|
11
|
+
# * ActiveAgent::TelemetryTrace — apps subclass it (the activeagents.ai
|
|
12
|
+
# platform does) and it is referenced from initializers.
|
|
13
|
+
# * ActiveAgent::Dashboard — every existing initializer calls
|
|
14
|
+
# ActiveAgent::Dashboard.configure.
|
|
15
|
+
#
|
|
16
|
+
# Each warns once through the deprecator and forwards to the new constant.
|
|
17
|
+
# Remove in the next major.
|
|
18
|
+
module ActionAgent
|
|
19
|
+
module Compatibility
|
|
20
|
+
RENAMED = {
|
|
21
|
+
"Dashboard" => "ActionAgent",
|
|
22
|
+
"TelemetryTrace" => "ActionAgent::TelemetryTrace",
|
|
23
|
+
"ProcessTelemetryTracesJob" => "ActionAgent::ProcessTelemetryTracesJob"
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
# Installs const_missing on ActiveAgent so the old names keep resolving
|
|
27
|
+
# without eagerly loading the engine's models (which would drag Active
|
|
28
|
+
# Record in at boot, the very thing the gem split fixed).
|
|
29
|
+
def self.install!
|
|
30
|
+
return if @installed
|
|
31
|
+
|
|
32
|
+
ActiveAgent.singleton_class.prepend(ConstMissing)
|
|
33
|
+
@installed = true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
module ConstMissing
|
|
37
|
+
def const_missing(name)
|
|
38
|
+
replacement = RENAMED[name.to_s]
|
|
39
|
+
return super unless replacement
|
|
40
|
+
|
|
41
|
+
ActionAgent.deprecator.warn(
|
|
42
|
+
"ActiveAgent::#{name} has moved to #{replacement}. " \
|
|
43
|
+
"The dashboard is now the actionagent gem; update your references."
|
|
44
|
+
)
|
|
45
|
+
replacement.constantize
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionAgent
|
|
4
|
+
# Rails engine for the Active Agent dashboard: the agent builder, runs,
|
|
5
|
+
# conversations, evaluations, traces, metrics, sandboxes and session
|
|
6
|
+
# recordings, plus the trace ingest API and the MCP server facade.
|
|
7
|
+
#
|
|
8
|
+
# Mount it wherever you like:
|
|
9
|
+
# mount ActionAgent::Engine => "/agents"
|
|
10
|
+
#
|
|
11
|
+
class Engine < ::Rails::Engine
|
|
12
|
+
isolate_namespace ActionAgent
|
|
13
|
+
|
|
14
|
+
engine_name "action_agent"
|
|
15
|
+
|
|
16
|
+
config.action_agent = ActiveSupport::OrderedOptions.new
|
|
17
|
+
|
|
18
|
+
# The dashboard's JS and CSS ship prebuilt in the gem. Adding the
|
|
19
|
+
# directory to the host app's asset paths is what lets a plain
|
|
20
|
+
# `mount ActionAgent::Engine` work without the host running a
|
|
21
|
+
# JavaScript build — or having a JavaScript build at all.
|
|
22
|
+
# Provider credentials and API keys are posted to the dashboard in the
|
|
23
|
+
# clear and encrypted at rest — filtering keeps them out of the request
|
|
24
|
+
# logs in between, where the gem would otherwise print them verbatim.
|
|
25
|
+
initializer "action_agent.filter_parameters" do |app|
|
|
26
|
+
app.config.filter_parameters += [ :credential, :api_key, :access_token ]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
initializer "action_agent.assets", before: :append_assets_path do |app|
|
|
30
|
+
builds = root.join("app", "assets", "builds").to_s
|
|
31
|
+
next unless File.directory?(builds)
|
|
32
|
+
|
|
33
|
+
next unless app.config.respond_to?(:assets)
|
|
34
|
+
|
|
35
|
+
if app.config.assets.respond_to?(:paths)
|
|
36
|
+
app.config.assets.paths << builds unless app.config.assets.paths.include?(builds)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Sprockets serves only what it was told to precompile, and the host's
|
|
40
|
+
# manifest.js cannot know about an engine's bundles. Without this the
|
|
41
|
+
# layout's stylesheet_link_tag/javascript_include_tag raise
|
|
42
|
+
# AssetNotPrecompiled in development and AssetNotFound in production —
|
|
43
|
+
# i.e. the dashboard's only page 500s on every sprockets-rails host.
|
|
44
|
+
# Propshaft serves everything on the load path and has no precompile
|
|
45
|
+
# list, so the respond_to? check is what distinguishes them.
|
|
46
|
+
if app.config.assets.respond_to?(:precompile)
|
|
47
|
+
app.config.assets.precompile |= %w[action_agent.js action_agent.css]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/action_agent.rb
CHANGED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionAgent
|
|
4
|
+
class << self
|
|
5
|
+
# Table name prefix for the engine's models. The engine's own
|
|
6
|
+
# migrations create `active_agent_*` tables, so the default matches.
|
|
7
|
+
#
|
|
8
|
+
# A host app that already owns these tables under different names (the
|
|
9
|
+
# activeagents.ai platform grew them unprefixed) sets this to "" rather
|
|
10
|
+
# than renaming production tables. The engine's migrations read the same
|
|
11
|
+
# value, so the schema and the models never disagree.
|
|
12
|
+
#
|
|
13
|
+
# Defined before the engine is required on purpose: Rails' isolate_namespace
|
|
14
|
+
# installs its own table_name_prefix on an on_load(:active_record) hook
|
|
15
|
+
# unless the module already has one, and that hook would win over any
|
|
16
|
+
# definition made afterwards.
|
|
17
|
+
attr_writer :table_name_prefix
|
|
18
|
+
|
|
19
|
+
def table_name_prefix
|
|
20
|
+
global = defined?(::ActiveRecord::Base) ? ::ActiveRecord::Base.table_name_prefix : ""
|
|
21
|
+
"#{global}#{@table_name_prefix ||= "active_agent_"}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Both are hard requirements, and both must be loaded here rather than left to
|
|
27
|
+
# the host app's Gemfile. Bundler.require only requires the gems an app lists
|
|
28
|
+
# directly, so a transitive dependency is installed and activated but never
|
|
29
|
+
# loaded:
|
|
30
|
+
#
|
|
31
|
+
# * active_agent — Compatibility.install! below dereferences ::ActiveAgent at
|
|
32
|
+
# load time. An app whose Gemfile happens to list actionagent first (which
|
|
33
|
+
# RuboCop's Bundler/OrderedGems will produce, since it sorts before
|
|
34
|
+
# activeagent) would otherwise die at Bundler.require.
|
|
35
|
+
# * solid_agent — AgentExecutionService includes SolidAgent::HasContext in the
|
|
36
|
+
# agent class it builds for every run, so without this every Run fails with
|
|
37
|
+
# an uninitialized-constant error on any install that does not list the gem
|
|
38
|
+
# itself.
|
|
39
|
+
require "active_agent"
|
|
40
|
+
require "solid_agent"
|
|
41
|
+
|
|
42
|
+
require "action_agent/version"
|
|
43
|
+
require "action_agent/engine"
|
|
44
|
+
require "action_agent/compatibility"
|
|
45
|
+
|
|
46
|
+
# Dashboard engine for visualizing telemetry data and managing agents.
|
|
47
|
+
#
|
|
48
|
+
# Mount the engine in your routes to access the full dashboard:
|
|
49
|
+
#
|
|
50
|
+
# # config/routes.rb
|
|
51
|
+
# mount ActionAgent::Engine => "/activeagents"
|
|
52
|
+
#
|
|
53
|
+
# The dashboard provides:
|
|
54
|
+
# - Agent management: Create, edit, version, and execute agents
|
|
55
|
+
# - Traces view: See all agent invocations with spans, timing, and token usage
|
|
56
|
+
# - Metrics view: Aggregate statistics and charts
|
|
57
|
+
# - Sandbox execution: Run agents in isolated environments
|
|
58
|
+
# - Session recordings: Capture and replay browser sessions
|
|
59
|
+
#
|
|
60
|
+
# = Configuration Modes
|
|
61
|
+
#
|
|
62
|
+
# == Local Mode (default)
|
|
63
|
+
# For self-hosted, single-tenant deployments:
|
|
64
|
+
#
|
|
65
|
+
# ActionAgent.configure do |config|
|
|
66
|
+
# config.authentication_method = ->(controller) { controller.authenticate_admin! }
|
|
67
|
+
# config.sandbox_service = :local # Docker/Incus
|
|
68
|
+
# end
|
|
69
|
+
#
|
|
70
|
+
# == Multi-tenant Mode
|
|
71
|
+
# For SaaS platforms with multiple accounts:
|
|
72
|
+
#
|
|
73
|
+
# ActionAgent.configure do |config|
|
|
74
|
+
# config.multi_tenant = true
|
|
75
|
+
# config.account_class = "Account"
|
|
76
|
+
# config.user_class = "User"
|
|
77
|
+
# config.current_account_method = :current_account
|
|
78
|
+
# config.current_user_method = :current_user
|
|
79
|
+
# config.authentication_method = ->(controller) { controller.authenticate_user! }
|
|
80
|
+
# config.sandbox_service = :cloud_run # Managed
|
|
81
|
+
# config.use_inertia = true
|
|
82
|
+
# end
|
|
83
|
+
#
|
|
84
|
+
module ActionAgent
|
|
85
|
+
class << self
|
|
86
|
+
# Deprecation warnings for this gem, routed through Rails' machinery so a
|
|
87
|
+
# host app can silence or escalate them like any other.
|
|
88
|
+
def deprecator
|
|
89
|
+
@deprecator ||= ActiveSupport::Deprecation.new("2.0", "ActionAgent")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Authentication method to call on controllers
|
|
93
|
+
# @return [Proc, nil] A proc that receives the controller instance
|
|
94
|
+
attr_accessor :authentication_method
|
|
95
|
+
|
|
96
|
+
# Enable multi-tenant mode (requires account association)
|
|
97
|
+
# @return [Boolean]
|
|
98
|
+
attr_accessor :multi_tenant
|
|
99
|
+
|
|
100
|
+
# Class name for the Account model (multi-tenant mode)
|
|
101
|
+
# @return [String, nil]
|
|
102
|
+
attr_accessor :account_class
|
|
103
|
+
|
|
104
|
+
# Class name for the User model
|
|
105
|
+
# @return [String, nil]
|
|
106
|
+
attr_accessor :user_class
|
|
107
|
+
|
|
108
|
+
# Method to call on controller to get current account (multi-tenant mode).
|
|
109
|
+
# Only usable when the host app has mixed that method into the engine's
|
|
110
|
+
# controllers; otherwise use current_account_resolver.
|
|
111
|
+
# @return [Symbol, nil]
|
|
112
|
+
attr_accessor :current_account_method
|
|
113
|
+
|
|
114
|
+
# Method to call on controller to get current user. Same caveat as
|
|
115
|
+
# current_account_method — see current_user_resolver.
|
|
116
|
+
# @return [Symbol, nil]
|
|
117
|
+
attr_accessor :current_user_method
|
|
118
|
+
|
|
119
|
+
# Resolves the signed-in user from the controller. Preferred over
|
|
120
|
+
# current_user_method: the engine's controllers are their own base
|
|
121
|
+
# class, so a host app's `current_user` helper is not on them unless
|
|
122
|
+
# the app deliberately put it there.
|
|
123
|
+
# @return [Proc, nil]
|
|
124
|
+
attr_accessor :current_user_resolver
|
|
125
|
+
|
|
126
|
+
# Resolves the current tenant from the controller. See
|
|
127
|
+
# current_user_resolver.
|
|
128
|
+
# @return [Proc, nil]
|
|
129
|
+
attr_accessor :current_account_resolver
|
|
130
|
+
|
|
131
|
+
# The tenant whose telemetry relates to +owner+. Traces belong to
|
|
132
|
+
# accounts while agents may belong to users, so the two are not always
|
|
133
|
+
# the same record and a host app says how to get from one to the other.
|
|
134
|
+
# @return [Proc, nil]
|
|
135
|
+
attr_accessor :tenant_resolver
|
|
136
|
+
|
|
137
|
+
# The agents an owner can reach. Defaults to the ones that owner owns.
|
|
138
|
+
# A host app where those differ — the platform's agents belong to users
|
|
139
|
+
# while its API keys belong to accounts — supplies its own scope.
|
|
140
|
+
# @return [Proc, nil]
|
|
141
|
+
attr_accessor :agent_scope_resolver
|
|
142
|
+
|
|
143
|
+
# Custom trace model class (for host app overrides)
|
|
144
|
+
# @return [String, nil]
|
|
145
|
+
attr_accessor :trace_model_class
|
|
146
|
+
|
|
147
|
+
# Enable React/Inertia frontend instead of ERB
|
|
148
|
+
# @return [Boolean]
|
|
149
|
+
attr_accessor :use_inertia
|
|
150
|
+
|
|
151
|
+
# Custom layout for the dashboard
|
|
152
|
+
# @return [String, nil]
|
|
153
|
+
attr_accessor :layout
|
|
154
|
+
|
|
155
|
+
# Sandbox service type (:local, :cloud_run, :kubernetes)
|
|
156
|
+
# @return [Symbol]
|
|
157
|
+
attr_accessor :sandbox_service
|
|
158
|
+
|
|
159
|
+
# Custom sandbox limits (overrides defaults)
|
|
160
|
+
# @return [Hash, nil]
|
|
161
|
+
attr_accessor :sandbox_limits
|
|
162
|
+
|
|
163
|
+
# Storage service for screenshots/snapshots
|
|
164
|
+
# @return [Object, nil] Object responding to #signed_url_for and #fetch_snapshot
|
|
165
|
+
attr_accessor :storage_service
|
|
166
|
+
|
|
167
|
+
# Bearer token required by the ingest API in single-tenant mode. When
|
|
168
|
+
# unset the local ingest endpoint accepts unauthenticated posts, so set
|
|
169
|
+
# it whenever the mount is reachable beyond your own machine.
|
|
170
|
+
# (Multi-tenant mode authenticates per-account keys instead.)
|
|
171
|
+
# @return [String, nil]
|
|
172
|
+
attr_accessor :ingest_api_key
|
|
173
|
+
|
|
174
|
+
# @deprecated Never consumed — dashboard controllers inherit
|
|
175
|
+
# ActionController::Base. Retained as a no-op so existing
|
|
176
|
+
# initializers that set it keep booting; remove in the next major.
|
|
177
|
+
# @return [String]
|
|
178
|
+
attr_accessor :base_controller_class
|
|
179
|
+
|
|
180
|
+
# Called before each run/trace-ingest to enforce host-app limits.
|
|
181
|
+
# Receives (owner, kind) where kind is :execution or :trace_ingest, and
|
|
182
|
+
# returns nil to allow, or to deny: a message String, or a Hash merged
|
|
183
|
+
# into the response so the app can surface its own usage numbers.
|
|
184
|
+
# Denials surface as HTTP 402 (execution) / 429 (ingest).
|
|
185
|
+
#
|
|
186
|
+
# Unset means unlimited, which is what a self-hosted install wants.
|
|
187
|
+
# @return [Proc, nil]
|
|
188
|
+
attr_accessor :quota_checker
|
|
189
|
+
|
|
190
|
+
# Resolves LLM provider credentials for a run. Receives
|
|
191
|
+
# (owner, provider_name) and returns a Hash merged into the agent's
|
|
192
|
+
# generation options (e.g. { access_token: "sk-..." } or
|
|
193
|
+
# { host: "http://localhost:11434" }), or nil to fall back to the
|
|
194
|
+
# host app's config/active_agent.yml.
|
|
195
|
+
#
|
|
196
|
+
# Unset means config/active_agent.yml is the only source, which is what
|
|
197
|
+
# a self-hosted install wants.
|
|
198
|
+
# @return [Proc, nil]
|
|
199
|
+
attr_accessor :provider_credentials_resolver
|
|
200
|
+
|
|
201
|
+
# Extra sandbox backends contributed by the host app, as
|
|
202
|
+
# { "cloud_run" => "CloudRunService" }. The engine ships :mock and
|
|
203
|
+
# :local (Docker); cloud backends live in the app that operates them.
|
|
204
|
+
# @return [Hash{String => String}]
|
|
205
|
+
attr_accessor :sandbox_backends
|
|
206
|
+
|
|
207
|
+
# Whether the dashboard may execute agents against real providers.
|
|
208
|
+
# Disable to run the dashboard as a read-only observability surface.
|
|
209
|
+
# @return [Boolean]
|
|
210
|
+
attr_accessor :execution_enabled
|
|
211
|
+
|
|
212
|
+
# Where the dashboard's upgrade CTAs should send people. Unset in a
|
|
213
|
+
# self-hosted install, where there is nothing to upgrade, and the CTAs
|
|
214
|
+
# say so instead of linking nowhere.
|
|
215
|
+
# @return [String, nil]
|
|
216
|
+
attr_accessor :upgrade_url
|
|
217
|
+
|
|
218
|
+
# Called after the dashboard performs a metered action, as
|
|
219
|
+
# (owner, kind) — the counterpart to quota_checker, for host apps that
|
|
220
|
+
# track usage against a plan. Unset means nothing is counted.
|
|
221
|
+
# @return [Proc, nil]
|
|
222
|
+
attr_accessor :usage_recorder
|
|
223
|
+
|
|
224
|
+
# Maps an ingested trace to the owner that its newly observed agents
|
|
225
|
+
# belong to. Defaults to the trace's account in multi-tenant mode and to
|
|
226
|
+
# nobody in single-tenant mode. A host app whose agents hang off a
|
|
227
|
+
# different record (the platform's hang off the account's owning user)
|
|
228
|
+
# supplies its own mapping.
|
|
229
|
+
# @return [Proc, nil]
|
|
230
|
+
attr_accessor :trace_owner_resolver
|
|
231
|
+
|
|
232
|
+
# How long telemetry traces are kept before TraceRetentionJob prunes
|
|
233
|
+
# them. A Duration applies to every trace; a callable receives each
|
|
234
|
+
# owner and returns that owner's window (nil keeps everything). Unset
|
|
235
|
+
# means nothing is ever deleted.
|
|
236
|
+
# @return [ActiveSupport::Duration, Proc, nil]
|
|
237
|
+
attr_accessor :trace_retention
|
|
238
|
+
|
|
239
|
+
# Whether API keys and provider credentials are encrypted at rest with
|
|
240
|
+
# Active Record Encryption. On by default, which requires the host app
|
|
241
|
+
# to have run `rails db:encryption:init`. Turning it off stores those
|
|
242
|
+
# secrets in plain text — a deliberate downgrade, never a default.
|
|
243
|
+
# @return [Boolean]
|
|
244
|
+
attr_accessor :encrypt_credentials
|
|
245
|
+
|
|
246
|
+
# Value stored in polymorphic *_type columns for dashboard agents
|
|
247
|
+
# (agent_memories.memorable_type, agent_contexts.contextable_type).
|
|
248
|
+
# Unset means the class name. A host app whose existing rows were
|
|
249
|
+
# written under its own constant sets its name here.
|
|
250
|
+
# @return [String, nil]
|
|
251
|
+
attr_accessor :agent_polymorphic_name
|
|
252
|
+
|
|
253
|
+
# Returns whether multi-tenant mode is enabled.
|
|
254
|
+
#
|
|
255
|
+
# @return [Boolean]
|
|
256
|
+
def multi_tenant?
|
|
257
|
+
@multi_tenant == true
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Returns whether agent execution is permitted.
|
|
261
|
+
#
|
|
262
|
+
# @return [Boolean]
|
|
263
|
+
def execution_enabled?
|
|
264
|
+
@execution_enabled != false
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Tells the host app that +owner+ performed +kind+. Never raises: a
|
|
268
|
+
# bookkeeping failure must not fail the action that was already taken.
|
|
269
|
+
def record_usage(owner, kind)
|
|
270
|
+
usage_recorder&.call(owner, kind)
|
|
271
|
+
rescue StandardError => e
|
|
272
|
+
Rails.logger.warn("[ActionAgent] usage recording failed: #{e.message}")
|
|
273
|
+
nil
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Asks the host app whether +owner+ may perform +kind+.
|
|
277
|
+
#
|
|
278
|
+
# @return [String, Hash, nil] denial message or payload, nil when allowed
|
|
279
|
+
def quota_denial(owner, kind)
|
|
280
|
+
return nil if quota_checker.nil?
|
|
281
|
+
|
|
282
|
+
quota_checker.call(owner, kind)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Provider options for +owner+, or {} when the host app has none and
|
|
286
|
+
# config/active_agent.yml should be used as-is.
|
|
287
|
+
#
|
|
288
|
+
# @return [Hash]
|
|
289
|
+
def provider_credentials(owner, provider)
|
|
290
|
+
return {} if provider_credentials_resolver.nil?
|
|
291
|
+
|
|
292
|
+
provider_credentials_resolver.call(owner, provider) || {}
|
|
293
|
+
rescue StandardError => e
|
|
294
|
+
Rails.logger.warn("[ActionAgent] provider credential lookup failed: #{e.message}")
|
|
295
|
+
{}
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Returns the trace model class to use.
|
|
299
|
+
#
|
|
300
|
+
# @return [Class] The trace model class
|
|
301
|
+
def trace_model
|
|
302
|
+
if trace_model_class
|
|
303
|
+
trace_model_class.constantize
|
|
304
|
+
else
|
|
305
|
+
ActionAgent::TelemetryTrace
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# The tenant +owner+ belongs to. Identity unless the host app says
|
|
310
|
+
# otherwise, which is right for every single-tenant install.
|
|
311
|
+
def tenant_for(owner)
|
|
312
|
+
return owner if tenant_resolver.nil?
|
|
313
|
+
|
|
314
|
+
tenant_resolver.call(owner)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# The agents +owner+ can reach.
|
|
318
|
+
#
|
|
319
|
+
# @return [ActiveRecord::Relation]
|
|
320
|
+
def agents_for(owner)
|
|
321
|
+
return agent_model.for_owner(owner) if agent_scope_resolver.nil?
|
|
322
|
+
|
|
323
|
+
agent_scope_resolver.call(owner) || agent_model.none
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Returns the agent model class to use.
|
|
327
|
+
#
|
|
328
|
+
# @return [Class] The agent model class
|
|
329
|
+
def agent_model
|
|
330
|
+
ActionAgent::Agent
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Returns the configured owner class: the Account in multi-tenant mode,
|
|
334
|
+
# the User otherwise. Nil when the host app configured neither, which
|
|
335
|
+
# is the single-user self-hosted case.
|
|
336
|
+
#
|
|
337
|
+
# @return [Class, nil]
|
|
338
|
+
def owner_class
|
|
339
|
+
name = multi_tenant? ? account_class : user_class
|
|
340
|
+
name&.safe_constantize
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Configures the dashboard.
|
|
344
|
+
#
|
|
345
|
+
# @yield [config] Configuration block
|
|
346
|
+
def configure
|
|
347
|
+
yield self
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# Reset configuration to defaults
|
|
351
|
+
def reset!
|
|
352
|
+
@authentication_method = nil
|
|
353
|
+
@multi_tenant = false
|
|
354
|
+
@account_class = nil
|
|
355
|
+
@user_class = nil
|
|
356
|
+
@current_account_method = nil
|
|
357
|
+
@current_user_method = nil
|
|
358
|
+
@current_user_resolver = nil
|
|
359
|
+
@current_account_resolver = nil
|
|
360
|
+
@agent_scope_resolver = nil
|
|
361
|
+
@tenant_resolver = nil
|
|
362
|
+
@trace_model_class = nil
|
|
363
|
+
@use_inertia = false
|
|
364
|
+
@layout = nil
|
|
365
|
+
@sandbox_service = :local
|
|
366
|
+
@sandbox_limits = nil
|
|
367
|
+
@storage_service = nil
|
|
368
|
+
@ingest_api_key = nil
|
|
369
|
+
@base_controller_class = "ActionController::Base" # deprecated no-op
|
|
370
|
+
@quota_checker = nil
|
|
371
|
+
@provider_credentials_resolver = nil
|
|
372
|
+
@sandbox_backends = {}
|
|
373
|
+
@execution_enabled = true
|
|
374
|
+
@table_name_prefix = "active_agent_"
|
|
375
|
+
@agent_polymorphic_name = nil
|
|
376
|
+
@encrypt_credentials = true
|
|
377
|
+
@trace_retention = nil
|
|
378
|
+
@trace_owner_resolver = nil
|
|
379
|
+
@usage_recorder = nil
|
|
380
|
+
@upgrade_url = nil
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Set defaults
|
|
385
|
+
reset!
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
ActionAgent::Compatibility.install!
|
data/lib/actionagent.rb
ADDED