completion-kit 0.12.0 → 0.12.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cdcc8d4cdaf4b7aa4b3cff7cb0dd3fe65ce213bbce7b8ab1ba52cba304bff19a
4
- data.tar.gz: d2b25e3b12b187b3df15e9b8347668a3dcf529b765fd5427a1dc2579665ee664
3
+ metadata.gz: a39761d576cee622378682d2b920ed0540bd4cbefbda033236c2108c1676d835
4
+ data.tar.gz: 7ad068f5c6a9a96513d47e689f5b0926e101aec4058e033076c9e6d87b31d003
5
5
  SHA512:
6
- metadata.gz: 81921860b28c13076623a462e9cc82569a3721f035f5eb8dbe236c177fa02f9277aa7425659ae583da6713dfcc9467fc03683b3305f65fd8eaf29525cc93e143
7
- data.tar.gz: c03aa7c4ad395228e4268ee166a908779a6c50e614c1a0591cf52569712236ffb4ccd9b6d8dc918d40e2ebb253fc48760fdd4e95d3e374e4cf68fad7b1b7ee19
6
+ metadata.gz: 80e6c44594f02b408911390576ea7ed9c377ef4ab4d7f068341b5177277a8baa6776e09986a4afedd430d723f84e700ca85962a504f949eb17a860d53cfeba82
7
+ data.tar.gz: 749da38fb1236140bc6f6cf45419d979500c671a1a8e92471c35aa79abe95fbb32a5d898286932cf4b82af0628e533b93a8ad6fc0b458c4322321efa98317c4d
@@ -5,6 +5,7 @@ document.addEventListener("turbo:load", function() {
5
5
  });
6
6
  ckTickRelativeTimes();
7
7
  ckAutoFocusFirstError();
8
+ if (ckDiscoveringInDom()) ckStartDiscoveryPolling(0);
8
9
  });
9
10
 
10
11
  document.addEventListener("input", function(e) {
@@ -120,6 +121,37 @@ function ckRefreshModels() {
120
121
  method: "POST",
121
122
  headers: { "X-CSRF-Token": csrfToken }
122
123
  });
124
+ ckStartDiscoveryPolling(8000);
125
+ }
126
+
127
+ var ckDiscoveryPollTimer = null;
128
+ var ckDiscoveryPollUntil = 0;
129
+
130
+ function ckDiscoveringInDom() {
131
+ return !!document.querySelector('[id^="discovery_status_"] .ck-discovery-bar:not(.ck-discovery-bar--failed):not(.ck-discovery-bar--completed)');
132
+ }
133
+
134
+ function ckPollDiscoveryOnce() {
135
+ fetch("/completion_kit/provider_credentials/statuses", { headers: { "Accept": "text/vnd.turbo-stream.html" } })
136
+ .then(function(r) { return r.ok ? r.text() : null; })
137
+ .then(function(html) {
138
+ if (html && window.Turbo && window.Turbo.renderStreamMessage) window.Turbo.renderStreamMessage(html);
139
+ })
140
+ .catch(function() {});
141
+ }
142
+
143
+ function ckStartDiscoveryPolling(graceMs) {
144
+ if (!document.querySelector('[id^="discovery_status_"]')) return;
145
+ ckDiscoveryPollUntil = Date.now() + (graceMs || 0);
146
+ if (ckDiscoveryPollTimer) return;
147
+ ckPollDiscoveryOnce();
148
+ ckDiscoveryPollTimer = setInterval(function() {
149
+ ckPollDiscoveryOnce();
150
+ if (!ckDiscoveringInDom() && Date.now() > ckDiscoveryPollUntil) {
151
+ clearInterval(ckDiscoveryPollTimer);
152
+ ckDiscoveryPollTimer = null;
153
+ }
154
+ }, 1500);
123
155
  }
124
156
 
