ruby_llm-agents 0.3.0 → 0.3.3

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.
@@ -1,48 +1,63 @@
1
- <div id="execution-<%= execution.id %>" class="py-3 hover:bg-gray-50 dark:hover:bg-gray-700 -mx-2 px-2 rounded-lg transition-colors">
1
+ <div id="execution-<%= execution.id %>" class="py-2.5 hover:bg-gray-50 dark:hover:bg-gray-700/50 -mx-2 px-2 rounded-lg transition-colors">
2
2
  <%= link_to ruby_llm_agents.execution_path(execution), data: { turbo: false }, class: "block" do %>
3
- <div class="flex items-start space-x-3">
4
- <!-- Timeline indicator -->
5
- <div class="flex flex-col items-center pt-1">
3
+ <!-- Row 1: Status dot + Agent + Badges + Timestamp -->
4
+ <div class="flex items-center justify-between gap-2">
5
+ <div class="flex items-center gap-2 min-w-0">
6
6
  <%= render "rubyllm/agents/shared/status_dot", status: execution.status %>
7
- </div>
8
-
9
- <!-- Content -->
10
- <div class="flex-1 min-w-0">
11
- <div class="flex items-center justify-between">
12
- <div class="flex items-center space-x-2">
13
- <p class="font-medium text-gray-900 dark:text-gray-100 text-sm">
14
- <%= execution.agent_type.gsub(/Agent$/, '') %>
15
- </p>
16
- <span class="text-xs text-gray-400 dark:text-gray-500">v<%= execution.agent_version %></span>
17
- <% if execution.status_running? %>
18
- <span class="text-xs text-blue-600 dark:text-blue-400 font-medium">Running...</span>
7
+ <span class="font-medium text-sm text-gray-900 dark:text-gray-100 truncate">
8
+ <%= execution.agent_type.gsub(/Agent$/, '') %>
9
+ </span>
10
+ <span class="hidden sm:inline text-xs text-gray-400 dark:text-gray-500">v<%= execution.agent_version %></span>
11
+ <% unless execution.status_running? %>
12
+ <div class="hidden sm:flex items-center gap-1">
13
+ <% if execution.streaming? %>
14
+ <span class="badge badge-cyan text-[10px] px-1.5 py-0.5">Stream</span>
15
+ <% end %>
16
+ <% if execution.cache_hit? %>
17
+ <span class="badge badge-purple text-[10px] px-1.5 py-0.5">Cached</span>
18
+ <% end %>
19
+ <% if execution.rate_limited? %>
20
+ <span class="badge badge-orange text-[10px] px-1.5 py-0.5">Limited</span>
19
21
  <% end %>
20
22
  </div>
21
- <p class="text-xs text-gray-400 dark:text-gray-500">
22
- <%= time_ago_in_words(execution.created_at) %> ago
23
- </p>
24
- </div>
25
-
26
- <% if execution.status_running? %>
27
- <p class="text-xs text-blue-500 dark:text-blue-400 mt-1">
28
- In progress...
29
- </p>
30
- <% else %>
31
- <p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
32
- <%= number_to_human_short(execution.total_tokens || 0) %> tokens
33
- <span class="text-gray-300 dark:text-gray-600 mx-1">&middot;</span>
34
- <%= number_to_human_short(execution.total_cost || 0, prefix: "$", precision: 2) %>
35
- <span class="text-gray-300 dark:text-gray-600 mx-1">&middot;</span>
36
- <%= number_to_human_short(execution.duration_ms || 0) %>ms
37
- </p>
38
- <% end %>
39
-
40
- <% if execution.status_error? && execution.error_message.present? %>
41
- <p class="text-xs text-red-600 dark:text-red-400 mt-1.5 bg-red-50 dark:bg-red-900/30 rounded px-2 py-1">
42
- <%= execution.error_class %>: <%= truncate(execution.error_message, length: 80) %>
43
- </p>
44
23
  <% end %>
45
24
  </div>
25
+ <span class="text-xs text-gray-400 dark:text-gray-500 whitespace-nowrap flex-shrink-0">
26
+ <%= time_ago_in_words(execution.created_at) %> ago
27
+ </span>
46
28
  </div>
