| 1 | import type { AppBindings } from "./bridge"; |
| 2 | import type { ProviderPresetView, SettingsView } from "./types"; |
| 3 | import type { ModelSettingsChange, ModelSettingsResult } from "./modelSettingsTypes"; |
| 4 | |
| 5 | export interface ModelSettingsBindings { |
| 6 | Settings(): Promise<SettingsView>; |
| 7 | ApplyModelSettings(change: ModelSettingsChange): Promise<ModelSettingsResult>; |
| 8 | GetModelSettingsRequest(requestId: string): Promise<ModelSettingsResult>; |
| 9 | GetModelSettingsApplication(): Promise<ModelSettingsResult>; |
| 10 | RetryModelSettingsApplication(tabID: string): Promise<ModelSettingsResult>; |
| 11 | } |
| 12 | |
| 13 | export function makeMockModelSettingsBindings( |
| 14 | settings: SettingsView, |
| 15 | loadCatalog: () => Promise<void> = async () => {}, |
| 16 | presets: () => ProviderPresetView[] = () => [], |
| 17 | ): ModelSettingsBindings { |
| 18 | const receipts = new Map<string, ModelSettingsResult>(); |
| 19 | const remember = (result: ModelSettingsResult) => { |
| 20 | receipts.set(result.requestId, result); |
| 21 | if (receipts.size > 128) receipts.delete(receipts.keys().next().value!); |
| 22 | return result; |
| 23 | }; |
| 24 | return { |
| 25 | async Settings() { |
| 26 | await loadCatalog(); |
| 27 | for (const preset of presets()) { |
| 28 | const existing = settings.providerPresets.find(p => p.id === preset.id); |
| 29 | if (existing) existing.catalog = preset.catalog; |
| 30 | else settings.providerPresets.push(preset); |
| 31 | } |
| 32 | return JSON.parse(JSON.stringify(settings)) as SettingsView; |
| 33 | }, |
| 34 | async GetModelSettingsApplication(this: AppBindings): Promise<ModelSettingsResult> { |
| 35 | return {requestId: "", persisted: true, revision: settings.modelSettingsFingerprint!, application: "not_required", targets: [], issues: [], appliedCatalogs: []}; |
| 36 | }, |
| 37 | async RetryModelSettingsApplication(this: AppBindings, _tabID: string): Promise<ModelSettingsResult> { |
| 38 | return this.GetModelSettingsApplication(); |
| 39 | }, |
| 40 | async ApplyModelSettings(this: AppBindings, change: ModelSettingsChange): Promise<ModelSettingsResult> { |
| 41 | const receipt = receipts.get(change.requestId); |
| 42 | if (receipt) return receipt; |
| 43 | const result = await this.GetModelSettingsApplication(); |
| 44 | result.requestId = change.requestId; |
| 45 | if (change.expectedFingerprint !== settings.modelSettingsFingerprint) { |
| 46 | return remember({...result, persisted: false, issues: [{code: "conflict", message: "Model settings changed; reload before saving."}]}); |
| 47 | } |
| 48 | try { |
| 49 | switch (change.kind) { |
| 50 | case "preference": |
| 51 | switch (change.field) { |
| 52 | case "default": await this.SetDefaultModel(change.ref); break; |
| 53 | case "planner": await this.SetPlannerModel(change.ref); break; |
| 54 | case "vision": await this.SetVisionModel(change.ref); break; |
| 55 | case "search": await this.SetWebSearchModel(change.ref); break; |
| 56 | case "subagent": await this.SetSubagentModel(change.ref); break; |
| 57 | case "subagent_effort": await this.SetSubagentEffort(change.ref); break; |
| 58 | case "depth": await this.SetMaxSubagentDepth(change.number); break; |
| 59 | case "concurrency": await this.SetMaxSubagentConcurrency(change.number); break; |
| 60 | case "writers": await this.SetMaxParallelWriters(change.number); break; |
| 61 | case "profile_model": await this.SetSubagentProfileModel(change.name, change.ref); break; |
| 62 | case "profile_effort": await this.SetSubagentProfileEffort(change.name, change.ref); break; |
| 63 | } |
| 64 | break; |
| 65 | case "provider_save": |
| 66 | if (change.key !== undefined) await this.SaveProviderWithKey(change.provider, change.key); |
| 67 | else await this.SaveProvider(change.provider); |
| 68 | break; |
| 69 | case "credential": for (const name of change.names ?? [change.name]) await this.SetConnectionKey(name, change.key); break; |
| 70 | case "web_search_capability": await this.SetProviderWebSearch(change.names, change.enabled); break; |
| 71 | case "connection_add": await this.AddProviderConnectionWithOptions(change.presetId ?? "", change.name ?? "", change.key, change.baseURL ?? "", change.protocol ?? ""); break; |
| 72 | case "official_add": await this.AddOfficialProviderAccess(change.name, change.key); break; |
| 73 | case "preset_add": await this.AddProviderPresetAccess(change.presetId, change.key); break; |
| 74 | case "preset_reset": await this.ResetProviderPresetAccess(change.presetId); break; |
| 75 | case "protocol_upgrade": await this.UpgradeDeepSeekProviderAccess(change.name); break; |
| 76 | case "catalogs": result.appliedCatalogs = await this.SaveProviderModelCatalogs(change.catalogs); break; |
| 77 | case "provider_remove": for (const name of change.names) await this.DeleteProvider(name); break; |
| 78 | case "access_remove": await this.RemoveProviderAccesses(change.names); break; |
| 79 | case "rename": await this.RenameProviderConnections(change.names, change.ref); break; |
| 80 | } |
| 81 | settings.modelSettingsFingerprint = `mock-model-settings-${crypto.randomUUID()}`; |
| 82 | return remember({...result, revision: settings.modelSettingsFingerprint}); |
| 83 | } catch { |
| 84 | return remember({...result, persisted: false, issues: [{code: "validation_failed", message: "Model settings could not be saved. Check the edited fields."}]}); |
| 85 | } |
| 86 | }, |
| 87 | async GetModelSettingsRequest(this: AppBindings, requestId: string) { |
| 88 | const receipt = receipts.get(requestId); |
| 89 | if (receipt) return receipt; |
| 90 | return {...await this.GetModelSettingsApplication(), requestId, persisted: false, issues: [{code: "unknown_result", message: "The save result could not be confirmed. Review the current settings before saving again."}]}; |
| 91 | }, |
| 92 | }; |
| 93 | } |
| 94 |