@pellux/goodvibes-tui 0.19.32 → 0.19.34
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.
- package/CHANGELOG.md +23 -0
- package/README.md +4 -2
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/audio/spoken-turn-model-routing.ts +117 -0
- package/src/input/command-registry.ts +2 -0
- package/src/input/commands/cloudflare-runtime.ts +343 -0
- package/src/input/commands/tts-runtime.ts +288 -7
- package/src/input/commands.ts +2 -0
- package/src/input/feed-context-factory.ts +1 -0
- package/src/input/handler-feed.ts +6 -0
- package/src/input/handler-modal-routes.ts +23 -10
- package/src/input/handler-modal-token-routes.ts +9 -0
- package/src/input/handler-onboarding-cloudflare.ts +391 -0
- package/src/input/handler-onboarding.ts +33 -0
- package/src/input/handler-picker-routes.ts +1 -1
- package/src/input/handler.ts +4 -1
- package/src/input/model-picker-types.ts +125 -0
- package/src/input/model-picker.ts +144 -134
- package/src/input/onboarding/onboarding-wizard-apply.ts +81 -0
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +449 -0
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +199 -0
- package/src/input/onboarding/onboarding-wizard-constants.ts +7 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +6 -6
- package/src/input/onboarding/onboarding-wizard-types.ts +8 -0
- package/src/input/settings-modal-types.ts +2 -1
- package/src/input/settings-modal.ts +30 -8
- package/src/main.ts +12 -1
- package/src/renderer/buffer.ts +40 -2
- package/src/renderer/compositor.ts +25 -17
- package/src/renderer/model-picker-overlay.ts +70 -0
- package/src/renderer/settings-modal-helpers.ts +1 -0
- package/src/runtime/bootstrap-command-parts.ts +4 -0
- package/src/runtime/cloudflare-control-plane.ts +328 -0
- package/src/runtime/onboarding/derivation.ts +25 -0
- package/src/runtime/onboarding/snapshot.ts +2 -0
- package/src/runtime/onboarding/types.ts +5 -1
- package/src/shell/ui-openers.ts +21 -2
- package/src/version.ts +1 -1
|
@@ -4,117 +4,11 @@ import { EFFORT_DESCRIPTIONS } from '@pellux/goodvibes-sdk/platform/providers/ef
|
|
|
4
4
|
import { getQualityTier, getQualityTierFromScore, compositeScore, A_TIER_THRESHOLD } from '@pellux/goodvibes-sdk/platform/providers/model-benchmarks';
|
|
5
5
|
import type { BenchmarkStore } from '@pellux/goodvibes-sdk/platform/providers/model-benchmarks';
|
|
6
6
|
import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers/registry';
|
|
7
|
+
import { detectFamily, POPULAR_PROVIDERS, tierToCategoryFilter } from './model-picker-types.ts';
|
|
8
|
+
import type { BenchmarkSort, CapabilityFilter, CategoryFilter, FilteredModelsCache, FilteredProvidersCache, GroupByMode, ModelItemsCache, ModelPickerTarget, PickerItem, PickerMode, ProviderItemsCache } from './model-picker-types.ts';
|
|
7
9
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Which config keys the model picker writes to on commit.
|
|
12
|
-
* 'main' → provider.provider + provider.model (default)
|
|
13
|
-
* 'helper' → helper.globalProvider + helper.globalModel (+ helper.enabled: true)
|
|
14
|
-
* 'tool' → tools.llmProvider + tools.llmModel (+ tools.llmEnabled: true)
|
|
15
|
-
*/
|
|
16
|
-
export type ModelPickerTarget = 'main' | 'helper' | 'tool';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Pricing tier filter.
|
|
20
|
-
* 'paid' matches ModelDefinition tiers 'standard' and 'premium' for forward-compat
|
|
21
|
-
* with future CatalogModel tiers ('free' | 'paid' | 'subscription').
|
|
22
|
-
*/
|
|
23
|
-
export type CategoryFilter = 'all' | 'free' | 'paid' | 'subscription';
|
|
24
|
-
|
|
25
|
-
/** Model family grouping names. */
|
|
26
|
-
export type ModelFamily =
|
|
27
|
-
| 'GPT'
|
|
28
|
-
| 'Claude'
|
|
29
|
-
| 'Gemini'
|
|
30
|
-
| 'Llama'
|
|
31
|
-
| 'Qwen'
|
|
32
|
-
| 'GLM'
|
|
33
|
-
| 'MiniMax'
|
|
34
|
-
| 'DeepSeek'
|
|
35
|
-
| 'Mistral'
|
|
36
|
-
| 'Command'
|
|
37
|
-
| 'Grok'
|
|
38
|
-
| 'Kimi'
|
|
39
|
-
| 'Other';
|
|
40
|
-
|
|
41
|
-
/** Capability filter — subset of ModelDefinition capabilities. */
|
|
42
|
-
export type CapabilityFilter = 'reasoning' | 'toolUse' | 'multimodal' | 'none';
|
|
43
|
-
|
|
44
|
-
/** Benchmark score sort order. */
|
|
45
|
-
export type BenchmarkSort = 'none' | 'composite' | 'swe' | 'gpqa';
|
|
46
|
-
|
|
47
|
-
/** Group-by cycling order. */
|
|
48
|
-
export type GroupByMode = 'provider' | 'family' | 'pricingTier' | 'qualityTier';
|
|
49
|
-
|
|
50
|
-
// ── Family detection helpers ──────────────────────────────────────────────────
|
|
51
|
-
|
|
52
|
-
/** Patterns for detecting model family from id/displayName. */
|
|
53
|
-
const FAMILY_PATTERNS: Array<{ pattern: RegExp; family: ModelFamily }> = [
|
|
54
|
-
{ pattern: /claude/i, family: 'Claude' },
|
|
55
|
-
{ pattern: /gpt|\bo1\b|\bo3\b|\bo4\b/i, family: 'GPT' },
|
|
56
|
-
{ pattern: /gemini/i, family: 'Gemini' },
|
|
57
|
-
{ pattern: /llama/i, family: 'Llama' },
|
|
58
|
-
{ pattern: /qwen/i, family: 'Qwen' },
|
|
59
|
-
{ pattern: /glm|chatglm/i, family: 'GLM' },
|
|
60
|
-
{ pattern: /minimax|abab/i, family: 'MiniMax' },
|
|
61
|
-
{ pattern: /deepseek/i, family: 'DeepSeek' },
|
|
62
|
-
{ pattern: /mistral|mixtral/i, family: 'Mistral' },
|
|
63
|
-
{ pattern: /command|cohere/i, family: 'Command' },
|
|
64
|
-
{ pattern: /grok/i, family: 'Grok' },
|
|
65
|
-
{ pattern: /kimi|moonshot/i, family: 'Kimi' },
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
/** Detect the model family from id and displayName. */
|
|
69
|
-
export function detectFamily(model: ModelDefinition): ModelFamily {
|
|
70
|
-
const haystack = `${model.id} ${model.displayName}`;
|
|
71
|
-
for (const { pattern, family } of FAMILY_PATTERNS) {
|
|
72
|
-
if (pattern.test(haystack)) return family;
|
|
73
|
-
}
|
|
74
|
-
return 'Other';
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Map ModelDefinition tier to CategoryFilter bucket.
|
|
79
|
-
* 'standard' and 'premium' both map to 'paid' for forward-compat.
|
|
80
|
-
*/
|
|
81
|
-
export function tierToCategoryFilter(tier: string | undefined): CategoryFilter {
|
|
82
|
-
if (tier === 'free') return 'free';
|
|
83
|
-
if (tier === 'subscription') return 'subscription';
|
|
84
|
-
return 'paid';
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** A generic selectable item for non-model modes. */
|
|
88
|
-
export interface PickerItem {
|
|
89
|
-
id: string;
|
|
90
|
-
label: string;
|
|
91
|
-
detail?: string;
|
|
92
|
-
/** If true, this item is a group header (not selectable). */
|
|
93
|
-
isGroupHeader?: boolean;
|
|
94
|
-
/** Quality tier badge for model items: S/A/B/C. */
|
|
95
|
-
qualityTier?: string;
|
|
96
|
-
/** Whether this model is pinned/favorited. */
|
|
97
|
-
isPinned?: boolean;
|
|
98
|
-
/** True when model tier is free. */
|
|
99
|
-
isFree?: boolean;
|
|
100
|
-
/** True when this provider item has a configured API key. */
|
|
101
|
-
isConfigured?: boolean;
|
|
102
|
-
/** How the provider is configured — shown as a badge in provider mode. */
|
|
103
|
-
configuredVia?: 'env' | 'secrets' | 'subscription' | 'anonymous';
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Provider IDs treated as "Popular" in the provider picker. */
|
|
107
|
-
export const POPULAR_PROVIDERS: ReadonlySet<string> = new Set([
|
|
108
|
-
'anthropic',
|
|
109
|
-
'google',
|
|
110
|
-
'groq',
|
|
111
|
-
'mistral',
|
|
112
|
-
'nvidia',
|
|
113
|
-
'ollama',
|
|
114
|
-
'openai',
|
|
115
|
-
'openrouter',
|
|
116
|
-
'synthetic',
|
|
117
|
-
]);
|
|
10
|
+
export { detectFamily, POPULAR_PROVIDERS, tierToCategoryFilter } from './model-picker-types.ts';
|
|
11
|
+
export type { BenchmarkSort, CapabilityFilter, CategoryFilter, GroupByMode, ModelFamily, ModelPickerTarget, PickerItem, PickerMode } from './model-picker-types.ts';
|
|
118
12
|
|
|
119
13
|
/**
|
|
120
14
|
* ModelPickerModal - Multi-step interactive picker for model, provider, and effort.
|
|
@@ -179,12 +73,18 @@ export class ModelPickerModal {
|
|
|
179
73
|
/** Current group-by mode. */
|
|
180
74
|
public groupBy: GroupByMode = 'provider';
|
|
181
75
|
|
|
76
|
+
private filteredModelsCache: FilteredModelsCache | null = null;
|
|
77
|
+
private filteredProvidersCache: FilteredProvidersCache | null = null;
|
|
78
|
+
private modelItemsCache: ModelItemsCache | null = null;
|
|
79
|
+
private providerItemsCache: ProviderItemsCache | null = null;
|
|
80
|
+
|
|
182
81
|
// ── Category filter cycling ───────────────────────────────────────────────
|
|
183
82
|
private static readonly CATEGORY_CYCLE: CategoryFilter[] = ['all', 'free', 'paid', 'subscription'];
|
|
184
83
|
/** Cycle to next pricing tier filter. */
|
|
185
84
|
cycleCategory(): void {
|
|
186
85
|
const idx = ModelPickerModal.CATEGORY_CYCLE.indexOf(this.categoryFilter);
|
|
187
86
|
this.categoryFilter = ModelPickerModal.CATEGORY_CYCLE[(idx + 1) % ModelPickerModal.CATEGORY_CYCLE.length];
|
|
87
|
+
this.clearFilteredCaches();
|
|
188
88
|
this._clampSelection();
|
|
189
89
|
}
|
|
190
90
|
|
|
@@ -194,6 +94,7 @@ export class ModelPickerModal {
|
|
|
194
94
|
cycleGroupBy(): void {
|
|
195
95
|
const idx = ModelPickerModal.GROUP_BY_CYCLE.indexOf(this.groupBy);
|
|
196
96
|
this.groupBy = ModelPickerModal.GROUP_BY_CYCLE[(idx + 1) % ModelPickerModal.GROUP_BY_CYCLE.length];
|
|
97
|
+
this.clearFilteredCaches();
|
|
197
98
|
this._clampSelection();
|
|
198
99
|
}
|
|
199
100
|
|
|
@@ -203,6 +104,7 @@ export class ModelPickerModal {
|
|
|
203
104
|
cycleBenchmarkSort(): void {
|
|
204
105
|
const idx = ModelPickerModal.BENCHMARK_SORT_CYCLE.indexOf(this.benchmarkSort);
|
|
205
106
|
this.benchmarkSort = ModelPickerModal.BENCHMARK_SORT_CYCLE[(idx + 1) % ModelPickerModal.BENCHMARK_SORT_CYCLE.length];
|
|
107
|
+
this.clearFilteredCaches();
|
|
206
108
|
this._clampSelection();
|
|
207
109
|
}
|
|
208
110
|
|
|
@@ -265,8 +167,9 @@ export class ModelPickerModal {
|
|
|
265
167
|
this.query = '';
|
|
266
168
|
this.categoryFilter = 'all';
|
|
267
169
|
this.capabilityFilter = 'none';
|
|
268
|
-
|
|
269
|
-
|
|
170
|
+
const filtered = this.getFilteredProviders();
|
|
171
|
+
const currentIndex = filtered.findIndex((provider) => provider === currentProvider);
|
|
172
|
+
this.selectedIndex = currentIndex >= 0 ? currentIndex : 0;
|
|
270
173
|
this.scrollOffset = 0;
|
|
271
174
|
}
|
|
272
175
|
|
|
@@ -314,6 +217,7 @@ export class ModelPickerModal {
|
|
|
314
217
|
this.query = '';
|
|
315
218
|
this.categoryFilter = 'all';
|
|
316
219
|
this.capabilityFilter = 'none';
|
|
220
|
+
this.clearCaches();
|
|
317
221
|
}
|
|
318
222
|
|
|
319
223
|
// ── Search helpers ─────────────────────────────────────────────────────────
|
|
@@ -321,6 +225,7 @@ export class ModelPickerModal {
|
|
|
321
225
|
/** Append a character to the search query and clamp selectedIndex. */
|
|
322
226
|
appendChar(ch: string): void {
|
|
323
227
|
this.query += ch;
|
|
228
|
+
this.clearFilteredCaches();
|
|
324
229
|
this._clampSelection();
|
|
325
230
|
}
|
|
326
231
|
|
|
@@ -328,6 +233,7 @@ export class ModelPickerModal {
|
|
|
328
233
|
deleteChar(): void {
|
|
329
234
|
if (this.query.length > 0) {
|
|
330
235
|
this.query = this.query.slice(0, -1);
|
|
236
|
+
this.clearFilteredCaches();
|
|
331
237
|
this._clampSelection();
|
|
332
238
|
}
|
|
333
239
|
}
|
|
@@ -335,6 +241,7 @@ export class ModelPickerModal {
|
|
|
335
241
|
/** Clear the search query and clamp selectedIndex. */
|
|
336
242
|
clearQuery(): void {
|
|
337
243
|
this.query = '';
|
|
244
|
+
this.clearFilteredCaches();
|
|
338
245
|
this._clampSelection();
|
|
339
246
|
}
|
|
340
247
|
|
|
@@ -353,18 +260,21 @@ export class ModelPickerModal {
|
|
|
353
260
|
/** Set category filter and clamp selectedIndex. */
|
|
354
261
|
setCategoryFilter(filter: CategoryFilter): void {
|
|
355
262
|
this.categoryFilter = filter;
|
|
263
|
+
this.clearFilteredCaches();
|
|
356
264
|
this._clampSelection();
|
|
357
265
|
}
|
|
358
266
|
|
|
359
267
|
/** Set capability filter and clamp selectedIndex. */
|
|
360
268
|
setCapabilityFilter(filter: CapabilityFilter): void {
|
|
361
269
|
this.capabilityFilter = filter;
|
|
270
|
+
this.clearFilteredCaches();
|
|
362
271
|
this._clampSelection();
|
|
363
272
|
}
|
|
364
273
|
|
|
365
274
|
/** Toggle the available-only filter. */
|
|
366
275
|
toggleAvailableOnly(): void {
|
|
367
276
|
this.availableOnly = !this.availableOnly;
|
|
277
|
+
this.clearFilteredCaches();
|
|
368
278
|
this._clampSelection();
|
|
369
279
|
}
|
|
370
280
|
|
|
@@ -395,15 +305,50 @@ export class ModelPickerModal {
|
|
|
395
305
|
|
|
396
306
|
/** Return providers matching the current query (case-insensitive substring), in grouped order. */
|
|
397
307
|
getFilteredProviders(): string[] {
|
|
308
|
+
const cached = this.filteredProvidersCache;
|
|
309
|
+
if (
|
|
310
|
+
cached !== null
|
|
311
|
+
&& cached.providersRef === this.providers
|
|
312
|
+
&& cached.query === this.query
|
|
313
|
+
) {
|
|
314
|
+
return cached.result;
|
|
315
|
+
}
|
|
316
|
+
|
|
398
317
|
const { popular, all } = this.getGroupedProviders();
|
|
399
318
|
const ordered = [...popular, ...all];
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
319
|
+
const result = this.query.trim().length === 0
|
|
320
|
+
? ordered
|
|
321
|
+
: ordered.filter(p => p.toLowerCase().includes(this.query.toLowerCase()));
|
|
322
|
+
this.filteredProvidersCache = {
|
|
323
|
+
providersRef: this.providers,
|
|
324
|
+
query: this.query,
|
|
325
|
+
result,
|
|
326
|
+
};
|
|
327
|
+
return result;
|
|
403
328
|
}
|
|
404
329
|
|
|
405
330
|
/** Return models matching all current filters, sorted per benchmarkSort. */
|
|
406
331
|
getFilteredModels(): ModelDefinition[] {
|
|
332
|
+
const configuredProvidersKey = setKey(this.configuredProviders);
|
|
333
|
+
const pinnedIdsKey = setKey(this.pinnedIds);
|
|
334
|
+
const recentIdsKey = orderedListKey(this.recentIds);
|
|
335
|
+
const cached = this.filteredModelsCache;
|
|
336
|
+
if (
|
|
337
|
+
cached !== null
|
|
338
|
+
&& cached.modelsRef === this.models
|
|
339
|
+
&& cached.configuredProvidersKey === configuredProvidersKey
|
|
340
|
+
&& cached.pinnedIdsKey === pinnedIdsKey
|
|
341
|
+
&& cached.recentIdsKey === recentIdsKey
|
|
342
|
+
&& cached.query === this.query
|
|
343
|
+
&& cached.categoryFilter === this.categoryFilter
|
|
344
|
+
&& cached.capabilityFilter === this.capabilityFilter
|
|
345
|
+
&& cached.availableOnly === this.availableOnly
|
|
346
|
+
&& cached.benchmarkSort === this.benchmarkSort
|
|
347
|
+
&& cached.groupBy === this.groupBy
|
|
348
|
+
) {
|
|
349
|
+
return cached.result;
|
|
350
|
+
}
|
|
351
|
+
|
|
407
352
|
let result = this.models;
|
|
408
353
|
|
|
409
354
|
// Available-only filter
|
|
@@ -519,6 +464,20 @@ export class ModelPickerModal {
|
|
|
519
464
|
result = [...recent, ...rest];
|
|
520
465
|
}
|
|
521
466
|
|
|
467
|
+
this.filteredModelsCache = {
|
|
468
|
+
modelsRef: this.models,
|
|
469
|
+
configuredProvidersKey,
|
|
470
|
+
pinnedIdsKey,
|
|
471
|
+
recentIdsKey,
|
|
472
|
+
query: this.query,
|
|
473
|
+
categoryFilter: this.categoryFilter,
|
|
474
|
+
capabilityFilter: this.capabilityFilter,
|
|
475
|
+
availableOnly: this.availableOnly,
|
|
476
|
+
benchmarkSort: this.benchmarkSort,
|
|
477
|
+
groupBy: this.groupBy,
|
|
478
|
+
result,
|
|
479
|
+
};
|
|
480
|
+
this.modelItemsCache = null;
|
|
522
481
|
return result;
|
|
523
482
|
}
|
|
524
483
|
|
|
@@ -528,6 +487,7 @@ export class ModelPickerModal {
|
|
|
528
487
|
*/
|
|
529
488
|
async loadRecentModels(n = 10): Promise<void> {
|
|
530
489
|
this.recentIds = await this.favoritesStore.getRecentModels(n);
|
|
490
|
+
this.clearFilteredCaches();
|
|
531
491
|
}
|
|
532
492
|
|
|
533
493
|
/**
|
|
@@ -573,6 +533,16 @@ export class ModelPickerModal {
|
|
|
573
533
|
getItems(): PickerItem[] {
|
|
574
534
|
if (this.mode === 'model') {
|
|
575
535
|
const filtered = this.getFilteredModels();
|
|
536
|
+
const pinnedIdsKey = setKey(this.pinnedIds);
|
|
537
|
+
const cached = this.modelItemsCache;
|
|
538
|
+
if (
|
|
539
|
+
cached !== null
|
|
540
|
+
&& cached.filteredModelsRef === filtered
|
|
541
|
+
&& cached.pinnedIdsKey === pinnedIdsKey
|
|
542
|
+
&& cached.groupBy === this.groupBy
|
|
543
|
+
) {
|
|
544
|
+
return cached.result;
|
|
545
|
+
}
|
|
576
546
|
|
|
577
547
|
// Separate pinned and unpinned
|
|
578
548
|
const pinned = filtered.filter(m => this.pinnedIds.has(m.id));
|
|
@@ -599,33 +569,45 @@ export class ModelPickerModal {
|
|
|
599
569
|
items.push(this._modelToItem(m, false));
|
|
600
570
|
}
|
|
601
571
|
|
|
572
|
+
this.modelItemsCache = {
|
|
573
|
+
filteredModelsRef: filtered,
|
|
574
|
+
pinnedIdsKey,
|
|
575
|
+
groupBy: this.groupBy,
|
|
576
|
+
result: items,
|
|
577
|
+
};
|
|
602
578
|
return items;
|
|
603
579
|
}
|
|
604
580
|
if (this.mode === 'provider') {
|
|
605
|
-
const
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
581
|
+
const filteredProviders = this.getFilteredProviders();
|
|
582
|
+
const configuredProvidersKey = setKey(this.configuredProviders);
|
|
583
|
+
const configuredViaKey = mapKey(this.configuredViaMap);
|
|
584
|
+
const cached = this.providerItemsCache;
|
|
585
|
+
if (
|
|
586
|
+
cached !== null
|
|
587
|
+
&& cached.filteredProvidersRef === filteredProviders
|
|
588
|
+
&& cached.configuredProvidersKey === configuredProvidersKey
|
|
589
|
+
&& cached.configuredViaKey === configuredViaKey
|
|
590
|
+
) {
|
|
591
|
+
return cached.result;
|
|
592
|
+
}
|
|
613
593
|
|
|
614
594
|
const providerItems: PickerItem[] = [];
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
providerItems.push({ id:
|
|
620
|
-
|
|
621
|
-
}
|
|
622
|
-
if (filteredAll.length > 0) {
|
|
623
|
-
providerItems.push({ id: '__header__all', label: 'All Providers', isGroupHeader: true });
|
|
624
|
-
for (const p of filteredAll) {
|
|
625
|
-
providerItems.push({ id: p, label: p, isConfigured: this.configuredProviders.has(p), configuredVia: this.configuredViaMap.get(p) });
|
|
595
|
+
let currentGroup: 'Popular' | 'All Providers' | null = null;
|
|
596
|
+
for (const p of filteredProviders) {
|
|
597
|
+
const group: 'Popular' | 'All Providers' = POPULAR_PROVIDERS.has(p.toLowerCase()) ? 'Popular' : 'All Providers';
|
|
598
|
+
if (group !== currentGroup) {
|
|
599
|
+
providerItems.push({ id: `__header__${group}`, label: group, isGroupHeader: true });
|
|
600
|
+
currentGroup = group;
|
|
626
601
|
}
|
|
602
|
+
providerItems.push({ id: p, label: p, isConfigured: this.configuredProviders.has(p), configuredVia: this.configuredViaMap.get(p) });
|
|
627
603
|
}
|
|
628
604
|
|
|
605
|
+
this.providerItemsCache = {
|
|
606
|
+
filteredProvidersRef: filteredProviders,
|
|
607
|
+
configuredProvidersKey,
|
|
608
|
+
configuredViaKey,
|
|
609
|
+
result: providerItems,
|
|
610
|
+
};
|
|
629
611
|
return providerItems;
|
|
630
612
|
}
|
|
631
613
|
// effort mode
|
|
@@ -742,4 +724,32 @@ export class ModelPickerModal {
|
|
|
742
724
|
getBenchmarkEntry(model: ModelDefinition) {
|
|
743
725
|
return this.benchmarkStore.getBenchmarks(model.id) ?? this.benchmarkStore.getBenchmarks(model.displayName);
|
|
744
726
|
}
|
|
727
|
+
|
|
728
|
+
private clearFilteredCaches(): void {
|
|
729
|
+
this.filteredModelsCache = null;
|
|
730
|
+
this.filteredProvidersCache = null;
|
|
731
|
+
this.modelItemsCache = null;
|
|
732
|
+
this.providerItemsCache = null;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private clearCaches(): void {
|
|
736
|
+
this.clearFilteredCaches();
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function setKey(values: ReadonlySet<string>): string {
|
|
741
|
+
if (values.size === 0) return '';
|
|
742
|
+
return [...values].sort().join('\u001f');
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function orderedListKey(values: readonly string[]): string {
|
|
746
|
+
return values.length === 0 ? '' : values.join('\u001f');
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function mapKey(values: ReadonlyMap<string, string | undefined>): string {
|
|
750
|
+
if (values.size === 0) return '';
|
|
751
|
+
return [...values.entries()]
|
|
752
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
753
|
+
.map(([key, value]) => `${key}\u001e${value ?? ''}`)
|
|
754
|
+
.join('\u001f');
|
|
745
755
|
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { OnboardingAcknowledgementTarget, OnboardingApplyOperation, OnboardingApplyRequest } from '../../runtime/onboarding/index.ts';
|
|
2
2
|
import { getServerSurfaceFeatureFlags } from '../../runtime/surface-feature-flags.ts';
|
|
3
|
+
import {
|
|
4
|
+
buildCloudflareApiTokenRef,
|
|
5
|
+
buildCloudflareOperationalTokenRef,
|
|
6
|
+
getCloudflareBatchMode,
|
|
7
|
+
getCloudflareComponentSelection,
|
|
8
|
+
getCloudflareSetupSource,
|
|
9
|
+
shouldShowCloudflareStep,
|
|
10
|
+
} from './onboarding-wizard-cloudflare.ts';
|
|
3
11
|
import {
|
|
4
12
|
EXTERNAL_SURFACE_SPECS,
|
|
5
13
|
getExternalSurfaceAutoStartDefaultValue,
|
|
@@ -111,6 +119,10 @@ export function buildOnboardingApplyRequest(controller: OnboardingWizardControll
|
|
|
111
119
|
|
|
112
120
|
setSecret('OPENAI_API_KEY', controller.getStringFieldValue('providers.openai-api-key', ''));
|
|
113
121
|
|
|
122
|
+
if (shouldShowCloudflareStep(controller)) {
|
|
123
|
+
addCloudflareOperations(controller, operations, setSecret);
|
|
124
|
+
}
|
|
125
|
+
|
|
114
126
|
const externalIntegrations = controller.isCapabilitySelected('external-integrations');
|
|
115
127
|
const enabledExternalSurfaceIds: string[] = [];
|
|
116
128
|
for (const surface of EXTERNAL_SURFACE_SPECS) {
|
|
@@ -165,6 +177,75 @@ export function buildOnboardingApplyRequest(controller: OnboardingWizardControll
|
|
|
165
177
|
};
|
|
166
178
|
}
|
|
167
179
|
|
|
180
|
+
function addCloudflareOperations(
|
|
181
|
+
controller: OnboardingWizardController,
|
|
182
|
+
operations: OnboardingApplyOperation[],
|
|
183
|
+
setSecret: (key: string, value: string) => void,
|
|
184
|
+
): void {
|
|
185
|
+
const setConfig = (
|
|
186
|
+
key: Extract<OnboardingApplyOperation, { kind: 'set-config' }>['key'],
|
|
187
|
+
value: unknown,
|
|
188
|
+
): void => {
|
|
189
|
+
operations.push({ kind: 'set-config', key, value });
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const config = controller.runtimeSnapshot?.config.cloudflare;
|
|
193
|
+
const enabledDefault = controller.isCapabilitySelected('cloudflare-batch') || config?.enabled === true;
|
|
194
|
+
const enabled = controller.getBooleanFieldValue('cloudflare.enabled', enabledDefault);
|
|
195
|
+
const components = getCloudflareComponentSelection(controller);
|
|
196
|
+
const batchMode = enabled ? getCloudflareBatchMode(controller) : 'off';
|
|
197
|
+
const setupSource = getCloudflareSetupSource(controller);
|
|
198
|
+
const existingApiTokenRef = config?.apiTokenRef ?? '';
|
|
199
|
+
let apiTokenRef = existingApiTokenRef;
|
|
200
|
+
|
|
201
|
+
if (!enabled) {
|
|
202
|
+
setConfig('cloudflare.enabled', false);
|
|
203
|
+
setConfig('batch.mode', 'off');
|
|
204
|
+
setConfig('batch.queueBackend', 'local');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (setupSource === 'operational-env') {
|
|
209
|
+
apiTokenRef = buildCloudflareApiTokenRef(
|
|
210
|
+
controller.getStringFieldValue('cloudflare.operational-env-name', 'CLOUDFLARE_API_TOKEN'),
|
|
211
|
+
);
|
|
212
|
+
} else if (setupSource === 'operational-token') {
|
|
213
|
+
const token = controller.getStringFieldValue('cloudflare.operational-token', '');
|
|
214
|
+
if (token.length > 0) {
|
|
215
|
+
setSecret('CLOUDFLARE_API_TOKEN', token);
|
|
216
|
+
apiTokenRef = buildCloudflareOperationalTokenRef();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
setConfig('cloudflare.enabled', true);
|
|
221
|
+
setConfig('cloudflare.freeTierMode', controller.getStringFieldValue('cloudflare.free-tier-mode', config?.freeTierMode === false ? 'no' : 'yes') === 'yes');
|
|
222
|
+
setConfig('cloudflare.accountId', controller.getStringFieldValue('cloudflare.account-id', config?.accountId ?? ''));
|
|
223
|
+
setConfig('cloudflare.apiTokenRef', apiTokenRef);
|
|
224
|
+
setConfig('cloudflare.zoneId', controller.getStringFieldValue('cloudflare.zone-id', config?.zoneId ?? ''));
|
|
225
|
+
setConfig('cloudflare.zoneName', controller.getStringFieldValue('cloudflare.zone-name', config?.zoneName ?? ''));
|
|
226
|
+
setConfig('cloudflare.workerName', controller.getStringFieldValue('cloudflare.worker-name', config?.workerName ?? 'goodvibes-batch-worker'));
|
|
227
|
+
setConfig('cloudflare.workerSubdomain', controller.getStringFieldValue('cloudflare.worker-subdomain', config?.workerSubdomain ?? ''));
|
|
228
|
+
setConfig('cloudflare.workerHostname', controller.getStringFieldValue('cloudflare.worker-hostname', config?.workerHostname ?? ''));
|
|
229
|
+
setConfig('cloudflare.workerBaseUrl', controller.getStringFieldValue('cloudflare.worker-base-url', config?.workerBaseUrl ?? ''));
|
|
230
|
+
setConfig('cloudflare.daemonBaseUrl', controller.getStringFieldValue('cloudflare.daemon-base-url', config?.daemonBaseUrl ?? ''));
|
|
231
|
+
setConfig('cloudflare.daemonHostname', controller.getStringFieldValue('cloudflare.daemon-hostname', config?.daemonHostname ?? ''));
|
|
232
|
+
setConfig('cloudflare.workerCron', controller.getStringFieldValue('cloudflare.worker-cron', config?.workerCron ?? '*/5 * * * *'));
|
|
233
|
+
setConfig('cloudflare.queueName', controller.getStringFieldValue('cloudflare.queue-name', config?.queueName ?? 'goodvibes-batch'));
|
|
234
|
+
setConfig('cloudflare.deadLetterQueueName', controller.getStringFieldValue('cloudflare.dead-letter-queue-name', config?.deadLetterQueueName ?? 'goodvibes-batch-dlq'));
|
|
235
|
+
setConfig('cloudflare.tunnelName', controller.getStringFieldValue('cloudflare.tunnel-name', config?.tunnelName ?? 'goodvibes-daemon'));
|
|
236
|
+
setConfig('cloudflare.tunnelId', controller.getStringFieldValue('cloudflare.tunnel-id', config?.tunnelId ?? ''));
|
|
237
|
+
setConfig('cloudflare.kvNamespaceName', controller.getStringFieldValue('cloudflare.kv-namespace-name', config?.kvNamespaceName ?? 'goodvibes-runtime'));
|
|
238
|
+
setConfig('cloudflare.kvNamespaceId', controller.getStringFieldValue('cloudflare.kv-namespace-id', config?.kvNamespaceId ?? ''));
|
|
239
|
+
setConfig('cloudflare.durableObjectNamespaceName', controller.getStringFieldValue('cloudflare.do-namespace-name', config?.durableObjectNamespaceName ?? 'GoodVibesCoordinator'));
|
|
240
|
+
setConfig('cloudflare.durableObjectNamespaceId', controller.getStringFieldValue('cloudflare.do-namespace-id', config?.durableObjectNamespaceId ?? ''));
|
|
241
|
+
setConfig('cloudflare.r2BucketName', controller.getStringFieldValue('cloudflare.r2-bucket-name', config?.r2BucketName ?? 'goodvibes-artifacts'));
|
|
242
|
+
setConfig('cloudflare.secretsStoreName', controller.getStringFieldValue('cloudflare.secrets-store-name', config?.secretsStoreName ?? 'goodvibes'));
|
|
243
|
+
setConfig('cloudflare.secretsStoreId', controller.getStringFieldValue('cloudflare.secrets-store-id', config?.secretsStoreId ?? ''));
|
|
244
|
+
setConfig('cloudflare.maxQueueOpsPerDay', controller.getNumberFieldValue('cloudflare.max-queue-ops-per-day', config?.maxQueueOpsPerDay ?? 10000, 1));
|
|
245
|
+
setConfig('batch.mode', batchMode);
|
|
246
|
+
setConfig('batch.queueBackend', batchMode !== 'off' && components.queues ? 'cloudflare' : 'local');
|
|
247
|
+
}
|
|
248
|
+
|
|
168
249
|
export function addNetworkOperations(
|
|
169
250
|
controller: OnboardingWizardController,
|
|
170
251
|
operations: OnboardingApplyOperation[],
|