29
+
30
+ <!-- Row 2: Model + Metrics -->
31
+ <div class="mt-1 text-xs text-gray-500 dark:text-gray-400">
32
+ <% if execution.status_running? %>
33
+ <span class="text-blue-500 dark:text-blue-400 animate-pulse">Processing...</span>
34
+ <% else %>
35
+ <!-- Mobile: compact -->
36
+ <span class="sm:hidden">
37
+ <%= number_to_human_short(execution.total_tokens || 0) %> tokens · $<%= number_with_precision(execution.total_cost || 0, precision: 2) %>
38
+ </span>
39
+ <!-- Desktop: full -->
40
+ <span class="hidden sm:inline">
41
+ <span class="font-mono text-gray-400 dark:text-gray-500"><%= execution.model_id %></span>
42
+ <span class="mx-1.5 text-gray-300 dark:text-gray-600">·</span>
43
+ <%= number_to_human_short(execution.total_tokens || 0) %> tokens
44
+ <span class="mx-1.5 text-gray-300 dark:text-gray-600">·</span>
45
+ $<%= number_with_precision(execution.total_cost || 0, precision: 4) %>
46
+ <span class="mx-1.5 text-gray-300 dark:text-gray-600">·</span>
47
+ <%= number_to_human_short(execution.duration_ms || 0) %>ms
48
+ <% if execution.attempts_count && execution.attempts_count > 1 %>
49
+ <span class="mx-1.5 text-gray-300 dark:text-gray-600">·</span>
50
+ <span class="text-amber-600 dark:text-amber-400"><%= execution.attempts_count %> retries</span>
51
+ <% end %>
52
+ </span>
53
+ <% end %>
54
+ </div>
55
+
56
+ <!-- Error display -->
57
+ <% if execution.status_error? && execution.error_message.present? %>
58
+ <p class="mt-1.5 text-xs text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/30 rounded px-2 py-1">
59
+ <%= execution.error_class %>: <%= truncate(execution.error_message, length: 80) %>
60
+ </p>
61
+ <% end %>
47
62
  <% end %>
48
63
  </div>
@@ -32,8 +32,7 @@
32
32
 
33
33
  <div
34
34
  id="hourly-chart"
35
- class="p-6 pb-3 pt-8"
36
- style="height: 400px;"
35
+ class="p-6 pb-3 pt-8 h-[280px] sm:h-[400px]"
37
36
  data-chart-url="<%= ruby_llm_agents.chart_data_path %>"
38
37
  >
39
38
  <div id="activity-chart" style="width: 100%; height: 100%;"></div>
@@ -159,38 +158,9 @@
159
158
  >
160
159
  <div class="px-6 py-4 border-b border-gray-100 dark:border-gray-700">
161
160
  <div class="flex justify-between items-center">
162
- <div class="flex items-center space-x-3">
163
- <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
164
- Live Activity
165
- </h3>
166
-
167
- <div
168
- class="
169
- flex items-center space-x-3 text-xs text-gray-400
170
- dark:text-gray-500
171
- "
172
- >
173
- <span class="flex items-center">
174
- <span class="w-2 h-2 bg-blue-500 rounded-full mr-1 animate-pulse"></span>
175
- running
176
- </span>
177
-
178
- <span class="flex items-center">
179
- <span class="w-2 h-2 bg-green-500 rounded-full mr-1"></span>
180
- success
181
- </span>
182
-
183
- <span class="flex items-center">
184
- <span class="w-2 h-2 bg-red-500 rounded-full mr-1"></span>
185
- error
186
- </span>
187
-
188
- <span class="flex items-center">
189
- <span class="w-2 h-2 bg-yellow-500 rounded-full mr-1"></span>
190
- timeout
191
- </span>
192
- </div>
193
- </div>
161
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
162
+ Live Activity
163
+ </h3>
194
164
 
195
165
  <%= link_to "View All", ruby_llm_agents.executions_path, data: { turbo: false }, class: "text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 text-sm font-medium" %>
196
166
  </div>
@@ -19,56 +19,109 @@
19
19
  </div>
20
20
 
21
21
  <!-- Header -->
