mindroot 9.6.0__py3-none-any.whl → 9.8.0__py3-none-any.whl

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.
@@ -29,6 +29,12 @@ async def get_admin_html():
29
29
  html = await render('admin', {"log_id": log_id})
30
30
  return html
31
31
 
32
+ @admin_router.get("/admin/model-preferences-v2", response_class=HTMLResponse)
33
+ async def get_model_preferences_v2_html():
34
+ """Serve the new Model Preferences V2 page"""
35
+ html = await render('model-preferences-v2', {})
36
+ return html
37
+
32
38
  @admin_router.post("/admin/get-version-info")
33
39
  async def get_version_info():
34
40
  """Get version information, trying git first, then falling back to cached file."""
@@ -10,6 +10,12 @@ from lib.db.organize_models import organize_for_display
10
10
  from copy import deepcopy
11
11
  from .service_models import get_service_models_from_providers
12
12
 
13
+ # Import the new v2 preferences system with try/except for backward compatibility
14
+ try:
15
+ from lib.providers.model_preferences_v2 import ModelPreferencesV2
16
+ except ImportError:
17
+ ModelPreferencesV2 = None
18
+
13
19
  router = APIRouter()
14
20
 
15
21
  SETTINGS_FILE_PATH = 'data/preferred_models.json'
@@ -160,3 +166,77 @@ async def get_organized_models():
160
166
  async def get_equivalent_flags():
161
167
  return read_equivalent_flags()
162
168
 
