opencode-free-fleet 0.1.0 → 0.2.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.
@@ -0,0 +1,546 @@
1
+ /**
2
+ * Provider Adapters for OpenCode Free Fleet v0.2.0
3
+ *
4
+ * Each adapter knows how to fetch models and identify free tier models
5
+ * for a specific provider.
6
+ *
7
+ * v0.2.1 - Build Repair: Removed dynamic imports
8
+ */
9
+ import { ELITE_FAMILIES } from '../../types/index.js';
10
+ /**
11
+ * OpenRouter Adapter
12
+ * All models with pricing.prompt === "0" AND pricing.completion === "0" are free
13
+ */
14
+ class OpenRouterAdapter {
15
+ providerId = 'openrouter';
16
+ providerName = 'OpenRouter';
17
+ async fetchModels() {
18
+ console.log('🔗 OpenRouter: Fetching models...');
19
+ const response = await fetch('https://openrouter.ai/api/v1/models', {
20
+ headers: { 'Accept': 'application/json' }
21
+ });
22
+ if (!response.ok) {
23
+ throw new Error(`OpenRouter API error: ${response.status} ${response.statusText}`);
24
+ }
25
+ const data = await response.json();
26
+ const models = data.data || [];
27
+ console.log(`✓ OpenRouter: Found ${models.length} models`);
28
+ return models.map((model) => ({
29
+ id: model.id,
30
+ name: model.name,
31
+ description: model.description,
32
+ context_length: model.context_length,
33
+ architecture: model.architecture,
34
+ pricing: model.pricing,
35
+ top_provider: model.top_provider
36
+ }));
37
+ }
38
+ isFreeModel(model) {
39
+ // OpenRouter: Free if both prompt and completion are "0"
40
+ const isFreePrompt = model.pricing?.prompt === '0' || model.pricing?.prompt === '0.0';
41
+ const isFreeCompletion = model.pricing?.completion === '0' || model.pricing?.completion === '0.0';
42
+ return isFreePrompt && isFreeCompletion;
43
+ }
44
+ normalizeModel(model) {
45
+ const isFree = this.isFreeModel(model);
46
+ // Determine category
47
+ const id = model.id.toLowerCase();
48
+ let category = 'writing';
49
+ if (id.includes('coder') || id.includes('code') || id.includes('function')) {
50
+ category = 'coding';
51
+ }
52
+ else if (id.includes('r1') || id.includes('reasoning') || id.includes('cot') || id.includes('qwq')) {
53
+ category = 'reasoning';
54
+ }
55
+ else if (id.includes('flash') || id.includes('distill') || id.includes('nano') || id.includes('lite')) {
56
+ category = 'speed';
57
+ }
58
+ else if (id.includes('vl') || id.includes('vision') || id.includes('molmo')) {
59
+ category = 'multimodal';
60
+ }
61
+ // Determine if elite
62
+ const elitePatterns = ELITE_FAMILIES[category] || [];
63
+ const isElite = elitePatterns.some((pattern) => id.includes(pattern.toLowerCase()));
64
+ return {
65
+ id: model.id,
66
+ provider: this.providerId,
67
+ name: model.name || model.id.split('/')[1],
68
+ description: model.description,
69
+ contextLength: model.context_length,
70
+ maxOutputTokens: model.max_output_tokens || model.architecture?.['context_length'],
71
+ pricing: {
72
+ prompt: model.pricing?.prompt || '0',
73
+ completion: model.pricing?.completion || '0',
74
+ request: model.pricing?.request || '0'
75
+ },
76
+ isFree,
77
+ isElite,
78
+ category
79
+ };
80
+ }
81
+ }
82
+ /**
83
+ * Groq Adapter
84
+ * Currently (as of 2026), most Groq models are free
85
+ */
86
+ class GroqAdapter {
87
+ providerId = 'groq';
88
+ providerName = 'Groq';
89
+ async fetchModels() {
90
+ console.log('🚀 Groq: Fetching models...');
91
+ const response = await fetch('https://api.groq.com/openai/v1/models', {
92
+ headers: { 'Accept': 'application/json' }
93
+ });
94
+ if (!response.ok) {
95
+ throw new Error(`Groq API error: ${response.status} ${response.statusText}`);
96
+ }
97
+ const data = await response.json();
98
+ const models = data.data || [];
99
+ console.log(`✓ Groq: Found ${models.length} models`);
100
+ return models.map((model) => ({
101
+ id: model.id,
102
+ name: model.name,
103
+ description: model.description,
104
+ context_length: model.context_length,
105
+ pricing: model.pricing || {},
106
+ top_provider: null
107
+ }));
108
+ }
109
+ isFreeModel(model) {
110
+ // Groq: Assume all models are free (current policy)
111
+ return true;
112
+ }
113
+ normalizeModel(model) {
114
+ const isFree = this.isFreeModel(model);
115
+ // Determine category
116
+ const id = model.id.toLowerCase();
117
+ let category = 'writing';
118
+ if (id.includes('coder') || id.includes('code') || id.includes('function')) {
119
+ category = 'coding';
120
+ }
121
+ else if (id.includes('r1') || id.includes('reasoning') || id.includes('cot') || id.includes('qwq')) {
122
+ category = 'reasoning';
123
+ }
124
+ else if (id.includes('flash') || id.includes('distill') || id.includes('nano') || id.includes('lite')) {
125
+ category = 'speed';
126
+ }
127
+ else if (id.includes('vl') || id.includes('vision') || id.includes('molmo')) {
128
+ category = 'multimodal';
129
+ }
130
+ // Determine if elite
131
+ const elitePatterns = ELITE_FAMILIES[category] || [];
132
+ const isElite = elitePatterns.some((pattern) => id.includes(pattern.toLowerCase()));
133
+ return {
134
+ id: model.id,
135
+ provider: this.providerId,
136
+ name: model.name || model.id.split('/')[1],
137
+ description: model.description,
138
+ contextLength: model.context_length,
139
+ maxOutputTokens: model.max_output_tokens || model.architecture?.['context_length'],
140
+ pricing: {
141
+ prompt: model.pricing?.prompt || '0',
142
+ completion: model.pricing?.completion || '0',
143
+ request: model.pricing?.request || '0'
144
+ },
145
+ isFree,
146
+ isElite,
147
+ category
148
+ };
149
+ }
150
+ }
151
+ /**
152
+ * Cerebras Adapter
153
+ * All models are currently free
154
+ */
155
+ class CerebrasAdapter {
156
+ providerId = 'cerebras';
157
+ providerName = 'Cerebras';
158
+ async fetchModels() {
159
+ console.log('⚡ Cerebras: Fetching models...');
160
+ const response = await fetch('https://api.cerebras.ai/v1/models', {
161
+ headers: { 'Accept': 'application/json' }
162
+ });
163
+ if (!response.ok) {
164
+ throw new Error(`Cerebras API error: ${response.status} ${response.statusText}`);
165
+ }
166
+ const data = await response.json();
167
+ const models = data.models || [];
168
+ console.log(`✓ Cerebras: Found ${models.length} models`);
169
+ return models.map((model) => ({
170
+ id: model.id,
171
+ name: model.name,
172
+ description: model.description,
173
+ context_length: model.context_length || model.context_window,
174
+ pricing: model.pricing || {},
175
+ top_provider: null
176
+ }));
177
+ }
178
+ isFreeModel(model) {
179
+ // Cerebras: Assume all models are free (current policy)
180
+ return true;
181
+ }
182
+ normalizeModel(model) {
183
+ const isFree = this.isFreeModel(model);
184
+ // Determine category
185
+ const id = model.id.toLowerCase();
186
+ let category = 'writing';
187
+ if (id.includes('coder') || id.includes('code') || id.includes('function')) {
188
+ category = 'coding';
189
+ }
190
+ else if (id.includes('r1') || id.includes('reasoning') || id.includes('cot') || id.includes('qwq')) {
191
+ category = 'reasoning';
192
+ }
193
+ else if (id.includes('flash') || id.includes('distill') || id.includes('nano') || id.includes('lite')) {
194
+ category = 'speed';
195
+ }
196
+ else if (id.includes('vl') || id.includes('vision') || id.includes('molmo')) {
197
+ category = 'multimodal';
198
+ }
199
+ // Determine if elite
200
+ const elitePatterns = ELITE_FAMILIES[category] || [];
201
+ const isElite = elitePatterns.some((pattern) => id.includes(pattern.toLowerCase()));
202
+ return {
203
+ id: model.id,
204
+ provider: this.providerId,
205
+ name: model.name || model.id.split('/')[1],
206
+ description: model.description,
207
+ contextLength: model.context_length || model.context_window,
208
+ maxOutputTokens: model.max_output_tokens || model.architecture?.['context_length'],
209
+ pricing: {
210
+ prompt: model.pricing?.prompt || '0',
211
+ completion: model.pricing?.completion || '0',
212
+ request: model.pricing?.request || '0'
213
+ },
214
+ isFree,
215
+ isElite,
216
+ category
217
+ };
218
+ }
219
+ }
220
+ /**
221
+ * Google Adapter
222
+ * Free models are limited (Gemini Flash, Nano)
223
+ */
224
+ class GoogleAdapter {
225
+ providerId = 'google';
226
+ providerName = 'Google';
227
+ async fetchModels() {
228
+ console.log('🔵 Google: Fetching models...');
229
+ // Note: This requires OAuth flow
230
+ // For now, return a placeholder list
231
+ const freeModels = [
232
+ {
233
+ id: 'gemini-1.5-flash',
234
+ name: 'Gemini 1.5 Flash',
235
+ description: 'Fast, lightweight multimodal model (Free Tier)',
236
+ context_length: 28000,
237
+ pricing: { prompt: '0', completion: '0', request: '0' }
238
+ },
239
+ {
240
+ id: 'gemini-1.5-flash-8b',
241
+ name: 'Gemini 1.5 Flash-8B',
242
+ description: 'Even smaller and faster (Free Tier)',
243
+ context_length: 1000000,
244
+ pricing: { prompt: '0', completion: '0', request: '0' }
245
+ }
246
+ ];
247
+ console.log(`✓ Google: Found ${freeModels.length} free models (cached)`);
248
+ return freeModels;
249
+ }
250
+ isFreeModel(model) {
251
+ // Google: Check if explicitly marked as free (pricing === "0")
252
+ const isFreePrompt = model.pricing?.prompt === '0' || model.pricing?.prompt === '0.0';
253
+ const isFreeCompletion = model.pricing?.completion === '0' || model.pricing?.completion === '0.0';
254
+ return isFreePrompt && isFreeCompletion;
255
+ }
256
+ normalizeModel(model) {
257
+ const isFree = this.isFreeModel(model);
258
+ // Determine category
259
+ const id = model.id.toLowerCase();
260
+ let category = 'writing';
261
+ if (id.includes('coder') || id.includes('code') || id.includes('function')) {
262
+ category = 'coding';
263
+ }
264
+ else if (id.includes('r1') || id.includes('reasoning') || id.includes('cot') || id.includes('qwq')) {
265
+ category = 'reasoning';
266
+ }
267
+ else if (id.includes('flash') || id.includes('distill') || id.includes('nano') || id.includes('lite')) {
268
+ category = 'speed';
269
+ }
270
+ else if (id.includes('vl') || id.includes('vision') || id.includes('molmo')) {
271
+ category = 'multimodal';
272
+ }
273
+ // Determine if elite
274
+ const elitePatterns = ELITE_FAMILIES[category] || [];
275
+ const isElite = elitePatterns.some((pattern) => id.includes(pattern.toLowerCase()));
276
+ return {
277
+ id: model.id,
278
+ provider: this.providerId,
279
+ name: model.name || model.id.split('/')[1],
280
+ description: model.description,
281
+ contextLength: model.context_length,
282
+ maxOutputTokens: model.max_output_tokens || model.architecture?.['context_length'],
283
+ pricing: {
284
+ prompt: model.pricing?.prompt || '0',
285
+ completion: model.pricing?.completion || '0',
286
+ request: model.pricing?.request || '0'
287
+ },
288
+ isFree,
289
+ isElite,
290
+ category
291
+ };
292
+ }
293
+ }
294
+ /**
295
+ * DeepSeek Adapter
296
+ * DeepSeek-V3.2 has 5M free tokens
297
+ */
298
+ class DeepSeekAdapter {
299
+ providerId = 'deepseek';
300
+ providerName = 'DeepSeek';
301
+ async fetchModels() {
302
+ console.log('🟣 DeepSeek: Fetching models...');
303
+ const response = await fetch('https://api.deepseek.com/v1/models', {
304
+ headers: { 'Accept': 'application/json' }
305
+ });
306
+ if (!response.ok) {
307
+ throw new Error(`DeepSeek API error: ${response.status} ${response.statusText}`);
308
+ }
309
+ const data = await response.json();
310
+ const models = data.data || [];
311
+ console.log(`✓ DeepSeek: Found ${models.length} models`);
312
+ return models.map((model) => ({
313
+ id: model.id,
314
+ name: model.name,
315
+ description: model.description,
316
+ context_length: model.context_length || model.max_context_tokens,
317
+ architecture: {
318
+ modality: model.modality,
319
+ tokenizer: model.tokenizer || 'unknown'
320
+ },
321
+ pricing: model.pricing || {},
322
+ top_provider: null
323
+ }));
324
+ }
325
+ isFreeModel(model) {
326
+ // DeepSeek: Check if marked as free or known free model
327
+ const knownFreeModels = ['deepseek-chat', 'deepseek-coder', 'deepseek-v3', 'deepseek-r1'];
328
+ const modelId = model.id.toLowerCase();
329
+ return knownFreeModels.some(freeModel => modelId.includes(freeModel));
330
+ }
331
+ normalizeModel(model) {
332
+ const isFree = this.isFreeModel(model);
333
+ // Determine category
334
+ const id = model.id.toLowerCase();
335
+ let category = 'writing';
336
+ if (id.includes('coder') || id.includes('code') || id.includes('function')) {
337
+ category = 'coding';
338
+ }
339
+ else if (id.includes('r1') || id.includes('reasoning') || id.includes('cot') || id.includes('qwq')) {
340
+ category = 'reasoning';
341
+ }
342
+ else if (id.includes('flash') || id.includes('distill') || id.includes('nano') || id.includes('lite')) {
343
+ category = 'speed';
344
+ }
345
+ else if (id.includes('vl') || id.includes('vision') || id.includes('molmo')) {
346
+ category = 'multimodal';
347
+ }
348
+ // Determine if elite
349
+ const elitePatterns = ELITE_FAMILIES[category] || [];
350
+ const isElite = elitePatterns.some((pattern) => id.includes(pattern.toLowerCase()));
351
+ return {
352
+ id: model.id,
353
+ provider: this.providerId,
354
+ name: model.name || model.id.split('/')[1],
355
+ description: model.description,
356
+ contextLength: model.context_length || model.max_context_tokens,
357
+ maxOutputTokens: model.max_output_tokens || model.architecture?.['context_length'],
358
+ pricing: {
359
+ prompt: model.pricing?.prompt || '0',
360
+ completion: model.pricing?.completion || '0',
361
+ request: model.pricing?.request || '0'
362
+ },
363
+ isFree,
364
+ isElite,
365
+ category
366
+ };
367
+ }
368
+ }
369
+ /**
370
+ * ModelScope Adapter
371
+ * Some free serverless inference models
372
+ */
373
+ class ModelScopeAdapter {
374
+ providerId = 'modelscope';
375
+ providerName = 'ModelScope';
376
+ async fetchModels() {
377
+ console.log('🔬 ModelScope: Fetching free serverless models...');
378
+ // Note: This requires authentication
379
+ // For now, return a placeholder list
380
+ const freeModels = [
381
+ {
382
+ id: 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo',
383
+ name: 'Meta Llama 3.1 70B',
384
+ description: 'Llama 3.1 with 128K context (Serverless Free)',
385
+ context_length: 128000,
386
+ pricing: { prompt: '0', completion: '0', request: '0' }
387
+ }
388
+ ];
389
+ console.log(`✓ ModelScope: Found ${freeModels.length} free models (cached)`);
390
+ return freeModels;
391
+ }
392
+ isFreeModel(model) {
393
+ // ModelScope: Check if marked as serverless free
394
+ return model.serverless_free === true || model.pricing?.prompt === '0';
395
+ }
396
+ normalizeModel(model) {
397
+ const isFree = this.isFreeModel(model);
398
+ // Determine category
399
+ const id = model.id.toLowerCase();
400
+ let category = 'writing';
401
+ if (id.includes('coder') || id.includes('code') || id.includes('function')) {
402
+ category = 'coding';
403
+ }
404
+ else if (id.includes('r1') || id.includes('reasoning') || id.includes('cot') || id.includes('qwq')) {
405
+ category = 'reasoning';
406
+ }
407
+ else if (id.includes('flash') || id.includes('distill') || id.includes('nano') || id.includes('lite')) {
408
+ category = 'speed';
409
+ }
410
+ else if (id.includes('vl') || id.includes('vision') || id.includes('molmo')) {
411
+ category = 'multimodal';
412
+ }
413
+ // Determine if elite
414
+ const elitePatterns = ELITE_FAMILIES[category] || [];
415
+ const isElite = elitePatterns.some((pattern) => id.includes(pattern.toLowerCase()));
416
+ return {
417
+ id: model.id,
418
+ provider: this.providerId,
419
+ name: model.name || model.id.split('/')[1],
420
+ description: model.description,
421
+ contextLength: model.context_length,
422
+ maxOutputTokens: model.max_output_tokens || model.architecture?.['context_length'],
423
+ pricing: {
424
+ prompt: model.pricing?.prompt || '0',
425
+ completion: model.pricing?.completion || '0',
426
+ request: model.pricing?.request || '0'
427
+ },
428
+ isFree,
429
+ isElite,
430
+ category
431
+ };
432
+ }
433
+ }
434
+ /**
435
+ * Hugging Face Adapter
436
+ * Some free serverless inference models
437
+ */
438
+ class HuggingFaceAdapter {
439
+ providerId = 'huggingface';
440
+ providerName = 'Hugging Face';
441
+ async fetchModels() {
442
+ console.log('🤗 Hugging Face: Fetching free serverless models...');
443
+ // Note: This requires complex filtering
444
+ // For now, return a placeholder list
445
+ const freeModels = [
446
+ {
447
+ id: 'Qwen/Qwen2.5-72B-Instruct-Turbo',
448
+ name: 'Qwen 2.5 72B',
449
+ description: 'Qwen 2.5 with 128K context (Serverless Free)',
450
+ context_length: 128000,
451
+ pricing: { prompt: '0', completion: '0', request: '0' }
452
+ }
453
+ ];
454
+ console.log(`✓ Hugging Face: Found ${freeModels.length} free models (cached)`);
455
+ return freeModels;
456
+ }
457
+ isFreeModel(model) {
458
+ // Hugging Face: Check if marked as serverless free
459
+ return model.serverless_free === true || model.pricing?.prompt === '0';
460
+ }
461
+ normalizeModel(model) {
462
+ const isFree = this.isFreeModel(model);
463
+ // Determine category
464
+ const id = model.id.toLowerCase();
465
+ let category = 'writing';
466
+ if (id.includes('coder') || id.includes('code') || id.includes('function')) {
467
+ category = 'coding';
468
+ }
469
+ else if (id.includes('r1') || id.includes('reasoning') || id.includes('cot') || id.includes('qwq')) {
470
+ category = 'reasoning';
471
+ }
472
+ else if (id.includes('flash') || id.includes('distill') || id.includes('nano') || id.includes('lite')) {
473
+ category = 'speed';
474
+ }
475
+ else if (id.includes('vl') || id.includes('vision') || id.includes('molmo')) {
476
+ category = 'multimodal';
477
+ }
478
+ // Determine if elite
479
+ const elitePatterns = ELITE_FAMILIES[category] || [];
480
+ const isElite = elitePatterns.some((pattern) => id.includes(pattern.toLowerCase()));
481
+ return {
482
+ id: model.id,
483
+ provider: this.providerId,
484
+ name: model.name || model.id.split('/')[1],
485
+ description: model.description,
486
+ contextLength: model.context_length,
487
+ maxOutputTokens: model.max_output_tokens || model.architecture?.['context_length'],
488
+ pricing: {
489
+ prompt: model.pricing?.prompt || '0',
490
+ completion: model.pricing?.completion || '0',
491
+ request: model.pricing?.request || '0'
492
+ },
493
+ isFree,
494
+ isElite,
495
+ category
496
+ };
497
+ }
498
+ }
499
+ /**
500
+ * Base Adapter implementation
501
+ */
502
+ class BaseAdapter {
503
+ providerId;
504
+ providerName;
505
+ constructor(providerId, providerName) {
506
+ this.providerId = providerId;
507
+ this.providerName = providerName;
508
+ }
509
+ async fetchModels() {
510
+ return [];
511
+ }
512
+ isFreeModel(model) {
513
+ return false;
514
+ }
515
+ normalizeModel(model) {
516
+ return {
517
+ id: model.id,
518
+ provider: this.providerId,
519
+ name: model.name,
520
+ pricing: { prompt: '0', completion: '0', request: '0' },
521
+ isFree: false,
522
+ isElite: false,
523
+ category: 'writing'
524
+ };
525
+ }
526
+ }
527
+ /**
528
+ * Create adapter instance by provider ID
529
+ */
530
+ export function createAdapter(providerId) {
531
+ const adapters = {
532
+ 'openrouter': () => new OpenRouterAdapter(),
533
+ 'groq': () => new GroqAdapter(),
534
+ 'cerebras': () => new CerebrasAdapter(),
535
+ 'google': () => new GoogleAdapter(),
536
+ 'deepseek': () => new DeepSeekAdapter(),
537
+ 'modelscope': () => new ModelScopeAdapter(),
538
+ 'huggingface': () => new HuggingFaceAdapter()
539
+ };
540
+ const adapterFactory = adapters[providerId];
541
+ if (!adapterFactory) {
542
+ console.warn(`⚠️ No adapter found for provider: ${providerId}, using base adapter`);
543
+ return new BaseAdapter(providerId, providerId);
544
+ }
545
+ return adapterFactory();
546
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Metadata Oracle - Cross-Provider Model Metadata Lookup
3
+ *
4
+ * v0.2.1 - Build Repair: Removed problematic Z.Ai SDK import
5
+ */
6
+ /**
7
+ * Static knowledge base of confirmed free models
8
+ * This can be updated without code changes
9
+ */
10
+ export declare const CONFIRMED_FREE_MODELS: Set<string>;
11
+ /**
12
+ * Oracle result with confidence score
13
+ */
14
+ export interface ModelMetadata {
15
+ id: string;
16
+ provider: string;
17
+ name: string;
18
+ isFree: boolean;
19
+ confidence: number;
20
+ reason: string;
21
+ lastVerified?: string;
22
+ pricing?: {
23
+ prompt: string;
24
+ completion: string;
25
+ request: string;
26
+ };
27
+ }
28
+ /**
29
+ * Provider adapter for metadata lookup
30
+ */
31
+ export interface MetadataAdapter {
32
+ providerId: string;
33
+ providerName: string;
34
+ /**
35
+ * Fetch metadata for a specific model
36
+ */
37
+ fetchModelMetadata(modelId: string): Promise<ModelMetadata>;
38
+ /**
39
+ * Batch fetch multiple models
40
+ */
41
+ fetchModelsMetadata(modelIds?: string[]): Promise<ModelMetadata[]>;
42
+ /**
43
+ * Check if this adapter is available
44
+ */
45
+ isAvailable(): boolean;
46
+ }
47
+ /**
48
+ * Unified metadata oracle
49
+ * Aggregates data from multiple metadata sources
50
+ */
51
+ export declare class MetadataOracle {
52
+ private adapters;
53
+ constructor();
54
+ /**
55
+ * Initialize all metadata adapters
56
+ */
57
+ private _initializeAdapters;
58
+ /**
59
+ * Check which adapters are available
60
+ */
61
+ getAvailableAdapters(): string[];
62
+ /**
63
+ * Fetch metadata for a specific model from all available sources
64
+ * This is the main method Scout should call for free tier detection
65
+ */
66
+ fetchModelMetadata(modelId: string): Promise<ModelMetadata>;
67
+ /**
68
+ * Batch fetch metadata for multiple models
69
+ */
70
+ fetchModelsMetadata(modelIds: string[]): Promise<ModelMetadata[]>;
71
+ /**
72
+ * Get list of all confirmed free models
73
+ */
74
+ getConfirmedFreeModels(): Set<string>;
75
+ /**
76
+ * Add a manually confirmed free model
77
+ * This allows updating of static knowledge base
78
+ */
79
+ addConfirmedFreeModel(modelId: string): void;
80
+ /**
81
+ * Remove a model from confirmed free list
82
+ */
83
+ removeConfirmedFreeModel(modelId: string): void;
84
+ }