Skip to content
Source
Sign inSign up

thread-list-environments.ts

129 lines4.7 KB

1import {
2 resolveEnvironmentMachineKind,
3 type EnvironmentId,
4 type EnvironmentMachineKind,
5 type ServerConfig,
6 type ServerProvider,
7} from "@t3tools/contracts";
8import { Atom } from "effect/reactivity";
9​
10export type ThreadListProvider = Pick<
11 ServerProvider,
12 "instanceId" | "driver" | "enabled" | "displayName" | "accentColor" | "iconUrl"
13>;
14​
15const capabilityKeys = ["threadAutoSettleOptOut"] as const;
16​
17function selectEnvironment(config: ServerConfig, isConnected: boolean) {
18 return {
19 isConnected,
20 providers: config.providers.map(
21 ({ instanceId, driver, enabled, displayName, accentColor, iconUrl }) => ({
22 instanceId,
23 driver,
24 enabled,
25 displayName,
26 accentColor,
27 iconUrl,
28 }),
29 ),
30 machineKind: resolveEnvironmentMachineKind(config),
31 capabilities: config.environment.capabilities,
32 snoozePresets: config.settings.snoozePresets,
33 };
35​
36type ListEnvironment = ReturnType<typeof selectEnvironment>;
37​
38function sameProviders(
39 left: ReadonlyArray<ThreadListProvider>,
40 right: ReadonlyArray<ThreadListProvider>,
41) {
42 return (
43 left.length === right.length &&
44 left.every((provider, index) => {
45 const other = right[index]!;
46 return (
47 provider.instanceId === other.instanceId &&
48 provider.driver === other.driver &&
49 provider.enabled === other.enabled &&
50 provider.displayName === other.displayName &&
51 provider.accentColor === other.accentColor &&
52 provider.iconUrl === other.iconUrl
53 );
54 })
55 );
57​
58function collectEnvironments(environments: ReadonlyMap<EnvironmentId, ListEnvironment>) {
59 const providersByEnvironmentId = new Map<EnvironmentId, ReadonlyArray<ThreadListProvider>>();
60 const machineByEnvironmentId = new Map<EnvironmentId, EnvironmentMachineKind>();
61 // Every protocol-2 server supports these thread actions; they only wait for its config.
62 const loadedEnvironmentIds = new Set<EnvironmentId>();
63 const autoSettleOptOutEnvironmentIds = new Set<EnvironmentId>();
64 const reorderableEnvironmentIds = new Set<EnvironmentId>();
65 const snoozePresetsByEnvironmentId = new Map<EnvironmentId, ListEnvironment["snoozePresets"]>();
66 for (const [
67 id,
68 { providers, machineKind, capabilities, isConnected, snoozePresets },
69 ] of environments) {
70 providersByEnvironmentId.set(id, providers);
71 snoozePresetsByEnvironmentId.set(id, snoozePresets);
72 machineByEnvironmentId.set(id, machineKind);
73 loadedEnvironmentIds.add(id);
74 if (capabilities.threadAutoSettleOptOut === true) autoSettleOptOutEnvironmentIds.add(id);
75 if (isConnected) reorderableEnvironmentIds.add(id);
76 }
77 return {
78 providersByEnvironmentId,
79 machineByEnvironmentId,
80 settlementEnvironmentIds: loadedEnvironmentIds,
81 snoozeEnvironmentIds: loadedEnvironmentIds,
82 pinningEnvironmentIds: loadedEnvironmentIds,
83 autoSettleOptOutEnvironmentIds,
84 pinReorderEnvironmentIds: reorderableEnvironmentIds,
85 activeReorderEnvironmentIds: reorderableEnvironmentIds,
86 titleRegenerationEnvironmentIds: loadedEnvironmentIds,
87 snoozePresetsByEnvironmentId,
88 };
90​
91export function createThreadListEnvironmentsAtom(
92 configsAtom: Atom.Atom<ReadonlyMap<EnvironmentId, ServerConfig>>,
93 connectedEnvironmentIdsAtom: Atom.Atom<ReadonlyArray<EnvironmentId>>,
94) {
95 let previous = new Map<EnvironmentId, ListEnvironment>();
96 let result = collectEnvironments(previous);
97 return Atom.make((get) => {
98 const configs = get(configsAtom);
99 const connectedEnvironmentIds = new Set(get(connectedEnvironmentIdsAtom));
100 const next = new Map<EnvironmentId, ListEnvironment>();
101 let changed = configs.size !== previous.size;
102 for (const [id, config] of configs) {
103 const selected = selectEnvironment(config, connectedEnvironmentIds.has(id));
104 const prior = previous.get(id);
105 if (prior && sameProviders(prior.providers, selected.providers)) {
106 selected.providers = prior.providers;
107 }
108 if (prior && JSON.stringify(prior.snoozePresets) === JSON.stringify(selected.snoozePresets)) {
109 selected.snoozePresets = prior.snoozePresets;
110 }
111 const unchanged =
112 prior &&
113 prior.providers === selected.providers &&
114 prior.machineKind === selected.machineKind &&
115 prior.isConnected === selected.isConnected &&
116 prior.snoozePresets === selected.snoozePresets &&
117 capabilityKeys.every(
118 (key) => (prior.capabilities[key] === true) === (selected.capabilities[key] === true),
119 );
120 next.set(id, unchanged ? prior : selected);
121 if (!unchanged) changed = true;
122 }
123 if (changed) {
124 previous = next;
125 result = collectEnvironments(next);
126 }
127 return result;
128 }).pipe(Atom.withLabel("thread-list-environments"));