169
+ # NEW V2 ENDPOINTS - These are additive and don't affect existing functionality
170
+
171
+ @router.get('/settings_v2', response_model=Dict[str, List[List[str]]])
172
+ async def get_settings_v2():
173
+ """Get preferences in new v2 format: {service: [[provider, model], ...]}"""
174
+ if ModelPreferencesV2 is None:
175
+ raise HTTPException(status_code=501, detail="Model Preferences V2 not available")
176
+
177
+ try:
178
+ prefs_manager = ModelPreferencesV2()
179
+ return prefs_manager.get_preferences()
180
+ except Exception as e:
181
+ print(f"Error getting v2 preferences: {e}")
182
+ raise HTTPException(status_code=500, detail=str(e))
183
+
184
+ @router.post('/settings_v2')
185
+ async def save_settings_v2(request: Request):
186
+ """Save preferences in new v2 format."""
187
+ if ModelPreferencesV2 is None:
188
+ raise HTTPException(status_code=501, detail="Model Preferences V2 not available")
189
+
190
+ try:
191
+ body = await request.json()
192
+ print(f"Received v2 preferences: {body}")
193
+
194
+ # Validate format: should be {service: [[provider, model], ...]}
195
+ if not isinstance(body, dict):
196
+ raise ValueError("Preferences must be a dictionary")
197
+
198
+ for service, provider_model_pairs in body.items():
199
+ if not isinstance(provider_model_pairs, list):
200
+ raise ValueError(f"Service '{service}' must have a list of provider/model pairs")
201
+
202
+ for pair in provider_model_pairs:
203
+ if not isinstance(pair, list) or len(pair) != 2:
204
+ raise ValueError(f"Each provider/model pair must be a list of exactly 2 items")
205
+ if not all(isinstance(item, str) for item in pair):
206
+ raise ValueError(f"Provider and model names must be strings")
207
+
208
+ prefs_manager = ModelPreferencesV2()
209
+ prefs_manager.save_preferences(body)
210
+
211
+ return {"success": True, "message": "Preferences saved successfully"}
212
+
213
+ except Exception as e:
214
+ print(f"Error saving v2 preferences: {e}")
215
+ raise HTTPException(status_code=422, detail=str(e))
216
+
217
+ @router.post('/migrate_settings')
218
+ async def migrate_settings():
219
+ """One-time migration from old format to new v2 format."""
220
+ if ModelPreferencesV2 is None:
221
+ raise HTTPException(status_code=501, detail="Model Preferences V2 not available")
222
+
223
+ try:
224
+ # Get old format preferences
225
+ old_preferences = read_settings()
226
+
227
+ # Convert to new format
228
+ prefs_manager = ModelPreferencesV2()
229
+ new_preferences = prefs_manager.migrate_from_old_format(old_preferences)
230
+
231
+ # Save new format
232
+ prefs_manager.save_preferences(new_preferences)
233
+
234
+ return {
235
+ "success": True,
236
+ "message": f"Migrated {len(old_preferences)} old preferences to new format",
237
+ "migrated_preferences": new_preferences
238
+ }
239
+
240
+ except Exception as e:
241
+ print(f"Error migrating preferences: {e}")
242
+ raise HTTPException(status_code=500, detail=str(e))
@@ -0,0 +1,587 @@
1
+ import { LitElement, html, css } from './lit-core.min.js';
2
+ import { BaseEl } from './base.js';
3
+
4
+ class ModelPreferencesV2 extends BaseEl {
5
+ static properties = {
6
+ preferences: { type: Object },
7
+ serviceModels: { type: Object },
8
+ loading: { type: Boolean },
9
+ saving: { type: Boolean },
10
+ selectedService: { type: String },
11
+ availableServices: { type: Array }
12
+ };
13
+
14
+ static styles = css`
15
+ :host {
16
+ display: block;
17
+ width: 100%;
18
+ height: 100%;
19
+ }
20
+
21
+ .preferences-v2-container {
22
+ display: flex;
23
+ flex-direction: column;
24
+ width: 100%;
25
+ max-width: 1200px;
26
+ margin: 0 auto;
27
+ gap: 20px;
28
+ }
29
+
30
+ .section {
31
+ background: rgb(10, 10, 25);
32
+ border-radius: 8px;
33
+ padding: 1rem;
34
+ border: 1px solid rgba(255, 255, 255, 0.1);
35
+ }
36
+
37
+ .section h3 {
38
+ margin: 0 0 1rem 0;
39
+ color: #fff;
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 0.5rem;
43
+ }
44
+
45
+ .service-selector {
46
+ display: flex;
47
+ gap: 10px;
48
+ align-items: center;
49
+ margin-bottom: 1rem;
50
+ }
51
+
52
+ .service-selector select {
53
+ background: #2a2a40;
54
+ color: #fff;
55
+ border: 1px solid rgba(255, 255, 255, 0.1);
56
+ padding: 0.5rem;
57
+ border-radius: 4px;
58
+ min-width: 150px;
59
+ }
60
+
61
+ .provider-list {
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: 10px;
65
+ }
66
+
67
+ .provider-item {
68
+ display: flex;
69
+ align-items: center;
70
+ gap: 10px;
71
+ padding: 0.75rem;
72
+ background: rgba(0, 0, 0, 0.2);
73
+ border-radius: 4px;
74
+ border: 1px solid rgba(255, 255, 255, 0.1);
75
+ }
76
+
77
+ .provider-item.dragging {
78
+ opacity: 0.5;
79
+ }
80
+
81
+ .drag-handle {
82
+ cursor: grab;
83
+ color: #888;
84
+ font-size: 1.2em;
85
+ }
86
+
87
+ .drag-handle:active {
88
+ cursor: grabbing;
89
+ }
90
+
91
+ .provider-info {
92
+ flex: 1;
93
+ display: flex;
94
+ align-items: center;
95
+ gap: 10px;
96
+ }
97
+
98
+ .provider-name {
99
+ font-weight: 500;
100
+ color: #fff;
101
+ min-width: 120px;
102
+ }
103
+
104
+ .model-select {
105
+ background: #2a2a40;
106
+ color: #fff;
107
+ border: 1px solid rgba(255, 255, 255, 0.1);
108
+ padding: 0.5rem;
109
+ border-radius: 4px;
110
+ min-width: 200px;
111
+ }
112
+
113
+ .order-controls {
114
+ display: flex;
115
+ flex-direction: column;
116
+ gap: 2px;
117
+ }
118
+
119
+ .order-btn {
120
+ background: #2a2a40;
121
+ color: #fff;
122
+ border: 1px solid rgba(255, 255, 255, 0.1);
123
+ padding: 0.25rem 0.5rem;
124
+ border-radius: 2px;
125
+ cursor: pointer;
126
+ font-size: 0.8em;
127
+ }
128
+
129
+ .order-btn:hover {
130
+ background: #3a3a50;
131
+ }
132
+
133
+ .order-btn:disabled {
134
+ opacity: 0.5;
135
+ cursor: not-allowed;
136
+ }
137
+
138
+ .remove-btn {
139
+ background: #402a2a;
140
+ color: #fff;
141
+ border: 1px solid rgba(255, 255, 255, 0.1);
142
+ padding: 0.5rem;
143
+ border-radius: 4px;
144
+ cursor: pointer;
145
+ }
146
+
147
+ .remove-btn:hover {
148
+ background: #503a3a;
149
+ }
150
+
151
+ .add-provider {
152
+ display: flex;
153
+ gap: 10px;
154
+ align-items: center;
155
+ margin-top: 1rem;
156
+ padding: 1rem;
157
+ background: rgba(0, 0, 0, 0.1);
158
+ border-radius: 4px;
159
+ border: 1px dashed rgba(255, 255, 255, 0.2);
160
+ }
161
+
162
+ .add-provider select {
163
+ background: #2a2a40;
164
+ color: #fff;
165
+ border: 1px solid rgba(255, 255, 255, 0.1);
166
+ padding: 0.5rem;
167
+ border-radius: 4px;
168
+ }
169
+
170
+ .add-btn {
171
+ background: #2a402a;
172
+ color: #fff;
173
+ border: 1px solid rgba(255, 255, 255, 0.1);
174
+ padding: 0.5rem 1rem;
175
+ border-radius: 4px;
176
+ cursor: pointer;
177
+ }
178
+
179
+ .add-btn:hover {
180
+ background: #3a503a;
181
+ }
182
+
183
+ .actions {
184
+ display: flex;
185
+ gap: 10px;
186
+ justify-content: flex-end;
187
+ }
188
+
189
+ .save-btn {
190
+ background: #2a402a;
191
+ color: #fff;
192
+ border: 1px solid rgba(255, 255, 255, 0.1);
193
+ padding: 0.75rem 1.5rem;
194
+ border-radius: 4px;
195
+ cursor: pointer;
196
+ font-weight: 500;
197
+ }
198
+
199
+ .save-btn:hover {
200
+ background: #3a503a;
201
+ }
202
+
203
+ .save-btn:disabled {
204
+ opacity: 0.5;
205
+ cursor: not-allowed;
206
+ }
207
+
208
+ .migrate-btn {
209
+ background: #403a2a;
210
+ color: #fff;
211
+ border: 1px solid rgba(255, 255, 255, 0.1);
212
+ padding: 0.75rem 1.5rem;
213
+ border-radius: 4px;
214
+ cursor: pointer;
215
+ }
216
+
217
+ .migrate-btn:hover {
218
+ background: #504a3a;
219
+ }
220
+
221
+ .loading {
222
+ text-align: center;
223
+ color: #888;
224
+ padding: 2rem;
225
+ }
226
+
227
+ .error {
228
+ color: #ff6b6b;
229
+ background: rgba(255, 107, 107, 0.1);
230
+ padding: 1rem;
231
+ border-radius: 4px;
232
+ margin-bottom: 1rem;
233
+ }
234
+
235
+ .success {
236
+ color: #51cf66;
237
+ background: rgba(81, 207, 102, 0.1);
238
+ padding: 1rem;
239
+ border-radius: 4px;
240
+ margin-bottom: 1rem;
241
+ }
242
+ `;
243
+
244
+ constructor() {
245
+ super();
246
+ this.preferences = {};
247
+ this.serviceModels = {};
248
+ this.loading = true;
249
+ this.saving = false;
250
+ this.selectedService = '';
251
+ this.availableServices = [];
252
+ this.fetchData();
253
+ }
254
+
255
+ async fetchData() {
256
+ this.loading = true;
257
+ try {
258
+ await Promise.all([
259
+ this.fetchPreferences(),
260
+ this.fetchServiceModels()
261
+ ]);
262
+ this.updateAvailableServices();
263
+ } catch (error) {
264
+ console.error('Error fetching data:', error);
265
+ } finally {
266
+ this.loading = false;
267
+ }
268
+ }
269
+
270
+ async fetchPreferences() {
271
+ try {
272
+ const response = await fetch('/settings_v2');
273
+ if (response.ok) {
274
+ this.preferences = await response.json();
275
+ } else {
276
+ console.log('V2 preferences not available, using empty preferences');
277
+ this.preferences = {};
278
+ }
279
+ } catch (error) {
280
+ console.error('Error fetching v2 preferences:', error);
281
+ this.preferences = {};
282
+ }
283
+ }
284
+
285
+ async fetchServiceModels() {
286
+ try {
287
+ const response = await fetch('/service-models');
288
+ this.serviceModels = await response.json();
289
+ } catch (error) {
290
+ console.error('Error fetching service models:', error);
291
+ this.serviceModels = {};
292
+ }
293
+ }
294
+
295
+ updateAvailableServices() {
296
+ const services = new Set();
297
+
298
+ // Add services from current preferences
299
+ Object.keys(this.preferences).forEach(service => services.add(service));
300
+
301
+ // Add services from available service models
302
+ Object.keys(this.serviceModels).forEach(service => services.add(service));
303
+
304
+ this.availableServices = Array.from(services).sort();
305
+
306
+ if (!this.selectedService && this.availableServices.length > 0) {
307
+ this.selectedService = this.availableServices[0];
308
+ }
309
+ }
310
+
311
+ handleServiceChange(event) {
312
+ this.selectedService = event.target.value;
313
+ }
314
+
315
+ moveProviderUp(index) {
316
+ if (index === 0) return;
317
+
318
+ const servicePrefs = [...(this.preferences[this.selectedService] || [])];
319
+ [servicePrefs[index - 1], servicePrefs[index]] = [servicePrefs[index], servicePrefs[index - 1]];
320
+
321
+ this.preferences = {
322
+ ...this.preferences,
323
+ [this.selectedService]: servicePrefs
324
+ };
325
+ }
326
+
327
+ moveProviderDown(index) {
328
+ const servicePrefs = this.preferences[this.selectedService] || [];
329
+ if (index === servicePrefs.length - 1) return;
330
+
331
+ const newPrefs = [...servicePrefs];
332
+ [newPrefs[index], newPrefs[index + 1]] = [newPrefs[index + 1], newPrefs[index]];
333
+
334
+ this.preferences = {
335
+ ...this.preferences,
336
+ [this.selectedService]: newPrefs
337
+ };
338
+ }
339
+
340
+ removeProvider(index) {
341
+ const servicePrefs = [...(this.preferences[this.selectedService] || [])];
342
+ servicePrefs.splice(index, 1);
343
+
344
+ this.preferences = {
345
+ ...this.preferences,
346
+ [this.selectedService]: servicePrefs
347
+ };
348
+ }
349
+
350
+ updateProviderModel(index, newModel) {
351
+ const servicePrefs = [...(this.preferences[this.selectedService] || [])];
352
+ if (servicePrefs[index]) {
353
+ servicePrefs[index] = [servicePrefs[index][0], newModel];
354
+
355
+ this.preferences = {
356
+ ...this.preferences,
357
+ [this.selectedService]: servicePrefs
358
+ };
359
+ }
360
+ }
361
+
362
+ addProvider() {
363
+ const providerSelect = this.shadowRoot.querySelector('.add-provider-select');
364
+ const modelSelect = this.shadowRoot.querySelector('.add-model-select');
365
+
366
+ const provider = providerSelect.value;
367
+ const model = modelSelect.value;
368
+
369
+ if (!provider || !model) return;
370
+
371
+ const servicePrefs = [...(this.preferences[this.selectedService] || [])];
372
+
373
+ // Check if this provider/model combination already exists
374
+ const exists = servicePrefs.some(([p, m]) => p === provider && m === model);
375
+ if (exists) return;
376
+
377
+ servicePrefs.push([provider, model]);
378
+
379
+ this.preferences = {
380
+ ...this.preferences,
381
+ [this.selectedService]: servicePrefs
382
+ };
383
+
384
+ // Reset selects
385
+ providerSelect.value = '';
386
+ modelSelect.value = '';
387
+ }
388
+
389
+ async savePreferences() {
390
+ this.saving = true;
391
+ try {
392
+ const response = await fetch('/settings_v2', {
393
+ method: 'POST',
394
+ headers: {
395
+ 'Content-Type': 'application/json'
396
+ },
397
+ body: JSON.stringify(this.preferences)
398
+ });
399
+
400
+ if (response.ok) {
401
+ this.showMessage('Preferences saved successfully!', 'success');
402
+ } else {
403
+ const error = await response.text();
404
+ this.showMessage(`Error saving preferences: ${error}`, 'error');
405
+ }
406
+ } catch (error) {
407
+ this.showMessage(`Error saving preferences: ${error.message}`, 'error');
408
+ } finally {
409
+ this.saving = false;
410
+ }
411
+ }
412
+
413
+ async migrateFromOld() {
414
+ try {
415
+ const response = await fetch('/migrate_settings', {
416
+ method: 'POST'
417
+ });
418
+
419
+ if (response.ok) {
420
+ const result = await response.json();
421
+ this.showMessage(result.message, 'success');
422
+ await this.fetchPreferences();
423
+ } else {
424
+ const error = await response.text();
425
+ this.showMessage(`Migration failed: ${error}`, 'error');
426
+ }
427
+ } catch (error) {
428
+ this.showMessage(`Migration failed: ${error.message}`, 'error');
429
+ }
430
+ }
431
+
432
+ showMessage(message, type) {
433
+ // Simple message display - could be enhanced with a proper notification system
434
+ const messageEl = document.createElement('div');
435
+ messageEl.className = type;
436
+ messageEl.textContent = message;
437
+
438
+ const container = this.shadowRoot.querySelector('.preferences-v2-container');
439
+ container.insertBefore(messageEl, container.firstChild);
440
+
441
+ setTimeout(() => {
442
+ messageEl.remove();
443
+ }, 5000);
444
+ }
445
+
446
+ getAvailableProviders() {
447
+ if (!this.selectedService || !this.serviceModels[this.selectedService]) {
448
+ return [];
449
+ }
450
+ return Object.keys(this.serviceModels[this.selectedService]);
451
+ }
452
+
453
+ getAvailableModels(provider) {
454
+ if (!this.selectedService || !this.serviceModels[this.selectedService] || !provider) {
455
+ return [];
456
+ }
457
+ return this.serviceModels[this.selectedService][provider] || [];
458
+ }
459
+
460
+ _render() {
461
+ if (this.loading) {
462
+ return html`<div class="loading">Loading preferences...</div>`;
463
+ }
464
+
465
+ const currentServicePrefs = this.preferences[this.selectedService] || [];
466
+ const availableProviders = this.getAvailableProviders();
467
+
468
+ return html`
469
+ <div class="preferences-v2-container">
470
+ <div class="section">
471
+ <h3>
472
+ <span class="material-icons">tune</span>
473
+ Model Preferences V2 (Ordered Fallback)
474
+ </h3>
475
+
476
+ <div class="service-selector">
477
+ <label>Service:</label>
478
+ <select @change=${this.handleServiceChange} .value=${this.selectedService}>
479
+ ${this.availableServices.map(service => html`
480
+ <option value="${service}">${service}</option>
481
+ `)}
482
+ </select>
483
+ </div>
484
+
485
+ ${this.selectedService ? html`
486
+ <div class="provider-list">
487
+ ${currentServicePrefs.map((providerModel, index) => {
488
+ const [provider, model] = providerModel;
489
+ const availableModels = this.getAvailableModels(provider);
490
+
491
+ return html`
492
+ <div class="provider-item">
493
+ <span class="drag-handle material-icons">drag_indicator</span>
494
+
495
+ <div class="provider-info">
496
+ <span class="provider-name">${provider}</span>
497
+ <select
498
+ class="model-select"
499
+ .value=${model}
500
+ @change=${(e) => this.updateProviderModel(index, e.target.value)}
501
+ >
502
+ ${availableModels.map(availableModel => html`
503
+ <option value="${availableModel}" ?selected=${availableModel === model}>
504
+ ${availableModel}
505
+ </option>
506
+ `)}
507
+ </select>
508
+ </div>
509
+
510
+ <div class="order-controls">
511
+ <button
512
+ class="order-btn"
513
+ ?disabled=${index === 0}
514
+ @click=${() => this.moveProviderUp(index)}
515
+ >↑</button>
516
+ <button
517
+ class="order-btn"
518
+ ?disabled=${index === currentServicePrefs.length - 1}
519
+ @click=${() => this.moveProviderDown(index)}
520
+ >↓</button>
521
+ </div>
522
+
523
+ <button class="remove-btn" @click=${() => this.removeProvider(index)}>
524
+ <span class="material-icons">delete</span>
525
+ </button>
526
+ </div>
527
+ `;
528
+ })}
529
+ </div>
530
+
531
+ <div class="add-provider">
532
+ <label>Add Provider:</label>
533
+ <select class="add-provider-select">
534
+ <option value="">Select Provider</option>
535
+ ${availableProviders.map(provider => html`
536
+ <option value="${provider}">${provider}</option>
537
+ `)}
538
+ </select>
539
+
540
+ <label>Model:</label>
541
+ <select class="add-model-select">
542
+ <option value="">Select Model</option>
543
+ </select>
544
+
545
+ <button class="add-btn" @click=${this.addProvider}>Add</button>
546
+ </div>
547
+ ` : html`<p>Select a service to configure preferences.</p>`}
548
+ </div>
549
+
550
+ <div class="actions">
551
+ <button class="migrate-btn" @click=${this.migrateFromOld}>
552
+ Migrate from Old Format
553
+ </button>
554
+ <button
555
+ class="save-btn"
556
+ ?disabled=${this.saving}
557
+ @click=${this.savePreferences}
558
+ >
559
+ ${this.saving ? 'Saving...' : 'Save Preferences'}
560
+ </button>
561
+ </div>
562
+ </div>
563
+ `;
564
+ }
565
+
566
+ updated(changedProperties) {
567
+ super.updated(changedProperties);
568
+
569
+ // Update model dropdown when provider changes
570
+ if (changedProperties.has('selectedService')) {
571
+ const providerSelect = this.shadowRoot.querySelector('.add-provider-select');
572
+ const modelSelect = this.shadowRoot.querySelector('.add-model-select');
573
+
574
+ if (providerSelect && modelSelect) {
575
+ providerSelect.addEventListener('change', (e) => {
576
+ const selectedProvider = e.target.value;
577
+ const availableModels = this.getAvailableModels(selectedProvider);
578
+
579
+ modelSelect.innerHTML = '<option value="">Select Model</option>' +
580
+ availableModels.map(model => `<option value="${model}">${model}</option>`).join('');
581
+ });
582
+ }
583
+ }
584
+ }
585
+ }
586
+
587
+ customElements.define('model-preferences-v2', ModelPreferencesV2);
@@ -20,7 +20,7 @@
20
20
  <script type="module" src="/admin/static/js/persona-editor.js"></script>
