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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -0
  3. data/README.md +103 -0
  4. data/app/assets/builds/action_agent.css +2 -0
  5. data/app/assets/builds/action_agent.js +163 -0
  6. data/app/controllers/action_agent/api/agent_runs_controller.rb +128 -0
  7. data/app/controllers/action_agent/api/agents_controller.rb +411 -0
  8. data/app/controllers/action_agent/api/analytics_controller.rb +94 -0
  9. data/app/controllers/action_agent/api/api_keys_controller.rb +45 -0
  10. data/app/controllers/action_agent/api/base_controller.rb +112 -0
  11. data/app/controllers/action_agent/api/evaluations_controller.rb +148 -0
  12. data/app/controllers/action_agent/api/instance_tiers_controller.rb +106 -0
  13. data/app/controllers/action_agent/api/interactions_controller.rb +197 -0
  14. data/app/controllers/action_agent/api/mcp_controller.rb +218 -0
  15. data/app/controllers/action_agent/api/mcp_servers_controller.rb +156 -0
  16. data/app/controllers/action_agent/api/metrics_controller.rb +178 -0
  17. data/app/controllers/action_agent/api/provider_keys_controller.rb +52 -0
  18. data/app/controllers/action_agent/api/provider_models_controller.rb +119 -0
  19. data/app/controllers/action_agent/api/sandboxes_controller.rb +224 -0
  20. data/app/controllers/action_agent/api/session_recordings_controller.rb +372 -0
  21. data/app/controllers/action_agent/api/templates_controller.rb +94 -0
  22. data/app/controllers/action_agent/api/tools_controller.rb +58 -0
  23. data/app/controllers/action_agent/api/trace_reports_controller.rb +68 -0
  24. data/app/controllers/action_agent/api/traces_controller.rb +136 -0
  25. data/app/controllers/action_agent/application_controller.rb +105 -0
  26. data/app/controllers/action_agent/dashboard_controller.rb +104 -0
  27. data/app/controllers/action_agent/traces_controller.rb +121 -0
  28. data/app/jobs/action_agent/agent_execution_job.rb +52 -0
  29. data/app/jobs/action_agent/application_job.rb +12 -0
  30. data/app/jobs/action_agent/process_telemetry_traces_job.rb +86 -0
  31. data/app/jobs/action_agent/sandbox_cleanup_job.rb +42 -0
  32. data/app/jobs/action_agent/sandbox_provision_job.rb +56 -0
  33. data/app/jobs/action_agent/sandbox_run_job.rb +285 -0
  34. data/app/jobs/action_agent/trace_retention_job.rb +57 -0
  35. data/app/models/action_agent/agent.rb +343 -0
  36. data/app/models/action_agent/agent_context.rb +129 -0
  37. data/app/models/action_agent/agent_generation.rb +48 -0
  38. data/app/models/action_agent/agent_memory.rb +50 -0
  39. data/app/models/action_agent/agent_memory_entry.rb +14 -0
  40. data/app/models/action_agent/agent_message.rb +43 -0
  41. data/app/models/action_agent/agent_run.rb +151 -0
  42. data/app/models/action_agent/agent_template.rb +182 -0
  43. data/app/models/action_agent/agent_version.rb +48 -0
  44. data/app/models/action_agent/api_key.rb +53 -0
  45. data/app/models/action_agent/application_record.rb +27 -0
  46. data/app/models/action_agent/evaluation.rb +80 -0
  47. data/app/models/action_agent/evaluation_run.rb +20 -0
  48. data/app/models/action_agent/model_pricing.rb +80 -0
  49. data/app/models/action_agent/provider_key.rb +60 -0
  50. data/app/models/action_agent/recording_action.rb +119 -0
  51. data/app/models/action_agent/recording_snapshot.rb +88 -0
  52. data/app/models/action_agent/sandbox_instance_tier.rb +368 -0
  53. data/app/models/action_agent/sandbox_run.rb +45 -0
  54. data/app/models/action_agent/sandbox_session.rb +160 -0
  55. data/app/models/action_agent/session_recording.rb +178 -0
  56. data/app/models/action_agent/telemetry_trace.rb +357 -0
  57. data/app/models/concerns/action_agent/adapter_aware.rb +50 -0
  58. data/app/models/concerns/action_agent/ownable.rb +86 -0
  59. data/app/models/concerns/action_agent/session_recordable.rb +91 -0
  60. data/app/queries/action_agent/agent_executions.rb +201 -0
  61. data/app/serializers/action_agent/agent_message_serializer.rb +23 -0
  62. data/app/serializers/action_agent/interaction_preview.rb +22 -0
  63. data/app/serializers/action_agent/telemetry_trace_serializer.rb +122 -0
  64. data/app/serializers/action_agent/trace_interaction_serializer.rb +246 -0
  65. data/app/services/action_agent/agent_execution_service.rb +572 -0
  66. data/app/services/action_agent/agent_registrar.rb +197 -0
  67. data/app/services/action_agent/agent_scorecard.rb +191 -0
  68. data/app/services/action_agent/agent_toolbox.rb +504 -0
  69. data/app/services/action_agent/evaluation_runner_service.rb +481 -0
  70. data/app/services/action_agent/mcp_catalog.rb +247 -0
  71. data/app/services/action_agent/mcp_recording_middleware.rb +241 -0
  72. data/app/services/action_agent/mock_sandbox_backend.rb +52 -0
  73. data/app/services/action_agent/playwright_mcp_client.rb +148 -0
  74. data/app/services/action_agent/sandbox_orchestrator.rb +242 -0
  75. data/app/services/action_agent/session_recording_service.rb +228 -0
  76. data/app/services/action_agent/tool_discovery.rb +617 -0
  77. data/app/views/action_agent/dashboard/index.html.erb +5 -0
  78. data/app/views/action_agent/traces/_trace_detail.html.erb +117 -0
  79. data/app/views/action_agent/traces/index.html.erb +135 -0
  80. data/app/views/action_agent/traces/metrics.html.erb +145 -0
  81. data/app/views/action_agent/traces/show.html.erb +36 -0
  82. data/app/views/layouts/action_agent/application.html.erb +94 -0
  83. data/app/views/layouts/action_agent/react.html.erb +19 -0
  84. data/config/routes.rb +144 -0
  85. data/lib/action_agent/compatibility.rb +49 -0
  86. data/lib/action_agent/engine.rb +51 -0
  87. data/lib/action_agent/version.rb +5 -0
  88. data/lib/action_agent.rb +388 -0
  89. data/lib/actionagent.rb +6 -0
  90. data/lib/generators/action_agent/install_generator.rb +137 -0
  91. data/lib/generators/action_agent/templates/action_agent.rb.erb +82 -0
  92. data/lib/generators/action_agent/templates/add_agent_id_to_active_agent_telemetry_traces.rb.erb +24 -0
  93. data/lib/generators/action_agent/templates/create_active_agent_dashboard_tables.rb.erb +319 -0
  94. data/lib/generators/action_agent/templates/create_active_agent_telemetry_traces.rb.erb +58 -0
  95. metadata +209 -12
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionAgent
4
+ module Api
5
+ # Model catalogs for the agent builder/editor dropdowns.
6
+ #
7
+ # Hosted providers get a curated list of current models (kept here, server
8
+ # side, so the UI can't drift stale). Ollama is queried live from the
9
+ # account's configured host (Settings -> Provider API Keys, falling back to
10
+ # the platform config) so locally pulled models appear; OpenRouter is
11
+ # queried from its public catalog. Live lookups fall back to the curated
12
+ # list on any failure.
13
+ class ProviderModelsController < BaseController
14
+ before_action :require_owner!
15
+
16
+ FETCH_TIMEOUT_SECONDS = 4
17
+
18
+ # Fallbacks when a live lookup isn't possible (reviewed 2026-07). First
19
+ # entry is the default the builder preselects.
20
+ CURATED = {
21
+ "openai" => %w[gpt-5.1 gpt-5 gpt-5-mini gpt-5-nano],
22
+ "anthropic" => %w[claude-opus-5 claude-fable-5 claude-sonnet-5 claude-opus-4-8 claude-haiku-4-5],
23
+ "openrouter" => %w[anthropic/claude-sonnet-4.5 openai/gpt-5.1 meta-llama/llama-3.3-70b-instruct qwen/qwen3-32b],
24
+ "ollama" => %w[qwen3:8b llama3.2 mistral gemma3]
25
+ }.freeze
26
+
27
+ # GET /api/provider_models?provider=ollama
28
+ def index
29
+ provider = params[:provider].to_s
30
+ unless Agent::PROVIDERS.include?(provider)
31
+ return render json: { error: "Unknown provider: #{provider}" }, status: :unprocessable_entity
32
+ end
33
+
34
+ models, source = case provider
35
+ when "ollama" then live_ollama_models
36
+ when "openrouter" then live_openrouter_models
37
+ when "anthropic" then live_anthropic_models
38
+ end
39
+
40
+ if models.blank?
41
+ models = CURATED.fetch(provider)
42
+ source = "curated"
43
+ end
44
+
45
+ render json: { provider: provider, models: models, source: source }
46
+ end
47
+
48
+ private
49
+
50
+ # The owner's configured Ollama endpoint, else the host app's default
51
+ # from config/active_agent.yml.
52
+ def ollama_host
53
+ key = owner_provider_key("ollama")
54
+ host = key&.credential.presence || ActiveAgent.configuration[:ollama]&.dig(:host)
55
+ host.presence
56
+ end
57
+
58
+ def owner_provider_key(provider)
59
+ owned(ProviderKey).find_by(provider: provider)
60
+ end
61
+
62
+ def live_ollama_models
63
+ host = ollama_host
64
+ return nil unless host
65
+
66
+ data = fetch_json(URI.join("#{host.chomp('/')}/", "models"))
67
+ ids = Array(data&.dig("data")).filter_map { |model| model["id"] }
68
+ [ ids.sort, "live" ] if ids.any?
69
+ rescue StandardError => e
70
+ Rails.logger.warn("[ProviderModels] ollama lookup failed: #{e.message}")
71
+ nil
72
+ end
73
+
74
+ # Queries the Anthropic Models API with the account's key (newest first,
75
+ # as returned by the API) so new model releases appear without a deploy.
76
+ def live_anthropic_models
77
+ key = current_user_provider_key("anthropic")&.credential
78
+ return nil if key.blank?
79
+
80
+ data = Rails.cache.fetch("provider_models:anthropic:#{Digest::SHA256.hexdigest(key)}", expires_in: 1.hour) do
81
+ uri = URI.parse("https://api.anthropic.com/v1/models?limit=50")
82
+ response = Net::HTTP.start(
83
+ uri.host, uri.port,
84
+ use_ssl: true, open_timeout: FETCH_TIMEOUT_SECONDS, read_timeout: FETCH_TIMEOUT_SECONDS
85
+ ) { |http| http.get(uri.request_uri, { "x-api-key" => key, "anthropic-version" => "2023-06-01" }) }
86
+ response.code.to_i == 200 ? JSON.parse(response.body) : nil
87
+ end
88
+ ids = Array(data&.dig("data")).filter_map { |model| model["id"] }
89
+ [ ids, "live" ] if ids.any?
90
+ rescue StandardError => e
91
+ Rails.logger.warn("[ProviderModels] anthropic lookup failed: #{e.message}")
92
+ nil
93
+ end
94
+
95
+ def live_openrouter_models
96
+ data = Rails.cache.fetch("provider_models:openrouter", expires_in: 1.hour) do
97
+ fetch_json(URI.parse("https://openrouter.ai/api/v1/models"))
98
+ end
99
+ ids = Array(data&.dig("data")).filter_map { |model| model["id"] }
100
+ [ ids.sort.first(100), "live" ] if ids.any?
101
+ rescue StandardError => e
102
+ Rails.logger.warn("[ProviderModels] openrouter lookup failed: #{e.message}")
103
+ nil
104
+ end
105
+
106
+ def fetch_json(uri)
107
+ response = Net::HTTP.start(
108
+ uri.host, uri.port,
109
+ use_ssl: uri.scheme == "https",
110
+ open_timeout: FETCH_TIMEOUT_SECONDS,
111
+ read_timeout: FETCH_TIMEOUT_SECONDS
112
+ ) { |http| http.get(uri.request_uri) }
113
+ return nil unless response.code.to_i == 200
114
+
115
+ JSON.parse(response.body)
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionAgent
4
+ module Api
5
+ class SandboxesController < BaseController
6
+ # Authenticated like the rest of the dashboard. This controller used to
7
+ # opt out entirely for an anonymous free tier, which made #run and
8
+ # #compare an open proxy: any unauthenticated caller could execute
9
+ # arbitrary prompts against the host app's provider credentials, and
10
+ # #show would read back any session by id. Opting out also skipped the
11
+ # callback that refuses to serve an unauthenticated dashboard outside
12
+ # development, so it bypassed that safeguard too.
13
+
14
+ before_action :require_execution_enabled!, only: [ :run, :compare ]
15
+ before_action :enforce_execution_quota!, only: [ :compare ]
16
+ before_action :set_sandbox, only: [ :show, :run, :destroy ]
17
+
18
+ # POST /api/sandboxes/compare
19
+ # Run multiple providers in a single sandbox using parallel generation jobs
20
+ def compare
21
+ providers = params[:providers] || %w[anthropic openai ollama]
22
+ task = params[:task]
23
+ sandbox_id = params[:sandbox_id]
24
+
25
+ return render json: { error: "Task required" }, status: :bad_request unless task.present?
26
+ return render json: { error: "At least 2 providers required" }, status: :bad_request if providers.size < 2
27
+
28
+ # Validate providers
29
+ invalid = providers - %w[anthropic openai ollama]
30
+ return render json: { error: "Invalid providers: #{invalid.join(', ')}" }, status: :bad_request if invalid.any?
31
+
32
+ # Use existing sandbox or create a new one (single container per user)
33
+ sandbox = if sandbox_id.present?
34
+ owned(SandboxSession).find_by!(session_id: sandbox_id)
35
+ else
36
+ s = SandboxSession.new(sandbox_type: params[:sandbox_type] || "playwright_mcp")
37
+ s.user = current_user if s.respond_to?(:user=)
38
+ s.save!
39
+ s.provision!
40
+ s.reload
41
+ s
42
+ end
43
+
44
+ unless sandbox.can_run?
45
+ return render json: {
46
+ error: sandbox.expired? ? "Session expired" : "Maximum runs exceeded",
47
+ sandbox: sandbox.summary
48
+ }, status: :unprocessable_entity
49
+ end
50
+
51
+ sandbox.update!(status: :running)
52
+ comparison_id = SecureRandom.uuid
53
+
54
+ # Spawn a separate generation job for each provider (all in same sandbox)
55
+ runs = providers.map do |provider|
56
+ run_id = SecureRandom.uuid
57
+ SandboxRunJob.perform_later(sandbox.id, run_id, task, provider)
58
+
59
+ {
60
+ provider: provider,
61
+ run_id: run_id,
62
+ status: "running"
63
+ }
64
+ end
65
+
66
+ render json: {
67
+ comparison_id: comparison_id,
68
+ task: task,
69
+ sandbox: sandbox.summary,
70
+ runs: runs
71
+ }, status: :accepted
72
+ end
73
+
74
+ # GET /api/sandboxes
75
+ # List available sandbox types and sample tasks
76
+ def index
77
+ render json: {
78
+ sandbox_types: SandboxSession::SANDBOX_TYPES,
79
+ free_tier_limits: SandboxSession::FREE_TIER_LIMITS,
80
+ templates: free_tier_templates,
81
+ sample_tasks: sample_tasks
82
+ }
83
+ end
84
+
85
+ # POST /api/sandboxes
86
+ # Create a new sandbox session, owned by whoever opened it.
87
+ def create
88
+ @sandbox = SandboxSession.new(sandbox_params)
89
+ # Guarded: the association only exists when the host app configured a
90
+ # user model, and a single-user install configures none.
91
+ @sandbox.user = current_user if @sandbox.respond_to?(:user=)
92
+ @sandbox.agent_template = AgentTemplate.find_by(slug: params[:template_slug]) if params[:template_slug]
93
+
94
+ if @sandbox.save
95
+ @sandbox.provision!
96
+ @sandbox.reload # Reload to get updated status after provisioning
97
+ render json: { sandbox: @sandbox.summary }, status: :created
98
+ else
99
+ render json: { errors: @sandbox.errors.full_messages }, status: :unprocessable_entity
100
+ end
101
+ end
102
+
103
+ # GET /api/sandboxes/:session_id
104
+ # Get sandbox status and run history
105
+ def show
106
+ render json: { sandbox: @sandbox.details }
107
+ end
108
+
109
+ # POST /api/sandboxes/:session_id/run
110
+ # Execute a task in the sandbox
111
+ def run
112
+ unless @sandbox.can_run?
113
+ return render json: {
114
+ error: @sandbox.expired? ? "Session expired" : "Maximum runs exceeded",
115
+ sandbox: @sandbox.summary
116
+ }, status: :unprocessable_entity
117
+ end
118
+
119
+ # Whatever limits the host app imposes on running an agent.
120
+ if (denial = ActionAgent.quota_denial(current_owner, :execution))
121
+ return render json: {
122
+ error: "Plan limit reached",
123
+ upgrade_required: true,
124
+ message: denial
125
+ }, status: :payment_required
126
+ end
127
+
128
+ task = params[:task]
129
+ return render json: { error: "Task required" }, status: :bad_request unless task.present?
130
+
131
+ # Provider selection (default to anthropic)
132
+ provider = params[:provider] || "anthropic"
133
+ unless %w[anthropic openai ollama].include?(provider)
134
+ return render json: { error: "Invalid provider" }, status: :bad_request
135
+ end
136
+
137
+ record_execution_usage
138
+
139
+ @sandbox.update!(status: :running)
140
+
141
+ # Execute via job for async processing
142
+ run_id = SecureRandom.uuid
143
+ SandboxRunJob.perform_later(@sandbox.id, run_id, task, provider)
144
+
145
+ render json: {
146
+ run_id: run_id,
147
+ status: "running",
148
+ provider: provider,
149
+ sandbox: @sandbox.summary
150
+ }, status: :accepted
151
+ end
152
+
153
+ # DELETE /api/sandboxes/:session_id
154
+ # End sandbox session
155
+ def destroy
156
+ @sandbox.expire!
157
+ render json: { deleted: true }
158
+ end
159
+
160
+ private
161
+
162
+ def set_sandbox
163
+ @sandbox = owned(SandboxSession).find_by!(session_id: params[:id])
164
+ end
165
+
166
+ def sandbox_params
167
+ params.permit(:sandbox_type)
168
+ end
169
+
170
+ def free_tier_templates
171
+ AgentTemplate.free_tier.featured.map do |template|
172
+ {
173
+ slug: template.slug,
174
+ name: template.name,
175
+ description: template.description,
176
+ icon: template.icon,
177
+ sandbox_type: template.preset_type == "playwright" ? "playwright_mcp" : template.preset_type
178
+ }
179
+ end
180
+ end
181
+
182
+ def sample_tasks
183
+ {
184
+ playwright_mcp: [
185
+ {
186
+ name: "Screenshot Example.com",
187
+ task: "Take a screenshot of https://example.com",
188
+ description: "Navigate to example.com and capture a screenshot"
189
+ },
190
+ {
191
+ name: "Extract Hacker News Headlines",
192
+ task: "Go to https://news.ycombinator.com and list the top 5 story titles with their scores",
193
+ description: "Scrape the front page of Hacker News"
194
+ },
195
+ {
196
+ name: "Check Wikipedia",
197
+ task: "Navigate to https://en.wikipedia.org/wiki/Artificial_intelligence and extract the first paragraph",
198
+ description: "Extract content from Wikipedia"
199
+ },
200
+ {
201
+ name: "GitHub Trending",
202
+ task: "Visit https://github.com/trending and list the top 3 trending repositories",
203
+ description: "Check GitHub's trending repositories"
204
+ }
205
+ ],
206
+ terminal: [
207
+ {
208
+ name: "System Info",
209
+ task: "Show the current system information",
210
+ description: "Display OS, memory, and CPU details"
211
+ }
212
+ ],
213
+ research: [
214
+ {
215
+ name: "Topic Summary",
216
+ task: "Research and summarize the latest developments in AI",
217
+ description: "Search and compile information on a topic"
218
+ }
219
+ ]
220
+ }
221
+ end
222
+ end
223
+ end
224
+ end