completion-kit 0.5.11 → 0.5.13
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/assets/stylesheets/completion_kit/application.css +43 -11
- data/app/helpers/completion_kit/application_helper.rb +12 -0
- data/app/models/completion_kit/mcp_session.rb +31 -6
- data/app/views/completion_kit/responses/show.html.erb +3 -3
- data/lib/completion_kit/version.rb +1 -1
- 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: 0ec878fca14222f69bc34d85967b065b298ea669be11f60c49b52af13aefdfe5
|
|
4
|
+
data.tar.gz: 8211fc882175c1e69c52f2cba5a5e14668af4ddfc5784362252e8c051a59bee1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2afc081fde8f6722aadee50f973bc1512be15e5cbd19913881f0f38f06df37fee4f1cdbda54cfcb230892e73df7b27f6fdf537ea71df7da49ddfba39d2c9f644
|
|
7
|
+
data.tar.gz: 02cfae669de0b9a1f7a10bbedd7eecda8d708802ff85c1d8c6a50368fb49385e43f19b388f2ea45313b90e14a3d53464688b897ad23378964cfb55103bd3a8c2
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
html {
|
|
42
|
+
color-scheme: dark;
|
|
42
43
|
font-size: 18px;
|
|
43
44
|
line-height: 1.5;
|
|
44
45
|
scrollbar-gutter: stable;
|
|
@@ -1284,6 +1285,24 @@ tr:hover .ck-chip--publish {
|
|
|
1284
1285
|
line-height: 1.75;
|
|
1285
1286
|
}
|
|
1286
1287
|
|
|
1288
|
+
/* Scrollable code-block wrapper — caps height for big payloads. Inner
|
|
1289
|
+
<pre> drops its own border/radius so the wrapper owns the edge. We
|
|
1290
|
+
leave the scrollbar unstyled so it matches the browser/page scrollbar. */
|
|
1291
|
+
.ck-code-scroll-wrap {
|
|
1292
|
+
margin-top: 0.5rem;
|
|
1293
|
+
max-height: 28rem;
|
|
1294
|
+
overflow: auto;
|
|
1295
|
+
border: 1px solid var(--ck-line);
|
|
1296
|
+
border-radius: var(--ck-radius);
|
|
1297
|
+
background: var(--ck-bg-strong);
|
|
1298
|
+
}
|
|
1299
|
+
.ck-code-scroll-wrap > .ck-code {
|
|
1300
|
+
margin-top: 0;
|
|
1301
|
+
border: 0;
|
|
1302
|
+
border-radius: 0;
|
|
1303
|
+
background: transparent;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1287
1306
|
.ck-note-box {
|
|
1288
1307
|
background: var(--ck-surface-soft);
|
|
1289
1308
|
border: 1px solid var(--ck-line);
|
|
@@ -1873,18 +1892,7 @@ tr:hover .ck-chip--publish {
|
|
|
1873
1892
|
background: var(--ck-bg-strong);
|
|
1874
1893
|
overflow: auto;
|
|
1875
1894
|
max-height: 60vh;
|
|
1876
|
-
scrollbar-width: thin;
|
|
1877
|
-
scrollbar-color: var(--ck-line-strong) transparent;
|
|
1878
1895
|
}
|
|
1879
|
-
.ck-csv-table-wrap::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
1880
|
-
.ck-csv-table-wrap::-webkit-scrollbar-track { background: transparent; }
|
|
1881
|
-
.ck-csv-table-wrap::-webkit-scrollbar-thumb {
|
|
1882
|
-
background: var(--ck-line-strong);
|
|
1883
|
-
border-radius: 5px;
|
|
1884
|
-
border: 2px solid var(--ck-bg-strong);
|
|
1885
|
-
}
|
|
1886
|
-
.ck-csv-table-wrap::-webkit-scrollbar-thumb:hover { background: var(--ck-muted); }
|
|
1887
|
-
.ck-csv-table-wrap::-webkit-scrollbar-corner { background: transparent; }
|
|
1888
1896
|
|
|
1889
1897
|
.ck-modal__body .ck-csv-table-wrap {
|
|
1890
1898
|
margin-top: 0;
|
|
@@ -3193,6 +3201,7 @@ a.ck-metric-group-pill {
|
|
|
3193
3201
|
white-space: nowrap;
|
|
3194
3202
|
}
|
|
3195
3203
|
|
|
3204
|
+
|
|
3196
3205
|
.ck-runs-table th {
|
|
3197
3206
|
vertical-align: middle;
|
|
3198
3207
|
white-space: nowrap;
|
|
@@ -3204,21 +3213,44 @@ a.ck-metric-group-pill {
|
|
|
3204
3213
|
padding-bottom: 0.7rem;
|
|
3205
3214
|
}
|
|
3206
3215
|
|
|
3216
|
+
.ck-runs-table th:first-child,
|
|
3217
|
+
.ck-runs-table td:first-child {
|
|
3218
|
+
width: 1%;
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3207
3221
|
.ck-runs-table th:nth-child(n+2),
|
|
3208
3222
|
.ck-runs-table td:nth-child(n+2) {
|
|
3209
3223
|
width: 1%;
|
|
3210
3224
|
white-space: nowrap;
|
|
3211
3225
|
}
|
|
3212
3226
|
|
|
3227
|
+
.ck-runs-table th:last-child,
|
|
3228
|
+
.ck-runs-table td:last-child {
|
|
3229
|
+
width: auto;
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3213
3232
|
.ck-runs-table__identity {
|
|
3214
3233
|
display: flex;
|
|
3215
3234
|
flex-direction: column;
|
|
3216
3235
|
gap: 0.25rem;
|
|
3217
3236
|
min-width: 0;
|
|
3237
|
+
width: 27rem;
|
|
3238
|
+
max-width: 100%;
|
|
3218
3239
|
}
|
|
3219
3240
|
|
|
3220
3241
|
.ck-runs-table .ck-run-name {
|
|
3221
3242
|
line-height: 1.2;
|
|
3243
|
+
display: flex;
|
|
3244
|
+
align-items: center;
|
|
3245
|
+
gap: 0.5rem;
|
|
3246
|
+
min-width: 0;
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
.ck-runs-table .ck-run-name strong {
|
|
3250
|
+
overflow: hidden;
|
|
3251
|
+
text-overflow: ellipsis;
|
|
3252
|
+
white-space: nowrap;
|
|
3253
|
+
min-width: 0;
|
|
3222
3254
|
}
|
|
3223
3255
|
|
|
3224
3256
|
.ck-runs-table__config {
|
|
@@ -179,6 +179,18 @@ module CompletionKit
|
|
|
179
179
|
["tag", "tag-#{tag.color}", ("tag-outline" if outline)].compact.join(" ")
|
|
180
180
|
end
|
|
181
181
|
|
|
182
|
+
def ck_format_maybe_json(text)
|
|
183
|
+
s = text.to_s
|
|
184
|
+
return s if s.strip.empty?
|
|
185
|
+
first = s.strip[0]
|
|
186
|
+
return s unless first == "{" || first == "["
|
|
187
|
+
begin
|
|
188
|
+
JSON.pretty_generate(JSON.parse(s))
|
|
189
|
+
rescue JSON::ParserError
|
|
190
|
+
s
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
182
194
|
def tag_filter_url(base_path, selected, toggling)
|
|
183
195
|
remaining = selected.reject { |t| t.id == toggling.id }
|
|
184
196
|
next_set = selected.include?(toggling) ? remaining : remaining + [toggling]
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
module CompletionKit
|
|
2
2
|
# MCP session marker — one row per active client session, kept in the
|
|
3
3
|
# database so sessions survive Puma restarts, deploys, and Rails.cache
|
|
4
|
-
# eviction.
|
|
5
|
-
#
|
|
4
|
+
# eviction.
|
|
5
|
+
#
|
|
6
|
+
# Two things to know about why every query goes through `unscoped`:
|
|
7
|
+
#
|
|
8
|
+
# 1. CompletionKit::ApplicationRecord applies the host app's tenant_scope as
|
|
9
|
+
# a default_scope. MCP sessions are per-CONNECTION, not per-tenant — the
|
|
10
|
+
# table has no organization_id column. Letting the tenant_scope into a
|
|
11
|
+
# session lookup turns "is this session live?" into either a SQL error
|
|
12
|
+
# (no such column) or a false negative (`WHERE 1=0`), which surfaces to
|
|
13
|
+
# the client as a spurious "Session not initialized" right after init.
|
|
14
|
+
# 2. `active?` slides expires_at forward when a session is more than halfway
|
|
15
|
+
# through its TTL, so an MCP connection that keeps making calls stays
|
|
16
|
+
# alive instead of expiring on the original 1-hour wall clock.
|
|
17
|
+
#
|
|
18
|
+
# Expired rows are opportunistically pruned on every new session start, so
|
|
19
|
+
# the table stays bounded by recent activity.
|
|
6
20
|
class McpSession < ApplicationRecord
|
|
7
21
|
self.table_name = "completion_kit_mcp_sessions"
|
|
8
22
|
|
|
@@ -10,20 +24,31 @@ module CompletionKit
|
|
|
10
24
|
|
|
11
25
|
def self.start!
|
|
12
26
|
prune_expired!
|
|
13
|
-
create!(session_id: SecureRandom.uuid, expires_at: SESSION_TTL.from_now).session_id
|
|
27
|
+
unscoped.create!(session_id: SecureRandom.uuid, expires_at: SESSION_TTL.from_now).session_id
|
|
14
28
|
end
|
|
15
29
|
|
|
16
30
|
def self.active?(session_id)
|
|
17
31
|
return false if session_id.blank?
|
|
18
|
-
|
|
32
|
+
|
|
33
|
+
row = unscoped.where(session_id: session_id).where("expires_at > ?", Time.current).first
|
|
34
|
+
return false unless row
|
|
35
|
+
|
|
36
|
+
slide_expiry(row)
|
|
37
|
+
true
|
|
19
38
|
end
|
|
20
39
|
|
|
21
40
|
def self.destroy_session(session_id)
|
|
22
|
-
where(session_id: session_id).delete_all
|
|
41
|
+
unscoped.where(session_id: session_id).delete_all
|
|
23
42
|
end
|
|
24
43
|
|
|
25
44
|
def self.prune_expired!
|
|
26
|
-
where("expires_at < ?", Time.current).delete_all
|
|
45
|
+
unscoped.where("expires_at < ?", Time.current).delete_all
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.slide_expiry(row)
|
|
49
|
+
half_ttl_from_now = (SESSION_TTL / 2).from_now
|
|
50
|
+
return if row.expires_at > half_ttl_from_now
|
|
51
|
+
row.update_column(:expires_at, SESSION_TTL.from_now)
|
|
27
52
|
end
|
|
28
53
|
end
|
|
29
54
|
end
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
|
|
68
68
|
<section>
|
|
69
69
|
<p class="ck-kicker">Input</p>
|
|
70
|
-
<
|
|
70
|
+
<div class="ck-code-scroll-wrap"><pre class="ck-code"><%= ck_format_maybe_json(@response.input_data) %></pre></div>
|
|
71
71
|
</section>
|
|
72
72
|
|
|
73
73
|
<section class="ck-card--spaced">
|
|
@@ -77,13 +77,13 @@
|
|
|
77
77
|
<span class="ck-chip ck-chip--soft" style="text-transform: none;"><%= @run.prompt.llm_model %></span>
|
|
78
78
|
<% end %>
|
|
79
79
|
</div>
|
|
80
|
-
<pre class="ck-code"><%= @response.response_text %></pre>
|
|
80
|
+
<div class="ck-code-scroll-wrap"><pre class="ck-code"><%= ck_format_maybe_json(@response.response_text) %></pre></div>
|
|
81
81
|
</section>
|
|
82
82
|
|
|
83
83
|
<% if @response.expected_output.present? %>
|
|
84
84
|
<section class="ck-card--spaced">
|
|
85
85
|
<p class="ck-kicker">Expected</p>
|
|
86
|
-
<pre class="ck-code"><%= @response.expected_output %></pre>
|
|
86
|
+
<div class="ck-code-scroll-wrap"><pre class="ck-code"><%= ck_format_maybe_json(@response.expected_output) %></pre></div>
|
|
87
87
|
</section>
|
|
88
88
|
<% end %>
|
|
89
89
|
|