21
21
  <script type="module" src="/admin/static/js/plugin-toggle.js"></script>
22
22
  <script type="module" src="/admin/static/js/agent-editor.js"></script>
23
- <script type="module" src="/admin/static/js/model-preferences.js"></script>
23
+ <script type="module" src="/admin/static/js/model-preferences-v2.js"></script>
24
24
  <script type="module" src="/admin/static/js/plugin-manager.js"></script>
25
25
  <script type="module" src="/admin/static/js/server-control.js"></script>
26
26
  <script type="module" src="/admin/static/js/about-info.js"></script>
@@ -119,11 +119,11 @@
119
119
 
120
120
  <details>
121
121
  <summary>
122
- <span class="material-icons">settings</span>
122
+ <span class="material-icons">tune</span>
123
123
  <span>Model Preferences</span>
124
124
  </summary>
125
125
  <div class="details-content">
126
- <model-preferences theme="dark" scope="local"></model-preferences>
126
+ <model-preferences-v2 theme="dark" scope="local"></model-preferences-v2>
127
127
  </div>
128
128
  </details>
129
129
  </details>
@@ -367,4 +367,4 @@ document.addEventListener('DOMContentLoaded', function() {
367
367
  {% block footer %}
368
368
  {% endblock %}
369
369
  </body>
370
- </html>
370
+ </html>
@@ -0,0 +1,27 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Model Preferences V2</title>
7
+ <link rel="stylesheet" href="/admin/static/css/reset.css">
8
+ <link rel="stylesheet" href="/admin/static/css/default.css">
9
+ <link rel="stylesheet" href="/admin/static/css/admin-custom.css">
10
+ <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
11
+ <script type="module" src="/admin/static/js/model-preferences-v2.js"></script>
12
+ </head>
13
+ <body>
14
+ <header class="admin-header">
15
+ <a href="/admin" class="logo">
16
+ <img src="/imgs/logo.png" class="logo"/>
17
+ </a>
18
+ <nav>
19
+ <a href="/admin">← Back to Admin</a>
20
+ </nav>
21
+ </header>
22
+
23
+ <div class="admin-container">
24
+ <model-preferences-v2 theme="dark" scope="local"></model-preferences-v2>
25
+ </div>
26
+ </body>
27
+ </html>
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "SysAdmin",
3
+ "description": "This is a sysadmin for Ubuntu Linux",
4
+ "hashver": "ea5e",
5
+ "commands": [
6
+ "tell_and_continue",
7
+ "wait_for_user_reply",
8
+ "markdown_await_user",
9
+ "append",
10
+ "write",
11
+ "read",
12
+ "dir",
13
+ "apply_udiff",
14
+ "execute_command",
15
+ "mkdir",
16
+ "think",
17
+ "task_complete"
18
+ ],
19
+ "preferred_providers": [],
20
+ "thinking_level": "off",
21
+ "persona": "Assistant",
22
+ "recommended_plugins": [],
23
+ "instructions": "\n\nYou are a sysadmin for an Ubuntu Linux system. You have root access. You are installed on a VPS.\n\nThe user may request your help with things that they might alternatively have used a control panel like Plesk for (there is no Plesk unless you install it), or for setting up libraries for software development, making nginx config files, or any system administration task.\n\nKeep track of your system info and changes etc. in /var/lib/ai-sysadmin/system-state.md . Make sure to read that at the start of each session. You can use commands like read(), write(), append(), and apply_udiff() (if necessary) as appropriate. Always double check apply_udiff() edits because they can be problematic. If you have to rebuild the file and it's a little longer than 400 lines, use write() for the first part followed by chunks in append() (but you should be able to just put details in other files and keep this file more compact).\n\nTry to keep the file under 400 lines or so and if necessary keep supplementary files with extra details for particular in the same directory or refer to system configuration files.\n\nYou should be cautious about system changes that could render the system inoperable if they do not complete properly, because the user is relying on you to update the system. If the system stops operating due to a configuration problem, they will not be able to access your chat interface, and may not be able to recover. If you are not sure, make a backup of key files and ask the user for confirmation. \n\nHowever, for tasks that seem routine and you do not have a reason to suspect they will break the system, go ahead and execute them.\n\n## System state file\n\n/var/lib/ai-sysadmin/system-state.md\n\nNote: if this file does not exist or is blank, start by populating it concisely with key system statistics and information.\n\n## Last command\n\nBefore doing significant changes you can record what you are about to do in /var/lib/ai-sysadmin/last-change.md . This way in case there is a problem there will be a record of the last specific modification made or attempted to help with rolling it back (in addition to the backup you made of any config files edited).\n\n",
24
+ "technicalInstructions": "",
25
+ "service_models": {
26
+ "stream_chat": {
27
+ "provider": "ah_anthropic",
28
+ "model": "claude-opus-4-1-20250805"
29
+ }
30
+ },
31
+ "flags": [],
32
+ "required_plugins": []
33
+ }
@@ -24,6 +24,9 @@ def init_agents():
24
24
  os.makedirs(agents_path)
25
25
  if not (agents_path / "Assistant").exists():
26
26
  shutil.copytree(assistant, agents_path / "Assistant")
27
+ sysadmin = script_path / "SysAdmin"
28
+ if not (agents_path / "SysAdmin").exists():
29
+ shutil.copytree(sysadmin, agents_path / "SysAdmin")
27
30
 
28
31
  init_models_and_providers()
29
32
  init_agents()
@@ -178,7 +178,7 @@ async def middleware(request: Request, call_next):
178
178
  filename = path_parts[-1]
179
179
  print(f"Checking for static file: {plugin_name} {static_part} {filename}")
180
180
  if static_part == 'static':
181
- if filename.endswith('.js') or filename.endswith('.css') or filename.endswith('.png') or filename.endswith('.mp4'):
181
+ if filename.endswith('.js') or filename.endswith('.css') or filename.endswith('.png') or filename.endswith('.mp4') or filename.endswith('.gif'):
182
182
  print('Static file requested:', filename)
183
183
  return await call_next(request)
184
184
  except Exception as e:
@@ -11,6 +11,12 @@ import sys
11
11
  import nanoid
12
12
  from termcolor import colored
13
13
 
14
+ # Import the new v2 preferences system with try/except for backward compatibility
15
+ try:
16
+ from .model_preferences_v2 import ModelPreferencesV2
17
+ except ImportError:
18
+ ModelPreferencesV2 = None
19
+
14
20
  class ProviderManager:
15
21
 
16
22
  def __init__(self):
@@ -71,6 +77,16 @@ class ProviderManager:
71
77
 
72
78
  need_model = await uses_models(name)
73
79
 
80
+ # DEBUG: Check what we have for model selection
81
+ print(f"\n=== MODEL SELECTION DEBUG for {name} ===")
82
+ print(f"args[0]: {args[0] if len(args) > 0 else 'N/A'}")
83
+ print(f"kwargs.get('model'): {kwargs.get('model', 'N/A')}")
84
+ if context and hasattr(context, 'agent') and context.agent:
85
+ print(f"Agent service_models: {context.agent.get('service_models', 'N/A')}")
86
+ else:
87
+ print("No agent context or service_models")
88
+ print("=== END DEBUG ===")
89
+
74
90
  if (len(args) > 0 and args[0] is None) and not 'model' in kwargs or ('model' in kwargs and kwargs['model'] is None):
75
91
  print("No model specified, checking service_models")
76
92
  if context is not None and context.agent is not None and 'service_models' in context.agent:
@@ -86,6 +102,36 @@ class ProviderManager:
86
102
  else:
87
103
  print("did not find service_models in agent")
88
104
  print('context.agent:', context.agent)
105
+
106
+ # NEW V2 PREFERENCES LOGIC - Only as fallback when no agent-specific model
107
+ if ModelPreferencesV2 is not None:
108
+ try:
109
+ print("No agent-specific model found, trying V2 system preferences...")
110
+ prefs_manager = ModelPreferencesV2()
111
+ ordered_providers = prefs_manager.get_ordered_providers_for_service(name)
112
+
113
+ if ordered_providers:
114
+ print(f"Found V2 preferences for {name}: {ordered_providers}")
115
+
116
+ for provider_name, model_name in ordered_providers:
117
+ # Check if this provider is available for this function
118
+ if name in self.functions:
119
+ for func_info in self.functions[name]:
120
+ if func_info['provider'] == provider_name:
121
+ try:
122
+ print(f"Trying V2 provider {provider_name} with model {model_name}")
123
+ # Set the model as first argument if needed
124
+ if len(args) > 0 and (args[0] is None or not args[0]):
125
+ args = (model_name, *args[1:])
126
+ elif 'model' not in kwargs:
127
+ kwargs['model'] = model_name
128
+
129
+ return await func_info['implementation'](*args, **kwargs)
130
+ except Exception as e:
131
+ print(f"V2 provider {provider_name} failed: {e}, trying next...")
132
+ continue
133
+ except Exception as e:
134
+ print(f"V2 preferences failed: {e}, continuing with existing logic")
89
135
  else:
90
136
  print("Found possible model in zeroth arg:")
91
137
  if len(args) > 0:
@@ -0,0 +1,165 @@
1
+ import json
2
+ import os
3
+ import shutil
4
+ from typing import Dict, List, Tuple, Optional
5
+ from pathlib import Path
6
+
7
+ class ModelPreferencesV2:
8
+ """New model preferences system supporting ordered provider/model pairs for fallback selection."""
9
+
10
+ def __init__(self):
11
+ # Use current working directory for data (supports multiple installations)
12
+ self.data_dir = Path.cwd() / 'data'
13
+ self.preferences_file = self.data_dir / 'preferred_models_v2.json'
14
+
15
+ # Locate template file relative to this script's location
16
+ script_dir = Path(__file__).parent.parent.parent / 'coreplugins' / 'admin'
17
+ self.template_file = script_dir / 'default_preferred_models.json'
18
+
19
+ def ensure_preferences_exist(self) -> None:
20
+ """Copy template preferences file to data directory if it doesn't exist."""
21
+ # Ensure data directory exists
22
+ self.data_dir.mkdir(exist_ok=True)
23
+
24
+ # If preferences file doesn't exist, copy from template
25
+ if not self.preferences_file.exists():
26
+ if self.template_file.exists():
27
+ shutil.copy2(self.template_file, self.preferences_file)
28
+ else:
29
+ # Create minimal default if template doesn't exist
30
+ default_prefs = {
31
+ "stream_chat": [
32
+ ["ah_anthropic", "claude-3-5-sonnet-20240620"],
33
+ ["ah_openai", "gpt-4o"]
34
+ ],
35
+ "text_to_image": [
36
+ ["ah_flux", "flux-dev"]
37
+ ]
38
+ }
39
+ self.save_preferences(default_prefs)
40
+
41
+ def get_preferences(self) -> Dict[str, List[List[str]]]:
42
+ """Get preferences in new format: {service: [[provider, model], ...]}"""
43
+ self.ensure_preferences_exist()
44
+
45
+ try:
46
+ with open(self.preferences_file, 'r') as f:
47
+ return json.load(f)
48
+ except (FileNotFoundError, json.JSONDecodeError) as e:
49
+ print(f"Error reading preferences v2: {e}")
50
+ return {}
51
+
52
+ def save_preferences(self, preferences: Dict[str, List[List[str]]]) -> None:
53
+ """Save preferences in new format."""
54
+
55
+ # Ensure data directory exists before saving
56
+ self.data_dir.mkdir(exist_ok=True)
57
+
58
+ try:
59
+ with open(self.preferences_file, 'w') as f:
60
+ json.dump(preferences, f, indent=2)
61
+ except Exception as e:
62
+ print(f"Error saving preferences v2: {e}")
63
+ raise
64
+
65
+ def get_ordered_providers_for_service(self, service_name: str) -> List[Tuple[str, str]]:
66
+ """Get ordered list of (provider, model) pairs for a service."""
67
+ preferences = self.get_preferences()
68
+
69
+ if service_name not in preferences:
70
+ return []
71
+
72
+ # Convert list of lists to list of tuples
73
+ return [(provider, model) for provider, model in preferences[service_name]]
74
+
75
+ def migrate_from_old_format(self, old_preferences: List[Dict]) -> Dict[str, List[List[str]]]:
76
+ """Convert old format preferences to new format.
77
+
78
+ Old format: [{"service_or_command_name": "stream_chat", "flag": "default", "model": "claude-3"}]
79
+ New format: {"stream_chat": [["ah_anthropic", "claude-3.5-sonnet"]]}
80
+ """
81
+ new_preferences = {}
82
+
83
+ for old_pref in old_preferences:
84
+ service = old_pref.get('service_or_command_name')
85
+ model = old_pref.get('model')
86
+
87
+ if not service or not model:
88
+ continue
89
+
90
+ # For migration, we'll need to guess the provider based on model name
91
+ # This is a best-effort conversion
92
+ provider = self._guess_provider_from_model(model)
93
+
94
+ if service not in new_preferences:
95
+ new_preferences[service] = []
96
+
97
+ # Add if not already present
98
+ provider_model_pair = [provider, model]
99
+ if provider_model_pair not in new_preferences[service]:
100
+ new_preferences[service].append(provider_model_pair)
101
+
102
+ return new_preferences
103
+
104
+ def _guess_provider_from_model(self, model_name: str) -> str:
105
+ """Best-effort guess of provider based on model name."""
106
+ model_lower = model_name.lower()
107
+
108
+ # Common model name patterns
109
+ if 'claude' in model_lower:
110
+ return 'ah_anthropic'
111
+ elif 'gpt' in model_lower or 'chatgpt' in model_lower:
112
+ return 'ah_openai'
113
+ elif 'gemini' in model_lower:
114
+ return 'mr_gemini'
115
+ elif 'llama' in model_lower:
116
+ return 'ah_together' # or another llama provider
117
+ elif 'flux' in model_lower:
118
+ return 'ah_flux'
119
+ elif 'stable' in model_lower or 'sd' in model_lower:
120
+ return 'ah_stability'
121
+ else:
122
+ # Default fallback
123
+ return 'unknown_provider'
124
+
125
+ def add_preference(self, service: str, provider: str, model: str, position: Optional[int] = None) -> None:
126
+ """Add a provider/model preference for a service at specified position (or end)."""
127
+ preferences = self.get_preferences()
128
+
129
+ if service not in preferences:
130
+ preferences[service] = []
131
+
132
+ provider_model_pair = [provider, model]
133
+
134
+ # Remove if already exists
135
+ preferences[service] = [pm for pm in preferences[service] if pm != provider_model_pair]
136
+
137
+ # Add at specified position or end
138
+ if position is not None and 0 <= position <= len(preferences[service]):
139
+ preferences[service].insert(position, provider_model_pair)
140
+ else:
141
+ preferences[service].append(provider_model_pair)
142
+
143
+ self.save_preferences(preferences)
144
+
145
+ def remove_preference(self, service: str, provider: str, model: str) -> bool:
146
+ """Remove a provider/model preference for a service. Returns True if removed."""
147
+ preferences = self.get_preferences()
148
+
149
+ if service not in preferences:
150
+ return False
151
+
152
+ provider_model_pair = [provider, model]
153
+
154
+ if provider_model_pair in preferences[service]:
155
+ preferences[service].remove(provider_model_pair)
156
+ self.save_preferences(preferences)
157
+ return True
158
+
159
+ return False
160
+
161
+ def reorder_preferences(self, service: str, ordered_pairs: List[List[str]]) -> None:
162
+ """Set the complete ordered list of provider/model pairs for a service."""
163
+ preferences = self.get_preferences()
164
+ preferences[service] = ordered_pairs
165
+ self.save_preferences(preferences)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mindroot
3
- Version: 9.6.0
3
+ Version: 9.8.0
4
4
  Summary: MindRoot AI Agent Framework
5
5
  Requires-Python: >=3.9
6
6
  License-File: LICENSE
@@ -22,10 +22,10 @@ mindroot/coreplugins/admin/plugin_router_fixed.py,sha256=LQAOOi4WdYBP4udOkVciOny
22
22
  mindroot/coreplugins/admin/plugin_router_new_not_working.py,sha256=IkmVLv9T28s0kz-rdjqdgQQIEvAYic0zjTS0HXvsi9A,5865
23
23
  mindroot/coreplugins/admin/plugin_routes.py,sha256=qthJjndFxd_PelKf2OuKXukfv1X-v-O53hDRpNLtqJA,3918
24
24
  mindroot/coreplugins/admin/registry_settings_routes.py,sha256=x739599dkE_A5uM1qQkmouo3Yzm28y7cCa2HsIpfi6U,4869
25
- mindroot/coreplugins/admin/router.py,sha256=FjiCZpjRQqGrmEMM_aD57NJzKGR0zmZ7ZbJlxuehq74,5683
25
+ mindroot/coreplugins/admin/router.py,sha256=MuGRPO-v8B7QkbnOOjiRvmzrXzDS-xFtHyjj_qzJfQ0,5923
26
26
  mindroot/coreplugins/admin/server_router.py,sha256=aZ5v7QBcK1LJ2yiciUA4elY26iSGl-8m03L1h3ogvP4,4533
27
27
  mindroot/coreplugins/admin/service_models.py,sha256=53Whq4X1Ci3D4FuzG_Eyrmz66LxnyQek-mOYw38qQm8,3355
28
- mindroot/coreplugins/admin/settings_router.py,sha256=N2h9BY0V4VqGzcx2VBBx4aIRacF3jSsbGejPcFAk23A,5533
28
+ mindroot/coreplugins/admin/settings_router.py,sha256=C3zhQuumSvoZXkhsCsQ3H0B3AARtyRLXBo8aiArwWsk,8794
29
29
  mindroot/coreplugins/admin/static/logo.png,sha256=YejLvBHILjTPkOBYE2t37BD8sTtXbPmLh1Zo-x8pbok,18260
30
30
  mindroot/coreplugins/admin/static/css/admin-custom.css,sha256=GZ2gxqyhzdjm1KBOQpiYl18YOOW39aoPN3Akb9gDgYU,14404
31
31
  mindroot/coreplugins/admin/static/css/dark.css,sha256=wOODR6WTWKhx0Cq8GDpY9THWwVC4LJBLc68PpYKIPzA,15597
@@ -48,6 +48,7 @@ mindroot/coreplugins/admin/static/js/markdown-renderer.js,sha256=SepuejgMmYSRm_o
48
48
  mindroot/coreplugins/admin/static/js/mcp-manager.js,sha256=o8UlP-E-Lxm4mEKUY-uqOhe-SflxIIVPfHQJqXkqDWo,8786
49
49
  mindroot/coreplugins/admin/static/js/mcp-publisher.js,sha256=Ut6wx3nUT5SfZ8TI0SmyuW8P46OJ52fkftscEg8KcKo,24330
50
50
  mindroot/coreplugins/admin/static/js/missing-commands.js,sha256=adNF9GWN981_KX7H25eOTw9QKlKIwTAsm8PGGkG5EcE,7949
51
+ mindroot/coreplugins/admin/static/js/model-preferences-v2.js,sha256=qK5kYnMDu61xnflRw3nqOTNEkNHcxhOvQ9t3DNODfEk,15691
51
52
  mindroot/coreplugins/admin/static/js/model-preferences.js,sha256=J0G7gcGACaPyslWJO42urf5wbZZsqO0LyPicAu-uV_Y,3365
52
53
  mindroot/coreplugins/admin/static/js/notification.js,sha256=296rVCr6MNtzvzdzW3bGiMa231-BnWJtwZZ_sDWX-3c,5633
53
54
  mindroot/coreplugins/admin/static/js/persona-editor.js,sha256=bVp-d1y3HPocmVkHpqsVh0HiRUf77vJ43kargj1iPHk,9830
@@ -435,19 +436,21 @@ mindroot/coreplugins/admin/static/js/lit-html/node/directives/until.js,sha256=j1
435
436
  mindroot/coreplugins/admin/static/js/lit-html/node/directives/until.js.map,sha256=7xiwSZ7_fGtr5XwW-10Dzs8n9QE2VUfXaZ0Sd6d82L0,6567
436
437
  mindroot/coreplugins/admin/static/js/lit-html/node/directives/when.js,sha256=NLe0NJ-6jqjVDUrT_DzmSpREsRaLo1yarzdYcV_5xHY,181
437
438
  mindroot/coreplugins/admin/static/js/lit-html/node/directives/when.js.map,sha256=tOonih_-EaqrunhNGshA9xN--WIVdGikjg8MkVp0itQ,1534
438
- mindroot/coreplugins/admin/templates/admin.jinja2,sha256=RXTNDI5X4hV7zxhzW39SHo4XaSQ441MYfLr0Chbl5Bc,13266
439
+ mindroot/coreplugins/admin/templates/admin.jinja2,sha256=H_oDqoWWk0Da0Jre67LIKvB3h30fmjcZz2T5knUyz0k,13272
440
+ mindroot/coreplugins/admin/templates/model-preferences-v2.jinja2,sha256=5J3rXYmtp_yMTFCk85SYN03gc4lbidF0Nip6YcqcIW4,891
439
441
  mindroot/coreplugins/agent/agent.py,sha256=X-EmtrpEpdfo-iUw9gj7mLveRVzAApsDWPTwMAuv7Ww,20715
440
442
  mindroot/coreplugins/agent/cmd_start_example.py,sha256=Mdcd9st6viI6-M7a0-zqkw3IxR9FAxIiZ_8G-tLdIJk,1416
441
443
  mindroot/coreplugins/agent/command_parser.py,sha256=dgMqtVLPQWE2BU7iyjqwKGy5Gh74jcZkiy1JDs07t4E,13166
442
444
  mindroot/coreplugins/agent/ensure_msg_type.py,sha256=P2XSBs3gtjlDQLOF7808nm-Dl5jswyO6qgv0lNRyHXM,240
443
445
  mindroot/coreplugins/agent/equivalent_flags.default.json,sha256=2U-3S-gIRTDd4g6ixRL4S9Fk9w40MwFqV5MD_ObqHlo,33
444
446
  mindroot/coreplugins/agent/escaping.md,sha256=b6VdJjQ3oYhLStV-72wzHm7DhQDnnJp5gKJFkTB-Geo,2798
445
- mindroot/coreplugins/agent/init_models.py,sha256=WSPk5awq2P-gecbFjtcyylH2vT12qjKsQdpX0VvU7OM,791
447
+ mindroot/coreplugins/agent/init_models.py,sha256=-o5P3NNlWmgbGltngfq5VYZ9Dm81N9kkMqTkG4p5PDA,939
446
448
  mindroot/coreplugins/agent/models.default.json,sha256=hX9-dlBWzJ-2QpMeG696hk7c483-CivujIcRRq4DcNs,1146
447
449
  mindroot/coreplugins/agent/preferred_models.default.json,sha256=Zpi6psgjhY750vyRfxN54LJM9N3afq83aL9HvmsquuU,276
448
450
  mindroot/coreplugins/agent/providers.default.json,sha256=FPmY5qVOrBy_Z4RgDJWQwLwxd-zWWI83nnAE6z5fEeg,1524
449
451
  mindroot/coreplugins/agent/system.j2.backup,sha256=itPx-urDBtKBqwps5T6yly4M9gX45AdrM-sznwefG_U,8927
450
452
  mindroot/coreplugins/agent/Assistant/agent.json,sha256=P4CaQpQaUTwx0PoyV9bCJHvfvANsFyBZlNcMtVlxM3M,1281
453
+ mindroot/coreplugins/agent/SysAdmin/agent.json,sha256=wkZvJbqM153Gr0cnt531F_XndV5DXt6dkkYnXH6x91c,2864
451
454
  mindroot/coreplugins/agent/templates/system.jinja2,sha256=nazLtFBcs_f_IkxLYij_JWRLwhHlfQt4oMPeBA7fgnE,10789
452
455
  mindroot/coreplugins/api_keys/__init__.py,sha256=znoBzjEYNIDi7AD3NQMntC4MINqLofXh75L5_Ez8_bY,138
453
456
  mindroot/coreplugins/api_keys/api_key_manager.py,sha256=GH2V1PGnpLguAB5KMumgPgVwTXoft1F0FsvBhD781go,3205
@@ -1699,7 +1702,7 @@ mindroot/coreplugins/index/static/js/lit-html/node/directives/until.js.map,sha25
1699
1702
  mindroot/coreplugins/index/static/js/lit-html/node/directives/when.js,sha256=NLe0NJ-6jqjVDUrT_DzmSpREsRaLo1yarzdYcV_5xHY,181
1700
1703
  mindroot/coreplugins/index/static/js/lit-html/node/directives/when.js.map,sha256=tOonih_-EaqrunhNGshA9xN--WIVdGikjg8MkVp0itQ,1534
1701
1704
  mindroot/coreplugins/jwt_auth/__init__.py,sha256=qFCBnx0oAKTtMSXiPEa7VXOIlWDTU-5CY0XvodgSUlM,79
1702
- mindroot/coreplugins/jwt_auth/middleware.py,sha256=Id2yrHbrMukFv6Grvgz4E4JX03YOP0glOGa1tSM9QZA,10851
1705
+ mindroot/coreplugins/jwt_auth/middleware.py,sha256=aOrKIQMDzLQArlizkGC3aRmUcPQuZRfKFUaceAgIYm8,10880
1703
1706
  mindroot/coreplugins/jwt_auth/mod.py,sha256=XqvAwBQVga-foEkziDJnQtISq1NwYcXXuvVRKeewfAQ,2070
1704
1707
  mindroot/coreplugins/jwt_auth/role_checks.py,sha256=bruZIIBSOvXNWB1YZ2s5btrbbXNf18w6MdORpJByV60,1555
1705
1708
  mindroot/coreplugins/jwt_auth/router.py,sha256=ecXYao_UG33UjQF15Hi-tf_X0eFsqLEldyqGpt7JNSw,1162
@@ -1838,10 +1841,11 @@ mindroot/lib/plugins/loader_with_l8n.py,sha256=4-kNgbk-GMN8UkCiSjg-29FGDtbml8Hhy
1838
1841
  mindroot/lib/plugins/manifest.py,sha256=Bquvg3xFg9dWxDFfRpHWei4kc8aWWxFQUP1nLDxIJtw,12078
1839
1842
  mindroot/lib/plugins/mapping.py,sha256=B3nxbStLzyYj8CEuEfxWXMbCu_dOtK_zVTbcCBw5mMc,838
1840
1843
  mindroot/lib/plugins/paths.py,sha256=vPDjEF9NU1xHMIcnN4_Hf1gjyleyXAAfRcf86WXt4M4,4312
1841
- mindroot/lib/providers/__init__.py,sha256=e0DzrmFHxZJCzWf-GE41_n6zjzN1XmL_d-8cBcmYh5U,11681
1844
+ mindroot/lib/providers/__init__.py,sha256=CxqC9iaRUTx6q2i4rfLAjGEmjy7t6d86SoyTYCF8zYY,14534
1842
1845
  mindroot/lib/providers/commands.py,sha256=wxR9tfwjdu7lbjJZQ1vNOWH-vEdhcpOxkaBf2G9OKv0,735
1843
1846
  mindroot/lib/providers/hooks.py,sha256=9cJoJRhZyzFmLBJNTYGaVijA7B6hAm2mJu9I2ij4WMo,359
1844
1847
  mindroot/lib/providers/missing.py,sha256=G6ZM25ZEVgsk4uWwgf0GygJ1SWEnstwfw7xsR9QEI_E,2457
1848
+ mindroot/lib/providers/model_preferences_v2.py,sha256=BNqOnnNLDzPCUg34CNfQYOZ_gsJgmgMMePNzBygSMCA,6756
1845
1849
  mindroot/lib/providers/services.py,sha256=nTPUbz_KjxAENg4s9ymZnztH4Z7dhBjM6bvSlIxMQQs,598
1846
1850
  mindroot/lib/providers/backup/__init__.py,sha256=8iL9fylkE7SaFap4oj9D2eikN_4EJp5IaANiPF75qWM,12186
1847
1851
  mindroot/lib/utils/backoff.py,sha256=P059hqyTtYM_unNQP_iHSdYNhWe3Hz6VqbJ_vX19eoI,5520
@@ -1857,9 +1861,9 @@ mindroot/protocols/services/stream_chat.py,sha256=fMnPfwaB5fdNMBLTEg8BXKAGvrELKH
1857
1861
  mindroot/registry/__init__.py,sha256=40Xy9bmPHsgdIrOzbtBGzf4XMqXVi9P8oZTJhn0r654,151
1858
1862
  mindroot/registry/component_manager.py,sha256=WZFNPg4SNvpqsM5NFiC2DpgmrJQCyR9cNhrCBpp30Qk,995
1859
1863
  mindroot/registry/data_access.py,sha256=81In5TwETpaqnnY1_-tBQM7rfWvUxZUZkG7lEelRUfU,5321
1860
- mindroot-9.6.0.dist-info/licenses/LICENSE,sha256=8plAmZh8y9ccuuqFFz4kp7G-cO_qsPgAOoHNvabSB4U,1070
1861
- mindroot-9.6.0.dist-info/METADATA,sha256=XHr4wMTESiDKc30DrK9QyKJ6U6UYohiAA9Gma7a16q4,1034
1862
- mindroot-9.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1863
- mindroot-9.6.0.dist-info/entry_points.txt,sha256=0bpyjMccLttx6VcjDp6zfJPN0Kk0rffor6IdIbP0j4c,50
1864
- mindroot-9.6.0.dist-info/top_level.txt,sha256=gwKm7DmNjhdrCJTYCrxa9Szne4lLpCtrEBltfsX-Mm8,9
1865
- mindroot-9.6.0.dist-info/RECORD,,
1864
+ mindroot-9.8.0.dist-info/licenses/LICENSE,sha256=8plAmZh8y9ccuuqFFz4kp7G-cO_qsPgAOoHNvabSB4U,1070
1865
+ mindroot-9.8.0.dist-info/METADATA,sha256=SKtDN2fVOx-GsuOwXDfguiNx8fFBdNKY8q2m9EaheL4,1034
1866
+ mindroot-9.8.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1867
+ mindroot-9.8.0.dist-info/entry_points.txt,sha256=0bpyjMccLttx6VcjDp6zfJPN0Kk0rffor6IdIbP0j4c,50
1868
+ mindroot-9.8.0.dist-info/top_level.txt,sha256=gwKm7DmNjhdrCJTYCrxa9Szne4lLpCtrEBltfsX-Mm8,9
1869
+ mindroot-9.8.0.dist-info/RECORD,,