125
157
  function ckUpdateRefreshProgress() {
@@ -6,6 +6,11 @@ module CompletionKit
6
6
  @provider_credentials = ProviderCredential.order(:provider)
7
7
  end
8
8
 
9
+ def statuses
10
+ @provider_credentials = ProviderCredential.order(:provider)
11
+ respond_to { |format| format.turbo_stream }
12
+ end
13
+
9
14
  def new
10
15
  @provider_credential = ProviderCredential.new(provider: params[:provider])
11
16
  end
@@ -35,7 +40,7 @@ module CompletionKit
35
40
  @provider_credential.update_columns(discovery_status: "discovering", discovery_current: 0, discovery_total: 0)
36
41
  @provider_credential.reload
37
42
  @provider_credential.broadcast_discovery_progress
38
- ModelDiscoveryJob.perform_later(@provider_credential.id)
43
+ ModelDiscoveryJob.perform_later(@provider_credential.id, force: true)
39
44
  head :ok
40
45
  end
41
46
 
@@ -44,7 +49,7 @@ module CompletionKit
44
49
  cred.update_columns(discovery_status: "discovering", discovery_current: 0, discovery_total: 0)
45
50
  cred.reload
46
51
  cred.broadcast_discovery_progress
47
- ModelDiscoveryJob.perform_later(cred.id)
52
+ ModelDiscoveryJob.perform_later(cred.id, force: true)
48
53
  end
49
54
 
50
55
  head :ok
@@ -24,7 +24,7 @@ module CompletionKit
24
24
  credential.broadcast_discovery_progress
25
25
  end
26
26
 
27
- def perform(provider_credential_id)
27
+ def perform(provider_credential_id, force: false)
28
28
  credential = ProviderCredential.find_by(id: provider_credential_id)
29
29
  return unless credential
30
30
 
@@ -38,7 +38,7 @@ module CompletionKit
38
38
  credential.broadcast_discovery_progress
39
39
 
40
40
  service = ModelDiscoveryService.new(config: credential.config_hash)
41
- service.refresh! do |current, total|
41
+ service.refresh!(force: force) do |current, total|
42
42
  credential.update_columns(discovery_current: current, discovery_total: total)
43
43
  credential.reload
44
44
  credential.broadcast_discovery_progress
@@ -79,11 +79,13 @@ module CompletionKit
79
79
  end
80
80
 
81
81
  def broadcast_discovery_progress
82
- broadcast_replace_to(
83
- "completion_kit_provider_#{id}",
84
- target: "discovery_status_#{id}",
85
- html: render_partial("completion_kit/provider_credentials/discovery_status", provider_credential: self)
86
- )
82
+ safely_broadcast do
83
+ broadcast_replace_to(
84
+ "completion_kit_provider_#{id}",
85
+ target: "discovery_status_#{id}",
86
+ html: render_partial("completion_kit/provider_credentials/discovery_status", provider_credential: self)
87
+ )
88
+ end
87
89
  broadcast_provider_models
88
90
  end
89
91
 
@@ -93,13 +95,15 @@ module CompletionKit
93
95
  end
94
96
 
95
97
  def broadcast_provider_models
96
- Turbo::StreamsChannel.broadcast_action_to(
97
- "completion_kit_provider_#{id}",
98
- action: "replace",
99
- target: "provider_models_#{id}",
100
- method: "morph",
101
- html: render_partial("completion_kit/provider_credentials/models_card", provider_credential: self)
102
- )
98
+ safely_broadcast do
99
+ Turbo::StreamsChannel.broadcast_action_to(
100
+ "completion_kit_provider_#{id}",
101
+ action: "replace",
102
+ target: "provider_models_#{id}",
103
+ method: "morph",
104
+ html: render_partial("completion_kit/provider_credentials/models_card", provider_credential: self)
105
+ )
106
+ end
103
107
  end
104
108
 
105
109
  private
@@ -133,6 +137,12 @@ module CompletionKit
133
137
  CompletionKit::ApplicationController.render(partial: partial, locals: locals)
134
138
  end
135
139
 
140
+ def safely_broadcast
141
+ yield
142
+ rescue StandardError => e
143
+ Rails.logger.error("[CompletionKit] discovery broadcast render failed: #{e.class}: #{e.message}")
144
+ end
145
+
136
146
  def api_endpoint_not_internal
137
147
  return if api_endpoint.blank?
138
148
 
@@ -12,7 +12,7 @@ module CompletionKit
12
12
  @api_endpoint = config[:api_endpoint]
13
13
  end
14
14
 
15
- def refresh!(&on_progress)
15
+ def refresh!(force: false, &on_progress)
16
16
  discovered = fetch_models
17
17
  reconcile(discovered)
18
18
  # OpenRouter publishes capability metadata (output modalities, etc.), so we
@@ -20,6 +20,7 @@ module CompletionKit
20
20
  # Judging stays unknown ("?") until a real run proves it.
21
21
  return if @provider == "openrouter"
22
22
 
23
+ reset_failed_generation if force
23
24
  probe_new_models(&on_progress)
24
25
  end
25
26
 
@@ -181,6 +182,11 @@ module CompletionKit
181
182
  end
182
183
  end
183
184
 
185
+ def reset_failed_generation
186
+ Model.where(provider: @provider, status: %w[active failed], supports_generation: false)
187
+ .update_all(supports_generation: nil, generation_error: nil)
188
+ end
189
+
184
190
  def probe_new_models(&on_progress)
185
191
  candidates = Model.where(provider: @provider, status: %w[active failed])
186
192
  .where("supports_generation IS NULL OR supports_judging IS NULL OR (generation_error IS NOT NULL AND #{retryable_error_sql('generation_error')}) OR (judging_error IS NOT NULL AND #{retryable_error_sql('judging_error')})")
@@ -220,7 +226,7 @@ module CompletionKit
220
226
 
221
227
  def probe_generation(model)
222
228
  probe_input = "Reply with exactly this token and nothing else: PING-OK"
223
- response = send_probe(model.model_id, probe_input, 65536)
229
+ response = send_probe(model.model_id, probe_input, probe_max_output_tokens)
224
230
  if response.success?
225
231
  text = extract_text(response).to_s
226
232
  if text.blank?
@@ -251,7 +257,7 @@ module CompletionKit
251
257
  AI output to evaluate: The sky is blue.
252
258
  PROMPT
253
259
 
254
- response = send_probe(model.model_id, judge_input, 65536)
260
+ response = send_probe(model.model_id, judge_input, probe_max_output_tokens)
255
261
  if response.success?
256
262
  text = extract_text(response).to_s
257
263
  if text.match?(/Score:\s*\d/i)
@@ -269,6 +275,13 @@ module CompletionKit
269
275
  model.judging_error = e.message
270
276
  end
271
277
 
278
+ OPENAI_REASONING_PROBE_BUDGET = 65_536
279
+ CHAT_PROBE_BUDGET = 1_024
280
+
281
+ def probe_max_output_tokens
282
+ @provider == "openai" ? OPENAI_REASONING_PROBE_BUDGET : CHAT_PROBE_BUDGET
283
+ end
284
+
272
285
  def send_probe(model_id, input, max_tokens)
273
286
  case @provider
274
287
  when "openai" then openai_probe(model_id, input, max_tokens)
@@ -17,7 +17,7 @@
17
17
  <% if provider_credential.discovery_status == "completed" %>
18
18
  <span class="ck-model-list__summary-stamp">updated <time data-relative-time datetime="<%= provider_credential.updated_at.utc.iso8601 %>"><%= time_ago_in_words(provider_credential.updated_at) %> ago</time></span>
19
19
  <% end %>
20
- <button type="button" class="ck-icon-btn ck-model-list__refresh<%= ' ck-icon-btn--spinning' if discovering %>" title="Refresh models" aria-label="Refresh available models" <%= 'disabled' if discovering %> onclick="event.preventDefault();event.stopPropagation();fetch('<%= refresh_provider_credential_path(provider_credential) %>', {method:'POST',headers:{'X-CSRF-Token':document.querySelector('meta[name=csrf-token]').content}})">
20
+ <button type="button" class="ck-icon-btn ck-model-list__refresh<%= ' ck-icon-btn--spinning' if discovering %>" title="Refresh models" aria-label="Refresh available models" <%= 'disabled' if discovering %> onclick="event.preventDefault();event.stopPropagation();fetch('<%= refresh_provider_credential_path(provider_credential) %>', {method:'POST',headers:{'X-CSRF-Token':document.querySelector('meta[name=csrf-token]').content}});if(window.ckStartDiscoveryPolling)ckStartDiscoveryPolling(8000)">
21
21
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" width="13" height="13" aria-hidden="true"><path fill-rule="evenodd" d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.681.75.75 0 0 1-1.264-.808 6 6 0 0 1 9.44-.908l.84.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44.908l-.84-.84v1.68a.75.75 0 0 1-1.5 0V9.567a.75.75 0 0 1 .75-.75h3.182a.75.75 0 0 1 0 1.5h-1.37l.84.841a4.5 4.5 0 0 0 7.08-.681.75.75 0 0 1 1.024-.274Z" clip-rule="evenodd"/></svg>
22
22
  </button>
23
23
  </span>
@@ -0,0 +1,4 @@
1
+ <% @provider_credentials.each do |pc| %>
2
+ <%= turbo_stream.replace "discovery_status_#{pc.id}" do %><%= render "discovery_status", provider_credential: pc %><% end %>
3
+ <%= turbo_stream.replace "provider_models_#{pc.id}" do %><%= render "models_card", provider_credential: pc %><% end %>
4
+ <% end %>
data/config/routes.rb CHANGED
@@ -53,6 +53,7 @@ CompletionKit::Engine.routes.draw do
53
53
 
54
54
  resources :provider_credentials, only: [:index, :new, :create, :edit, :update] do
55
55
  post :refresh, on: :member
56
+ get :statuses, on: :collection
56
57
  end
57
58
  post "refresh_models", to: "provider_credentials#refresh_all", as: :refresh_models
58
59
 
@@ -1,3 +1,3 @@
1
1
  module CompletionKit
2
- VERSION = "0.12.0"
2
+ VERSION = "0.12.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: completion-kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Damien Bastin
@@ -379,6 +379,7 @@ files:
379
379
  - app/views/completion_kit/provider_credentials/edit.html.erb
380
380
  - app/views/completion_kit/provider_credentials/index.html.erb
381
381
  - app/views/completion_kit/provider_credentials/new.html.erb
382
+ - app/views/completion_kit/provider_credentials/statuses.turbo_stream.erb
382
383
  - app/views/completion_kit/responses/show.html.erb
383
384
  - app/views/completion_kit/runs/_actions.html.erb
384
385
  - app/views/completion_kit/runs/_form.html.erb