22
- <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
23
- <div class="flex items-center justify-between">
24
- <div>
25
- <div class="flex items-center gap-3">
26
- <h2 class="text-xl font-bold text-gray-900 dark:text-gray-100">
27
- <%= @execution.agent_type.gsub(/Agent$/, '') %>
28
- </h2>
29
-
30
- <%= render "rubyllm/agents/shared/status_badge", status: @execution.status, size: :md %>
31
-
32
- <% if @execution.streaming? %>
33
- <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-cyan-100 dark:bg-cyan-900/50 text-cyan-800 dark:text-cyan-300">
34
- Streaming
35
- </span>
36
- <% end %>
37
-
38
- <% if @execution.cache_hit %>
39
- <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-900/50 text-purple-800 dark:text-purple-300">
40
- Cached
41
- </span>
42
- <% end %>
22
+ <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-4 sm:p-5 mb-6">
23
+ <!-- Desktop: Row 1 - Agent name + badges + buttons + date -->
24
+ <div class="hidden sm:flex sm:items-center sm:justify-between gap-4">
25
+ <div class="flex items-center gap-3 min-w-0">
26
+ <h2 class="text-lg font-bold text-gray-900 dark:text-gray-100 truncate">
27
+ <%= @execution.agent_type.gsub(/Agent$/, '') %>
28
+ </h2>
29
+ <%= render "rubyllm/agents/shared/status_badge", status: @execution.status, size: :md %>
30
+ <% if @execution.streaming? %>
31
+ <span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-cyan-100 dark:bg-cyan-900/50 text-cyan-800 dark:text-cyan-300">Stream</span>
32
+ <% end %>
33
+ <% if @execution.cache_hit %>
34
+ <span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-900/50 text-purple-800 dark:text-purple-300">Cached</span>
35
+ <% end %>
36
+ <% if @execution.finish_reason.present? %>
37
+ <% finish_colors = {
38
+ 'stop' => 'bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-300',
39
+ 'length' => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-300',
40
+ 'content_filter' => 'bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-300',
41
+ 'tool_calls' => 'bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300'
42
+ } %>
43
+ <span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium <%= finish_colors[@execution.finish_reason] || 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' %>"><%= @execution.finish_reason %></span>
44
+ <% end %>
45
+ </div>
46
+ <div class="flex items-center gap-3 flex-shrink-0">
47
+ <%= button_to rerun_execution_path(@execution, dry_run: true),
48
+ method: :post,
49
+ data: { turbo: false },
50
+ class: "inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors",
51
+ title: "Preview what would be sent without making an API call" do %>
52
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
53
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
54
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
55
+ </svg>
56
+ Dry Run
57
+ <% end %>
58
+ <button
59
+ type="button"
60
+ onclick="confirmRerun()"
61
+ class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
62
+ title="Re-execute this agent with the same parameters"
63
+ >
64
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
65
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
66
+ </svg>
67
+ Rerun
68
+ </button>
69
+ <span class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap"><%= @execution.created_at.strftime("%b %d, %H:%M") %></span>
70
+ </div>
71
+ </div>
43
72
 
44
- <% if @execution.finish_reason.present? %>
45
- <% finish_colors = {
46
- 'stop' => 'bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-300',
47
- 'length' => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-300',
48
- 'content_filter' => 'bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-300',
49
- 'tool_calls' => 'bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300'
50
- } %>
51
- <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= finish_colors[@execution.finish_reason] || 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' %>">
52
- <%= @execution.finish_reason %>
53
- </span>
54
- <% end %>
55
- </div>
73
+ <!-- Desktop: Row 2 - Info line + relative time -->
74
+ <div class="hidden sm:flex sm:items-center sm:justify-between mt-1.5">
75
+ <p class="text-xs text-gray-500 dark:text-gray-400">
76
+ #<%= @execution.id %> · v<%= @execution.agent_version %>
77
+ <% if @execution.model_provider.present? %>
78
+ · <%= @execution.model_provider %>
79
+ <% end %>
80
+ </p>
81
+ <span class="text-xs text-gray-400 dark:text-gray-500"><%= time_ago_in_words(@execution.created_at) %> ago</span>
82
+ </div>
56
83
 
57
- <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
58
- Execution #<%= @execution.id %> · v<%= @execution.agent_version %>
59
- <% if @execution.model_provider.present? %>
60
- · <%= @execution.model_provider %>
61
- <% end %>
62
- </p>
84
+ <!-- Mobile: Stacked layout -->
85
+ <div class="sm:hidden">
86
+ <h2 class="text-lg font-bold text-gray-900 dark:text-gray-100 truncate">
87
+ <%= @execution.agent_type.gsub(/Agent$/, '') %>
88
+ </h2>
89
+ <div class="flex flex-wrap items-center gap-2 mt-2">
90
+ <%= render "rubyllm/agents/shared/status_badge", status: @execution.status, size: :md %>
91
+ <% if @execution.streaming? %>
92
+ <span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-medium bg-cyan-100 dark:bg-cyan-900/50 text-cyan-800 dark:text-cyan-300">Stream</span>
93
+ <% end %>
94
+ <% if @execution.cache_hit %>
95
+ <span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-medium bg-purple-100 dark:bg-purple-900/50 text-purple-800 dark:text-purple-300">Cached</span>
96
+ <% end %>
97
+ <% if @execution.finish_reason.present? %>
98
+ <% finish_colors = {
99
+ 'stop' => 'bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-300',
100
+ 'length' => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-300',
101
+ 'content_filter' => 'bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-300',
102
+ 'tool_calls' => 'bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300'
103
+ } %>
104
+ <span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-medium <%= finish_colors[@execution.finish_reason] || 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' %>"><%= @execution.finish_reason %></span>
105
+ <% end %>
63
106
  </div>
