@runtypelabs/persona 3.21.3 → 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/README.md +67 -0
- package/dist/animations/glyph-cycle.cjs +2 -262
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/glyph-cycle.js +2 -235
- package/dist/animations/{types-CWPIj66R.d.cts → types-BZVr1YOV.d.cts} +10 -0
- package/dist/animations/{types-CWPIj66R.d.ts → types-BZVr1YOV.d.ts} +10 -0
- package/dist/animations/wipe.cjs +2 -72
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/animations/wipe.js +2 -45
- package/dist/index.cjs +52 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +474 -6
- package/dist/index.d.ts +474 -6
- package/dist/index.global.js +107 -97
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +52 -45
- package/dist/index.js.map +1 -1
- package/dist/smart-dom-reader.cjs +23 -0
- package/dist/smart-dom-reader.d.cts +4521 -0
- package/dist/smart-dom-reader.d.ts +4521 -0
- package/dist/smart-dom-reader.js +23 -0
- package/dist/testing.cjs +3 -84
- package/dist/testing.js +3 -55
- package/dist/theme-editor.cjs +57 -22501
- package/dist/theme-editor.d.cts +348 -1
- package/dist/theme-editor.d.ts +348 -1
- package/dist/theme-editor.js +57 -22503
- package/package.json +16 -6
- package/src/client.test.ts +165 -0
- package/src/client.ts +144 -23
- package/src/components/event-stream-view.ts +122 -1
- package/src/index.ts +26 -0
- package/src/session.test.ts +258 -0
- package/src/session.ts +886 -30
- package/src/session.webmcp.test.ts +815 -0
- package/src/smart-dom-reader.test.ts +135 -0
- package/src/smart-dom-reader.ts +135 -0
- package/src/theme-editor/color-utils.test.ts +59 -0
- package/src/theme-editor/color-utils.ts +38 -2
- package/src/theme-editor/index.ts +35 -0
- package/src/theme-editor/webmcp/coerce.test.ts +86 -0
- package/src/theme-editor/webmcp/coerce.ts +286 -0
- package/src/theme-editor/webmcp/index.ts +45 -0
- package/src/theme-editor/webmcp/summary.ts +324 -0
- package/src/theme-editor/webmcp/tools.test.ts +205 -0
- package/src/theme-editor/webmcp/tools.ts +795 -0
- package/src/theme-editor/webmcp/types.ts +87 -0
- package/src/types.ts +186 -0
- package/src/ui.composer-keyboard.test.ts +229 -0
- package/src/ui.ts +151 -8
- package/src/utils/composer-history.test.ts +128 -0
- package/src/utils/composer-history.ts +113 -0
- package/src/utils/message-fingerprint.test.ts +20 -0
- package/src/utils/message-fingerprint.ts +2 -0
- package/src/utils/smart-dom-adapter.test.ts +257 -0
- package/src/utils/smart-dom-adapter.ts +217 -0
- package/src/utils/throughput-tracker.test.ts +366 -0
- package/src/utils/throughput-tracker.ts +427 -0
- package/{LICENSE → src/vendor/smart-dom-reader/LICENSE} +2 -2
- package/src/vendor/smart-dom-reader/README.md +61 -0
- package/src/vendor/smart-dom-reader/index.d.ts +476 -0
- package/src/vendor/smart-dom-reader/index.js +1618 -0
- package/src/webmcp-bridge.test.ts +429 -0
- package/src/webmcp-bridge.ts +547 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Output Throughput Tracker
|
|
3
|
+
// ============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Derives an output tokens-per-second metric from the widget's existing SSE
|
|
6
|
+
// event stream, for display in the Events diagnostics screen. This is a passive
|
|
7
|
+
// consumer: it never mutates dispatch payloads, never forces debug mode, and
|
|
8
|
+
// never changes the wire contract — it only inspects the `(type, payload)`
|
|
9
|
+
// events that already flow through the SSE tap.
|
|
10
|
+
//
|
|
11
|
+
// Throughput is estimated live from visible text deltas while a run streams,
|
|
12
|
+
// then prefers exact provider usage (output tokens) when terminal events carry
|
|
13
|
+
// it. A run starts when the stream starts (or lazily on the first visible
|
|
14
|
+
// delta), stays "running" across intermediate step/turn completions, and only
|
|
15
|
+
// finalizes on terminal `flow_complete` / `agent_complete`. Stream errors mark
|
|
16
|
+
// the metric unavailable rather than leaving it stuck "running".
|
|
17
|
+
|
|
18
|
+
export type ThroughputMetricStatus = "idle" | "running" | "complete" | "error";
|
|
19
|
+
|
|
20
|
+
export type ThroughputMetricSource = "usage" | "estimate";
|
|
21
|
+
|
|
22
|
+
export interface ThroughputMetric {
|
|
23
|
+
status: ThroughputMetricStatus;
|
|
24
|
+
/** Output tokens per second, when computable. */
|
|
25
|
+
tokensPerSecond?: number;
|
|
26
|
+
/** Output tokens counted/estimated for the run. */
|
|
27
|
+
outputTokens?: number;
|
|
28
|
+
/** Duration window the rate was computed over (ms). */
|
|
29
|
+
durationMs?: number;
|
|
30
|
+
/** Whether `outputTokens` came from provider usage or a text estimate. */
|
|
31
|
+
source?: ThroughputMetricSource;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ThroughputRunStats {
|
|
35
|
+
startedAt: number;
|
|
36
|
+
firstDeltaAt?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Running character count of accumulated visible text, estimated via the
|
|
39
|
+
* ~4 chars/token heuristic. Tracked as a counter (not the concatenated
|
|
40
|
+
* string) so the estimate stays O(1) per delta over a long stream.
|
|
41
|
+
*/
|
|
42
|
+
visibleCharCount: number;
|
|
43
|
+
exactOutputTokens: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Below this streamed window we don't trust the rate; fall back to provider
|
|
47
|
+
// execution time or whole-request duration instead.
|
|
48
|
+
const THROUGHPUT_MIN_DURATION_MS = 250;
|
|
49
|
+
|
|
50
|
+
// Request-level lifecycle events: each marks the beginning of a NEW request.
|
|
51
|
+
// The SSE tap fires for every payload type regardless of whether the client has
|
|
52
|
+
// a handler for it, so any of these that the server emits starts the run with
|
|
53
|
+
// an accurate `startedAt` (capturing time-to-first-token). These RESET any run
|
|
54
|
+
// already in progress, so a prior stream that ended without a terminal/error
|
|
55
|
+
// frame (e.g. `session.cancel()`) doesn't bleed its tokens into the next one.
|
|
56
|
+
const REQUEST_START_EVENTS = new Set([
|
|
57
|
+
"flow_start",
|
|
58
|
+
"flow_run_start",
|
|
59
|
+
"agent_start",
|
|
60
|
+
"dispatch_start",
|
|
61
|
+
"run_start",
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
// Per-step markers that fire repeatedly WITHIN a single request (a flow emits
|
|
65
|
+
// one per step). These only lazily begin a run — they must never reset, or a
|
|
66
|
+
// multi-step response would restart the metric between steps. If no request- or
|
|
67
|
+
// step-start event is emitted, the first visible delta lazily starts the run.
|
|
68
|
+
const STEP_START_EVENTS = new Set(["step_start", "execution_start"]);
|
|
69
|
+
|
|
70
|
+
const VISIBLE_DELTA_EVENTS = new Set([
|
|
71
|
+
"step_delta",
|
|
72
|
+
"step_chunk",
|
|
73
|
+
"chunk",
|
|
74
|
+
"agent_turn_delta",
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
const INTERMEDIATE_COMPLETE_EVENTS = new Set([
|
|
78
|
+
"step_complete",
|
|
79
|
+
"agent_turn_complete",
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
const TERMINAL_COMPLETE_EVENTS = new Set(["flow_complete", "agent_complete"]);
|
|
83
|
+
|
|
84
|
+
const ERROR_EVENTS = new Set([
|
|
85
|
+
"step_error",
|
|
86
|
+
"flow_error",
|
|
87
|
+
"agent_error",
|
|
88
|
+
"dispatch_error",
|
|
89
|
+
"error",
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
93
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
94
|
+
|
|
95
|
+
const toFiniteNumber = (value: unknown): number | undefined =>
|
|
96
|
+
typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
97
|
+
|
|
98
|
+
const getRecord = (
|
|
99
|
+
value: Record<string, unknown>,
|
|
100
|
+
key: string
|
|
101
|
+
): Record<string, unknown> | undefined => {
|
|
102
|
+
const nested = value[key];
|
|
103
|
+
return isRecord(nested) ? nested : undefined;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/** Token estimate from a character count: ~4 chars/token, floor of 1. */
|
|
107
|
+
function estimateTokensFromCharCount(charCount: number): number {
|
|
108
|
+
return charCount > 0 ? Math.max(1, Math.ceil(charCount / 4)) : 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Simple token estimate matching the dashboard heuristic: ~4 chars/token. */
|
|
112
|
+
export function estimateOutputTokens(text: string): number {
|
|
113
|
+
return estimateTokensFromCharCount(text.trim().length);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function calculateTokensPerSecond(
|
|
117
|
+
outputTokens: number,
|
|
118
|
+
durationMs: number | undefined
|
|
119
|
+
): number | undefined {
|
|
120
|
+
if (
|
|
121
|
+
outputTokens <= 0 ||
|
|
122
|
+
durationMs === undefined ||
|
|
123
|
+
durationMs < THROUGHPUT_MIN_DURATION_MS
|
|
124
|
+
) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
return outputTokens / (durationMs / 1000);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function resolveEventType(
|
|
131
|
+
eventType: string,
|
|
132
|
+
payload: Record<string, unknown>
|
|
133
|
+
): string {
|
|
134
|
+
return typeof payload.type === "string" ? payload.type : eventType;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function getTextDelta(payload: Record<string, unknown>): string {
|
|
138
|
+
if (typeof payload.text === "string") return payload.text;
|
|
139
|
+
if (typeof payload.delta === "string") return payload.delta;
|
|
140
|
+
if (typeof payload.content === "string") return payload.content;
|
|
141
|
+
if (typeof payload.chunk === "string") return payload.chunk;
|
|
142
|
+
return "";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Only count visible model output.
|
|
147
|
+
*
|
|
148
|
+
* For `agent_turn_delta`, count only a contentType of exactly `text` as
|
|
149
|
+
* visible, matching the client renderer (which appends streaming assistant
|
|
150
|
+
* text only when `contentType === "text"`); `thinking`, `tool_input`, any
|
|
151
|
+
* other value, and a missing contentType are ignored so throughput never
|
|
152
|
+
* includes deltas the chat UI doesn't render.
|
|
153
|
+
*
|
|
154
|
+
* For `step_delta` / `step_chunk`, skip tool and context steps — those carry
|
|
155
|
+
* tool I/O, not model-visible text — mirroring the widget's own renderer.
|
|
156
|
+
*/
|
|
157
|
+
function isVisibleTextDelta(
|
|
158
|
+
type: string,
|
|
159
|
+
payload: Record<string, unknown>
|
|
160
|
+
): boolean {
|
|
161
|
+
if (type === "step_delta" || type === "step_chunk") {
|
|
162
|
+
return payload.stepType !== "tool" && payload.executionType !== "context";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (type !== "agent_turn_delta") return true;
|
|
166
|
+
|
|
167
|
+
const contentType =
|
|
168
|
+
typeof payload.contentType === "string"
|
|
169
|
+
? payload.contentType
|
|
170
|
+
: typeof payload.content_type === "string"
|
|
171
|
+
? payload.content_type
|
|
172
|
+
: undefined;
|
|
173
|
+
|
|
174
|
+
return contentType === "text";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Extract exact output tokens from a variety of usage payload shapes. */
|
|
178
|
+
function getOutputTokens(payload: Record<string, unknown>): number | undefined {
|
|
179
|
+
const result = getRecord(payload, "result");
|
|
180
|
+
const candidates = [
|
|
181
|
+
getRecord(payload, "tokens"),
|
|
182
|
+
getRecord(payload, "totalTokens"),
|
|
183
|
+
result ? getRecord(result, "tokens") : undefined,
|
|
184
|
+
getRecord(payload, "usage"),
|
|
185
|
+
result ? getRecord(result, "usage") : undefined,
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
for (const candidate of candidates) {
|
|
189
|
+
if (!candidate) continue;
|
|
190
|
+
const outputTokens =
|
|
191
|
+
toFiniteNumber(candidate.output) ??
|
|
192
|
+
toFiniteNumber(candidate.outputTokens) ??
|
|
193
|
+
toFiniteNumber(candidate.completionTokens);
|
|
194
|
+
if (outputTokens !== undefined) return outputTokens;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
toFiniteNumber(payload.outputTokens) ??
|
|
199
|
+
toFiniteNumber(payload.completionTokens) ??
|
|
200
|
+
(result
|
|
201
|
+
? (toFiniteNumber(result.outputTokens) ??
|
|
202
|
+
toFiniteNumber(result.completionTokens))
|
|
203
|
+
: undefined)
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Extract provider execution time (ms) from a variety of payload shapes. */
|
|
208
|
+
function getExecutionTimeMs(
|
|
209
|
+
payload: Record<string, unknown>
|
|
210
|
+
): number | undefined {
|
|
211
|
+
const result = getRecord(payload, "result");
|
|
212
|
+
return (
|
|
213
|
+
toFiniteNumber(payload.executionTime) ??
|
|
214
|
+
toFiniteNumber(payload.executionTimeMs) ??
|
|
215
|
+
toFiniteNumber(payload.execution_time) ??
|
|
216
|
+
toFiniteNumber(payload.duration) ??
|
|
217
|
+
(result
|
|
218
|
+
? (toFiniteNumber(result.executionTime) ??
|
|
219
|
+
toFiniteNumber(result.executionTimeMs))
|
|
220
|
+
: undefined)
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function defaultClock(): number {
|
|
225
|
+
if (
|
|
226
|
+
typeof performance !== "undefined" &&
|
|
227
|
+
typeof performance.now === "function"
|
|
228
|
+
) {
|
|
229
|
+
return performance.now();
|
|
230
|
+
}
|
|
231
|
+
return Date.now();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Tracks output throughput across one streamed run at a time. Feed it every SSE
|
|
236
|
+
* event via {@link processEvent}; read the current state via {@link getMetric}.
|
|
237
|
+
*/
|
|
238
|
+
export class ThroughputTracker {
|
|
239
|
+
private metric: ThroughputMetric = { status: "idle" };
|
|
240
|
+
private run: ThroughputRunStats | null = null;
|
|
241
|
+
private readonly now: () => number;
|
|
242
|
+
|
|
243
|
+
constructor(now: () => number = defaultClock) {
|
|
244
|
+
this.now = now;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
getMetric(): ThroughputMetric {
|
|
248
|
+
// While a run is streaming, recompute the elapsed window (and rate) from the
|
|
249
|
+
// clock on each read. The view polls this every ~200ms, so without this a
|
|
250
|
+
// pause between deltas would keep showing the stale rate from the last
|
|
251
|
+
// event; recomputing lets the displayed tok/s decay as time passes.
|
|
252
|
+
const run = this.run;
|
|
253
|
+
if (
|
|
254
|
+
run &&
|
|
255
|
+
this.metric.status === "running" &&
|
|
256
|
+
run.firstDeltaAt !== undefined &&
|
|
257
|
+
this.metric.outputTokens !== undefined
|
|
258
|
+
) {
|
|
259
|
+
const durationMs = this.now() - run.firstDeltaAt;
|
|
260
|
+
return {
|
|
261
|
+
...this.metric,
|
|
262
|
+
durationMs,
|
|
263
|
+
tokensPerSecond: calculateTokensPerSecond(
|
|
264
|
+
this.metric.outputTokens,
|
|
265
|
+
durationMs
|
|
266
|
+
),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return this.metric;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Reset back to idle (e.g. when the chat is cleared). */
|
|
273
|
+
reset(): void {
|
|
274
|
+
this.run = null;
|
|
275
|
+
this.metric = { status: "idle" };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private startRun(now: number): void {
|
|
279
|
+
this.run = {
|
|
280
|
+
startedAt: now,
|
|
281
|
+
visibleCharCount: 0,
|
|
282
|
+
exactOutputTokens: 0,
|
|
283
|
+
};
|
|
284
|
+
this.metric = { status: "running" };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
processEvent(eventType: string, payload: unknown): void {
|
|
288
|
+
if (!isRecord(payload)) {
|
|
289
|
+
// Non-object payloads can still signal lifecycle (e.g. bare "error").
|
|
290
|
+
if (ERROR_EVENTS.has(eventType) && this.run) {
|
|
291
|
+
this.run = null;
|
|
292
|
+
this.metric = { status: "error" };
|
|
293
|
+
}
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const type = resolveEventType(eventType, payload);
|
|
298
|
+
const now = this.now();
|
|
299
|
+
|
|
300
|
+
if (REQUEST_START_EVENTS.has(type)) {
|
|
301
|
+
// New request — start fresh, discarding any incomplete prior run.
|
|
302
|
+
this.startRun(now);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (STEP_START_EVENTS.has(type)) {
|
|
307
|
+
// Mid-request step marker — only begin a run if none is active.
|
|
308
|
+
if (!this.run) this.startRun(now);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (VISIBLE_DELTA_EVENTS.has(type)) {
|
|
313
|
+
if (!isVisibleTextDelta(type, payload)) return;
|
|
314
|
+
const text = getTextDelta(payload);
|
|
315
|
+
if (!text) return;
|
|
316
|
+
|
|
317
|
+
// Lazily start a run if the stream began without a recognized start event.
|
|
318
|
+
if (!this.run) this.startRun(now);
|
|
319
|
+
const stats = this.run!;
|
|
320
|
+
|
|
321
|
+
stats.firstDeltaAt ??= now;
|
|
322
|
+
stats.visibleCharCount += text.length;
|
|
323
|
+
|
|
324
|
+
// Add the live char estimate of the CURRENT (not-yet-completed) step on
|
|
325
|
+
// top of any exact usage already booked from completed steps, so the
|
|
326
|
+
// count only grows — it never drops back to a bare estimate mid-run.
|
|
327
|
+
const outputTokens =
|
|
328
|
+
stats.exactOutputTokens +
|
|
329
|
+
estimateTokensFromCharCount(stats.visibleCharCount);
|
|
330
|
+
const durationMs = now - stats.firstDeltaAt;
|
|
331
|
+
this.metric = {
|
|
332
|
+
status: "running",
|
|
333
|
+
tokensPerSecond: calculateTokensPerSecond(outputTokens, durationMs),
|
|
334
|
+
outputTokens,
|
|
335
|
+
durationMs,
|
|
336
|
+
source: stats.exactOutputTokens > 0 ? "usage" : "estimate",
|
|
337
|
+
};
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (INTERMEDIATE_COMPLETE_EVENTS.has(type)) {
|
|
342
|
+
// Accumulate exact usage but keep the run going — these fire per
|
|
343
|
+
// step/turn, not at the end of the whole run.
|
|
344
|
+
if (!this.run) return;
|
|
345
|
+
const stats = this.run;
|
|
346
|
+
const exact = getOutputTokens(payload);
|
|
347
|
+
if (exact !== undefined) {
|
|
348
|
+
stats.exactOutputTokens += exact;
|
|
349
|
+
// This step's visible text is now represented exactly by provider
|
|
350
|
+
// usage — drop it from the running char estimate so the two don't
|
|
351
|
+
// double-count once the next step starts streaming.
|
|
352
|
+
stats.visibleCharCount = 0;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const usingExact = stats.exactOutputTokens > 0;
|
|
356
|
+
const outputTokens =
|
|
357
|
+
stats.exactOutputTokens +
|
|
358
|
+
estimateTokensFromCharCount(stats.visibleCharCount);
|
|
359
|
+
const durationMs = this.resolveDuration(stats, payload, now);
|
|
360
|
+
this.metric = {
|
|
361
|
+
status: "running",
|
|
362
|
+
tokensPerSecond: calculateTokensPerSecond(outputTokens, durationMs),
|
|
363
|
+
outputTokens,
|
|
364
|
+
durationMs,
|
|
365
|
+
source: usingExact ? "usage" : "estimate",
|
|
366
|
+
};
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (TERMINAL_COMPLETE_EVENTS.has(type)) {
|
|
371
|
+
if (!this.run) return;
|
|
372
|
+
const stats = this.run;
|
|
373
|
+
// Prefer exact output tokens from this terminal event, else accumulated
|
|
374
|
+
// usage from intermediate completes, else the text estimate.
|
|
375
|
+
const terminalExact = getOutputTokens(payload);
|
|
376
|
+
// Prefer a total from the terminal event; otherwise sum exact usage booked
|
|
377
|
+
// from intermediate completes plus the estimate of any still-streamed text
|
|
378
|
+
// not yet covered by a usage report.
|
|
379
|
+
const outputTokens =
|
|
380
|
+
terminalExact ??
|
|
381
|
+
stats.exactOutputTokens +
|
|
382
|
+
estimateTokensFromCharCount(stats.visibleCharCount);
|
|
383
|
+
const source: ThroughputMetricSource =
|
|
384
|
+
terminalExact !== undefined || stats.exactOutputTokens > 0
|
|
385
|
+
? "usage"
|
|
386
|
+
: "estimate";
|
|
387
|
+
const durationMs = this.resolveDuration(stats, payload, now);
|
|
388
|
+
this.metric = {
|
|
389
|
+
status: "complete",
|
|
390
|
+
tokensPerSecond: calculateTokensPerSecond(outputTokens, durationMs),
|
|
391
|
+
outputTokens,
|
|
392
|
+
durationMs,
|
|
393
|
+
source,
|
|
394
|
+
};
|
|
395
|
+
this.run = null;
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (ERROR_EVENTS.has(type)) {
|
|
400
|
+
if (!this.run) return;
|
|
401
|
+
this.run = null;
|
|
402
|
+
this.metric = { status: "error" };
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Prefer the streamed visible-output window when it clears the minimum
|
|
408
|
+
* threshold; otherwise fall back to provider execution time, then to the
|
|
409
|
+
* whole-request duration.
|
|
410
|
+
*/
|
|
411
|
+
private resolveDuration(
|
|
412
|
+
stats: ThroughputRunStats,
|
|
413
|
+
payload: Record<string, unknown>,
|
|
414
|
+
now: number
|
|
415
|
+
): number {
|
|
416
|
+
const streamedDurationMs =
|
|
417
|
+
stats.firstDeltaAt !== undefined ? now - stats.firstDeltaAt : undefined;
|
|
418
|
+
if (
|
|
419
|
+
streamedDurationMs !== undefined &&
|
|
420
|
+
streamedDurationMs >= THROUGHPUT_MIN_DURATION_MS
|
|
421
|
+
) {
|
|
422
|
+
return streamedDurationMs;
|
|
423
|
+
}
|
|
424
|
+
const executionTimeMs = getExecutionTimeMs(payload);
|
|
425
|
+
return executionTimeMs ?? now - stats.startedAt;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2025 mcp-b contributors
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Vendored: `@mcp-b/smart-dom-reader`
|
|
2
|
+
|
|
3
|
+
This directory contains a **vendored copy** of [`@mcp-b/smart-dom-reader`](https://github.com/WebMCP-org/npm-packages/tree/main/packages/smart-dom-reader)
|
|
4
|
+
(v2.3.1, MIT, © 2025 mcp-b contributors), consumed only by the optional
|
|
5
|
+
`@runtypelabs/persona/smart-dom-reader` entry point (`src/smart-dom-reader.ts`)
|
|
6
|
+
and the pure mapper (`src/utils/smart-dom-adapter.ts`, type-only).
|
|
7
|
+
|
|
8
|
+
## Why vendored instead of a dependency
|
|
9
|
+
|
|
10
|
+
Every published version of the package (2.3.1, 2.3.2, 3.0.0) is **mis-published**:
|
|
11
|
+
its `package.json` declares
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
but the build tool (`vp pack`, vite-plus/rolldown) only emits `dist/index.mjs` +
|
|
20
|
+
`dist/index.d.mts`. The files referenced by `package.json` are **absent from the
|
|
21
|
+
tarball**, so the package cannot be imported by name in Node or any bundler
|
|
22
|
+
(`ERR_MODULE_NOT_FOUND`), and TypeScript cannot resolve its types. This affects
|
|
23
|
+
the package itself and every downstream consumer, so making it an optional peer
|
|
24
|
+
dependency would ship a feature that no integrator could actually load.
|
|
25
|
+
|
|
26
|
+
Vendoring the built artifact into this opt-in entry sidesteps the broken module
|
|
27
|
+
resolution entirely: the code is bundled into `dist/smart-dom-reader.{js,cjs}`
|
|
28
|
+
and never touches a package resolver. Consumers who never import the
|
|
29
|
+
`/smart-dom-reader` entry pay nothing.
|
|
30
|
+
|
|
31
|
+
> **Follow-up:** raise the packaging bug with the upstream maintainer. Once a
|
|
32
|
+
> corrected release exists, this vendor dir can be replaced with a normal
|
|
33
|
+
> optional peer dependency (see the original plan).
|
|
34
|
+
|
|
35
|
+
## Files
|
|
36
|
+
|
|
37
|
+
- `index.js` — upstream `dist/index.mjs`, with two local edits (see header comment).
|
|
38
|
+
- `index.d.ts` — upstream `dist/index.d.mts` verbatim (sourceMappingURL stripped).
|
|
39
|
+
- `LICENSE` — upstream MIT license.
|
|
40
|
+
|
|
41
|
+
## Local modifications to `index.js`
|
|
42
|
+
|
|
43
|
+
The **only** changes from upstream `dist/index.mjs` are:
|
|
44
|
+
|
|
45
|
+
1. Removed the top-level `import { createRequire } from "node:module";` — a
|
|
46
|
+
Node-only builtin that breaks browser bundling.
|
|
47
|
+
2. Replaced `var __require = createRequire(import.meta.url);` with
|
|
48
|
+
`var __require = void 0;`. `__require` is referenced only inside a guarded
|
|
49
|
+
`typeof __require === "function"` Node fallback in `resolveSmartDomReader()`;
|
|
50
|
+
the browser path (`typeof window !== "undefined"`) returns before reaching it,
|
|
51
|
+
so this is a runtime no-op in the browser.
|
|
52
|
+
|
|
53
|
+
## How to update
|
|
54
|
+
|
|
55
|
+
1. `npm pack @mcp-b/smart-dom-reader@<version>` and extract the tarball.
|
|
56
|
+
2. Copy `dist/index.mjs` → `index.js` and `dist/index.d.mts` → `index.d.ts`.
|
|
57
|
+
3. Re-apply the two edits above (strip the `node:module` import; neutralize
|
|
58
|
+
`__require`) and the provenance headers. Strip trailing `sourceMappingURL`
|
|
59
|
+
comments.
|
|
60
|
+
4. Copy the upstream `LICENSE`.
|
|
61
|
+
5. Re-run `pnpm --filter @runtypelabs/persona build typecheck test:run`.
|