mindroot 9.7.0__py3-none-any.whl → 9.9.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.

Potentially problematic release.


This version of mindroot might be problematic. Click here for more details.

@@ -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>
@@ -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:
@@ -778,6 +778,58 @@ async def cancel_subscription(subscription_id: str, at_period_end: bool = True,
778
778
 
779
779
  return updated_subscription.to_dict()
780
780
 
781
+ @service()
782
+ async def get_subscription_by_provider_id(provider_subscription_id: str, context=None) -> Optional[Dict]:
783
+ """Get subscription by provider subscription ID (e.g., Stripe subscription ID)
784
+
785
+ Args:
786
+ provider_subscription_id: Provider's subscription ID
787
+ context: Request context
788
+
789
+ Returns:
790
+ Optional[Dict]: Subscription details or None if not found
791
+ """
792
+ global _subscription_manager
793
+ if not _subscription_manager:
794
+ plugin = SubscriptionsPlugin(get_base_path(context))
795
+ _, subscription_manager, _ = plugin.create_components()
796
+ _subscription_manager = subscription_manager
797
+
798
+ try:
799
+ subscriptions = await _subscription_manager.get_subscriptions_by_provider_id(
800
+ provider='stripe',
801
+ provider_subscription_id=provider_subscription_id
802
+ )
803
+ return subscriptions[0].to_dict() if subscriptions else None
804
+ except Exception as e:
805
+ logger.error(f"Error getting subscription by provider ID: {e}")
806
+ return None
807
+
808
+ @service()
809
+ async def update_subscription_status(subscription_id: str, status: str, context=None) -> Optional[Dict]:
810
+ """Update a subscription's status
811
+
812
+ Args:
813
+ subscription_id: Internal subscription ID
814
+ status: New status (active, canceled, etc.)
815
+ context: Request context
816
+
817
+ Returns:
818
+ Optional[Dict]: Updated subscription details or None if not found
819
+ """
820
+ global _subscription_manager
821
+ if not _subscription_manager:
822
+ plugin = SubscriptionsPlugin(get_base_path(context))
823
+ _, subscription_manager, _ = plugin.create_components()
824
+ _subscription_manager = subscription_manager
825
+
826
+ try:
827
+ subscription = await _subscription_manager.update_subscription_status(subscription_id, status)
828
+ return subscription.to_dict() if subscription else None
829
+ except Exception as e:
830
+ logger.error(f"Error updating subscription status: {e}")
831
+ return None
832
+
781
833
  # Command methods
782
834
 
783
835
  @command()
@@ -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.7.0
3
+ Version: 9.9.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,7 +436,8 @@ 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
@@ -1700,7 +1702,7 @@ mindroot/coreplugins/index/static/js/lit-html/node/directives/until.js.map,sha25
1700
1702
  mindroot/coreplugins/index/static/js/lit-html/node/directives/when.js,sha256=NLe0NJ-6jqjVDUrT_DzmSpREsRaLo1yarzdYcV_5xHY,181
1701
1703
  mindroot/coreplugins/index/static/js/lit-html/node/directives/when.js.map,sha256=tOonih_-EaqrunhNGshA9xN--WIVdGikjg8MkVp0itQ,1534
1702
1704
  mindroot/coreplugins/jwt_auth/__init__.py,sha256=qFCBnx0oAKTtMSXiPEa7VXOIlWDTU-5CY0XvodgSUlM,79
1703
- mindroot/coreplugins/jwt_auth/middleware.py,sha256=Id2yrHbrMukFv6Grvgz4E4JX03YOP0glOGa1tSM9QZA,10851
1705
+ mindroot/coreplugins/jwt_auth/middleware.py,sha256=aOrKIQMDzLQArlizkGC3aRmUcPQuZRfKFUaceAgIYm8,10880
1704
1706
  mindroot/coreplugins/jwt_auth/mod.py,sha256=XqvAwBQVga-foEkziDJnQtISq1NwYcXXuvVRKeewfAQ,2070
1705
1707
  mindroot/coreplugins/jwt_auth/role_checks.py,sha256=bruZIIBSOvXNWB1YZ2s5btrbbXNf18w6MdORpJByV60,1555
1706
1708
  mindroot/coreplugins/jwt_auth/router.py,sha256=ecXYao_UG33UjQF15Hi-tf_X0eFsqLEldyqGpt7JNSw,1162
@@ -1759,7 +1761,7 @@ mindroot/coreplugins/startup/mod.py,sha256=6NsrpuVFvLeKGjgV3erCBBeYhy5Y3pT8gJmI6
1759
1761
  mindroot/coreplugins/subscriptions/__init__.py,sha256=4uQ6vAhRyCOIAuegQGf03CMabsCPHMz00g1UMkCH2Ec,96
1760
1762
  mindroot/coreplugins/subscriptions/credit_integration.py,sha256=X92tUNcWxvJXv2uCzmhLKTnjbhZC_pLlWYEPLlhQbCY,2103
1761
1763
  mindroot/coreplugins/subscriptions/default_templates.py,sha256=S32UkKZLbLyEYUn1Js34TWTvwVA7paYP72UYRJiqv6Y,9749
1762
- mindroot/coreplugins/subscriptions/mod.py,sha256=oKOFBcTKzy1l2jRMs0L5EsVncm2c5c9p0sFoROt7CoQ,31315
1764
+ mindroot/coreplugins/subscriptions/mod.py,sha256=OGdUUbJzAtew6o0F8CpLk8tWAPxXH3VQeaLYZ1DqX6Q,33296
1763
1765
  mindroot/coreplugins/subscriptions/models.py,sha256=XOUkmWTXE_bjSzluNwW9UmGczXvHLfqa_WNb-lgaPBU,4183
1764
1766
  mindroot/coreplugins/subscriptions/plugin_info.json,sha256=o31KLpE2xY6Gjy-AmmDhZjVSO4iEw7Pg0199sdmsRWI,810
1765
1767
  mindroot/coreplugins/subscriptions/router.py,sha256=Uyo72_Ocip2CmFCdKmgkWxtaEABlM9E4XXCt812beno,13092
@@ -1839,10 +1841,11 @@ mindroot/lib/plugins/loader_with_l8n.py,sha256=4-kNgbk-GMN8UkCiSjg-29FGDtbml8Hhy
1839
1841
  mindroot/lib/plugins/manifest.py,sha256=Bquvg3xFg9dWxDFfRpHWei4kc8aWWxFQUP1nLDxIJtw,12078
1840
1842
  mindroot/lib/plugins/mapping.py,sha256=B3nxbStLzyYj8CEuEfxWXMbCu_dOtK_zVTbcCBw5mMc,838
1841
1843
  mindroot/lib/plugins/paths.py,sha256=vPDjEF9NU1xHMIcnN4_Hf1gjyleyXAAfRcf86WXt4M4,4312
1842
- mindroot/lib/providers/__init__.py,sha256=e0DzrmFHxZJCzWf-GE41_n6zjzN1XmL_d-8cBcmYh5U,11681
1844
+ mindroot/lib/providers/__init__.py,sha256=CxqC9iaRUTx6q2i4rfLAjGEmjy7t6d86SoyTYCF8zYY,14534
1843
1845
  mindroot/lib/providers/commands.py,sha256=wxR9tfwjdu7lbjJZQ1vNOWH-vEdhcpOxkaBf2G9OKv0,735
1844
1846
  mindroot/lib/providers/hooks.py,sha256=9cJoJRhZyzFmLBJNTYGaVijA7B6hAm2mJu9I2ij4WMo,359
1845
1847
  mindroot/lib/providers/missing.py,sha256=G6ZM25ZEVgsk4uWwgf0GygJ1SWEnstwfw7xsR9QEI_E,2457
1848
+ mindroot/lib/providers/model_preferences_v2.py,sha256=BNqOnnNLDzPCUg34CNfQYOZ_gsJgmgMMePNzBygSMCA,6756
1846
1849
  mindroot/lib/providers/services.py,sha256=nTPUbz_KjxAENg4s9ymZnztH4Z7dhBjM6bvSlIxMQQs,598
1847
1850
  mindroot/lib/providers/backup/__init__.py,sha256=8iL9fylkE7SaFap4oj9D2eikN_4EJp5IaANiPF75qWM,12186
1848
1851
  mindroot/lib/utils/backoff.py,sha256=P059hqyTtYM_unNQP_iHSdYNhWe3Hz6VqbJ_vX19eoI,5520
@@ -1858,9 +1861,9 @@ mindroot/protocols/services/stream_chat.py,sha256=fMnPfwaB5fdNMBLTEg8BXKAGvrELKH
1858
1861
  mindroot/registry/__init__.py,sha256=40Xy9bmPHsgdIrOzbtBGzf4XMqXVi9P8oZTJhn0r654,151
1859
1862
  mindroot/registry/component_manager.py,sha256=WZFNPg4SNvpqsM5NFiC2DpgmrJQCyR9cNhrCBpp30Qk,995
1860
1863
  mindroot/registry/data_access.py,sha256=81In5TwETpaqnnY1_-tBQM7rfWvUxZUZkG7lEelRUfU,5321
1861
- mindroot-9.7.0.dist-info/licenses/LICENSE,sha256=8plAmZh8y9ccuuqFFz4kp7G-cO_qsPgAOoHNvabSB4U,1070
1862
- mindroot-9.7.0.dist-info/METADATA,sha256=M6q_GMjkyjUngvQbaW5mK4IVM9Nb2qhc5R8n8YHioUU,1034
1863
- mindroot-9.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1864
- mindroot-9.7.0.dist-info/entry_points.txt,sha256=0bpyjMccLttx6VcjDp6zfJPN0Kk0rffor6IdIbP0j4c,50
1865
- mindroot-9.7.0.dist-info/top_level.txt,sha256=gwKm7DmNjhdrCJTYCrxa9Szne4lLpCtrEBltfsX-Mm8,9
1866
- mindroot-9.7.0.dist-info/RECORD,,
1864
+ mindroot-9.9.0.dist-info/licenses/LICENSE,sha256=8plAmZh8y9ccuuqFFz4kp7G-cO_qsPgAOoHNvabSB4U,1070
1865
+ mindroot-9.9.0.dist-info/METADATA,sha256=7UhfmTWrsy8tCK6W5zrAbwq_bOhhkuFo1Fo8qdf8dHs,1034
1866
+ mindroot-9.9.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1867
+ mindroot-9.9.0.dist-info/entry_points.txt,sha256=0bpyjMccLttx6VcjDp6zfJPN0Kk0rffor6IdIbP0j4c,50
1868
+ mindroot-9.9.0.dist-info/top_level.txt,sha256=gwKm7DmNjhdrCJTYCrxa9Szne4lLpCtrEBltfsX-Mm8,9
1869
+ mindroot-9.9.0.dist-info/RECORD,,