107
+ <p class="text-xs text-gray-500 dark:text-gray-400 mt-2">
108
+ #<%= @execution.id %> · v<%= @execution.agent_version %>
109
+ <% if @execution.model_provider.present? %>
110
+ · <%= @execution.model_provider %>
111
+ <% end %>
112
+ </p>
64
113
 
65
- <div class="flex items-center gap-4">
66
- <!-- Rerun Buttons -->
114
+ <!-- Mobile: Date + Buttons -->
115
+ <div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
116
+ <p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
117
+ <%= @execution.created_at.strftime("%b %d, %Y at %H:%M") %>
118
+ <span class="text-gray-400 dark:text-gray-500">· <%= time_ago_in_words(@execution.created_at) %> ago</span>
119
+ </p>
67
120
  <div class="flex items-center gap-2">
68
121
  <%= button_to rerun_execution_path(@execution, dry_run: true),
69
122
  method: :post,
70
123
  data: { turbo: false },
71
- class: "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors",
124
+ class: "flex-1 inline-flex items-center justify-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors",
72
125
  title: "Preview what would be sent without making an API call" do %>
73
126
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
74
127
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
@@ -76,11 +129,10 @@
76
129
  </svg>
77
130
  Dry Run
78
131
  <% end %>
79
-
80
132
  <button
81
133
  type="button"
82
134
  onclick="confirmRerun()"
83
- class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
135
+ class="flex-1 inline-flex items-center justify-center gap-1.5 px-3 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
84
136
  title="Re-execute this agent with the same parameters"
85
137
  >
86
138
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -89,14 +141,6 @@
89
141
  Rerun
90
142
  </button>
91
143
  </div>
92
-
93
- <div class="text-right text-sm text-gray-500 dark:text-gray-400">
94
- <p><%= @execution.created_at.strftime("%b %d, %Y at %H:%M") %></p>
95
-
96
- <p class="text-xs text-gray-400 dark:text-gray-500">
97
- <%= time_ago_in_words(@execution.created_at) %> ago
98
- </p>
99
- </div>
100
144
  </div>
101
145
  </div>
102
146
  </div>
@@ -438,6 +482,76 @@
438
482
  </div>
439
483
  <% end %>
440
484
 
485
+ <!-- Tool Calls -->
486
+ <% if @execution.tool_calls.present? && @execution.tool_calls.any? %>
487
+ <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
488
+ <div class="flex items-center justify-between mb-4">
489
+ <div class="flex items-center gap-2">
490
+ <svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
491
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
492
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
493
+ </svg>
494
+ <h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Tool Calls</h3>
495
+ <span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-300">
496
+ <%= @execution.tool_calls.size %>
497
+ </span>
498
+ </div>
499
+ <button
500
+ type="button"
501
+ data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.tool_calls)) %>"
502
+ class="copy-json-btn inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
503
+ >
504
+ <svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
505
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
506
+ </svg>
507
+ <svg class="w-4 h-4 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
508
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
509
+ </svg>
510
+ <span>Copy</span>
511
+ </button>
512
+ </div>
513
+
514
+ <div class="space-y-4">
515
+ <% @execution.tool_calls.each_with_index do |tool_call, index| %>
516
+ <%
517
+ # Handle both symbol and string keys
518
+ tool_id = tool_call['id'] || tool_call[:id]
519
+ tool_name = tool_call['name'] || tool_call[:name]
520
+ tool_args = tool_call['arguments'] || tool_call[:arguments] || {}
521
+ %>
522
+ <div class="border border-gray-100 dark:border-gray-700 rounded-lg overflow-hidden">
523
+ <!-- Tool Call Header -->
524
+ <div class="bg-gray-50 dark:bg-gray-900/50 px-4 py-3 flex items-center justify-between">
525
+ <div class="flex items-center gap-3">
526
+ <span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 text-xs font-medium">
527
+ <%= index + 1 %>
528
+ </span>
529
+ <code class="text-sm font-semibold text-gray-900 dark:text-gray-100"><%= tool_name %></code>
530
+ </div>
531
+ <% if tool_id.present? %>
532
+ <span class="text-xs text-gray-400 dark:text-gray-500 font-mono truncate max-w-xs" title="<%= tool_id %>">
533
+ <%= tool_id.to_s.truncate(24) %>
534
+ </span>
535
+ <% end %>
536
+ </div>
537
+
538
+ <!-- Tool Call Arguments -->
539
+ <% if tool_args.present? && tool_args.any? %>
540
+ <div class="px-4 py-3">
541
+ <p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">Arguments</p>
542
+ <pre class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-3 text-sm overflow-x-auto font-mono"><%= highlight_json(tool_args) %></pre>
543
+ </div>
544
+ <% else %>
545
+ <div class="px-4 py-3">
546
+ <p class="text-xs text-gray-400 dark:text-gray-500 italic">No arguments</p>
547
+ </div>
548
+ <% end %>
549
+ </div>
550
+ <% end %>
551
+ </div>
552
+ </div>
553
+ <% end %>
554
+
441
555
  <!-- Metadata -->
