@fyeeme/pi-thinking-ui 1.0.0
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 +11 -0
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/index.ts +360 -0
- package/internal-patch.ts +441 -0
- package/package.json +62 -0
- package/parse.ts +1145 -0
- package/persistence.ts +86 -0
- package/render.ts +414 -0
- package/state.ts +417 -0
- package/types.ts +63 -0
package/state.ts
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import type { ActiveThinkingState, ThinkingUIMode } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
const STATE_KEY = Symbol.for("pi-extensions.thinking-ui.state");
|
|
4
|
+
const DEFAULT_SCOPE_KEY = "__default__";
|
|
5
|
+
const LABEL_REFRESH_SUFFIX = "\u2060";
|
|
6
|
+
|
|
7
|
+
type PatchCleanup = () => void | Promise<void>;
|
|
8
|
+
type PatchInstallPromise = Promise<PatchCleanup>;
|
|
9
|
+
type PatchRelease = () => Promise<void>;
|
|
10
|
+
|
|
11
|
+
interface ThinkingActiveEntry {
|
|
12
|
+
contentIndex?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ThinkingUIGlobalState {
|
|
16
|
+
currentScopeKey: string;
|
|
17
|
+
modeByScopeKey: Record<string, ThinkingUIMode>;
|
|
18
|
+
activeByScopeKey: Record<string, Record<string, ThinkingActiveEntry>>;
|
|
19
|
+
lastActiveByScopeKey: Record<string, ActiveThinkingState>;
|
|
20
|
+
refreshToggleByScope: Record<string, boolean>;
|
|
21
|
+
messageScopeByObject: WeakMap<object, string>;
|
|
22
|
+
messageObjectsByScope: Record<string, Set<object>>;
|
|
23
|
+
messageScopeByTimestamp: Record<string, string>;
|
|
24
|
+
patchReleasesByScope: Record<string, PatchRelease[]>;
|
|
25
|
+
patchRefCount: number;
|
|
26
|
+
patchCleanup?: PatchCleanup | undefined;
|
|
27
|
+
patchInstallPromise?: PatchInstallPromise | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface LegacyThinkingUIGlobalState {
|
|
31
|
+
mode?: unknown;
|
|
32
|
+
active?: unknown;
|
|
33
|
+
currentScopeKey?: unknown;
|
|
34
|
+
modeByScopeKey?: unknown;
|
|
35
|
+
activeByScopeKey?: unknown;
|
|
36
|
+
lastActiveByScopeKey?: unknown;
|
|
37
|
+
refreshToggleByScope?: unknown;
|
|
38
|
+
messageScopeByObject?: unknown;
|
|
39
|
+
messageObjectsByScope?: unknown;
|
|
40
|
+
messageScopeByTimestamp?: unknown;
|
|
41
|
+
patchReleasesByScope?: unknown;
|
|
42
|
+
patchRefCount?: unknown;
|
|
43
|
+
patchCleanup?: unknown;
|
|
44
|
+
patchInstallPromise?: unknown;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isRecord(value: unknown): value is Record<PropertyKey, unknown> {
|
|
48
|
+
return typeof value === "object" && value !== null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeThinkingScopeKey(scopeKey?: string): string {
|
|
52
|
+
const trimmed = scopeKey?.trim();
|
|
53
|
+
return trimmed && trimmed.length > 0 ? trimmed : DEFAULT_SCOPE_KEY;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeThinkingMode(mode: unknown): ThinkingUIMode {
|
|
57
|
+
return mode === "collapsed" || mode === "summary" || mode === "expanded" ? mode : "summary";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeActiveThinkingState(value: unknown): ActiveThinkingState {
|
|
61
|
+
if (!isRecord(value) || value.active !== true) {
|
|
62
|
+
return { active: false };
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
active: true,
|
|
66
|
+
messageTimestamp: typeof value.messageTimestamp === "number" ? value.messageTimestamp : undefined,
|
|
67
|
+
contentIndex: typeof value.contentIndex === "number" ? value.contentIndex : undefined,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeModeByScopeKey(value: unknown, currentScopeKey: string, legacyMode: unknown): Record<string, ThinkingUIMode> {
|
|
72
|
+
const modeByScopeKey: Record<string, ThinkingUIMode> = {};
|
|
73
|
+
if (isRecord(value)) {
|
|
74
|
+
for (const [scopeKey, scopeMode] of Object.entries(value)) {
|
|
75
|
+
modeByScopeKey[normalizeThinkingScopeKey(scopeKey)] = normalizeThinkingMode(scopeMode);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
modeByScopeKey[currentScopeKey] ??= normalizeThinkingMode(legacyMode);
|
|
79
|
+
return modeByScopeKey;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function normalizeActiveByScopeKey(value: unknown): Record<string, Record<string, ThinkingActiveEntry>> {
|
|
83
|
+
const activeByScopeKey: Record<string, Record<string, ThinkingActiveEntry>> = {};
|
|
84
|
+
if (!isRecord(value)) return activeByScopeKey;
|
|
85
|
+
for (const [scopeKey, entries] of Object.entries(value)) {
|
|
86
|
+
const normalizedScopeKey = normalizeThinkingScopeKey(scopeKey);
|
|
87
|
+
activeByScopeKey[normalizedScopeKey] = {};
|
|
88
|
+
if (!isRecord(entries)) continue;
|
|
89
|
+
for (const [messageTimestamp, entry] of Object.entries(entries)) {
|
|
90
|
+
if (!isRecord(entry)) continue;
|
|
91
|
+
activeByScopeKey[normalizedScopeKey]![messageTimestamp] = {
|
|
92
|
+
contentIndex: typeof entry.contentIndex === "number" ? entry.contentIndex : undefined,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return activeByScopeKey;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function normalizeLastActiveByScopeKey(value: unknown): Record<string, ActiveThinkingState> {
|
|
100
|
+
const lastActiveByScopeKey: Record<string, ActiveThinkingState> = {};
|
|
101
|
+
if (!isRecord(value)) return lastActiveByScopeKey;
|
|
102
|
+
for (const [scopeKey, entry] of Object.entries(value)) {
|
|
103
|
+
lastActiveByScopeKey[normalizeThinkingScopeKey(scopeKey)] = normalizeActiveThinkingState(entry);
|
|
104
|
+
}
|
|
105
|
+
return lastActiveByScopeKey;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function ensureGlobalStateShape(state: ThinkingUIGlobalState & LegacyThinkingUIGlobalState): ThinkingUIGlobalState {
|
|
109
|
+
const currentScopeKey = normalizeThinkingScopeKey(typeof state.currentScopeKey === "string" ? state.currentScopeKey : undefined);
|
|
110
|
+
const modeByScopeKey = normalizeModeByScopeKey(state.modeByScopeKey, currentScopeKey, state.mode);
|
|
111
|
+
const activeByScopeKey = normalizeActiveByScopeKey(state.activeByScopeKey);
|
|
112
|
+
const lastActiveByScopeKey = normalizeLastActiveByScopeKey(state.lastActiveByScopeKey);
|
|
113
|
+
const legacyActive = normalizeActiveThinkingState(state.active);
|
|
114
|
+
const refreshToggleByScope: Record<string, boolean> = isRecord(state.refreshToggleByScope)
|
|
115
|
+
? Object.fromEntries(Object.entries(state.refreshToggleByScope).map(([scopeKey, enabled]) => [normalizeThinkingScopeKey(scopeKey), enabled === true]))
|
|
116
|
+
: {};
|
|
117
|
+
const messageScopeByObject = state.messageScopeByObject instanceof WeakMap ? state.messageScopeByObject as WeakMap<object, string> : new WeakMap<object, string>();
|
|
118
|
+
const messageObjectsByScope: Record<string, Set<object>> = isRecord(state.messageObjectsByScope)
|
|
119
|
+
? Object.fromEntries(Object.entries(state.messageObjectsByScope).map(([scopeKey, messages]) => [normalizeThinkingScopeKey(scopeKey), messages instanceof Set ? messages as Set<object> : new Set<object>()]))
|
|
120
|
+
: {};
|
|
121
|
+
const messageScopeByTimestamp: Record<string, string> = isRecord(state.messageScopeByTimestamp)
|
|
122
|
+
? Object.fromEntries(Object.entries(state.messageScopeByTimestamp).filter((entry): entry is [string, string] => typeof entry[1] === "string").map(([messageTimestamp, scopeKey]) => [messageTimestamp, normalizeThinkingScopeKey(scopeKey)]))
|
|
123
|
+
: {};
|
|
124
|
+
const legacyPatchReleasesByScope: Record<string, PatchRelease[]> = isRecord(state.patchReleasesByScope)
|
|
125
|
+
? Object.fromEntries(Object.entries(state.patchReleasesByScope).map(([scopeKey, releases]) => [normalizeThinkingScopeKey(scopeKey), Array.isArray(releases) ? releases as PatchRelease[] : []]))
|
|
126
|
+
: {};
|
|
127
|
+
const patchReleasesByScope: Record<string, PatchRelease[]> = { ...legacyPatchReleasesByScope };
|
|
128
|
+
|
|
129
|
+
for (const scopeKey of Object.keys(modeByScopeKey)) {
|
|
130
|
+
activeByScopeKey[scopeKey] ??= {};
|
|
131
|
+
lastActiveByScopeKey[scopeKey] ??= { active: false };
|
|
132
|
+
refreshToggleByScope[scopeKey] ??= false;
|
|
133
|
+
messageObjectsByScope[scopeKey] ??= new Set<object>();
|
|
134
|
+
patchReleasesByScope[scopeKey] ??= [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (legacyActive.active) {
|
|
138
|
+
lastActiveByScopeKey[currentScopeKey] = legacyActive;
|
|
139
|
+
if (legacyActive.messageTimestamp !== undefined) {
|
|
140
|
+
activeByScopeKey[currentScopeKey]![String(legacyActive.messageTimestamp)] = {
|
|
141
|
+
contentIndex: legacyActive.contentIndex,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
state.currentScopeKey = currentScopeKey;
|
|
147
|
+
state.modeByScopeKey = modeByScopeKey;
|
|
148
|
+
state.activeByScopeKey = activeByScopeKey;
|
|
149
|
+
state.lastActiveByScopeKey = lastActiveByScopeKey;
|
|
150
|
+
state.refreshToggleByScope = refreshToggleByScope;
|
|
151
|
+
state.messageScopeByObject = messageScopeByObject;
|
|
152
|
+
state.messageObjectsByScope = messageObjectsByScope;
|
|
153
|
+
state.messageScopeByTimestamp = messageScopeByTimestamp;
|
|
154
|
+
state.patchReleasesByScope = patchReleasesByScope;
|
|
155
|
+
state.patchRefCount = typeof state.patchRefCount === "number" && Number.isFinite(state.patchRefCount)
|
|
156
|
+
? state.patchRefCount
|
|
157
|
+
: 0;
|
|
158
|
+
state.patchCleanup = typeof state.patchCleanup === "function" ? state.patchCleanup as PatchCleanup : undefined;
|
|
159
|
+
state.patchInstallPromise = state.patchInstallPromise instanceof Promise ? state.patchInstallPromise as PatchInstallPromise : undefined;
|
|
160
|
+
return state;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const globalState = (() => {
|
|
164
|
+
const existing = (globalThis as Record<PropertyKey, unknown>)[STATE_KEY];
|
|
165
|
+
if (isRecord(existing)) {
|
|
166
|
+
return ensureGlobalStateShape(existing as unknown as ThinkingUIGlobalState & LegacyThinkingUIGlobalState);
|
|
167
|
+
}
|
|
168
|
+
const created: ThinkingUIGlobalState = {
|
|
169
|
+
currentScopeKey: DEFAULT_SCOPE_KEY,
|
|
170
|
+
modeByScopeKey: { [DEFAULT_SCOPE_KEY]: "summary" },
|
|
171
|
+
activeByScopeKey: { [DEFAULT_SCOPE_KEY]: {} },
|
|
172
|
+
lastActiveByScopeKey: { [DEFAULT_SCOPE_KEY]: { active: false } },
|
|
173
|
+
refreshToggleByScope: {},
|
|
174
|
+
messageScopeByObject: new WeakMap<object, string>(),
|
|
175
|
+
messageObjectsByScope: { [DEFAULT_SCOPE_KEY]: new Set<object>() },
|
|
176
|
+
messageScopeByTimestamp: {},
|
|
177
|
+
patchReleasesByScope: {},
|
|
178
|
+
patchRefCount: 0,
|
|
179
|
+
};
|
|
180
|
+
(globalThis as Record<PropertyKey, unknown>)[STATE_KEY] = created;
|
|
181
|
+
return created;
|
|
182
|
+
})();
|
|
183
|
+
|
|
184
|
+
function ensureScopeState(scopeKey: string): void {
|
|
185
|
+
if (!(scopeKey in globalState.modeByScopeKey)) {
|
|
186
|
+
globalState.modeByScopeKey[scopeKey] = "summary";
|
|
187
|
+
}
|
|
188
|
+
if (!(scopeKey in globalState.activeByScopeKey)) {
|
|
189
|
+
globalState.activeByScopeKey[scopeKey] = {};
|
|
190
|
+
}
|
|
191
|
+
if (!(scopeKey in globalState.lastActiveByScopeKey)) {
|
|
192
|
+
globalState.lastActiveByScopeKey[scopeKey] = { active: false };
|
|
193
|
+
}
|
|
194
|
+
if (!(scopeKey in globalState.refreshToggleByScope)) {
|
|
195
|
+
globalState.refreshToggleByScope[scopeKey] = false;
|
|
196
|
+
}
|
|
197
|
+
if (!(scopeKey in globalState.messageObjectsByScope)) {
|
|
198
|
+
globalState.messageObjectsByScope[scopeKey] = new Set<object>();
|
|
199
|
+
}
|
|
200
|
+
if (!(scopeKey in globalState.patchReleasesByScope)) {
|
|
201
|
+
globalState.patchReleasesByScope[scopeKey] = [];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function getCurrentThinkingScopeKey(): string {
|
|
206
|
+
return globalState.currentScopeKey;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function setCurrentThinkingScopeKey(scopeKey: string): void {
|
|
210
|
+
const normalizedScopeKey = normalizeThinkingScopeKey(scopeKey);
|
|
211
|
+
ensureScopeState(normalizedScopeKey);
|
|
212
|
+
globalState.currentScopeKey = normalizedScopeKey;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function getThinkingUIMode(scopeKey?: string): ThinkingUIMode {
|
|
216
|
+
const normalizedScopeKey = normalizeThinkingScopeKey(scopeKey ?? globalState.currentScopeKey);
|
|
217
|
+
ensureScopeState(normalizedScopeKey);
|
|
218
|
+
return globalState.modeByScopeKey[normalizedScopeKey] ?? "summary";
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function setThinkingUIMode(mode: ThinkingUIMode, scopeKey?: string): void {
|
|
222
|
+
const normalizedScopeKey = normalizeThinkingScopeKey(scopeKey ?? globalState.currentScopeKey);
|
|
223
|
+
ensureScopeState(normalizedScopeKey);
|
|
224
|
+
globalState.modeByScopeKey[normalizedScopeKey] = mode;
|
|
225
|
+
globalState.currentScopeKey = normalizedScopeKey;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function getActiveThinkingState(messageTimestamp?: number, scopeKey?: string): ActiveThinkingState {
|
|
229
|
+
const normalizedScopeKey = normalizeThinkingScopeKey(scopeKey ?? globalState.currentScopeKey);
|
|
230
|
+
ensureScopeState(normalizedScopeKey);
|
|
231
|
+
|
|
232
|
+
if (messageTimestamp !== undefined) {
|
|
233
|
+
const entry = globalState.activeByScopeKey[normalizedScopeKey]![String(messageTimestamp)];
|
|
234
|
+
if (!entry) return { active: false };
|
|
235
|
+
return { active: true, messageTimestamp, contentIndex: entry.contentIndex };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return { ...globalState.lastActiveByScopeKey[normalizedScopeKey]! };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function setActiveThinkingState(state: ActiveThinkingState, scopeKey?: string): void {
|
|
242
|
+
const normalizedScopeKey = normalizeThinkingScopeKey(scopeKey ?? globalState.currentScopeKey);
|
|
243
|
+
ensureScopeState(normalizedScopeKey);
|
|
244
|
+
globalState.lastActiveByScopeKey[normalizedScopeKey] = { ...state };
|
|
245
|
+
|
|
246
|
+
if (!state.active || state.messageTimestamp === undefined) {
|
|
247
|
+
if (state.messageTimestamp !== undefined) {
|
|
248
|
+
delete globalState.activeByScopeKey[normalizedScopeKey]![String(state.messageTimestamp)];
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
globalState.activeByScopeKey[normalizedScopeKey]![String(state.messageTimestamp)] = {
|
|
254
|
+
contentIndex: state.contentIndex,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function clearActiveThinkingState(messageTimestamp?: number, scopeKey?: string): void {
|
|
259
|
+
if (messageTimestamp !== undefined) {
|
|
260
|
+
const normalizedScopeKey = normalizeThinkingScopeKey(scopeKey ?? globalState.currentScopeKey);
|
|
261
|
+
ensureScopeState(normalizedScopeKey);
|
|
262
|
+
delete globalState.activeByScopeKey[normalizedScopeKey]![String(messageTimestamp)];
|
|
263
|
+
if (globalState.lastActiveByScopeKey[normalizedScopeKey]!.messageTimestamp === messageTimestamp) {
|
|
264
|
+
globalState.lastActiveByScopeKey[normalizedScopeKey] = { active: false };
|
|
265
|
+
}
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (scopeKey !== undefined) {
|
|
270
|
+
const normalizedScopeKey = normalizeThinkingScopeKey(scopeKey);
|
|
271
|
+
ensureScopeState(normalizedScopeKey);
|
|
272
|
+
globalState.activeByScopeKey[normalizedScopeKey] = {};
|
|
273
|
+
globalState.lastActiveByScopeKey[normalizedScopeKey] = { active: false };
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
for (const existingScopeKey of Object.keys(globalState.modeByScopeKey)) {
|
|
278
|
+
ensureScopeState(existingScopeKey);
|
|
279
|
+
globalState.activeByScopeKey[existingScopeKey] = {};
|
|
280
|
+
globalState.lastActiveByScopeKey[existingScopeKey] = { active: false };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function nextThinkingRefreshLabel(label: string, scopeKey?: string): string {
|
|
285
|
+
const normalizedScopeKey = normalizeThinkingScopeKey(scopeKey ?? globalState.currentScopeKey);
|
|
286
|
+
ensureScopeState(normalizedScopeKey);
|
|
287
|
+
const useInvisibleSuffix = globalState.refreshToggleByScope[normalizedScopeKey] ?? false;
|
|
288
|
+
globalState.refreshToggleByScope[normalizedScopeKey] = !useInvisibleSuffix;
|
|
289
|
+
return useInvisibleSuffix ? `${label}${LABEL_REFRESH_SUFFIX}` : label;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function registerThinkingPatchRelease(scopeKey: string, release: PatchRelease): void {
|
|
293
|
+
const normalizedScopeKey = normalizeThinkingScopeKey(scopeKey);
|
|
294
|
+
ensureScopeState(normalizedScopeKey);
|
|
295
|
+
globalState.patchReleasesByScope[normalizedScopeKey]!.push(release);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function takeThinkingPatchRelease(scopeKey: string): PatchRelease | undefined {
|
|
299
|
+
const normalizedScopeKey = normalizeThinkingScopeKey(scopeKey);
|
|
300
|
+
ensureScopeState(normalizedScopeKey);
|
|
301
|
+
return globalState.patchReleasesByScope[normalizedScopeKey]!.pop();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function recordThinkingMessageScope(message: object, scopeKey?: string): void {
|
|
305
|
+
const requestedScopeKey = normalizeThinkingScopeKey(scopeKey ?? globalState.currentScopeKey);
|
|
306
|
+
ensureScopeState(requestedScopeKey);
|
|
307
|
+
|
|
308
|
+
const existingScopeKey = globalState.messageScopeByObject.get(message);
|
|
309
|
+
const normalizedScopeKey = existingScopeKey ?? requestedScopeKey;
|
|
310
|
+
ensureScopeState(normalizedScopeKey);
|
|
311
|
+
if (!existingScopeKey) {
|
|
312
|
+
globalState.messageScopeByObject.set(message, normalizedScopeKey);
|
|
313
|
+
}
|
|
314
|
+
globalState.messageObjectsByScope[normalizedScopeKey]!.add(message);
|
|
315
|
+
|
|
316
|
+
const timestamp = typeof (message as { timestamp?: unknown }).timestamp === "number"
|
|
317
|
+
? (message as { timestamp: number }).timestamp
|
|
318
|
+
: undefined;
|
|
319
|
+
if (timestamp !== undefined) {
|
|
320
|
+
globalState.messageScopeByTimestamp[String(timestamp)] = normalizedScopeKey;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function resolveThinkingMessageScope(message: object, fallbackScopeKey?: string): string {
|
|
325
|
+
const objectScopeKey = globalState.messageScopeByObject.get(message);
|
|
326
|
+
if (objectScopeKey) {
|
|
327
|
+
ensureScopeState(objectScopeKey);
|
|
328
|
+
return objectScopeKey;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const timestamp = typeof (message as { timestamp?: unknown }).timestamp === "number"
|
|
332
|
+
? (message as { timestamp: number }).timestamp
|
|
333
|
+
: undefined;
|
|
334
|
+
if (timestamp !== undefined) {
|
|
335
|
+
const timestampScopeKey = globalState.messageScopeByTimestamp[String(timestamp)];
|
|
336
|
+
if (timestampScopeKey) {
|
|
337
|
+
ensureScopeState(timestampScopeKey);
|
|
338
|
+
return timestampScopeKey;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const normalizedScopeKey = normalizeThinkingScopeKey(fallbackScopeKey ?? globalState.currentScopeKey);
|
|
343
|
+
ensureScopeState(normalizedScopeKey);
|
|
344
|
+
return normalizedScopeKey;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function clearThinkingMessageOwnership(scopeKey?: string): void {
|
|
348
|
+
if (scopeKey !== undefined) {
|
|
349
|
+
const normalizedScopeKey = normalizeThinkingScopeKey(scopeKey);
|
|
350
|
+
ensureScopeState(normalizedScopeKey);
|
|
351
|
+
const ownedMessages = globalState.messageObjectsByScope[normalizedScopeKey] ?? new Set<object>();
|
|
352
|
+
for (const message of ownedMessages) {
|
|
353
|
+
globalState.messageScopeByObject.delete(message);
|
|
354
|
+
}
|
|
355
|
+
globalState.messageObjectsByScope[normalizedScopeKey] = new Set<object>();
|
|
356
|
+
for (const [messageTimestamp, ownerScopeKey] of Object.entries(globalState.messageScopeByTimestamp)) {
|
|
357
|
+
if (ownerScopeKey === normalizedScopeKey) {
|
|
358
|
+
delete globalState.messageScopeByTimestamp[messageTimestamp];
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
globalState.messageScopeByObject = new WeakMap<object, string>();
|
|
365
|
+
globalState.messageObjectsByScope = { [DEFAULT_SCOPE_KEY]: new Set<object>() };
|
|
366
|
+
globalState.messageScopeByTimestamp = {};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function resetThinkingUIViewState(scopeKey?: string): void {
|
|
370
|
+
if (scopeKey !== undefined) {
|
|
371
|
+
const normalizedScopeKey = normalizeThinkingScopeKey(scopeKey);
|
|
372
|
+
globalState.currentScopeKey = normalizedScopeKey;
|
|
373
|
+
globalState.modeByScopeKey[normalizedScopeKey] = "summary";
|
|
374
|
+
globalState.refreshToggleByScope[normalizedScopeKey] = false;
|
|
375
|
+
globalState.activeByScopeKey[normalizedScopeKey] = {};
|
|
376
|
+
globalState.lastActiveByScopeKey[normalizedScopeKey] = { active: false };
|
|
377
|
+
clearThinkingMessageOwnership(normalizedScopeKey);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
globalState.currentScopeKey = DEFAULT_SCOPE_KEY;
|
|
382
|
+
globalState.modeByScopeKey = { [DEFAULT_SCOPE_KEY]: "summary" };
|
|
383
|
+
globalState.activeByScopeKey = { [DEFAULT_SCOPE_KEY]: {} };
|
|
384
|
+
globalState.lastActiveByScopeKey = { [DEFAULT_SCOPE_KEY]: { active: false } };
|
|
385
|
+
globalState.refreshToggleByScope = {};
|
|
386
|
+
clearThinkingMessageOwnership();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function getPatchRefCount(): number {
|
|
390
|
+
return globalState.patchRefCount;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function incrementPatchRefCount(): number {
|
|
394
|
+
globalState.patchRefCount += 1;
|
|
395
|
+
return globalState.patchRefCount;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function decrementPatchRefCount(): number {
|
|
399
|
+
globalState.patchRefCount = Math.max(0, globalState.patchRefCount - 1);
|
|
400
|
+
return globalState.patchRefCount;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export function getPatchCleanup(): PatchCleanup | undefined {
|
|
404
|
+
return globalState.patchCleanup;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export function setPatchCleanup(cleanup: PatchCleanup | undefined): void {
|
|
408
|
+
globalState.patchCleanup = cleanup;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function getPatchInstallPromise(): PatchInstallPromise | undefined {
|
|
412
|
+
return globalState.patchInstallPromise;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function setPatchInstallPromise(installPromise: PatchInstallPromise | undefined): void {
|
|
416
|
+
globalState.patchInstallPromise = installPromise;
|
|
417
|
+
}
|
package/types.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type ThinkingUIMode = "collapsed" | "summary" | "expanded";
|
|
2
|
+
export type PersistedThinkingUIPreferenceScope = "project" | "global";
|
|
3
|
+
|
|
4
|
+
export type ThinkingSemanticRole =
|
|
5
|
+
| "inspect"
|
|
6
|
+
| "plan"
|
|
7
|
+
| "compare"
|
|
8
|
+
| "verify"
|
|
9
|
+
| "write"
|
|
10
|
+
| "search"
|
|
11
|
+
| "error"
|
|
12
|
+
| "default";
|
|
13
|
+
|
|
14
|
+
export type ThinkingSummaryEventType =
|
|
15
|
+
| "failure"
|
|
16
|
+
| "success"
|
|
17
|
+
| "decision"
|
|
18
|
+
| "plan_change"
|
|
19
|
+
| "uncertainty"
|
|
20
|
+
| "action"
|
|
21
|
+
| "focus"
|
|
22
|
+
| "generic";
|
|
23
|
+
|
|
24
|
+
export interface ThinkingSummaryEvent {
|
|
25
|
+
type: ThinkingSummaryEventType;
|
|
26
|
+
text: string;
|
|
27
|
+
order: number;
|
|
28
|
+
priority: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ThinkingSourceBlock {
|
|
32
|
+
contentIndex: number;
|
|
33
|
+
text: string;
|
|
34
|
+
redacted?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DerivedThinkingStep {
|
|
38
|
+
id: string;
|
|
39
|
+
contentIndex: number;
|
|
40
|
+
blockIndex: number;
|
|
41
|
+
stepIndex: number;
|
|
42
|
+
summary: string;
|
|
43
|
+
body: string;
|
|
44
|
+
role: ThinkingSemanticRole;
|
|
45
|
+
icon: string;
|
|
46
|
+
baselineSummary?: string;
|
|
47
|
+
challengerSummary?: string;
|
|
48
|
+
summaryEvents?: ThinkingSummaryEvent[];
|
|
49
|
+
collapsedPriority?: number;
|
|
50
|
+
hasExplicitFailure?: boolean;
|
|
51
|
+
hasExplicitSuccess?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ActiveThinkingState {
|
|
55
|
+
messageTimestamp?: number;
|
|
56
|
+
contentIndex?: number;
|
|
57
|
+
active: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ThinkingThemeLike {
|
|
61
|
+
fg(color: string, text: string): string;
|
|
62
|
+
bold(text: string): string;
|
|
63
|
+
}
|