actionagent 1.2.0 → 1.2.2
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/app/models/action_agent/telemetry_trace.rb +1 -1
- data/app/services/action_agent/agent_execution_service.rb +11 -3
- data/app/services/action_agent/agent_toolbox.rb +24 -1
- data/lib/action_agent/engine.rb +76 -0
- data/lib/action_agent/version.rb +1 -1
- data/lib/action_agent.rb +19 -0
- data/lib/generators/action_agent/templates/create_active_agent_dashboard_tables.rb.erb +47 -32
- data/lib/generators/action_agent/templates/create_active_agent_telemetry_traces.rb.erb +17 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6e31cd17a84e6621881630137c3478268d2e31c8bba138516a397c23964c4f9
|
|
4
|
+
data.tar.gz: 28e83e7b84704c5ff9de2b475998273ac3dd328929515472f31e3743981927de
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3a9e49dce9507b1243dd494b842c967bb5cb04921a7825ae2e16e37ed3cf25e845ae5b86de26b50cacdb01e7120859624ea7667d05c0d04048489bc7af5b534e
|
|
7
|
+
data.tar.gz: bebd359520c4ea07d69288ef8381afd5e5f663ea6df3264e17db81c2bc1a6d78836be216a8677666ede6223a1069f39e3e8e3a709025adcf8c9817a2be9750c9
|
|
@@ -135,7 +135,7 @@ module ActionAgent
|
|
|
135
135
|
# Add account if in multi-tenant mode
|
|
136
136
|
attrs[:account] = account if ActionAgent.multi_tenant? && account
|
|
137
137
|
|
|
138
|
-
create!(attrs)
|
|
138
|
+
create!(attrs).tap { |record| AgentRegistrar.call(record) }
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
# Sums a span's token counts (used to decide which spans carry the
|
|
@@ -354,8 +354,14 @@ module ActionAgent
|
|
|
354
354
|
define_singleton_method(:name) { klass_name }
|
|
355
355
|
|
|
356
356
|
# Persist the conversation (agent_contexts / agent_messages /
|
|
357
|
-
# agent_generations) via solid_agent.
|
|
358
|
-
# is loaded explicitly in the action below.
|
|
357
|
+
# agent_generations) via solid_agent. Auto-context is switched off —
|
|
358
|
+
# the context is loaded explicitly in the action below.
|
|
359
|
+
#
|
|
360
|
+
# The keyword that switches it off was renamed (contextable: ->
|
|
361
|
+
# contextual:) between solid_agent 0.1 and 0.2, and the gemspec floor
|
|
362
|
+
# admits both, so it is resolved from the installed method rather than
|
|
363
|
+
# hard-coded: passing the wrong one is an ArgumentError that only
|
|
364
|
+
# surfaces when a run executes.
|
|
359
365
|
#
|
|
360
366
|
# The model classes are named explicitly because solid_agent infers
|
|
361
367
|
# bare "AgentContext"/"AgentMessage"/"AgentGeneration" and resolves
|
|
@@ -363,10 +369,12 @@ module ActionAgent
|
|
|
363
369
|
# inferred names only resolve in a host app that happens to have
|
|
364
370
|
# top-level models of its own.
|
|
365
371
|
include SolidAgent::HasContext
|
|
366
|
-
has_context
|
|
372
|
+
has_context(
|
|
373
|
+
ActionAgent.solid_agent_auto_context_keyword => false,
|
|
367
374
|
class_name: "ActionAgent::AgentContext",
|
|
368
375
|
message_class: "ActionAgent::AgentMessage",
|
|
369
376
|
generation_class: "ActionAgent::AgentGeneration"
|
|
377
|
+
)
|
|
370
378
|
|
|
371
379
|
if effective_provider == :mock
|
|
372
380
|
# Test environment only (see #provider_available?).
|
|
@@ -399,7 +399,7 @@ module ActionAgent
|
|
|
399
399
|
if defined?(SolidAgent::ToolCache)
|
|
400
400
|
SolidAgent::ToolCache.fetch(tool: name.to_s, args: kwargs, ttl: CACHE_TTL, &block)
|
|
401
401
|
else
|
|
402
|
-
key =
|
|
402
|
+
key = fallback_cache_key(name, kwargs)
|
|
403
403
|
cached = Rails.cache.read(key)
|
|
404
404
|
return cached.merge(cached: true) unless cached.nil?
|
|
405
405
|
|
|
@@ -411,6 +411,29 @@ module ActionAgent
|
|
|
411
411
|
end
|
|
412
412
|
end
|
|
413
413
|
|
|
414
|
+
# Byte-for-byte the key SolidAgent::ToolCache would compute, so an app
|
|
415
|
+
# that upgrades solid_agent mid-TTL keeps reading what it already
|
|
416
|
+
# cached instead of silently starting over. Nested hashes and
|
|
417
|
+
# symbol/string keys have to normalize the same way, which a plain
|
|
418
|
+
# `kwargs.sort.to_h.to_json` does not do.
|
|
419
|
+
#
|
|
420
|
+
# test/integration/solid_agent/tool_cache_test.rb asserts the two
|
|
421
|
+
# schemes still agree.
|
|
422
|
+
def fallback_cache_key(name, kwargs)
|
|
423
|
+
"solid_agent:tool_cache:#{name}:#{Digest::SHA256.hexdigest(normalize_cache_args(kwargs).to_json)}"
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def normalize_cache_args(args)
|
|
427
|
+
case args
|
|
428
|
+
when Hash
|
|
429
|
+
args.map { |key, value| [ key.to_s, normalize_cache_args(value) ] }.sort_by(&:first)
|
|
430
|
+
when Array
|
|
431
|
+
args.map { |value| normalize_cache_args(value) }
|
|
432
|
+
else
|
|
433
|
+
args
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
414
437
|
# SSRF guard for fetch_url: reject hosts that resolve to loopback,
|
|
415
438
|
# private, or link-local addresses.
|
|
416
439
|
def public_host?(host)
|
data/lib/action_agent/engine.rb
CHANGED
|
@@ -13,6 +13,13 @@ module ActionAgent
|
|
|
13
13
|
|
|
14
14
|
engine_name "action_agent"
|
|
15
15
|
|
|
16
|
+
# Basenames this engine spells differently from Zeitwerk's default
|
|
17
|
+
# camelization, consulted by the inflections initializer below. Empty
|
|
18
|
+
# today: every file here is named for the constant default camelization
|
|
19
|
+
# produces. An engine file that wants a genuine acronym in its constant
|
|
20
|
+
# adds its basename here rather than relying on the host to register one.
|
|
21
|
+
INFLECTION_OVERRIDES = {}.freeze
|
|
22
|
+
|
|
16
23
|
config.action_agent = ActiveSupport::OrderedOptions.new
|
|
17
24
|
|
|
18
25
|
# The dashboard's JS and CSS ship prebuilt in the gem. Adding the
|
|
@@ -26,6 +33,75 @@ module ActionAgent
|
|
|
26
33
|
app.config.filter_parameters += [ :credential, :api_key, :access_token ]
|
|
27
34
|
end
|
|
28
35
|
|
|
36
|
+
# This engine's constants are spelled the way Zeitwerk's own inflector
|
|
37
|
+
# spells them — Api, McpCatalog, ApiKey — but an engine's files are
|
|
38
|
+
# autoloaded by the host's `rails.main` loader, under the *host's*
|
|
39
|
+
# inflections. A host that declares `inflect.acronym "API"` or "MCP" (both
|
|
40
|
+
# common, and documented by Rails) makes Zeitwerk expect
|
|
41
|
+
# ActionAgent::API::TracesController or ActionAgent::MCPCatalog from files
|
|
42
|
+
# that define ActionAgent::Api::TracesController and
|
|
43
|
+
# ActionAgent::McpCatalog. The constant never resolves and the request
|
|
44
|
+
# raises Zeitwerk::NameError.
|
|
45
|
+
#
|
|
46
|
+
# Every path under this engine therefore camelizes with Zeitwerk's default
|
|
47
|
+
# rules, ignoring whatever acronyms the host has registered. Applied by
|
|
48
|
+
# path rather than through `inflect`: the loader is shared with the host,
|
|
49
|
+
# so a blanket rule would re-spell the host's own constants. `camelize`
|
|
50
|
+
# receives the absolute path, which is the only hook that can tell this
|
|
51
|
+
# engine's files from the host's.
|
|
52
|
+
#
|
|
53
|
+
# Basenames whose spelling this engine cannot express through default
|
|
54
|
+
# camelization (a genuine acronym it wants uppercased) go in OVERRIDES.
|
|
55
|
+
initializer "action_agent.inflections", before: :set_autoload_paths do
|
|
56
|
+
engine_root = File.join(root.to_s, "")
|
|
57
|
+
default = Zeitwerk::Inflector.new
|
|
58
|
+
|
|
59
|
+
Rails.autoloaders.main.inflector.singleton_class.prepend(Module.new do
|
|
60
|
+
define_method(:camelize) do |basename, abspath|
|
|
61
|
+
next super(basename, abspath) unless abspath.to_s.start_with?(engine_root)
|
|
62
|
+
|
|
63
|
+
ActionAgent::Engine::INFLECTION_OVERRIDES.fetch(basename) { default.camelize(basename, abspath) }
|
|
64
|
+
end
|
|
65
|
+
end)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Rails resolves a route's controller by camelizing the stored path
|
|
69
|
+
# ("action_agent/api/traces") with the host's *global* inflections, and no
|
|
70
|
+
# engine-level setting scopes that — so an acronym host looks up
|
|
71
|
+
# ActionAgent::API::TracesController and raises NameError even though the
|
|
72
|
+
# autoloader named the module correctly above. No single spelling satisfies
|
|
73
|
+
# both kinds of host: a plain host camelizes to Api, an acronym host to API.
|
|
74
|
+
# So the namespace answers to both. `const_missing` rather than an eager
|
|
75
|
+
# alias because the controllers are autoloaded on demand, and naming them at
|
|
76
|
+
# boot would load the whole dashboard.
|
|
77
|
+
# The router does not consult the autoloader's inflector, so an acronym
|
|
78
|
+
# host asks for ActionAgent::API::MCPServersController while the constants
|
|
79
|
+
# are Api::McpServersController. Rather than enumerate the pairs, an
|
|
80
|
+
# all-caps run in a missing constant is retried in the spelling default
|
|
81
|
+
# camelization produces: API -> Api, MCPServersController -> McpServers-
|
|
82
|
+
# Controller. Only the engine's own namespaces are touched, and only for a
|
|
83
|
+
# constant that is already missing.
|
|
84
|
+
inflection_shim = Module.new do
|
|
85
|
+
def const_missing(name)
|
|
86
|
+
relaxed = name.to_s.gsub(/([A-Z])([A-Z]+)(?=[A-Z][a-z]|\d|\z)/) { "#{$1}#{$2.downcase}" }
|
|
87
|
+
|
|
88
|
+
return super if relaxed == name.to_s || !const_defined?(relaxed, false)
|
|
89
|
+
|
|
90
|
+
const_get(relaxed, false)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
ActionAgent.singleton_class.prepend(inflection_shim)
|
|
95
|
+
|
|
96
|
+
# ActionAgent::Api is autoloaded, so it cannot be reopened at this point.
|
|
97
|
+
# Zeitwerk hands it over as soon as it is defined, which is before the
|
|
98
|
+
# router can ask it for a controller.
|
|
99
|
+
initializer "action_agent.api_inflection_shim" do
|
|
100
|
+
Rails.autoloaders.main.on_load("ActionAgent::Api") do |mod, _abspath|
|
|
101
|
+
mod.singleton_class.prepend(inflection_shim)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
29
105
|
initializer "action_agent.assets", before: :append_assets_path do |app|
|
|
30
106
|
builds = root.join("app", "assets", "builds").to_s
|
|
31
107
|
next unless File.directory?(builds)
|
data/lib/action_agent/version.rb
CHANGED
data/lib/action_agent.rb
CHANGED
|
@@ -20,6 +20,25 @@ module ActionAgent
|
|
|
20
20
|
global = defined?(::ActiveRecord::Base) ? ::ActiveRecord::Base.table_name_prefix : ""
|
|
21
21
|
"#{global}#{@table_name_prefix ||= "active_agent_"}"
|
|
22
22
|
end
|
|
23
|
+
|
|
24
|
+
# Which keyword the installed solid_agent uses to switch has_context's
|
|
25
|
+
# auto-context off: `contextable:` up to 0.1, `contextual:` from 0.2. The
|
|
26
|
+
# gemspec floor admits both, and passing the wrong one raises an
|
|
27
|
+
# ArgumentError deep inside a run rather than at boot — so
|
|
28
|
+
# AgentExecutionService asks rather than assumes.
|
|
29
|
+
#
|
|
30
|
+
# Covered by test/integration/solid_agent, which runs this engine against
|
|
31
|
+
# solid_agent's main branch as well as the released gem.
|
|
32
|
+
def solid_agent_auto_context_keyword
|
|
33
|
+
@solid_agent_auto_context_keyword ||= begin
|
|
34
|
+
keywords = ::SolidAgent::HasContext::ClassMethods
|
|
35
|
+
.instance_method(:has_context).parameters
|
|
36
|
+
.select { |type, _| [ :key, :keyreq ].include?(type) }
|
|
37
|
+
.map(&:last)
|
|
38
|
+
|
|
39
|
+
keywords.include?(:contextual) ? :contextual : :contextable
|
|
40
|
+
end
|
|
41
|
+
end
|
|
23
42
|
end
|
|
24
43
|
end
|
|
25
44
|
|
|
@@ -26,13 +26,13 @@ class CreateActiveAgentDashboardTables < ActiveRecord::Migration<%= migration_ve
|
|
|
26
26
|
t.string :agent_class_name
|
|
27
27
|
t.string :action_name
|
|
28
28
|
t.integer :status, default: 0, null: false
|
|
29
|
-
t.column :appearance, json_type,
|
|
30
|
-
t.column :instruction_sets, json_type,
|
|
31
|
-
t.column :tools, json_type,
|
|
32
|
-
t.column :mcp_servers, json_type,
|
|
33
|
-
t.column :model_config, json_type,
|
|
34
|
-
t.column :response_format, json_type,
|
|
35
|
-
t.column :action_prompts, json_type,
|
|
29
|
+
t.column :appearance, json_type, **json_default({})
|
|
30
|
+
t.column :instruction_sets, json_type, **json_default([])
|
|
31
|
+
t.column :tools, json_type, **json_default([])
|
|
32
|
+
t.column :mcp_servers, json_type, **json_default([])
|
|
33
|
+
t.column :model_config, json_type, **json_default({})
|
|
34
|
+
t.column :response_format, json_type, **json_default({})
|
|
35
|
+
t.column :action_prompts, json_type, **json_default([], null: false)
|
|
36
36
|
|
|
37
37
|
# Agents discovered from reported telemetry rather than authored here.
|
|
38
38
|
t.string :service_name
|
|
@@ -52,7 +52,7 @@ class CreateActiveAgentDashboardTables < ActiveRecord::Migration<%= migration_ve
|
|
|
52
52
|
create_table "#{prefix}agent_versions" do |t|
|
|
53
53
|
t.bigint :agent_id, null: false
|
|
54
54
|
t.integer :version_number, default: 1, null: false
|
|
55
|
-
t.column :configuration_snapshot, json_type,
|
|
55
|
+
t.column :configuration_snapshot, json_type, **json_default({}, null: false)
|
|
56
56
|
t.string :change_summary
|
|
57
57
|
t.string :created_by
|
|
58
58
|
t.timestamps
|
|
@@ -65,10 +65,10 @@ class CreateActiveAgentDashboardTables < ActiveRecord::Migration<%= migration_ve
|
|
|
65
65
|
t.string :action_name
|
|
66
66
|
t.integer :status, default: 0, null: false
|
|
67
67
|
t.text :input_prompt
|
|
68
|
-
t.column :input_params, json_type,
|
|
68
|
+
t.column :input_params, json_type, **json_default({})
|
|
69
69
|
t.text :output
|
|
70
|
-
t.column :output_metadata, json_type,
|
|
71
|
-
t.column :logs, json_type,
|
|
70
|
+
t.column :output_metadata, json_type, **json_default({})
|
|
71
|
+
t.column :logs, json_type, **json_default([])
|
|
72
72
|
t.text :error_message
|
|
73
73
|
t.text :error_backtrace
|
|
74
74
|
t.integer :input_tokens
|
|
@@ -93,11 +93,11 @@ class CreateActiveAgentDashboardTables < ActiveRecord::Migration<%= migration_ve
|
|
|
93
93
|
t.string :model, default: "gpt-4o-mini"
|
|
94
94
|
t.text :instructions
|
|
95
95
|
t.string :preset_type
|
|
96
|
-
t.column :appearance, json_type,
|
|
97
|
-
t.column :instruction_sets, json_type,
|
|
98
|
-
t.column :tools, json_type,
|
|
99
|
-
t.column :mcp_servers, json_type,
|
|
100
|
-
t.column :model_config, json_type,
|
|
96
|
+
t.column :appearance, json_type, **json_default({})
|
|
97
|
+
t.column :instruction_sets, json_type, **json_default([])
|
|
98
|
+
t.column :tools, json_type, **json_default([])
|
|
99
|
+
t.column :mcp_servers, json_type, **json_default({})
|
|
100
|
+
t.column :model_config, json_type, **json_default({})
|
|
101
101
|
t.boolean :featured, default: false
|
|
102
102
|
t.boolean :free_tier, default: false
|
|
103
103
|
t.boolean :public, default: true
|
|
@@ -114,7 +114,7 @@ class CreateActiveAgentDashboardTables < ActiveRecord::Migration<%= migration_ve
|
|
|
114
114
|
t.string :contextable_type
|
|
115
115
|
t.bigint :contextable_id
|
|
116
116
|
t.text :instructions
|
|
117
|
-
t.column :options, json_type,
|
|
117
|
+
t.column :options, json_type, **json_default({})
|
|
118
118
|
t.string :trace_id
|
|
119
119
|
t.integer :total_input_tokens, default: 0
|
|
120
120
|
t.integer :total_output_tokens, default: 0
|
|
@@ -130,11 +130,11 @@ class CreateActiveAgentDashboardTables < ActiveRecord::Migration<%= migration_ve
|
|
|
130
130
|
t.string :content_checksum
|
|
131
131
|
t.string :tool_call_id
|
|
132
132
|
t.string :tool_name
|
|
133
|
-
t.column :tool_arguments, json_type,
|
|
133
|
+
t.column :tool_arguments, json_type, **json_default({})
|
|
134
134
|
t.column :tool_result, json_type
|
|
135
|
-
t.column :attachments, json_type,
|
|
136
|
-
t.column :metadata, json_type,
|
|
137
|
-
t.column :provenance, json_type,
|
|
135
|
+
t.column :attachments, json_type, **json_default([])
|
|
136
|
+
t.column :metadata, json_type, **json_default({})
|
|
137
|
+
t.column :provenance, json_type, **json_default({})
|
|
138
138
|
t.timestamps
|
|
139
139
|
t.index :agent_context_id
|
|
140
140
|
end
|
|
@@ -150,9 +150,9 @@ class CreateActiveAgentDashboardTables < ActiveRecord::Migration<%= migration_ve
|
|
|
150
150
|
t.integer :cached_tokens, default: 0
|
|
151
151
|
t.integer :reasoning_tokens, default: 0
|
|
152
152
|
t.float :duration_seconds
|
|
153
|
-
t.column :tool_calls, json_type,
|
|
153
|
+
t.column :tool_calls, json_type, **json_default([])
|
|
154
154
|
t.column :raw_response, json_type
|
|
155
|
-
t.column :provenance, json_type,
|
|
155
|
+
t.column :provenance, json_type, **json_default({})
|
|
156
156
|
t.string :trace_id
|
|
157
157
|
t.timestamps
|
|
158
158
|
t.index :agent_context_id
|
|
@@ -183,8 +183,8 @@ class CreateActiveAgentDashboardTables < ActiveRecord::Migration<%= migration_ve
|
|
|
183
183
|
t.string :judge_kind, default: "rules", null: false
|
|
184
184
|
t.string :judge_model
|
|
185
185
|
t.integer :sample_size, default: 20, null: false
|
|
186
|
-
t.column :criteria, json_type,
|
|
187
|
-
t.column :config, json_type,
|
|
186
|
+
t.column :criteria, json_type, **json_default([], null: false)
|
|
187
|
+
t.column :config, json_type, **json_default({}, null: false)
|
|
188
188
|
t.timestamps
|
|
189
189
|
t.index [ :agent_id, :name ], unique: true
|
|
190
190
|
end
|
|
@@ -192,7 +192,7 @@ class CreateActiveAgentDashboardTables < ActiveRecord::Migration<%= migration_ve
|
|
|
192
192
|
create_table "#{prefix}evaluation_runs" do |t|
|
|
193
193
|
t.bigint :evaluation_id, null: false
|
|
194
194
|
t.integer :status, default: 0, null: false
|
|
195
|
-
t.column :scores, json_type,
|
|
195
|
+
t.column :scores, json_type, **json_default({})
|
|
196
196
|
t.integer :samples_evaluated, default: 0
|
|
197
197
|
t.integer :samples_passed, default: 0
|
|
198
198
|
t.text :error_message
|
|
@@ -208,11 +208,11 @@ class CreateActiveAgentDashboardTables < ActiveRecord::Migration<%= migration_ve
|
|
|
208
208
|
t.string :sandbox_type, default: "playwright_mcp"
|
|
209
209
|
t.string :cloud_run_job_id
|
|
210
210
|
t.string :cloud_run_url
|
|
211
|
-
t.column :runs, json_type,
|
|
211
|
+
t.column :runs, json_type, **json_default([])
|
|
212
212
|
# MCP servers this session was started with, so the MCP Services view
|
|
213
213
|
# can show a launched server as running rather than offering to start
|
|
214
214
|
# a second copy.
|
|
215
|
-
t.column :mcp_servers, json_type,
|
|
215
|
+
t.column :mcp_servers, json_type, **json_default([])
|
|
216
216
|
t.integer :runs_count, default: 0
|
|
217
217
|
t.integer :max_runs, default: 10
|
|
218
218
|
t.integer :timeout_seconds, default: 300
|
|
@@ -234,7 +234,7 @@ class CreateActiveAgentDashboardTables < ActiveRecord::Migration<%= migration_ve
|
|
|
234
234
|
t.integer :status, default: 0, null: false
|
|
235
235
|
t.text :result
|
|
236
236
|
t.text :error
|
|
237
|
-
t.column :screenshots, json_type,
|
|
237
|
+
t.column :screenshots, json_type, **json_default([])
|
|
238
238
|
t.integer :tokens_used, default: 0
|
|
239
239
|
t.integer :duration_ms
|
|
240
240
|
t.datetime :started_at
|
|
@@ -250,7 +250,7 @@ class CreateActiveAgentDashboardTables < ActiveRecord::Migration<%= migration_ve
|
|
|
250
250
|
t.integer :status, default: 0, null: false
|
|
251
251
|
t.integer :action_count, default: 0
|
|
252
252
|
t.integer :duration_ms
|
|
253
|
-
t.column :metadata, json_type,
|
|
253
|
+
t.column :metadata, json_type, **json_default({})
|
|
254
254
|
t.bigint :user_id
|
|
255
255
|
t.bigint :account_id
|
|
256
256
|
t.timestamps
|
|
@@ -269,7 +269,7 @@ class CreateActiveAgentDashboardTables < ActiveRecord::Migration<%= migration_ve
|
|
|
269
269
|
t.integer :timestamp_ms, null: false
|
|
270
270
|
t.string :screenshot_key
|
|
271
271
|
t.string :dom_snapshot_key
|
|
272
|
-
t.column :metadata, json_type,
|
|
272
|
+
t.column :metadata, json_type, **json_default({})
|
|
273
273
|
t.timestamps
|
|
274
274
|
t.index [ :session_recording_id, :sequence ], unique: true,
|
|
275
275
|
name: "index_active_agent_recording_actions_on_recording_and_sequence"
|
|
@@ -314,6 +314,21 @@ class CreateActiveAgentDashboardTables < ActiveRecord::Migration<%= migration_ve
|
|
|
314
314
|
|
|
315
315
|
# jsonb where the adapter has it, json where it doesn't.
|
|
316
316
|
def json_type
|
|
317
|
-
@json_type ||=
|
|
317
|
+
@json_type ||= postgres? ? :jsonb : :json
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# MySQL rejects a default on a JSON column outright ("BLOB, TEXT, GEOMETRY or
|
|
321
|
+
# JSON column can't have a default value"), which aborts create_table, so the
|
|
322
|
+
# column is created without one there. `null: false` is dropped with it:
|
|
323
|
+
# nothing assigns these before validation, so a NOT NULL column with no
|
|
324
|
+
# default would reject the very inserts the default was there to satisfy.
|
|
325
|
+
def json_default(value, null: nil)
|
|
326
|
+
return {} unless postgres?
|
|
327
|
+
|
|
328
|
+
null.nil? ? { default: value } : { default: value, null: null }
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def postgres?
|
|
332
|
+
connection.adapter_name.to_s.downcase.include?("postgres")
|
|
318
333
|
end
|
|
319
334
|
end
|
|
@@ -15,9 +15,9 @@ class CreateActiveAgentTelemetryTraces < ActiveRecord::Migration<%= migration_ve
|
|
|
15
15
|
t.string :service_name
|
|
16
16
|
t.string :environment
|
|
17
17
|
t.datetime :timestamp, index: true
|
|
18
|
-
t.column :spans, json_type,
|
|
19
|
-
t.column :resource_attributes, json_type,
|
|
20
|
-
t.column :sdk_info, json_type,
|
|
18
|
+
t.column :spans, json_type, **json_default([])
|
|
19
|
+
t.column :resource_attributes, json_type, **json_default({})
|
|
20
|
+
t.column :sdk_info, json_type, **json_default({})
|
|
21
21
|
t.decimal :total_duration_ms, precision: 12, scale: 3
|
|
22
22
|
t.integer :total_input_tokens, default: 0
|
|
23
23
|
t.integer :total_output_tokens, default: 0
|
|
@@ -53,6 +53,19 @@ class CreateActiveAgentTelemetryTraces < ActiveRecord::Migration<%= migration_ve
|
|
|
53
53
|
# traverses spans in SQL on PostgreSQL, and jsonb_array_elements rejects a
|
|
54
54
|
# json argument, so the column type and the query have to agree.
|
|
55
55
|
def json_type
|
|
56
|
-
@json_type ||=
|
|
56
|
+
@json_type ||= postgres? ? :jsonb : :json
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# MySQL rejects a default on a JSON column outright ("BLOB, TEXT, GEOMETRY or
|
|
60
|
+
# JSON column can't have a default value"), which aborts create_table, so the
|
|
61
|
+
# column is created without one there. ActionAgent::TelemetryTrace reads every
|
|
62
|
+
# JSON column through `Array(...)`/`|| {}`, and ingest always assigns all
|
|
63
|
+
# three, so a NULL reads the same as the empty default would.
|
|
64
|
+
def json_default(value)
|
|
65
|
+
postgres? ? { default: value } : {}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def postgres?
|
|
69
|
+
connection.adapter_name.to_s.downcase.include?("postgres")
|
|
57
70
|
end
|
|
58
71
|
end
|