442
556
  <% if @execution.metadata.present? && @execution.metadata.any? %>
443
557
  <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Migration to add dedicated tool_calls column to executions
4
+ #
5
+ # This migration adds a dedicated column for storing tool call data,
6
+ # enabling easier querying and display of tool/function calls made
7
+ # during agent execution.
8
+ #
9
+ # Tool call structure:
10
+ # [
11
+ # { "id": "call_abc123", "name": "search", "arguments": { "query": "..." } },
12
+ # { "id": "call_def456", "name": "calculate", "arguments": { ... } }
13
+ # ]
14
+ #
15
+ # Run with: rails db:migrate
16
+ class AddToolCallsToRubyLLMAgentsExecutions < ActiveRecord::Migration<%= migration_version %>
17
+ def change
18
+ # Add tool_calls JSONB array for storing tool call details
19
+ # Each tool call contains: id, name, arguments
20
+ add_column :ruby_llm_agents_executions, :tool_calls, :jsonb, null: false, default: []
21
+
22
+ # Add counter for quick access to tool call count
23
+ add_column :ruby_llm_agents_executions, :tool_calls_count, :integer, null: false, default: 0
24
+
25
+ # Add index for querying executions that have tool calls
26
+ add_index :ruby_llm_agents_executions, :tool_calls_count
27
+ end
28
+ end
@@ -18,6 +18,10 @@ RubyLLM::Agents.configure do |config|
18
18
  # Default timeout in seconds for each LLM request
19
19
  # config.default_timeout = 60
20
20
 
21
+ # Enable streaming by default for all agents
22
+ # When enabled, agents stream responses and track time-to-first-token
23
+ # config.default_streaming = false
24
+
21
25
  # ============================================
22
26
  # Caching
23
27
  # ============================================
@@ -67,6 +67,10 @@ class CreateRubyLLMAgentsExecutions < ActiveRecord::Migration<%= migration_versi
67
67
  t.text :system_prompt
68
68
  t.text :user_prompt
69
69
 
70
+ # Tool calls tracking
71
+ t.jsonb :tool_calls, null: false, default: []
72
+ t.integer :tool_calls_count, null: false, default: 0
73
+
70
74
  t.timestamps
71
75
  end
72
76
 
@@ -89,6 +93,9 @@ class CreateRubyLLMAgentsExecutions < ActiveRecord::Migration<%= migration_versi
89
93
  # Caching index
90
94
  add_index :ruby_llm_agents_executions, :response_cache_key
91
95
 
96
+ # Tool calls index
97
+ add_index :ruby_llm_agents_executions, :tool_calls_count
98
+
92
99
  # Foreign keys for execution hierarchy
93
100
  add_foreign_key :ruby_llm_agents_executions, :ruby_llm_agents_executions,
94
101
  column: :parent_execution_id, on_delete: :nullify
@@ -107,6 +107,19 @@ module RubyLlmAgents
107
107
  )
108
108
  end
109
109
 
110
+ def create_add_tool_calls_migration
111
+ # Check if columns already exist
112
+ if column_exists?(:ruby_llm_agents_executions, :tool_calls)
113
+ say_status :skip, "tool_calls column already exists", :yellow
114
+ return
115
+ end
116
+
117
+ migration_template(
118
+ "add_tool_calls_migration.rb.tt",
119
+ File.join(db_migrate_path, "add_tool_calls_to_ruby_llm_agents_executions.rb")
120
+ )
121
+ end
122
+
110
123
  def show_post_upgrade_message
111
124
  say ""
112
125
  say "RubyLLM::Agents upgrade migration created!", :green