dsh-thinkbar 0.1.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/lib/client.cjs ADDED
@@ -0,0 +1,679 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-thinkbar",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
8
+ let react_dom = require("react-dom");
9
+ let react = require("react");
10
+ let react_jsx_runtime = require("react/jsx-runtime");
11
+ //#region src/client/thermometer.ts
12
+ const FULL_SCALE_MS = 2e4;
13
+ const MIN_FILL = .08;
14
+ const DRAIN_MS = 240;
15
+ const IRON_STOPS = [
16
+ {
17
+ t: 0,
18
+ hex: "var(--dsw-alias-button-info-fill)"
19
+ },
20
+ {
21
+ t: 1 / 3,
22
+ hex: "#dc2626"
23
+ },
24
+ {
25
+ t: 2 / 3,
26
+ hex: "#e07020"
27
+ },
28
+ {
29
+ t: 1,
30
+ hex: "#ffcc00"
31
+ }
32
+ ];
33
+ function reasoningWaitAppearance(projection, elapsed) {
34
+ if (projection?.active !== true) return { phase: "idle" };
35
+ const t = Math.min(1, Math.max(0, elapsed / FULL_SCALE_MS));
36
+ return {
37
+ phase: "thermometer",
38
+ t,
39
+ height: MIN_FILL + .92 * easeOutFill(t),
40
+ color: ironScale(t)
41
+ };
42
+ }
43
+ function extrapolateProjectionClock(projection, frameNow, anchor, identity) {
44
+ if (projection?.active !== true) return {
45
+ elapsed: 0,
46
+ anchor: null
47
+ };
48
+ const eventElapsed = Math.max(0, projection.streamTime - projection.waitOrigin);
49
+ const stamp = `${identity}:${projection.waitOrigin}:${projection.streamTime}`;
50
+ if (anchor?.stamp !== stamp) {
51
+ const elapsed = Math.max(anchor?.identity === identity ? anchor.renderedElapsed : 0, eventElapsed);
52
+ return {
53
+ elapsed,
54
+ anchor: {
55
+ stamp,
56
+ identity,
57
+ eventElapsed,
58
+ observedAt: frameNow,
59
+ renderedElapsed: elapsed
60
+ }
61
+ };
62
+ }
63
+ const elapsed = Math.max(anchor.renderedElapsed, anchor.eventElapsed + frameNow - anchor.observedAt);
64
+ return {
65
+ elapsed,
66
+ anchor: {
67
+ ...anchor,
68
+ renderedElapsed: elapsed
69
+ }
70
+ };
71
+ }
72
+ function advanceReasoningWait(previous, input) {
73
+ const snapshot = reasoningWaitAppearance(input.projection, input.elapsed);
74
+ if (snapshot.phase === "thermometer") {
75
+ if (previous.phase === "thermometer" && previous.identity === input.identity && previous.t === snapshot.t && previous.height === snapshot.height && previous.color === snapshot.color) return previous;
76
+ return {
77
+ ...snapshot,
78
+ identity: input.identity
79
+ };
80
+ }
81
+ if (input.reducedMotion) return previous.phase === "idle" ? previous : { phase: "idle" };
82
+ const sameIdentity = previous.phase !== "idle" && previous.identity === input.identity;
83
+ if (previous.phase === "thermometer" && sameIdentity) return {
84
+ phase: "drain",
85
+ identity: input.identity,
86
+ t: previous.t,
87
+ height: previous.height,
88
+ color: previous.color,
89
+ fromHeight: previous.height,
90
+ startedAt: input.frameNow
91
+ };
92
+ if (previous.phase === "drain" && sameIdentity) {
93
+ const elapsed = input.frameNow - previous.startedAt;
94
+ if (elapsed >= DRAIN_MS) return { phase: "idle" };
95
+ const height = previous.fromHeight * (1 - elapsed / DRAIN_MS);
96
+ if (height === previous.height) return previous;
97
+ return {
98
+ ...previous,
99
+ height
100
+ };
101
+ }
102
+ return previous.phase === "idle" ? previous : { phase: "idle" };
103
+ }
104
+ function nearestStop(t) {
105
+ for (const stop of IRON_STOPS) if (Math.abs(t - stop.t) <= 1 / FULL_SCALE_MS) return stop.hex;
106
+ }
107
+ function ironScale(t) {
108
+ const snapped = nearestStop(t);
109
+ if (snapped !== void 0) return snapped;
110
+ if (t < IRON_STOPS[1].t) return mixOklch(IRON_STOPS[0].hex, IRON_STOPS[1].hex, t / (1 / 3));
111
+ if (t < IRON_STOPS[2].t) return mixOklch(IRON_STOPS[1].hex, IRON_STOPS[2].hex, (t - 1 / 3) / (1 / 3));
112
+ return mixOklch(IRON_STOPS[2].hex, IRON_STOPS[3].hex, (t - 2 / 3) / (1 / 3));
113
+ }
114
+ function mixOklch(fromHex, toHex, amount) {
115
+ return `color-mix(in oklch, ${fromHex}, ${toHex} ${amount * 100}%)`;
116
+ }
117
+ function easeOutFill(linearT) {
118
+ if (linearT <= 0) return 0;
119
+ if (linearT >= 1) return 1;
120
+ const xAt = (u) => 3 * (1 - u) * u * u * .58 + u ** 3;
121
+ const yAt = (u) => 3 * (1 - u) * u * u + u ** 3;
122
+ let low = 0;
123
+ let high = 1;
124
+ for (let i = 0; i < 24; i++) {
125
+ const mid = (low + high) / 2;
126
+ if (xAt(mid) < linearT) low = mid;
127
+ else high = mid;
128
+ }
129
+ return yAt((low + high) / 2);
130
+ }
131
+ //#endregion
132
+ //#region src/client/service.ts
133
+ /** Stateless reasoning-wait frame service. */
134
+ var ReasoningWaitService = class extends _deepseek_ai_cordis.Service {
135
+ static inject = [];
136
+ constructor(ctx) {
137
+ super(ctx, "reasoningWait");
138
+ }
139
+ clock(projection, frameNow, anchor, identity) {
140
+ return extrapolateProjectionClock(projection, frameNow, anchor, identity);
141
+ }
142
+ advance(previous, input) {
143
+ return advanceReasoningWait(previous, input);
144
+ }
145
+ };
146
+ //#endregion
147
+ //#region \0dsh-thinkbar-css:C:\Workspace\tower1229\dsh-thinkbar\src\client\ReasoningWaitIndicator.module.css.mjs
148
+ const css = ".lL-Mhq_anchor{display:none}[data-dsh-thinkbar-host]{isolation:isolate;position:relative;overflow:hidden}[data-dsh-thinkbar-host]>:not([data-dsh-thinkbar-layer]){z-index:1;position:relative}.lL-Mhq_root,.lL-Mhq_fill{border-radius:inherit;pointer-events:none;position:absolute;inset:0}.lL-Mhq_root{z-index:0;overflow:hidden}.lL-Mhq_fill{inset:0 auto 0 0;overflow:hidden}.lL-Mhq_particle{background:var(--dsw-static-neutral-bluish-00);opacity:var(--dsh-particle-opacity,.8);animation:lL-Mhq_reasoning-wait-particle-drift var(--dsh-particle-dur,3s) ease-in-out var(--dsh-particle-delay,0s) infinite;border-radius:50%;position:absolute}@keyframes lL-Mhq_reasoning-wait-particle-drift{0%,to{opacity:var(--dsh-particle-opacity,.8);transform:translate(0)}50%{transform:translate(var(--dsh-particle-dx,0px), var(--dsh-particle-dy,-2px));opacity:calc(var(--dsh-particle-opacity,.8) * .5)}}@media (prefers-reduced-motion:reduce){.lL-Mhq_particle{animation:none}}";
149
+ const tagId = "dsh-thinkbar/ReasoningWaitIndicator.module.css";
150
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
151
+ const tag = document.createElement("style");
152
+ tag.dataset.plugin = "dsh-thinkbar";
153
+ tag.dataset.pluginCss = tagId;
154
+ tag.textContent = css;
155
+ document.head.appendChild(tag);
156
+ }
157
+ var ReasoningWaitIndicator_module_css_default = {
158
+ "anchor": "lL-Mhq_anchor",
159
+ "fill": "lL-Mhq_fill",
160
+ "particle": "lL-Mhq_particle",
161
+ "reasoning-wait-particle-drift": "lL-Mhq_reasoning-wait-particle-drift",
162
+ "root": "lL-Mhq_root"
163
+ };
164
+ //#endregion
165
+ //#region src/client/ReasoningWaitIndicator.tsx
166
+ const FILL_PARTICLES = [
167
+ {
168
+ x: 10,
169
+ y: 15,
170
+ size: 2,
171
+ opacity: 1,
172
+ dur: 2.6,
173
+ delay: 0,
174
+ dx: 3,
175
+ dy: -2
176
+ },
177
+ {
178
+ x: 26,
179
+ y: 8,
180
+ size: 3,
181
+ opacity: .95,
182
+ dur: 3.4,
183
+ delay: -.8,
184
+ dx: -2,
185
+ dy: 3
186
+ },
187
+ {
188
+ x: 40,
189
+ y: 19,
190
+ size: 2,
191
+ opacity: 1,
192
+ dur: 2.2,
193
+ delay: -1.4,
194
+ dx: 3,
195
+ dy: -3
196
+ },
197
+ {
198
+ x: 56,
199
+ y: 12,
200
+ size: 2,
201
+ opacity: .9,
202
+ dur: 4.1,
203
+ delay: -.4,
204
+ dx: -3,
205
+ dy: 2
206
+ },
207
+ {
208
+ x: 72,
209
+ y: 21,
210
+ size: 3,
211
+ opacity: 1,
212
+ dur: 3,
213
+ delay: -2.1,
214
+ dx: 2,
215
+ dy: -2
216
+ },
217
+ {
218
+ x: 90,
219
+ y: 7,
220
+ size: 2,
221
+ opacity: .95,
222
+ dur: 2.8,
223
+ delay: -1.1,
224
+ dx: -2,
225
+ dy: 3
226
+ },
227
+ {
228
+ x: 106,
229
+ y: 16,
230
+ size: 2,
231
+ opacity: 1,
232
+ dur: 3.6,
233
+ delay: -.6,
234
+ dx: 3,
235
+ dy: -2
236
+ },
237
+ {
238
+ x: 124,
239
+ y: 10,
240
+ size: 3,
241
+ opacity: .9,
242
+ dur: 4.4,
243
+ delay: -2.8,
244
+ dx: -3,
245
+ dy: 2
246
+ },
247
+ {
248
+ x: 142,
249
+ y: 19,
250
+ size: 2,
251
+ opacity: 1,
252
+ dur: 2.4,
253
+ delay: -1.7,
254
+ dx: 2,
255
+ dy: -3
256
+ },
257
+ {
258
+ x: 160,
259
+ y: 13,
260
+ size: 2,
261
+ opacity: .95,
262
+ dur: 3.2,
263
+ delay: -.9,
264
+ dx: -2,
265
+ dy: 3
266
+ },
267
+ {
268
+ x: 180,
269
+ y: 8,
270
+ size: 3,
271
+ opacity: .95,
272
+ dur: 3.8,
273
+ delay: -2.4,
274
+ dx: 3,
275
+ dy: -2
276
+ },
277
+ {
278
+ x: 202,
279
+ y: 18,
280
+ size: 2,
281
+ opacity: 1,
282
+ dur: 2.7,
283
+ delay: -1.2,
284
+ dx: -3,
285
+ dy: 2
286
+ },
287
+ {
288
+ x: 226,
289
+ y: 12,
290
+ size: 2,
291
+ opacity: .9,
292
+ dur: 4.3,
293
+ delay: -.3,
294
+ dx: 2,
295
+ dy: -3
296
+ },
297
+ {
298
+ x: 250,
299
+ y: 17,
300
+ size: 3,
301
+ opacity: 1,
302
+ dur: 3.1,
303
+ delay: -1.9,
304
+ dx: -2,
305
+ dy: 2
306
+ },
307
+ {
308
+ x: 278,
309
+ y: 9,
310
+ size: 2,
311
+ opacity: .95,
312
+ dur: 2.9,
313
+ delay: -.7,
314
+ dx: 3,
315
+ dy: -2
316
+ },
317
+ {
318
+ x: 306,
319
+ y: 14,
320
+ size: 2,
321
+ opacity: .9,
322
+ dur: 3.7,
323
+ delay: -2.6,
324
+ dx: -2,
325
+ dy: 3
326
+ }
327
+ ];
328
+ function particleStyle(particle) {
329
+ return {
330
+ left: `${particle.x}px`,
331
+ top: `${particle.y}px`,
332
+ width: `${particle.size}px`,
333
+ height: `${particle.size}px`,
334
+ "--dsh-particle-opacity": String(particle.opacity),
335
+ "--dsh-particle-dx": `${particle.dx}px`,
336
+ "--dsh-particle-dy": `${particle.dy}px`,
337
+ "--dsh-particle-dur": `${particle.dur}s`,
338
+ "--dsh-particle-delay": `${particle.delay}s`
339
+ };
340
+ }
341
+ function ReasoningWaitIndicator({ identity, projection, clock, advance }) {
342
+ const [waitState, setWaitState] = (0, react.useState)({ phase: "idle" });
343
+ const clockAnchorRef = (0, react.useRef)(null);
344
+ const reducedMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches ?? false;
345
+ const frameNow = useFrameNow(projection?.active === true || waitState.phase !== "idle");
346
+ const clockRead = clock(projection, frameNow, clockAnchorRef.current, identity);
347
+ clockAnchorRef.current = clockRead.anchor;
348
+ const input = {
349
+ projection,
350
+ elapsed: clockRead.elapsed,
351
+ frameNow,
352
+ reducedMotion,
353
+ identity
354
+ };
355
+ const view = advance(waitState, input);
356
+ (0, react.useLayoutEffect)(() => {
357
+ setWaitState((previous) => advance(previous, input));
358
+ }, [
359
+ projection,
360
+ clockRead.elapsed,
361
+ frameNow,
362
+ reducedMotion,
363
+ identity,
364
+ advance
365
+ ]);
366
+ if (view.phase === "idle") return null;
367
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
368
+ className: ReasoningWaitIndicator_module_css_default.root,
369
+ "data-reasoning-wait": view.phase,
370
+ "aria-hidden": "true",
371
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
372
+ className: ReasoningWaitIndicator_module_css_default.fill,
373
+ "data-reasoning-wait-fill": "",
374
+ style: {
375
+ width: `${view.height * 100}%`,
376
+ backgroundColor: view.color
377
+ },
378
+ children: FILL_PARTICLES.map((particle, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
379
+ className: ReasoningWaitIndicator_module_css_default.particle,
380
+ "data-reasoning-wait-particle": "",
381
+ style: particleStyle(particle)
382
+ }, index))
383
+ })
384
+ });
385
+ }
386
+ function useFrameNow(active) {
387
+ const [, setTick] = (0, react.useState)(0);
388
+ (0, react.useEffect)(() => {
389
+ if (!active) return;
390
+ let frame = 0;
391
+ const tick = () => {
392
+ setTick((value) => value + 1);
393
+ frame = requestAnimationFrame(tick);
394
+ };
395
+ frame = requestAnimationFrame(tick);
396
+ return () => cancelAnimationFrame(frame);
397
+ }, [active]);
398
+ return performance.now();
399
+ }
400
+ //#endregion
401
+ //#region src/client/model-trigger-adapter.ts
402
+ const COMPOSER_SELECTOR = "[data-composer-card]";
403
+ const MODEL_TRIGGER_SELECTOR = "button[aria-haspopup=\"menu\"]";
404
+ const HOST_ATTRIBUTE = "data-dsh-thinkbar-host";
405
+ const LAYER_ATTRIBUTE = "data-dsh-thinkbar-layer";
406
+ let compatibilityWarningShown = false;
407
+ /** Resolve the only semantic menu trigger following the public Slot anchor. */
408
+ function resolveModelTrigger(anchor) {
409
+ const composer = anchor.closest(COMPOSER_SELECTOR);
410
+ if (composer === null) return null;
411
+ const candidates = [...composer.querySelectorAll(MODEL_TRIGGER_SELECTOR)].filter((candidate) => Boolean(anchor.compareDocumentPosition(candidate) & Node.DOCUMENT_POSITION_FOLLOWING));
412
+ return candidates.length === 1 ? candidates[0] ?? null : null;
413
+ }
414
+ function warnCompatibility() {
415
+ if (compatibilityWarningShown) return;
416
+ compatibilityWarningShown = true;
417
+ console.warn("[dsh-thinkbar] Could not uniquely identify the DeepSeek Harness model selector; the indicator is disabled.");
418
+ }
419
+ function existingLayer(target) {
420
+ return [...target.children].find((child) => child instanceof HTMLSpanElement && child.hasAttribute(LAYER_ATTRIBUTE)) ?? null;
421
+ }
422
+ /** Attach one plugin-owned Portal layer without moving or rewriting host children. */
423
+ function mountModelTrigger(anchor) {
424
+ const target = resolveModelTrigger(anchor);
425
+ if (target === null) {
426
+ warnCompatibility();
427
+ return null;
428
+ }
429
+ const layer = existingLayer(target) ?? document.createElement("span");
430
+ layer.setAttribute(LAYER_ATTRIBUTE, "");
431
+ if (!layer.isConnected) target.prepend(layer);
432
+ target.setAttribute(HOST_ATTRIBUTE, "");
433
+ let disposed = false;
434
+ return {
435
+ layer,
436
+ dispose: () => {
437
+ if (disposed) return;
438
+ disposed = true;
439
+ layer.remove();
440
+ if (existingLayer(target) === null) target.removeAttribute(HOST_ATTRIBUTE);
441
+ }
442
+ };
443
+ }
444
+ //#endregion
445
+ //#region src/client/use-model-trigger.ts
446
+ /** Keep a Portal layer attached across ModelSelect and composer reconstruction. */
447
+ function useModelTrigger(anchorRef) {
448
+ const [layer, setLayer] = (0, react.useState)(null);
449
+ (0, react.useLayoutEffect)(() => {
450
+ const anchor = anchorRef.current;
451
+ if (anchor === null) return;
452
+ const composer = anchor.closest("[data-composer-card]");
453
+ if (composer === null) return;
454
+ let mount = null;
455
+ const refresh = () => {
456
+ const target = resolveModelTrigger(anchor);
457
+ if (mount?.layer.isConnected === true && mount.layer.parentElement === target) return;
458
+ mount?.dispose();
459
+ mount = mountModelTrigger(anchor);
460
+ setLayer(mount?.layer ?? null);
461
+ };
462
+ refresh();
463
+ const observer = new MutationObserver(refresh);
464
+ observer.observe(composer, {
465
+ childList: true,
466
+ subtree: true
467
+ });
468
+ return () => {
469
+ observer.disconnect();
470
+ mount?.dispose();
471
+ };
472
+ }, [anchorRef]);
473
+ return layer;
474
+ }
475
+ //#endregion
476
+ //#region src/client/ModelTriggerBridge.tsx
477
+ /** Public-Slot lifecycle bridge that portals the indicator into the model trigger. */
478
+ function ModelTriggerBridge({ useSession, sessionId, clock, advance }) {
479
+ const anchorRef = (0, react.useRef)(null);
480
+ const layer = useModelTrigger(anchorRef);
481
+ const projection = useSession((snapshot) => snapshot.views.get("dsh-thinkbar"));
482
+ const identity = projection === null || projection === void 0 ? String(sessionId) : `${String(sessionId)}:${projection.turn}:${projection.step}`;
483
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
484
+ ref: anchorRef,
485
+ className: ReasoningWaitIndicator_module_css_default.anchor,
486
+ "data-dsh-thinkbar-anchor": "",
487
+ "aria-hidden": "true",
488
+ children: layer === null ? null : (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReasoningWaitIndicator, {
489
+ identity,
490
+ projection,
491
+ clock,
492
+ advance
493
+ }), layer)
494
+ });
495
+ }
496
+ //#endregion
497
+ //#region src/client/reasoning-wait-projection.ts
498
+ function isCompactChunkEvent(event) {
499
+ const type = String(event.type);
500
+ return type === "chunkrow/text-chunks" || type === "chunkrow/reasoning-chunks" || type === "chunkrow/tool-call-chunks";
501
+ }
502
+ function projectionOf(state) {
503
+ const { retryPending: _retryPending, ...projection } = state;
504
+ return projection;
505
+ }
506
+ function eventIdentity(event) {
507
+ if (isCompactChunkEvent(event)) return `${event.data.turn}:${event.data.step}`;
508
+ if (event.type === "step/start" || event.type === "step/end" || event.type === "assistant/chunk" || event.type === "assistant/message" || event.type === "llm/retry" || event.type === "llm/retry-started") return `${event.data.turn}:${event.data.step}`;
509
+ return null;
510
+ }
511
+ function transition(state, time, tailKind, active, originTime = time) {
512
+ return {
513
+ ...state,
514
+ waitOrigin: state.retryPending && tailKind === "reasoning" ? originTime : state.waitOrigin,
515
+ streamTime: time,
516
+ active,
517
+ tailKind,
518
+ retryPending: state.retryPending && tailKind === "empty"
519
+ };
520
+ }
521
+ function chunkTransition(state, match) {
522
+ if (match.event.type !== "assistant/chunk") return state;
523
+ const { chunk } = match.event.data;
524
+ switch (chunk.type) {
525
+ case "block-start":
526
+ if (chunk.blockType === "reasoning") return transition(state, match.event.time, "reasoning", true);
527
+ if (chunk.blockType === "text") return transition(state, match.event.time, "text", false);
528
+ if (chunk.blockType === "tool-call") return transition(state, match.event.time, "tool", false);
529
+ return transition(state, match.event.time, "other", false);
530
+ case "reasoning-delta": return transition(state, match.event.time, "reasoning", true);
531
+ case "text-delta": return transition(state, match.event.time, "text", false);
532
+ case "tool-call-delta": return transition(state, match.event.time, "tool", false);
533
+ case "block-end":
534
+ if (chunk.block.type === "reasoning") return transition(state, match.event.time, "reasoning", true);
535
+ if (chunk.block.type === "text") return transition(state, match.event.time, "text", false);
536
+ if (chunk.block.type === "tool-call") return transition(state, match.event.time, "tool", false);
537
+ return transition(state, match.event.time, "other", false);
538
+ default: return state;
539
+ }
540
+ }
541
+ function compactEndTime(event) {
542
+ return event.data.dt.reduce((time, gap) => time + gap, event.time);
543
+ }
544
+ function compactFirstEvidenceTime(event) {
545
+ let time = event.time;
546
+ for (let index = 0; index < (event.data.texts?.length ?? 0); index++) {
547
+ if (event.data.texts?.[index] !== "") return time;
548
+ time += event.data.dt[index] ?? 0;
549
+ }
550
+ return null;
551
+ }
552
+ function compactTransition(state, event) {
553
+ const streamTime = compactEndTime(event);
554
+ if (event.type === "chunkrow/reasoning-chunks") {
555
+ const firstEvidenceTime = compactFirstEvidenceTime(event);
556
+ return firstEvidenceTime === null ? state : transition(state, streamTime, "reasoning", true, firstEvidenceTime);
557
+ }
558
+ if (event.type === "chunkrow/text-chunks") return transition(state, streamTime, "text", false);
559
+ return transition(state, streamTime, "tool", false);
560
+ }
561
+ function updateState(state, match) {
562
+ const event = match.event;
563
+ if (isCompactChunkEvent(event)) return compactTransition(state, event);
564
+ if (event.type === "assistant/chunk") return chunkTransition(state, match);
565
+ if (event.type === "llm/retry" || event.type === "llm/retry-started") return {
566
+ ...state,
567
+ streamTime: event.time,
568
+ active: false,
569
+ tailKind: "empty",
570
+ retryPending: true
571
+ };
572
+ if (event.type === "assistant/message" || event.type === "step/end") return {
573
+ ...state,
574
+ streamTime: event.time,
575
+ active: false,
576
+ retryPending: false
577
+ };
578
+ return state;
579
+ }
580
+ /** Per-Step event state machine for the reasoning indicator. */
581
+ const reasoningWaitDefinition = {
582
+ kind: "dsh-thinkbar/reasoning-wait",
583
+ target: "dsh-thinkbar",
584
+ match: (event) => {
585
+ const id = eventIdentity(event);
586
+ if (id === null) return null;
587
+ return {
588
+ id,
589
+ role: event.type === "step/start" ? "start" : "update"
590
+ };
591
+ },
592
+ start: (_context, match) => {
593
+ if (match.event.type !== "step/start") throw new Error("reasoning-wait start requires step/start");
594
+ return {
595
+ turn: match.event.data.turn,
596
+ step: match.event.data.step,
597
+ waitOrigin: match.event.time,
598
+ streamTime: match.event.time,
599
+ active: false,
600
+ tailKind: "empty",
601
+ retryPending: false
602
+ };
603
+ },
604
+ update: (context, match) => updateState(context.state, match),
605
+ publication: (match) => match.event.type === "assistant/chunk" || isCompactChunkEvent(match.event) ? "animation-frame" : "immediate",
606
+ buildViewNode: (context) => {
607
+ if (context.state === void 0) return null;
608
+ return {
609
+ key: context.key,
610
+ kind: context.kind,
611
+ id: context.id,
612
+ target: "dsh-thinkbar",
613
+ anchorSeq: context.matches.at(-1)?.event.seq ?? context.start?.event.seq ?? 0,
614
+ data: projectionOf(context.state)
615
+ };
616
+ }
617
+ };
618
+ function latestProjection(nodes) {
619
+ let latest;
620
+ for (const node of nodes) if (latest === void 0 || node.anchorSeq > latest.anchorSeq) latest = node;
621
+ return latest?.data ?? null;
622
+ }
623
+ /** Session-owned reducer selecting the latest Step projection. */
624
+ const reasoningWaitView = {
625
+ target: "dsh-thinkbar",
626
+ create: () => {
627
+ const nodes = /* @__PURE__ */ new Map();
628
+ return {
629
+ empty: null,
630
+ replace: (input) => {
631
+ nodes.clear();
632
+ for (const node of input.nodes) nodes.set(node.key, node);
633
+ return latestProjection(nodes.values());
634
+ },
635
+ apply: (input) => {
636
+ for (const node of input.upserts) nodes.set(node.key, node);
637
+ return latestProjection(nodes.values());
638
+ }
639
+ };
640
+ }
641
+ };
642
+ //#endregion
643
+ //#region src/client/index.ts
644
+ /** Required public rc.2 registries. */
645
+ const inject = [
646
+ "slots",
647
+ "conversationEvents",
648
+ "conversationViews"
649
+ ];
650
+ /** Mount the projection, frame service, and public-Slot DOM bridge. */
651
+ function apply(ctx) {
652
+ ctx.conversationEvents.register(reasoningWaitDefinition);
653
+ ctx.conversationViews.register(reasoningWaitView);
654
+ ctx.plugin(ReasoningWaitService);
655
+ ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
656
+ name: "conversation.input.right",
657
+ id: "dsh-thinkbar",
658
+ order: 20,
659
+ inject: () => {
660
+ const reasoningWait = () => ctx.get("reasoningWait");
661
+ return {
662
+ clock: (projection, frameNow, anchor, identity) => reasoningWait()?.clock(projection, frameNow, anchor, identity) ?? {
663
+ elapsed: 0,
664
+ anchor: null
665
+ },
666
+ advance: (previous, input) => reasoningWait()?.advance(previous, input) ?? { phase: "idle" }
667
+ };
668
+ }
669
+ }, ModelTriggerBridge));
670
+ }
671
+ //#endregion
672
+ exports.ReasoningWaitService = ReasoningWaitService;
673
+ exports.apply = apply;
674
+ exports.inject = inject;
675
+ return module.exports;
676
+ }
677
+ });
678
+
679
+ //# sourceMappingURL=client.cjs.map