@runtypelabs/persona 3.22.0 → 3.23.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/dist/animations/glyph-cycle.cjs +2 -262
- package/dist/animations/glyph-cycle.js +2 -235
- package/dist/animations/wipe.cjs +2 -72
- package/dist/animations/wipe.js +2 -45
- package/dist/index.cjs +50 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.global.js +83 -83
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +49 -49
- package/dist/index.js.map +1 -1
- package/dist/smart-dom-reader.cjs +22 -1874
- package/dist/smart-dom-reader.js +22 -1847
- package/dist/testing.cjs +3 -84
- package/dist/testing.js +3 -55
- package/dist/theme-editor.cjs +54 -24695
- package/dist/theme-editor.js +54 -24682
- package/package.json +9 -6
- package/src/components/event-stream-view.ts +122 -1
- package/src/ui.ts +24 -3
- package/src/utils/throughput-tracker.test.ts +366 -0
- package/src/utils/throughput-tracker.ts +427 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/persona",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.23.0",
|
|
4
4
|
"description": "Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"zod": "^3.22.4"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
+
"@size-limit/file": "^12.1.0",
|
|
61
62
|
"@types/node": "^20.12.7",
|
|
62
63
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
63
64
|
"@typescript-eslint/parser": "^7.0.0",
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
"eslint-config-prettier": "^9.1.0",
|
|
67
68
|
"fake-indexeddb": "^6.2.5",
|
|
68
69
|
"rimraf": "^5.0.5",
|
|
70
|
+
"size-limit": "^12.1.0",
|
|
69
71
|
"tsup": "^8.0.1",
|
|
70
72
|
"typescript": "^5.4.5",
|
|
71
73
|
"vitest": "^4.0.9"
|
|
@@ -98,10 +100,10 @@
|
|
|
98
100
|
},
|
|
99
101
|
"scripts": {
|
|
100
102
|
"build": "rimraf dist && pnpm run build:styles && pnpm run build:client && pnpm run build:installer && pnpm run build:theme-ref && pnpm run build:theme-editor && pnpm run build:testing && pnpm run build:smart-dom-reader && pnpm run build:animations",
|
|
101
|
-
"build:theme-editor": "tsup src/theme-editor.ts --format esm,cjs --dts --out-dir dist --no-splitting",
|
|
102
|
-
"build:testing": "tsup src/testing.ts --format esm,cjs --dts --out-dir dist --no-splitting",
|
|
103
|
-
"build:smart-dom-reader": "tsup src/smart-dom-reader.ts --format esm,cjs --dts --out-dir dist --no-splitting",
|
|
104
|
-
"build:animations": "tsup src/animations/glyph-cycle.ts src/animations/wipe.ts --format esm,cjs --dts --out-dir dist/animations --no-splitting",
|
|
103
|
+
"build:theme-editor": "tsup src/theme-editor.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
|
|
104
|
+
"build:testing": "tsup src/testing.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
|
|
105
|
+
"build:smart-dom-reader": "tsup src/smart-dom-reader.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
|
|
106
|
+
"build:animations": "tsup src/animations/glyph-cycle.ts src/animations/wipe.ts --format esm,cjs --minify --dts --out-dir dist/animations --no-splitting",
|
|
105
107
|
"build:theme-ref": "tsup src/theme-reference.ts --format esm,cjs --minify --dts",
|
|
106
108
|
"build:styles": "node -e \"const fs=require('fs');fs.mkdirSync('dist',{recursive:true});fs.copyFileSync('src/styles/widget.css','dist/widget.css');\"",
|
|
107
109
|
"build:client": "tsup src/index.ts --format esm,cjs --minify --sourcemap --splitting false --dts --loader \".css=text\" && tsup src/index-global.ts --format iife --global-name AgentWidget --minify --sourcemap --splitting false --out-dir dist --loader \".css=text\" && node -e \"const fs=require('fs');for(const ext of ['.global.js','.global.js.map']){const from='dist/index-global'+ext;if(fs.existsSync(from))fs.renameSync(from,'dist/index'+ext);}\"",
|
|
@@ -110,6 +112,7 @@
|
|
|
110
112
|
"typecheck": "tsc --noEmit",
|
|
111
113
|
"test": "vitest",
|
|
112
114
|
"test:ui": "vitest --ui",
|
|
113
|
-
"test:run": "vitest run"
|
|
115
|
+
"test:run": "vitest run",
|
|
116
|
+
"size": "size-limit"
|
|
114
117
|
}
|
|
115
118
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
resolveFollowStateFromWheel
|
|
8
8
|
} from "../utils/auto-follow";
|
|
9
9
|
import type { EventStreamBuffer } from "../utils/event-stream-buffer";
|
|
10
|
+
import type { ThroughputMetric } from "../utils/throughput-tracker";
|
|
10
11
|
import type {
|
|
11
12
|
SSEEventRecord,
|
|
12
13
|
AgentWidgetConfig,
|
|
@@ -151,6 +152,36 @@ function formatEventForCopy(event: SSEEventRecord): string {
|
|
|
151
152
|
);
|
|
152
153
|
}
|
|
153
154
|
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// Output Throughput Summary
|
|
157
|
+
// ============================================================================
|
|
158
|
+
|
|
159
|
+
/** Format the headline value, e.g. `23.9 tok/s` or `-- tok/s` when unavailable. */
|
|
160
|
+
function formatThroughputValue(metric: ThroughputMetric): string {
|
|
161
|
+
if (
|
|
162
|
+
metric.tokensPerSecond === undefined ||
|
|
163
|
+
!Number.isFinite(metric.tokensPerSecond)
|
|
164
|
+
) {
|
|
165
|
+
return "-- tok/s";
|
|
166
|
+
}
|
|
167
|
+
return `${metric.tokensPerSecond.toFixed(1)} tok/s`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Compact supporting details: output tokens, duration, and usage/estimate source. */
|
|
171
|
+
function formatThroughputMeta(metric: ThroughputMetric): string {
|
|
172
|
+
const parts: string[] = [];
|
|
173
|
+
if (metric.outputTokens !== undefined) {
|
|
174
|
+
parts.push(`${metric.outputTokens.toLocaleString()} tok`);
|
|
175
|
+
}
|
|
176
|
+
if (metric.durationMs !== undefined) {
|
|
177
|
+
parts.push(`${(metric.durationMs / 1000).toFixed(2)}s`);
|
|
178
|
+
}
|
|
179
|
+
if (metric.source) {
|
|
180
|
+
parts.push(metric.source);
|
|
181
|
+
}
|
|
182
|
+
return parts.join(" · ");
|
|
183
|
+
}
|
|
184
|
+
|
|
154
185
|
// ============================================================================
|
|
155
186
|
// Inline Payload Component
|
|
156
187
|
// ============================================================================
|
|
@@ -381,6 +412,12 @@ export type EventStreamViewOptions = {
|
|
|
381
412
|
onClose?: () => void;
|
|
382
413
|
config?: AgentWidgetConfig;
|
|
383
414
|
plugins?: AgentWidgetPlugin[];
|
|
415
|
+
/**
|
|
416
|
+
* Optional accessor for the current output-throughput metric, derived from
|
|
417
|
+
* the same SSE event stream. When provided, a compact "Output throughput"
|
|
418
|
+
* summary row is rendered and refreshed on each update.
|
|
419
|
+
*/
|
|
420
|
+
getThroughput?: () => ThroughputMetric;
|
|
384
421
|
};
|
|
385
422
|
|
|
386
423
|
export function createEventStreamView(
|
|
@@ -396,6 +433,7 @@ export function createEventStreamView(
|
|
|
396
433
|
onClose,
|
|
397
434
|
config,
|
|
398
435
|
plugins = [],
|
|
436
|
+
getThroughput,
|
|
399
437
|
} = options;
|
|
400
438
|
const scrollToBottomConfig = config?.features?.scrollToBottom;
|
|
401
439
|
const scrollToBottomEnabled = scrollToBottomConfig?.enabled !== false;
|
|
@@ -474,11 +512,17 @@ export function createEventStreamView(
|
|
|
474
512
|
let copyAllBtn!: HTMLButtonElement;
|
|
475
513
|
let searchInput!: HTMLInputElement;
|
|
476
514
|
let searchClearBtn!: HTMLButtonElement;
|
|
515
|
+
// Inline "Throughput <tok/s>" group, rendered into the header bar next to
|
|
516
|
+
// the "Events" count when getThroughput is provided. The detailed
|
|
517
|
+
// breakdown is revealed on hover via the native title tooltip.
|
|
518
|
+
let throughputValueEl: HTMLElement | null = null;
|
|
519
|
+
let throughputContainer: HTMLElement | null = null;
|
|
520
|
+
let throughputTooltipEl: HTMLElement | null = null;
|
|
477
521
|
|
|
478
522
|
function buildDefaultToolbar(): HTMLElement {
|
|
479
523
|
const toolbarOuter = createElement(
|
|
480
524
|
"div",
|
|
481
|
-
"persona-flex persona-flex-col persona-flex-shrink-0"
|
|
525
|
+
"persona-relative persona-flex persona-flex-col persona-flex-shrink-0"
|
|
482
526
|
);
|
|
483
527
|
|
|
484
528
|
// --- Header bar ---
|
|
@@ -502,6 +546,58 @@ export function createEventStreamView(
|
|
|
502
546
|
);
|
|
503
547
|
countBadge.textContent = "0";
|
|
504
548
|
|
|
549
|
+
// Inline throughput group: "Throughput 146.3 tok/s", grouped with the
|
|
550
|
+
// Events count. Hover reveals tokens · duration · source via a custom
|
|
551
|
+
// tooltip (shown instantly, unlike the slow native `title` delay).
|
|
552
|
+
if (getThroughput) {
|
|
553
|
+
throughputContainer = createElement(
|
|
554
|
+
"div",
|
|
555
|
+
"persona-relative persona-flex persona-items-center persona-gap-1.5 persona-whitespace-nowrap persona-ml-1"
|
|
556
|
+
);
|
|
557
|
+
throughputContainer.style.cursor = "help";
|
|
558
|
+
// Label styled to match the "Events" title.
|
|
559
|
+
const throughputLabel = createElement(
|
|
560
|
+
"span",
|
|
561
|
+
"persona-text-sm persona-font-medium persona-text-persona-primary persona-whitespace-nowrap"
|
|
562
|
+
);
|
|
563
|
+
throughputLabel.textContent = "Throughput";
|
|
564
|
+
// Same bounding box + styling as the Events count badge.
|
|
565
|
+
throughputValueEl = createElement(
|
|
566
|
+
"span",
|
|
567
|
+
"persona-text-[11px] persona-font-mono persona-bg-persona-container persona-text-persona-muted persona-px-2 persona-py-0.5 persona-rounded persona-border persona-border-persona-border persona-tabular-nums"
|
|
568
|
+
);
|
|
569
|
+
throughputValueEl.textContent = "-- tok/s";
|
|
570
|
+
|
|
571
|
+
// Custom hover tooltip — appears instantly (no native title delay).
|
|
572
|
+
// Appended to the (non-clipping) toolbar wrapper rather than the header
|
|
573
|
+
// bar, which has overflow-hidden and would clip a dropdown. Position is
|
|
574
|
+
// measured on hover so it sits just under the throughput group.
|
|
575
|
+
throughputTooltipEl = createElement(
|
|
576
|
+
"div",
|
|
577
|
+
"persona-absolute persona-z-50 persona-whitespace-nowrap persona-rounded persona-border persona-border-persona-border persona-bg-persona-container persona-text-persona-primary persona-text-[11px] persona-font-mono persona-px-2 persona-py-1 persona-shadow"
|
|
578
|
+
);
|
|
579
|
+
throughputTooltipEl.style.display = "none";
|
|
580
|
+
throughputTooltipEl.style.pointerEvents = "none";
|
|
581
|
+
const group = throughputContainer;
|
|
582
|
+
const tooltip = throughputTooltipEl;
|
|
583
|
+
const showTooltip = () => {
|
|
584
|
+
if (!tooltip.textContent) return;
|
|
585
|
+
const gRect = group.getBoundingClientRect();
|
|
586
|
+
const pRect = toolbarOuter.getBoundingClientRect();
|
|
587
|
+
tooltip.style.left = `${gRect.left - pRect.left}px`;
|
|
588
|
+
tooltip.style.top = `${gRect.bottom - pRect.top + 4}px`;
|
|
589
|
+
tooltip.style.display = "block";
|
|
590
|
+
};
|
|
591
|
+
const hideTooltip = () => {
|
|
592
|
+
tooltip.style.display = "none";
|
|
593
|
+
};
|
|
594
|
+
throughputContainer.addEventListener("mouseenter", showTooltip);
|
|
595
|
+
throughputContainer.addEventListener("mouseleave", hideTooltip);
|
|
596
|
+
|
|
597
|
+
throughputContainer.appendChild(throughputLabel);
|
|
598
|
+
throughputContainer.appendChild(throughputValueEl);
|
|
599
|
+
}
|
|
600
|
+
|
|
505
601
|
const headerSpacer = createElement("div", "persona-flex-1");
|
|
506
602
|
|
|
507
603
|
// Filter dropdown
|
|
@@ -540,6 +636,7 @@ export function createEventStreamView(
|
|
|
540
636
|
|
|
541
637
|
headerBar.appendChild(title);
|
|
542
638
|
headerBar.appendChild(countBadge);
|
|
639
|
+
if (throughputContainer) headerBar.appendChild(throughputContainer);
|
|
543
640
|
headerBar.appendChild(headerSpacer);
|
|
544
641
|
headerBar.appendChild(filterSelect);
|
|
545
642
|
headerBar.appendChild(copyAllBtn);
|
|
@@ -592,6 +689,7 @@ export function createEventStreamView(
|
|
|
592
689
|
|
|
593
690
|
toolbarOuter.appendChild(headerBar);
|
|
594
691
|
toolbarOuter.appendChild(searchBar);
|
|
692
|
+
if (throughputTooltipEl) toolbarOuter.appendChild(throughputTooltipEl);
|
|
595
693
|
return toolbarOuter;
|
|
596
694
|
}
|
|
597
695
|
|
|
@@ -630,6 +728,28 @@ export function createEventStreamView(
|
|
|
630
728
|
);
|
|
631
729
|
truncationBanner.style.display = "none";
|
|
632
730
|
|
|
731
|
+
// Refresh the inline header throughput value + hover tooltip. The elements
|
|
732
|
+
// live in the header bar (built by buildDefaultToolbar); this is a no-op
|
|
733
|
+
// when getThroughput is absent or a plugin replaced the toolbar.
|
|
734
|
+
function updateThroughputSummary(): void {
|
|
735
|
+
if (!getThroughput || !throughputValueEl || !throughputContainer) return;
|
|
736
|
+
const metric = getThroughput();
|
|
737
|
+
throughputValueEl.textContent = formatThroughputValue(metric);
|
|
738
|
+
// Detailed breakdown is revealed on hover via the custom tooltip; mirror
|
|
739
|
+
// it into aria-label for assistive tech. When there's nothing to show,
|
|
740
|
+
// hide the tooltip so an empty box never flashes on hover.
|
|
741
|
+
const meta = formatThroughputMeta(metric);
|
|
742
|
+
if (throughputTooltipEl) {
|
|
743
|
+
throughputTooltipEl.textContent = meta;
|
|
744
|
+
if (!meta) throughputTooltipEl.style.display = "none";
|
|
745
|
+
}
|
|
746
|
+
if (meta) {
|
|
747
|
+
throughputContainer.setAttribute("aria-label", meta);
|
|
748
|
+
} else {
|
|
749
|
+
throughputContainer.removeAttribute("aria-label");
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
633
753
|
// ========================================================================
|
|
634
754
|
// Events List (simple DOM, no virtual scroller)
|
|
635
755
|
// ========================================================================
|
|
@@ -804,6 +924,7 @@ export function createEventStreamView(
|
|
|
804
924
|
lastRenderTime = Date.now();
|
|
805
925
|
pendingUpdate = false;
|
|
806
926
|
|
|
927
|
+
updateThroughputSummary();
|
|
807
928
|
updateFilterOptions();
|
|
808
929
|
|
|
809
930
|
// Truncation banner
|
package/src/ui.ts
CHANGED
|
@@ -89,6 +89,7 @@ import { createApprovalBubble } from "./components/approval-bubble";
|
|
|
89
89
|
import { createSuggestions } from "./components/suggestions";
|
|
90
90
|
import { EventStreamBuffer } from "./utils/event-stream-buffer";
|
|
91
91
|
import { EventStreamStore } from "./utils/event-stream-store";
|
|
92
|
+
import { ThroughputTracker } from "./utils/throughput-tracker";
|
|
92
93
|
import { createEventStreamView } from "./components/event-stream-view";
|
|
93
94
|
import { createArtifactPane, type ArtifactPaneApi } from "./components/artifact-pane";
|
|
94
95
|
import {
|
|
@@ -669,6 +670,8 @@ export const createAgentExperience = (
|
|
|
669
670
|
let eventStreamStore = showEventStreamToggle ? new EventStreamStore(eventStreamDbName) : null;
|
|
670
671
|
const eventStreamMaxEvents = config.features?.eventStream?.maxEvents ?? 2000;
|
|
671
672
|
let eventStreamBuffer = showEventStreamToggle ? new EventStreamBuffer(eventStreamMaxEvents, eventStreamStore) : null;
|
|
673
|
+
// Passive output-throughput tracker, fed from the same SSE tap as the buffer.
|
|
674
|
+
let throughputTracker = showEventStreamToggle ? new ThroughputTracker() : null;
|
|
672
675
|
let eventStreamView: ReturnType<typeof createEventStreamView> | null = null;
|
|
673
676
|
let eventStreamVisible = false;
|
|
674
677
|
let eventStreamRAF: number | null = null;
|
|
@@ -858,6 +861,8 @@ export const createAgentExperience = (
|
|
|
858
861
|
onClose: () => toggleEventStreamOff(),
|
|
859
862
|
config,
|
|
860
863
|
plugins,
|
|
864
|
+
getThroughput: () =>
|
|
865
|
+
throughputTracker?.getMetric() ?? { status: "idle" },
|
|
861
866
|
});
|
|
862
867
|
}
|
|
863
868
|
if (eventStreamView) {
|
|
@@ -4508,6 +4513,7 @@ export const createAgentExperience = (
|
|
|
4508
4513
|
if (eventStreamBuffer || config.onSSEEvent) {
|
|
4509
4514
|
session.setSSEEventCallback((type: string, payload: unknown) => {
|
|
4510
4515
|
config.onSSEEvent?.(type, payload);
|
|
4516
|
+
throughputTracker?.processEvent(type, payload);
|
|
4511
4517
|
eventStreamBuffer?.push({
|
|
4512
4518
|
id: `evt-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
4513
4519
|
type,
|
|
@@ -4563,6 +4569,10 @@ export const createAgentExperience = (
|
|
|
4563
4569
|
// intact so the user can edit and resend without retyping.
|
|
4564
4570
|
if (session.isStreaming()) {
|
|
4565
4571
|
session.cancel();
|
|
4572
|
+
// Cancelling emits no terminal/error SSE frame, so reset the throughput
|
|
4573
|
+
// tracker (as clear-chat does) to avoid a stale `running` row lingering.
|
|
4574
|
+
throughputTracker?.reset();
|
|
4575
|
+
eventStreamView?.update();
|
|
4566
4576
|
return;
|
|
4567
4577
|
}
|
|
4568
4578
|
|
|
@@ -4697,6 +4707,10 @@ export const createAgentExperience = (
|
|
|
4697
4707
|
if (!session.isStreaming()) return;
|
|
4698
4708
|
if (!event.composedPath().includes(container)) return;
|
|
4699
4709
|
session.cancel();
|
|
4710
|
+
// Cancelling emits no terminal/error SSE frame — reset throughput so the
|
|
4711
|
+
// Events row doesn't keep showing a live rate from the stopped stream.
|
|
4712
|
+
throughputTracker?.reset();
|
|
4713
|
+
eventStreamView?.update();
|
|
4700
4714
|
resetHistoryNavigation();
|
|
4701
4715
|
event.preventDefault();
|
|
4702
4716
|
event.stopImmediatePropagation();
|
|
@@ -5562,8 +5576,9 @@ export const createAgentExperience = (
|
|
|
5562
5576
|
persistentMetadata = {};
|
|
5563
5577
|
actionManager.syncFromMetadata();
|
|
5564
5578
|
|
|
5565
|
-
// Clear event stream buffer and store
|
|
5579
|
+
// Clear event stream buffer and store, and reset throughput tracking
|
|
5566
5580
|
eventStreamBuffer?.clear();
|
|
5581
|
+
throughputTracker?.reset();
|
|
5567
5582
|
eventStreamView?.update();
|
|
5568
5583
|
});
|
|
5569
5584
|
};
|
|
@@ -5732,10 +5747,12 @@ export const createAgentExperience = (
|
|
|
5732
5747
|
if (!eventStreamBuffer) {
|
|
5733
5748
|
eventStreamStore = new EventStreamStore(eventStreamDbName);
|
|
5734
5749
|
eventStreamBuffer = new EventStreamBuffer(eventStreamMaxEvents, eventStreamStore);
|
|
5750
|
+
throughputTracker = throughputTracker ?? new ThroughputTracker();
|
|
5735
5751
|
eventStreamStore.open().then(() => eventStreamBuffer?.restore()).catch(() => {});
|
|
5736
|
-
// Register the SSE event callback (host tap + buffer)
|
|
5752
|
+
// Register the SSE event callback (host tap + buffer + throughput)
|
|
5737
5753
|
session.setSSEEventCallback((type: string, payload: unknown) => {
|
|
5738
5754
|
config.onSSEEvent?.(type, payload);
|
|
5755
|
+
throughputTracker?.processEvent(type, payload);
|
|
5739
5756
|
eventStreamBuffer!.push({
|
|
5740
5757
|
id: `evt-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
5741
5758
|
type,
|
|
@@ -5784,6 +5801,8 @@ export const createAgentExperience = (
|
|
|
5784
5801
|
eventStreamStore?.destroy();
|
|
5785
5802
|
eventStreamBuffer = null;
|
|
5786
5803
|
eventStreamStore = null;
|
|
5804
|
+
throughputTracker?.reset();
|
|
5805
|
+
throughputTracker = null;
|
|
5787
5806
|
}
|
|
5788
5807
|
|
|
5789
5808
|
if (config.launcher?.enabled === false && launcherButtonInstance) {
|
|
@@ -7024,8 +7043,9 @@ export const createAgentExperience = (
|
|
|
7024
7043
|
persistentMetadata = {};
|
|
7025
7044
|
actionManager.syncFromMetadata();
|
|
7026
7045
|
|
|
7027
|
-
// Clear event stream buffer and store
|
|
7046
|
+
// Clear event stream buffer and store, and reset throughput tracking
|
|
7028
7047
|
eventStreamBuffer?.clear();
|
|
7048
|
+
throughputTracker?.reset();
|
|
7029
7049
|
eventStreamView?.update();
|
|
7030
7050
|
},
|
|
7031
7051
|
setMessage(message: string): boolean {
|
|
@@ -7179,6 +7199,7 @@ export const createAgentExperience = (
|
|
|
7179
7199
|
/** Push a raw event into the event stream buffer (for testing/debugging) */
|
|
7180
7200
|
__pushEventStreamEvent(event: { type: string; payload: unknown }): void {
|
|
7181
7201
|
if (eventStreamBuffer) {
|
|
7202
|
+
throughputTracker?.processEvent(event.type, event.payload);
|
|
7182
7203
|
eventStreamBuffer.push({
|
|
7183
7204
|
id: `evt-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
7184
7205
|
type: event.type,
|