@tangle-network/charts 1.0.1 → 2.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/README.md +1 -1
- package/dist/{flow-trace-DBdQtcQe.d.ts → flow-trace-CF4Q07cA.d.ts} +4 -3
- package/dist/{timeline-B7c-cede.js → flow-trace-k097Ukvs.js} +10 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +15 -18
- package/dist/react/flow-waterfall.css +11 -9
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +78 -46
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -337,7 +337,7 @@ const trace = otlpFlowTrace(spans, { price: catalogPrice(await (await fetch(cata
|
|
|
337
337
|
|
|
338
338
|
`defaultView="agents"` opens the agent timeline: one row per agent with its model, thinking and tool marks, hatched waiting while it delegates, elbow connectors to each agent it spawned and back, retry and failure glyphs, and a headline with the measured speedup over sequential (agent busy time over wall time). Expanding an agent opens the span view with that agent unfolded. The span view virtualizes rows (5,000 spans scroll at 58+ fps in headless Chromium), pins ancestors while their children scroll, and labels each bar with its duration and recorded cost. Hover shows a span card; click pins it and zooms the axis to a span too narrow to read. `onSelect(span)` hears each opened row and pinned bar, so a host can open its own drill-down; `markers={[{ at, label, tone }]}` draws labelled instants, such as releases, across the rows. Give an agent span an `outcome` (`{ tone, label }`) and its lane takes the outcome color and the header a legend. `detail` becomes the agent's subtitle. The card is a table of what the row recorded, every value with its unit: model, duration, tokens in, out and cached (the span's `promptTokens`, `completionTokens` and `cachedTokens`, else the sum of its children's), LLM latency (median and p95 of its model calls), cost, LLM calls, tool calls by tool (the name before " · "), failed steps, and retries and restarts (`retryOf`, `marks` of kind `restart`); a value nobody recorded is left out. `facts` (`[{ label, value }]`) adds the host's rows after it. The card opens at the pointer, above or below its row, whichever side fits the viewport. `onTimeChange` reports where the reader scrubbed (null when live), and markers double as an event track (click, N or P to jump). A partly priced trace shows its floor and coverage (`costCoverage`), and `billedUsd` shows the real charge beside the list price. Ctrl/Cmd+wheel or `+`/`-`/`0` zoom, Shift+wheel pans, the scrub track under the controls shows the whole trace, and arrow keys walk the tree.
|
|
339
339
|
|
|
340
|
-
Live views append spans as they arrive: `traces watch <id|run-dir> --format jsonl --spans` emits a `traces.stream.span` event per new or changed span, and `otlpFlowTrace` keeps the last span per `span_id`. Running agent spans grow to the clock between deltas without React renders. `activityAt` supplies trace-relative event times and `medianEventGapMs` preserves producer cadence before tail truncation; without it, child-step activity supplies the samples. Silence beyond five minutes and three median event gaps hatches the tail and names the quiet duration. The Errors filter expands matching agents and steps; its rug locates each error, marking unreported times separately. Containers below 600px use stacked 44px rows and a Key disclosure. Search supports Cmd/Ctrl+F and keeps ancestor context. Missing cost and authoritative LLM counts stay Unknown; a cost priced from a catalog is labelled list price. Invalid intervals retain a per-row `—` timing mark. The static phase waterfall remains a separate chart for disjoint phases.
|
|
340
|
+
Live views append spans as they arrive: `traces watch <id|run-dir> --format jsonl --spans` emits a `traces.stream.span` event per new or changed span, and `otlpFlowTrace` keeps the last span per `span_id`. Running agent spans grow to the clock between deltas without React renders. `activityAt` supplies trace-relative event times and `medianEventGapMs` preserves producer cadence before tail truncation; without it, child-step activity supplies the samples. Silence beyond five minutes and three median event gaps hatches the tail and names the quiet duration. The Errors filter expands matching agents and steps; its rug locates each error, marking unreported times separately. Containers below 600px use stacked 44px rows and a Key disclosure. Search supports Cmd/Ctrl+F and keeps ancestor context. Token totals are optional: omit unknown counts, retain measured zero, and guard optional token fields when reading a trace. Missing, partial and nonfinite usage displays a dash, including during playback. Missing cost and authoritative LLM counts stay Unknown; a cost priced from a catalog is labelled list price. Invalid intervals retain a per-row `—` timing mark. The static phase waterfall remains a separate chart for disjoint phases.
|
|
341
341
|
|
|
342
342
|
`scripts/flow-waterfall-demo.mjs` serves the demo routes (reference-shaped fixtures, recorded runs, a synthetic load trace and OTLP files from `FW_DATA_DIR`); `scripts/flow-waterfall-capture.mjs` captures frames, hover cards, fps, first paint and recordings, and with `FW_LIVE_RELAY` records a live session through `scripts/flow-waterfall-live-relay.mjs` with every span's event-to-pixel time.
|
|
343
343
|
|
|
@@ -368,7 +368,7 @@ interface FlowPause {
|
|
|
368
368
|
/** The full reason, shown on hover and focus of the label rather than printed across the rows. */
|
|
369
369
|
detail?: string;
|
|
370
370
|
}
|
|
371
|
-
/**
|
|
371
|
+
/** An optional host outcome; absent outcomes use the canonical lifecycle status. */
|
|
372
372
|
interface FlowOutcome {
|
|
373
373
|
/** `live` is a running agent: the one running hue the host uses for every live mark. */
|
|
374
374
|
tone: 'ok' | 'live' | 'neutral' | 'failed' | 'warn';
|
|
@@ -391,8 +391,9 @@ interface FlowMarker {
|
|
|
391
391
|
interface FlowTrace {
|
|
392
392
|
spans: FlowSpan[];
|
|
393
393
|
totalMs: number;
|
|
394
|
-
|
|
395
|
-
|
|
394
|
+
/** Recorded totals only; absent or invalid values remain unknown, including during playback. */
|
|
395
|
+
promptTokens?: number;
|
|
396
|
+
completionTokens?: number;
|
|
396
397
|
/** Computed when per-token pricing is supplied. */
|
|
397
398
|
costUsd?: number;
|
|
398
399
|
/** `list`: priced from a model catalog's list prices, not a bill; the summary says so. */
|
|
@@ -1198,4 +1198,13 @@ function axisTicks(t0, t1, px, left, right, label) {
|
|
|
1198
1198
|
return fit([t0, t1]) ? [t0, t1] : [t0];
|
|
1199
1199
|
}
|
|
1200
1200
|
//#endregion
|
|
1201
|
-
|
|
1201
|
+
//#region src/flow-trace.ts
|
|
1202
|
+
/** Internal quantity boundaries shared by construction, aggregation and presentation. */
|
|
1203
|
+
const finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1204
|
+
const reportedCount = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
1205
|
+
function sumReportedCounts(values) {
|
|
1206
|
+
if (!values.length || values.some((value) => reportedCount(value) === void 0)) return void 0;
|
|
1207
|
+
return reportedCount(values.reduce((sum, value) => sum + value, 0));
|
|
1208
|
+
}
|
|
1209
|
+
//#endregion
|
|
1210
|
+
export { rect as $, intervalGap as A, tierSummary as B, isRefusal as C, drawable as D, costText as E, pts as F, MARKER_SHAPES as G, usd as H, rateDataGap as I, hatchPath as J, circle as K, rateIntervalGap as L, kn as M, pct as N, duration as O, perPass as P, polyline as Q, rateOrder as R, markName as S, TIERS_BEST_FIRST as T, usdTick as U, tierTotal as V, table as W, line as X, hbar as Y, markerPath as Z, drawMarks as _, CHECKER_HUES as a, esc as at, groupRowHeight as b, OUTCOME_COLORS as c, plural as ct, checkerColor as d, wrap as dt, svgRoot as et, EVENTS_READ as f, drawGroups as g, drawBands as h, timeline as i, capitalize as it, intervalName as j, estimateNote as k, OUTCOME_ORDER as l, textBlock as lt, MARK_VERDICTS as m, reportedCount as n, titled as nt, ENDING_COLORS as o, fitLines as ot, MARK_HEADS as p, hatch as q, sumReportedCounts as r, wideWidth as rt, ENDING_ORDER as s, listJoin as st, finiteNumber as t, text as tt, checkerClass as u, textWidth as ut, eventsGap as v, METHOD_NAME as w, markLabelRows as x, eventsTable as y, tierAtLeast as z };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as BreakdownInput, B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, H as Tier, I as Interval, L as IntervalMethod, M as CostBasis, N as Estimate, O as YTarget, P as Exclusion, R as MatrixCell, S as ChartMark, T as MarkerShape, U as TierCounts, V as Setup, W as isRefusal, _ as checkerColor, a as TimelineLane, b as BandKind, c as TimelineTick, d as ENDING_COLORS, f as ENDING_ORDER, g as TickOutcome, h as RunEnding, i as FlowTrace, j as ComparisonRow, k as YTick, l as timeline, m as OUTCOME_ORDER, n as FlowOutcome, o as TimelineOptions, p as OUTCOME_COLORS, r as FlowSpan, s as TimelineSpan, t as FlowMarker, u as CHECKER_HUES, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph, z as RateRow } from "./flow-trace-
|
|
1
|
+
import { A as BreakdownInput, B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, H as Tier, I as Interval, L as IntervalMethod, M as CostBasis, N as Estimate, O as YTarget, P as Exclusion, R as MatrixCell, S as ChartMark, T as MarkerShape, U as TierCounts, V as Setup, W as isRefusal, _ as checkerColor, a as TimelineLane, b as BandKind, c as TimelineTick, d as ENDING_COLORS, f as ENDING_ORDER, g as TickOutcome, h as RunEnding, i as FlowTrace, j as ComparisonRow, k as YTick, l as timeline, m as OUTCOME_ORDER, n as FlowOutcome, o as TimelineOptions, p as OUTCOME_COLORS, r as FlowSpan, s as TimelineSpan, t as FlowMarker, u as CHECKER_HUES, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph, z as RateRow } from "./flow-trace-CF4Q07cA.js";
|
|
2
2
|
//#region src/block.d.ts
|
|
3
3
|
interface RenderFigureOptions {
|
|
4
4
|
/** Chart uses a closed data disclosure; plot keeps only the SVG and accessible data. */
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as rect, A as intervalGap, B as tierSummary, C as isRefusal, D as drawable, E as costText, F as pts, G as MARKER_SHAPES, H as usd, I as rateDataGap, K as circle, L as rateIntervalGap, M as kn, N as pct, O as duration, P as perPass, Q as polyline, R as rateOrder, T as TIERS_BEST_FIRST, U as usdTick, V as tierTotal, W as table, X as line, Y as hbar, Z as markerPath, _ as drawMarks, a as CHECKER_HUES, at as esc, b as groupRowHeight, c as OUTCOME_COLORS, ct as plural, d as checkerColor, dt as wrap, et as svgRoot, f as EVENTS_READ, g as drawGroups, h as drawBands, i as timeline, it as capitalize, j as intervalName, k as estimateNote, l as OUTCOME_ORDER, lt as textBlock, m as MARK_VERDICTS, n as reportedCount, nt as titled, o as ENDING_COLORS, ot as fitLines, q as hatch, r as sumReportedCounts, rt as wideWidth, s as ENDING_ORDER, st as listJoin, t as finiteNumber, tt as text, u as checkerClass, ut as textWidth, v as eventsGap, w as METHOD_NAME, x as markLabelRows, y as eventsTable, z as tierAtLeast } from "./flow-trace-k097Ukvs.js";
|
|
2
2
|
//#region src/block.ts
|
|
3
3
|
/**
|
|
4
4
|
* The figure block: one markup for every figure on every surface.
|
|
@@ -1138,7 +1138,7 @@ function plot$1(costed, unplaced, drawLine, o) {
|
|
|
1138
1138
|
/** The figure block and mark styles (src/figure.css), for documents that inline their CSS. */
|
|
1139
1139
|
const figureCss = "/* @tangle-network/charts — figure block and mark styles.\n *\n * Every colour is a --chart-* token from @tangle-network/brand tokens.css, so a\n * figure follows the page theme. A standalone document without brand tokens\n * embeds `chartTokensCss` first. Fonts fall back to Inter and JetBrains Mono.\n *\n * Each figure carries a 720 px and a 358 px render. The swap follows the\n * figure's own width (a container query), so a narrow column shows the phone\n * render even on a wide screen. */\n\n.tgc-figure {\n margin: 32px 0 0;\n padding: 0;\n max-width: 720px;\n color: var(--chart-ink);\n font-family: var(--font-sans, Inter, ui-sans-serif, system-ui, sans-serif);\n}\n\n.tgc-finding {\n margin: 0 0 8px;\n font-size: 18px;\n line-height: 24px;\n font-weight: 600;\n color: var(--chart-ink);\n}\n\n.tgc-noteref {\n margin-left: 2px;\n font-size: 14px;\n font-weight: 400;\n color: var(--chart-ink-muted);\n text-decoration: none;\n font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace);\n}\n.tgc-noteref:hover,\n.tgc-noteref:focus-visible {\n color: var(--chart-accent);\n text-decoration: underline;\n}\n\n.tgc-lede {\n margin: 0 0 16px;\n font-size: 14px;\n line-height: 20px;\n color: var(--text-secondary, var(--chart-ink));\n}\n\n.tgc-plot {\n container-type: inline-size;\n}\n.tgc-wide svg,\n.tgc-narrow svg {\n display: block;\n overflow: visible;\n}\n.tgc-narrow {\n display: none;\n}\n.tgc-narrow svg {\n max-width: 100%;\n height: auto;\n}\n@container (max-width: 719.98px) {\n .tgc-wide {\n display: none;\n }\n .tgc-narrow {\n display: block;\n }\n}\n\n.tgc-read {\n margin: 8px 0 0;\n font-size: 14px;\n line-height: 20px;\n color: var(--chart-ink-muted);\n}\n\n.tgc-data {\n margin: 8px 0 0;\n font-size: 14px;\n line-height: 20px;\n}\n.tgc-data > summary {\n cursor: pointer;\n color: var(--chart-ink-muted);\n width: fit-content;\n}\n.tgc-data > summary:hover {\n color: var(--chart-ink);\n}\n.tgc-data[open] > summary {\n margin-bottom: 8px;\n}\n\n.tgc-scroll {\n overflow-x: auto;\n}\n.tgc-scroll:focus-visible {\n outline: 2px solid var(--chart-accent);\n outline-offset: 2px;\n}\n\n.tgc-table {\n border-collapse: collapse;\n min-width: 100%;\n font-size: 14px;\n line-height: 20px;\n color: var(--chart-ink);\n /* A host page's table rules must not restyle the figure's table. */\n border: 0;\n border-radius: 0;\n background: none;\n}\n.tgc-table caption {\n text-align: left;\n padding: 0 0 8px;\n color: var(--chart-ink-muted);\n}\n.tgc-table th,\n.tgc-table td {\n padding: 8px 12px 8px 0;\n border-bottom: 1px solid var(--chart-rule);\n text-align: left;\n vertical-align: top;\n font-weight: 400;\n font-size: inherit;\n color: inherit;\n background: none;\n text-transform: none;\n letter-spacing: normal;\n}\n.tgc-table thead th {\n color: var(--chart-ink-muted);\n white-space: nowrap;\n}\n.tgc-table tbody th {\n position: sticky;\n left: 0;\n background: var(--chart-canvas, var(--depth-1, transparent));\n}\n.tgc-table .tgc-n {\n font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n.tgc-table thead .tgc-n {\n font-family: inherit;\n}\n.tgc-table tbody tr:hover {\n background: var(--chart-shade);\n}\n.tgc-narrow-table .tgc-table {\n display: block;\n overflow-x: auto;\n}\n\n.tgc-notes {\n margin: 16px 0 0;\n padding-left: 20px;\n font-size: 14px;\n line-height: 20px;\n color: var(--chart-ink-muted);\n}\n.tgc-notes li {\n margin: 0 0 8px;\n padding-left: 4px;\n}\n.tgc-notes li:target {\n color: var(--chart-ink);\n}\n.tgc-backlink {\n color: var(--chart-ink-muted);\n text-decoration: none;\n}\n\n.tgc-refusals {\n margin: 8px 0 0;\n padding-left: 20px;\n font-size: 14px;\n line-height: 22px;\n color: var(--chart-ink-muted);\n}\n\n/* Marks */\n.tgc-svg {\n font-family: var(--font-sans, Inter, ui-sans-serif, system-ui, sans-serif);\n}\n.tgc-svg .tgc-num {\n font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace);\n font-variant-numeric: tabular-nums;\n}\n.tgc-ink {\n fill: var(--chart-ink);\n}\n.tgc-ink-muted {\n fill: var(--chart-ink-muted);\n}\n.tgc-bar,\n.tgc-ci,\n.tgc-pt-front {\n fill: var(--chart-accent);\n}\n.tgc-gridline {\n stroke: var(--chart-grid);\n stroke-width: 1;\n}\n.tgc-rule {\n stroke: var(--chart-rule);\n stroke-width: 1;\n}\n.tgc-axis,\n.tgc-zero {\n stroke: var(--chart-muted);\n stroke-width: 1;\n}\n.tgc-whisker,\n.tgc-tick {\n stroke: var(--chart-ink);\n stroke-width: 2;\n stroke-linecap: round;\n}\n.tgc-threshold {\n stroke: var(--chart-ink);\n stroke-width: 1.5;\n stroke-dasharray: 3 3;\n}\n.tgc-front-line {\n fill: none;\n stroke: var(--chart-ink);\n stroke-width: 1.5;\n stroke-linejoin: round;\n}\n.tgc-pt,\n.tgc-open {\n fill: none;\n stroke: var(--chart-muted);\n stroke-width: 1.5;\n}\n.tgc-pt-est {\n fill: none;\n stroke: var(--chart-accent);\n stroke-width: 2;\n}\n.tgc-pt-ci,\n.tgc-strike {\n stroke: var(--chart-muted);\n stroke-width: 1.5;\n}\n.tgc-leader {\n stroke: var(--chart-muted);\n stroke-width: 1;\n}\n.tgc-ci-exact,\n.tgc-dot {\n fill: var(--chart-muted);\n}\n.tgc-dashed {\n stroke-dasharray: 2 2;\n}\n.tgc-tier-c {\n fill: var(--chart-tier-c);\n}\n.tgc-tier-b {\n fill: var(--chart-tier-b);\n}\n.tgc-tier-a {\n fill: var(--chart-tier-a);\n}\n.tgc-tier-s {\n fill: var(--chart-tier-s);\n}\n/* Neutral tint for counts: never the accent, capped at half strength so ink keeps 6:1. */\n.tgc-tint {\n fill: color-mix(in srgb, var(--chart-muted) calc(var(--s, 0) * 50%), transparent);\n}\n\n@media print {\n .tgc-plot {\n container-type: normal;\n }\n .tgc-wide {\n display: block;\n }\n .tgc-narrow {\n display: none;\n }\n .tgc-wide svg {\n width: 100%;\n height: auto;\n }\n .tgc-figure {\n break-inside: avoid;\n }\n}\n\n/* Chart-led surfaces keep supporting text in the native disclosure. */\n.tgc-figure-chart { max-width: none; margin: 0; }\n.tgc-figure-chart .tgc-wide svg { width: 100%; height: auto; }\n.tgc-wide:has(.tgc-vertical), .tgc-narrow:has(.tgc-vertical) { overflow-x: auto; }\n.tgc-vertical { width: 100%; }\n.tgc-narrow .tgc-vertical { max-width: none; }\n.tgc-column-hit { fill: transparent; stroke: transparent; stroke-width: 2; }\n.tgc-column:focus-visible { outline: none; }\n.tgc-column:focus-visible .tgc-column-hit { stroke: var(--chart-accent); }\n.tgc-vertical-bar { fill: var(--chart-accent); }\n/* Identity assets keep their approved colors on a white plate in either theme. */\n.tgc-logo-plate { fill: #fff; stroke: var(--chart-rule); }\n.tgc-column-tip { opacity: 0; pointer-events: none; }\n.tgc-tip-bg { fill: var(--chart-ink); }\n.tgc-tip-ink { fill: var(--chart-canvas); }\n\n.tgc-series-line { fill: none; stroke: var(--tgc-series, var(--chart-accent)); stroke-width: 2; stroke-linejoin: round; }\n.tgc-series-point, .tgc-series-key { fill: var(--tgc-series, var(--chart-accent)); stroke: var(--chart-canvas); stroke-width: 2; }\n\n/* Semantic states stay tied to Brand status tokens. No-data has its own neutral mark. */\n.tgc-heat-pass { fill: color-mix(in srgb, var(--chart-pass, var(--status-running)) 19%, var(--chart-canvas)); stroke: var(--chart-pass, var(--status-running)); stroke-width: .8; }\n.tgc-heat-breach { fill: color-mix(in srgb, var(--chart-breach, var(--status-error)) 17%, var(--chart-canvas)); stroke: var(--chart-breach, var(--status-error)); stroke-width: .8; }\n.tgc-heat-no-data { fill: var(--chart-grid); stroke: var(--chart-rule); stroke-width: .8; }\n.tgc-heat-label { fill: var(--chart-ink); }\n.tgc-heat-missing { fill: var(--chart-ink-muted); }\n.tgc-wide:has(.tgc-heat-pass), .tgc-wide:has(.tgc-heat-breach) { overflow-x: auto; }\n.tgc-range-pass { fill: var(--chart-pass, var(--status-running)); }\n.tgc-range-breach { fill: var(--chart-breach, var(--status-error)); }\n.tgc-range-dot-pass { fill: var(--chart-canvas); stroke: var(--chart-pass, var(--status-running)); stroke-width: 3; }\n.tgc-range-dot-breach { fill: var(--chart-canvas); stroke: var(--chart-breach, var(--status-error)); stroke-width: 3; }\n.tgc-waterfall-bar { fill: var(--chart-accent); }\n.tgc-series-hit { fill: transparent; stroke: transparent; }\n.tgc-series-observation:focus-visible { outline: none; }\n.tgc-series-observation:focus-visible .tgc-series-hit { stroke: var(--chart-ink); stroke-width: 2; }\n.tgc-series-tip { opacity: 0; pointer-events: none; }\n\n.tgc-scalar-table { min-width: 620px; }\n.tgc-scalar-table tbody th { min-width: 180px; max-width: 260px; }\n\n/* Plot presentation keeps the same complete data available to assistive technology. */\n.tgc-accessible-data { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }\n.tgc-figure-plot .tgc-wide svg { box-sizing: content-box; width: calc(100% - 48px); padding: 0 24px; }\n/* One plot text size on the 12/14/18/32 scale; numbers differ by tabular figures, not size. */\n.tgc-figure-plot svg text { font-size: 14px; }\n/* Larger tooltip text needs padding outside the original report backdrop. */\n.tgc-figure-plot .tgc-tip-bg { stroke: var(--chart-ink); stroke-width: 24px; }\n.tgc-figure-plot .tgc-phone-plot svg { min-width: var(--tgc-phone-width); }\n.tgc-figure-plot .tgc-wide, .tgc-figure-plot .tgc-narrow { overflow-x: auto; }\n\n.tgc-figure-plot .tgc-wide-fallback svg { min-width: var(--tgc-fallback-width); }\n.tgc-phone-plot:focus-visible, .tgc-wide-fallback:focus-visible { outline: 2px solid var(--chart-accent); outline-offset: 2px; }\n\n/* Recorded events and spans. Kinds differ by stroke pattern as well as token, so they read without colour. */\n.tgc-mark { stroke-width: 1.5; }\n.tgc-mark-pass { stroke: var(--chart-pass, var(--status-running)); }\n.tgc-mark-deadline { stroke: var(--chart-ink); stroke-dasharray: 5 3; }\n.tgc-mark-died { stroke: var(--chart-breach, var(--status-error)); stroke-width: 2; }\n.tgc-mark-event { stroke: var(--chart-ink-muted); stroke-dasharray: 1 3; stroke-linecap: round; }\n.tgc-mark-release { stroke: var(--chart-ink-muted); }\n.tgc-mark-steer { stroke: var(--chart-accent); stroke-dasharray: 1 3; stroke-linecap: round; }\n.tgc-mark-ruling { stroke: var(--chart-ink); stroke-dasharray: 6 2 1 2; }\n.tgc-mark-wake { stroke: var(--chart-ink-muted); stroke-dasharray: 1 3; stroke-linecap: round; }\n.tgc-mark-head { fill: var(--chart-ink-muted); stroke: var(--chart-canvas); stroke-width: 1; }\n[data-mark=\"steer\"] .tgc-mark-head { fill: var(--chart-accent); }\n[data-mark=\"ruling\"] .tgc-mark-head { fill: var(--chart-ink); }\n/* A recorded verdict colours the rule and head: pass and fail take the status tones, unknown the muted tone. */\n.tgc-verdict-pass .tgc-mark { stroke: var(--chart-pass, var(--status-running)); }\n.tgc-verdict-pass .tgc-mark-head { fill: var(--chart-pass, var(--status-running)); }\n.tgc-verdict-fail .tgc-mark { stroke: var(--chart-breach, var(--status-error)); }\n.tgc-verdict-fail .tgc-mark-head { fill: var(--chart-breach, var(--status-error)); }\n.tgc-verdict-unknown .tgc-mark { stroke: var(--chart-muted); }\n.tgc-verdict-unknown .tgc-mark-head { fill: var(--chart-muted); }\n.tgc-mark-label { fill: var(--chart-ink); paint-order: stroke; stroke: var(--chart-canvas); stroke-width: 3px; stroke-linejoin: round; }\n.tgc-mark-edge { fill: var(--chart-ink-muted); }\n.tgc-band-after-pass { fill: color-mix(in srgb, var(--chart-pass, var(--status-running)) 12%, transparent); }\n.tgc-band-idle { fill: var(--chart-shade); }\n/* A system-wide wall, such as a quota window: a breach wash across every lane, topped by a solid edge. */\n.tgc-band-blocked { fill: color-mix(in srgb, var(--chart-breach, var(--status-error)) 9%, transparent); }\n.tgc-band-wall { stroke: var(--chart-breach, var(--status-error)); stroke-width: 2; }\n/* Missing data: a neutral wash under hatch lines, never a zero-height mark. */\n.tgc-hatch-bg { fill: color-mix(in srgb, var(--chart-muted) 10%, transparent); }\n.tgc-hatch { fill: none; stroke: var(--chart-muted); stroke-width: 1; }\n.tgc-span { fill: var(--chart-accent); }\n.tgc-span-muted { fill: var(--chart-muted); }\n.tgc-span-open { stroke: var(--chart-ink); stroke-width: 1.5; stroke-dasharray: 3 2; }\n.tgc-now { stroke: var(--chart-ink); stroke-width: 1.5; }\n/* Timeline trees and attempts. Ok ticks rise above the bar and failed ticks drop below it, so they read without colour. */\n.tgc-tree { fill: none; stroke: var(--chart-muted); stroke-width: 1; }\n.tgc-caret { fill: var(--chart-ink-muted); }\n.tgc-lane-toggle { cursor: pointer; }\n.tgc-lane-toggle:focus-visible { outline: none; }\n.tgc-lane-hit { fill: transparent; stroke: transparent; stroke-width: 2; }\n.tgc-lane-toggle:hover .tgc-lane-hit { fill: var(--chart-shade); }\n.tgc-lane-toggle:focus-visible .tgc-lane-hit { stroke: var(--chart-accent); }\n.tgc-tick { stroke-width: 1.5; }\n.tgc-tick-ok { stroke: var(--chart-outcome-ok); }\n.tgc-tick-quota { stroke: var(--chart-outcome-quota); }\n.tgc-tick-lost { stroke: var(--chart-outcome-lost); }\n.tgc-tick-rate-limit { stroke: var(--chart-outcome-rate-limit); }\n.tgc-tick-oom { stroke: var(--chart-outcome-oom); }\n.tgc-tick-refused { stroke: var(--chart-outcome-refused); }\n.tgc-tick-aborted { stroke: var(--chart-outcome-neutral); }\n.tgc-tick-other { stroke: var(--chart-outcome-other); }\n/* Lane labels and counts keep a canvas halo, so rules and ticks under them never strike through the text. */\n.tgc-lane-label, .tgc-count { paint-order: stroke; stroke: var(--chart-canvas); stroke-width: 3px; stroke-linejoin: round; }\n.tgc-count { fill: var(--chart-ink); }\n.tgc-count-ok { fill: var(--chart-pass, var(--status-running)); }\n.tgc-count-failed { fill: var(--chart-breach, var(--status-error)); }\n\n/* FigureView (React) picks the render by measured width; the wide plot fills its container. */\n.tgc-figure-view { max-width: none; margin: 0; }\n.tgc-view-plot svg { display: block; overflow: visible; }\n.tgc-view-wide svg { width: 100%; height: auto; }\n.tgc-view-narrow svg { max-width: 100%; height: auto; }\n\n/*\n * One fixed palette in every view (src/outcomes.ts names them), each a light-dark() pair picked by the page's\n * color-scheme. Green, red and amber are status colours and mean one thing everywhere: green is a turn or run that\n * worked (an `ok` attempt, a solved run), red a run that died, amber a run stalled or a turn rate-limited. Outcomes\n * pass the dataviz colour-vision checks as adjacent pairs in stack order (ok, quota, lost, rate-limit, oom, refused) on\n * the light surface and on #161616; `aborted` and `other` are two distinct greys. Checkers take hues no status uses\n * (blue for the current checker, then magenta, cyan and violet for older ones) and pass as adjacent series; their\n * marks also carry a vN label. Run endings reuse those colours. `scripts/palette-check.mjs` re-runs the validator. A\n * page may override any of these properties; :where() keeps them at zero specificity.\n */\n:where(:root) {\n --chart-outcome-ok: light-dark(#1baf7a, #199e70);\n --chart-outcome-quota: light-dark(#eb6834, #d95926);\n --chart-outcome-lost: light-dark(#0f9fb8, #1a9cb3);\n --chart-outcome-rate-limit: light-dark(#eda100, #c98500);\n --chart-outcome-oom: light-dark(#e87ba4, #d55181);\n --chart-outcome-refused: light-dark(#6250d6, #9085e9);\n --chart-outcome-neutral: #898781;\n --chart-outcome-other: #b5b3ac;\n --chart-checker-0: light-dark(#2a78d6, #3987e5);\n --chart-checker-1: light-dark(#b8478f, #c2408f);\n --chart-checker-2: light-dark(#0f9fb8, #1a9cb3);\n --chart-checker-3: light-dark(#6250d6, #8c7ce6);\n /* How a run ended: solved green, died red, stalled amber, live blue; not solved and cancelled are neutral. */\n --chart-ending-solved: var(--chart-outcome-ok);\n --chart-ending-died: light-dark(#d03b3b, #e05252);\n --chart-ending-stalled: var(--chart-outcome-rate-limit);\n --chart-ending-live: var(--chart-checker-0);\n --chart-ending-not-solved: light-dark(#6b7a90, #8494ab);\n --chart-ending-cancelled: var(--chart-outcome-neutral);\n --chart-ending-never-started: color-mix(in srgb, var(--chart-outcome-other) 45%, transparent);\n}\n/* A series drawn in a checker's hue; past the fourth hue it is muted and named by its label. */\n.tgc-checker-0 { --tgc-series: var(--chart-checker-0); }\n.tgc-checker-1 { --tgc-series: var(--chart-checker-1); }\n.tgc-checker-2 { --tgc-series: var(--chart-checker-2); }\n.tgc-checker-3 { --tgc-series: var(--chart-checker-3); }\n.tgc-checker-other { --tgc-series: var(--chart-muted); }\n/* Recorded verdicts: a pass is filled, a fail or an incomparable value is an outline, an undecided one dashed. */\n.tgc-series-point.tgc-point-outline { fill: var(--chart-canvas); stroke: var(--tgc-series, var(--chart-accent)); stroke-width: 1.75; }\n.tgc-series-point.tgc-point-undecided { fill: var(--chart-canvas); stroke: var(--tgc-series, var(--chart-accent)); stroke-width: 1.5; stroke-dasharray: 2 1.5; }\n.tgc-strip-hatch .tgc-hatch { stroke: var(--tgc-series, var(--chart-muted)); }\n/* A failing verdict without a value is a neutral ring: in its checker's hue it read as that checker passing. */\n.tgc-series-point.tgc-point-outline.tgc-strip-fail { stroke: var(--chart-ink-muted); }\n.tgc-strip-edge { fill: none; stroke: var(--tgc-series, var(--chart-muted)); stroke-width: 1.25; }\n.tgc-strip-observation:focus-visible { outline: none; }\n.tgc-strip-observation:focus-visible .tgc-series-hit { stroke: var(--chart-ink); stroke-width: 2; }\n.tgc-point-badge { paint-order: stroke; stroke: var(--chart-canvas); stroke-width: 3px; stroke-linejoin: round; font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace); }\n.tgc-point-label { fill: var(--chart-ink-muted); }\n.tgc-glyph-note { fill: var(--chart-ink); font-weight: 600; }\n/* Status colours appear only on these glyphs. */\n.tgc-glyph-check { fill: var(--chart-pass, var(--status-running)); font-weight: 600; }\n.tgc-glyph-cross { fill: var(--chart-breach, var(--status-error)); font-weight: 600; }\n/* Recorded targets sit under the data: a target is a soft band between solid edges, a stretch a dashed outline. */\n.tgc-target-target { fill: color-mix(in srgb, var(--chart-ink) 7%, transparent); }\n.tgc-target-stretch { fill: none; }\n.tgc-target-target-edge, .tgc-target-target.tgc-target-line { stroke: var(--chart-ink-muted); stroke-width: 1; }\n.tgc-target-stretch-edge, .tgc-target-stretch.tgc-target-line { stroke: var(--chart-ink-muted); stroke-width: 1; stroke-dasharray: 4 3; }\n.tgc-target-label { fill: var(--chart-ink-muted); paint-order: stroke; stroke: var(--chart-canvas); stroke-width: 3px; stroke-linejoin: round; }\n/* Group headers: a labelled bracket above the plot and a light rule at each inner edge. */\n.tgc-group-rule { stroke: var(--chart-ink-muted); stroke-width: 1.5; }\n.tgc-group-edge { stroke: var(--chart-rule); stroke-width: 1; }\n.tgc-group-label { fill: var(--chart-ink); paint-order: stroke; stroke: var(--chart-canvas); stroke-width: 3px; stroke-linejoin: round; }\n/* The verdict key: glyphs in the neutral ink, so they describe a mark's form, not a series. */\n.tgc-key-mark { stroke-width: 1.5; }\n.tgc-key-filled { fill: var(--chart-ink-muted); stroke: none; }\n.tgc-key-outline { fill: none; stroke: var(--chart-ink-muted); }\n.tgc-key-dashed { fill: none; stroke: var(--chart-ink-muted); stroke-dasharray: 2 1.5; }\n";
|
|
1140
1140
|
/** --chart-* resolved per theme, for documents without brand tokens: light, dark by preference or [data-theme="dark"], light in print. */
|
|
1141
|
-
const chartTokensCss = ":root{--chart-ink:hsl(0 0%
|
|
1141
|
+
const chartTokensCss = ":root{--chart-ink:hsl(0 0% 24.7059%);--chart-ink-muted:hsl(0 0% 41.9608%);--chart-muted:hsl(0 0% 36.4706%);--chart-grid:hsl(225 28.5714% 94.5098%);--chart-rule:hsl(222.8571 6.6667% 79.4118%);--chart-shade:color-mix(in srgb, hsl(246.2264 92.1739% 54.902%) 7%, transparent);--chart-accent:hsl(245.9504 54.2601% 56.2745%);--chart-tier-c:hsl(246.2687 54.4715% 75.8824%);--chart-tier-b:hsl(246.055 54.7739% 60.9804%);--chart-tier-a:hsl(246.0465 54.8936% 46.0784%);--chart-tier-s:hsl(245.5172 54.717% 31.1765%);--chart-pass:hsl(154.8148 64.8% 24.5098%);--chart-breach:hsl(0 75.1244% 39.4118%);--chart-canvas:hsl(0 0% 100%);color-scheme:light}\n@media (prefers-color-scheme: dark){:root:not([data-theme=\"light\"]){--chart-ink:hsl(0 0% 84.3137%);--chart-ink-muted:hsl(0 0% 60.3922%);--chart-muted:hsl(0 0% 67.0588%);--chart-grid:hsl(243.3333 20.4545% 17.2549%);--chart-rule:hsl(245 18.4615% 25.4902%);--chart-shade:color-mix(in srgb, hsl(245.5556 93.1034% 88.6275%) 7%, transparent);--chart-accent:hsl(246.0465 85.4305% 70.3922%);--chart-tier-c:hsl(246.1017 55.1402% 41.9608%);--chart-tier-b:hsl(246.1905 54.7826% 54.902%);--chart-tier-a:hsl(245.3933 54.6012% 68.0392%);--chart-tier-s:hsl(245.6604 54.6392% 80.9804%);--chart-pass:hsl(155.1429 64.8148% 78.8235%);--chart-breach:hsl(0 74.4681% 90.7843%);--chart-canvas:hsl(246.6667 31.0345% 5.6863%);color-scheme:dark}}\n:root[data-theme=\"dark\"]{--chart-ink:hsl(0 0% 84.3137%);--chart-ink-muted:hsl(0 0% 60.3922%);--chart-muted:hsl(0 0% 67.0588%);--chart-grid:hsl(243.3333 20.4545% 17.2549%);--chart-rule:hsl(245 18.4615% 25.4902%);--chart-shade:color-mix(in srgb, hsl(245.5556 93.1034% 88.6275%) 7%, transparent);--chart-accent:hsl(246.0465 85.4305% 70.3922%);--chart-tier-c:hsl(246.1017 55.1402% 41.9608%);--chart-tier-b:hsl(246.1905 54.7826% 54.902%);--chart-tier-a:hsl(245.3933 54.6012% 68.0392%);--chart-tier-s:hsl(245.6604 54.6392% 80.9804%);--chart-pass:hsl(155.1429 64.8148% 78.8235%);--chart-breach:hsl(0 74.4681% 90.7843%);--chart-canvas:hsl(246.6667 31.0345% 5.6863%);color-scheme:dark}\n@media print{:root,:root:not([data-theme=\"light\"]),:root[data-theme=\"dark\"]{--chart-ink:hsl(0 0% 24.7059%);--chart-ink-muted:hsl(0 0% 41.9608%);--chart-muted:hsl(0 0% 36.4706%);--chart-grid:hsl(225 28.5714% 94.5098%);--chart-rule:hsl(222.8571 6.6667% 79.4118%);--chart-shade:color-mix(in srgb, hsl(246.2264 92.1739% 54.902%) 7%, transparent);--chart-accent:hsl(245.9504 54.2601% 56.2745%);--chart-tier-c:hsl(246.2687 54.4715% 75.8824%);--chart-tier-b:hsl(246.055 54.7739% 60.9804%);--chart-tier-a:hsl(246.0465 54.8936% 46.0784%);--chart-tier-s:hsl(245.5172 54.717% 31.1765%);--chart-pass:hsl(154.8148 64.8% 24.5098%);--chart-breach:hsl(0 75.1244% 39.4118%);--chart-canvas:hsl(0 0% 100%);color-scheme:light}}";
|
|
1142
1142
|
//#endregion
|
|
1143
1143
|
//#region src/vertical-columns.ts
|
|
1144
1144
|
function image(mark, x, y, size) {
|
|
@@ -3349,8 +3349,9 @@ const CACHE_KEYS = [...CACHE_READ_KEYS, "gen_ai.usage.cache_creation_input_token
|
|
|
3349
3349
|
function catalogPrice(catalog) {
|
|
3350
3350
|
const rates = /* @__PURE__ */ new Map();
|
|
3351
3351
|
for (const model of catalog.data ?? []) {
|
|
3352
|
-
const
|
|
3353
|
-
|
|
3352
|
+
const rate = (value) => typeof value === "string" ? value.trim() ? finiteNumber(Number(value)) : void 0 : finiteNumber(value);
|
|
3353
|
+
const input = rate(model.pricing?.prompt), output = rate(model.pricing?.completion);
|
|
3354
|
+
if (model.id && input !== void 0 && output !== void 0) rates.set(model.id, {
|
|
3354
3355
|
input,
|
|
3355
3356
|
output
|
|
3356
3357
|
});
|
|
@@ -3391,7 +3392,6 @@ function parseOtlpJsonl(text) {
|
|
|
3391
3392
|
};
|
|
3392
3393
|
}
|
|
3393
3394
|
const str = (value) => typeof value === "string" && value ? value : void 0;
|
|
3394
|
-
const num = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3395
3395
|
const pick = (attributes, keys) => {
|
|
3396
3396
|
for (const key of keys) {
|
|
3397
3397
|
const value = str(attributes[key]);
|
|
@@ -3474,8 +3474,7 @@ function otlpFlowTrace(input, options = {}) {
|
|
|
3474
3474
|
const topModel = (key) => [...models.get(key)?.entries() ?? []].sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
3475
3475
|
const out = [];
|
|
3476
3476
|
let omitted = 0;
|
|
3477
|
-
|
|
3478
|
-
let completionTokens = 0;
|
|
3477
|
+
const usage = [];
|
|
3479
3478
|
let toolCalls = 0;
|
|
3480
3479
|
let modelCalls = 0;
|
|
3481
3480
|
let errors = 0;
|
|
@@ -3530,9 +3529,9 @@ function otlpFlowTrace(input, options = {}) {
|
|
|
3530
3529
|
if (open) endMs = Math.max(endMs, last - origin);
|
|
3531
3530
|
const parentId = span.parent_span_id && byId.has(span.parent_span_id) ? span.parent_span_id : groupOf.has(span.span_id) ? `group:${groupOf.get(span.span_id)}` : void 0;
|
|
3532
3531
|
const model = pick(a, MODEL_KEYS);
|
|
3533
|
-
const prompt =
|
|
3534
|
-
const completion =
|
|
3535
|
-
const cached = CACHE_READ_KEYS.map((key) =>
|
|
3532
|
+
const prompt = reportedCount(a["gen_ai.usage.input_tokens"]);
|
|
3533
|
+
const completion = reportedCount(a["gen_ai.usage.output_tokens"]);
|
|
3534
|
+
const cached = CACHE_READ_KEYS.map((key) => reportedCount(a[key])).find((n) => n !== void 0);
|
|
3536
3535
|
let kind = span.kind === "LLM" ? "model" : span.kind === "TOOL" ? "tool" : "pipeline";
|
|
3537
3536
|
if (kind === "tool" && (a["traces.agent.spawn"] === true || spawnTarget.has(span.span_id) || handoffTo.has(span.span_id))) kind = "handoff";
|
|
3538
3537
|
let name = str(a["gen_ai.tool.name"]) ?? span.name;
|
|
@@ -3542,16 +3541,13 @@ function otlpFlowTrace(input, options = {}) {
|
|
|
3542
3541
|
if (kind === "model" && !aggregate) modelCalls++;
|
|
3543
3542
|
if (kind === "tool" || kind === "handoff") toolCalls++;
|
|
3544
3543
|
if (failed) errors++;
|
|
3545
|
-
if (!aggregate)
|
|
3546
|
-
|
|
3547
|
-
completionTokens += completion ?? 0;
|
|
3548
|
-
}
|
|
3549
|
-
const listPrice = kind === "model" && !aggregate && model && options.price && (prompt !== void 0 || completion !== void 0) ? options.price(model, prompt ?? 0, completion ?? 0) : void 0;
|
|
3544
|
+
if (!aggregate && (kind === "model" || prompt !== void 0 || completion !== void 0)) usage.push([prompt, completion]);
|
|
3545
|
+
const listPrice = finiteNumber(kind === "model" && !aggregate && model && options.price && prompt !== void 0 && completion !== void 0 ? options.price(model, prompt, completion) : void 0);
|
|
3550
3546
|
if (listPrice !== void 0) {
|
|
3551
3547
|
cost += listPrice;
|
|
3552
3548
|
priced++;
|
|
3553
3549
|
if (CACHE_KEYS.some((key) => a[key] !== void 0)) cacheSplit = true;
|
|
3554
|
-
} else if (kind === "model" && !aggregate
|
|
3550
|
+
} else if (kind === "model" && !aggregate) unpriced++;
|
|
3555
3551
|
out.push({
|
|
3556
3552
|
id: span.span_id,
|
|
3557
3553
|
...parentId ? { parentId } : {},
|
|
@@ -3559,6 +3555,7 @@ function otlpFlowTrace(input, options = {}) {
|
|
|
3559
3555
|
name,
|
|
3560
3556
|
startMs,
|
|
3561
3557
|
endMs,
|
|
3558
|
+
...aggregate ? { meta: { "cli_bridge.usage.scope": "sum_of_children" } } : {},
|
|
3562
3559
|
status: open ? "running" : failed ? "failed" : "done",
|
|
3563
3560
|
...endMs === startMs && kind !== "pipeline" ? { approx: true } : {},
|
|
3564
3561
|
...isAgent(span) ? { detail: topModel(span.span_id) } : kind === "model" && model ? { detail: shortModel(model) } : {},
|
|
@@ -3573,8 +3570,8 @@ function otlpFlowTrace(input, options = {}) {
|
|
|
3573
3570
|
return {
|
|
3574
3571
|
spans: out,
|
|
3575
3572
|
totalMs: Number.isFinite(last - origin) ? last - origin : 0,
|
|
3576
|
-
promptTokens,
|
|
3577
|
-
completionTokens,
|
|
3573
|
+
promptTokens: sumReportedCounts(usage.map(([value]) => value)),
|
|
3574
|
+
completionTokens: sumReportedCounts(usage.map(([, value]) => value)),
|
|
3578
3575
|
toolCalls,
|
|
3579
3576
|
modelCalls,
|
|
3580
3577
|
errors,
|
|
@@ -13,15 +13,15 @@
|
|
|
13
13
|
--fw-ok: var(--surface-success-text);
|
|
14
14
|
--fw-amber: var(--surface-warning-text);
|
|
15
15
|
--fw-live: var(--fw-accent);
|
|
16
|
-
--fw-kind-model: var(--chart-accent);
|
|
17
|
-
--fw-kind-tool: var(--tone-cyan-icon);
|
|
16
|
+
--fw-kind-model: light-dark(oklch(from var(--chart-accent) .50 min(c, .10) h), oklch(from var(--chart-accent) .72 min(c, .10) h));
|
|
17
|
+
--fw-kind-tool: light-dark(oklch(from var(--tone-cyan-icon) .80 min(c, .10) h), oklch(from var(--tone-cyan-icon) .46 min(c, .10) h));
|
|
18
18
|
--fw-neutral-bar: color-mix(in oklab, var(--fw-text) 15%, var(--fw-surface));
|
|
19
19
|
--fw-hatch: color-mix(in oklab, var(--fw-text) 22%, transparent);
|
|
20
20
|
--fw-h0: var(--chart-muted);
|
|
21
21
|
--fw-h1: light-dark(oklch(from var(--tone-blue-icon) .58 .11 calc(h + 39)), oklch(from var(--tone-blue-icon) .67 .11 calc(h + 39)));
|
|
22
22
|
--fw-h2: light-dark(oklch(from var(--tone-violet-icon) .55 .11 h), oklch(from var(--tone-violet-icon) .70 .11 h));
|
|
23
23
|
--fw-h3: light-dark(oklch(from var(--tone-pink-icon) .55 .11 h), oklch(from var(--tone-pink-icon) .67 .11 calc(h - 16)));
|
|
24
|
-
--fw-h4: light-dark(oklch(from var(--tone-cyan-icon) .
|
|
24
|
+
--fw-h4: light-dark(oklch(from var(--tone-cyan-icon) .60 .11 calc(h - 16.256)), oklch(from var(--tone-cyan-icon) .67 .11 calc(h - 26.148)));
|
|
25
25
|
--fw-h5: light-dark(oklch(from var(--tone-blue-icon) .58 .03 calc(h + 37.6)), oklch(from var(--tone-blue-icon) .67 .03 calc(h + 29.5)));
|
|
26
26
|
--fw-ease: var(--ease-standard, cubic-bezier(.2,.8,.2,1));
|
|
27
27
|
--fw-bar-h: 20px;
|
|
@@ -260,9 +260,8 @@
|
|
|
260
260
|
.fw-root .fw-view-agents .fw-seg .fw-glyph { flex: none; margin-left: 0; display: grid; place-items: center; width: min(14px, 100%); height: 14px; border-radius: 50%; background: var(--fw-failed); color: light-dark(var(--fw-surface), var(--bg-root)); }
|
|
261
261
|
.fw-root .fw-view-agents .fw-seg .fw-glyph[data-glyph="retry"] { background: var(--fw-muted); }
|
|
262
262
|
.fw-root .fw-view-agents .fw-seg .fw-glyph svg { width: min(9px, 100%); height: 9px; stroke-width: 2.5; }
|
|
263
|
-
.fw-root:is(.fw-fill-muted, .fw-fill-outcome) .fw-view-agents :is(.fw-seg-alt.fw-seg-model, .fw-seg-alt.fw-seg-tool) { filter: brightness(.88); }
|
|
264
263
|
.fw-root .fw-seg-failed { background: color-mix(in oklab, var(--fw-failed) 18%, var(--fw-surface)); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-failed) 70%, transparent); color: var(--fw-failed); }
|
|
265
|
-
.fw-root .fw-view-agents .fw-seg-failed { background: var(--
|
|
264
|
+
.fw-root .fw-view-agents .fw-seg-failed { background: var(--tool-fill); box-shadow: inset 0 0 0 1px var(--dim-line); }
|
|
266
265
|
.fw-root .fw-glyph { display: inline-flex; color: currentColor; }
|
|
267
266
|
.fw-root .fw-glyph svg { width: 11px; height: 11px; stroke-width: 2; }
|
|
268
267
|
.fw-root .fw-seg-model .fw-glyph, .fw-root .fw-k-model .fw-glyph { color: light-dark(var(--tone), var(--fw-text)); }
|
|
@@ -371,6 +370,9 @@
|
|
|
371
370
|
.fw-root .fw-legend button[aria-pressed="true"] { color: var(--fw-text); text-decoration: underline; text-underline-offset: 4px; }
|
|
372
371
|
.fw-root .fw-summary button { appearance: none; background: transparent; border: 0; padding: 0; cursor: pointer; color: inherit; }
|
|
373
372
|
.fw-root .fw-key > summary { display: none; }
|
|
373
|
+
.fw-root .fw-key > summary svg { width: 12px; height: 12px; margin-left: 6px; }
|
|
374
|
+
.fw-root .fw-key[open] > summary svg { transform: rotate(90deg); }
|
|
375
|
+
.fw-root .fw-row-state { display: none; font-size: 12px; font-weight: 600; }
|
|
374
376
|
.fw-root .fw-error-rug { position: relative; height: 10px; margin-left: var(--fw-label-col); overflow: hidden; }
|
|
375
377
|
.fw-root .fw-error-tick { position: absolute; width: 2px; height: 8px; background: var(--fw-failed); left: calc(var(--fw-pad) + (var(--s) - var(--fw-v0)) * var(--fw-k) * 1px); }
|
|
376
378
|
.fw-root .fw-error-tick[data-unlocated] { left: auto; right: calc(var(--error-index) * 3px); }
|
|
@@ -387,12 +389,15 @@
|
|
|
387
389
|
.fw-root .fw-summary > div:nth-child(4) { border-left: 0; }
|
|
388
390
|
.fw-root .fw-summary > div:nth-child(n+4) { border-top: 1px solid var(--fw-border); }
|
|
389
391
|
.fw-root :is(.fw-zoom, .fw-branch-controls, .fw-resize, .fw-links, .fw-gridlayer, .fw-marker-layer, .fw-subtitle, .fw-badge, .fw-icon-tile) { display: none; }
|
|
392
|
+
.fw-root .fw-row-state { display: inline; }
|
|
390
393
|
.fw-root .fw-key > summary { display: flex; align-items: center; min-height: 32px; cursor: pointer; }
|
|
391
394
|
.fw-root .fw-key:not([open]) .fw-legends { display: none; }
|
|
392
395
|
.fw-root .fw-heading { padding: 12px 8px; }
|
|
393
396
|
.fw-root .fw-title-line { flex-wrap: wrap; }
|
|
394
397
|
.fw-root .fw-search-input { width: 120px; }
|
|
395
398
|
.fw-root .fw-axis-label { border: 0; padding: 0; }
|
|
399
|
+
.fw-root .fw-tick:is(:nth-child(2), :nth-child(3)) { transform: translate(-50%, -50%); }
|
|
400
|
+
.fw-root .fw-tick:nth-child(4) { transform: translate(calc(-100% - 4px), -50%); }
|
|
396
401
|
.fw-root .fw-row { transform: none; top: var(--row-y); contain: style; }
|
|
397
402
|
.fw-root .fw-label { position: absolute; z-index: 3; height: 20px; top: 0; left: 0; right: 0; padding: 0 calc(var(--fw-end-w, 0px) + 8px) 0 calc(8px + var(--depth) * 12px); border: 0; gap: 4px; background: transparent; pointer-events: none; }
|
|
398
403
|
.fw-root .fw-view-agents .fw-names { flex-direction: row; font-size: 14px; line-height: 20px; }
|
|
@@ -401,15 +406,12 @@
|
|
|
401
406
|
.fw-root .fw-lane { grid-column: 2; }
|
|
402
407
|
.fw-root .fw-end { top: 0; height: 20px; left: auto; right: 4px; }
|
|
403
408
|
.fw-root .fw-duration { font-size: 12px; }
|
|
404
|
-
.fw-root .fw-time { display: block; position: absolute; top: -12px; left:
|
|
409
|
+
.fw-root .fw-time { display: block; position: absolute; top: -12px; left: var(--fw-progress); transform: translateX(calc(-1 * var(--fw-progress))); width: max-content; max-width: 100%; white-space: nowrap; background: var(--fw-surface); }
|
|
405
410
|
.fw-root .fw-playback { position: relative; padding: 12px 8px 8px; }
|
|
406
411
|
.fw-root .fw-overview { margin-top: 12px; }
|
|
407
412
|
}
|
|
408
413
|
|
|
409
|
-
@container flow-waterfall (max-width: 599px) { .fw-root .fw-tick:nth-child(4) { transform: translate(calc(-100% - 4px), -50%); } }
|
|
410
414
|
|
|
411
415
|
.fw-root.fw-fill-agent .fw-view-agents .fw-seg-failed[data-model] { background: var(--model-fill); --fw-seg-ink: light-dark(var(--fw-surface), var(--bg-root)); }
|
|
412
416
|
|
|
413
417
|
.fw-root.fw-root.fw-root-agents[data-selection] .fw-row:not([aria-selected="true"]) .fw-seg { opacity: var(--on); background: color-mix(in oklab, var(--fw-surface) 80%, var(--tone)); --fw-seg-ink: var(--fw-text); }
|
|
414
|
-
|
|
415
|
-
.fw-root:is(.fw-fill-muted, .fw-fill-outcome) .fw-view-agents .fw-seg-tool { --fw-seg-ink: light-dark(var(--fw-surface), var(--bg-root)); }
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, O as YTarget, S as ChartMark, T as MarkerShape, a as TimelineLane, b as BandKind, c as TimelineTick, g as TickOutcome, i as FlowTrace, k as YTick, n as FlowOutcome, o as TimelineOptions, r as FlowSpan, s as TimelineSpan, t as FlowMarker, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph } from "../flow-trace-
|
|
1
|
+
import { B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, O as YTarget, S as ChartMark, T as MarkerShape, a as TimelineLane, b as BandKind, c as TimelineTick, g as TickOutcome, i as FlowTrace, k as YTick, n as FlowOutcome, o as TimelineOptions, r as FlowSpan, s as TimelineSpan, t as FlowMarker, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph } from "../flow-trace-CF4Q07cA.js";
|
|
2
2
|
import { CSSProperties, ReactElement, ReactNode } from "react";
|
|
3
3
|
//#region src/react/sparkline.d.ts
|
|
4
4
|
type SparklineDirection = "rising" | "falling" | "flat";
|
package/dist/react/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as isRefusal, J as hatchPath, O as duration, S as markName, Z as markerPath, i as timeline, n as reportedCount, p as MARK_HEADS, r as sumReportedCounts, t as finiteNumber, v as eventsGap } from "../flow-trace-k097Ukvs.js";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Fragment as Fragment$1, createContext, createElement, forwardRef, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
//#region src/react/sparkline.tsx
|
|
@@ -6515,12 +6515,14 @@ function formatSpanDuration(ms) {
|
|
|
6515
6515
|
}
|
|
6516
6516
|
/** $0.12, $0.005, $12.40; never rounds a recorded nonzero cost to $0.00. */
|
|
6517
6517
|
function formatCost(usd) {
|
|
6518
|
+
if (finiteNumber(usd) === void 0) return "—";
|
|
6518
6519
|
if (usd === 0) return "$0.00";
|
|
6519
6520
|
if (usd < .01) return `$${usd.toPrecision(1).replace(/0+$/, "")}`;
|
|
6520
6521
|
return `$${usd.toFixed(2)}`;
|
|
6521
6522
|
}
|
|
6522
6523
|
/** 9.8k, 754.4k, 1.2M with the exact count left to a title. */
|
|
6523
6524
|
function formatCount(n) {
|
|
6525
|
+
if (reportedCount(n) === void 0) return "—";
|
|
6524
6526
|
if (n < 1e3) return String(n);
|
|
6525
6527
|
if (n < 1e6) return `${(n / 1e3).toFixed(1)}K`;
|
|
6526
6528
|
return `${(n / 1e6).toFixed(1)}M`;
|
|
@@ -6560,10 +6562,22 @@ function mergeSegments(marks, gapMs, cap = 240) {
|
|
|
6560
6562
|
* Orphaned parents become roots; cycles are broken without dropping any span. */
|
|
6561
6563
|
function flowWaterfallRows(trace, resolutionMs) {
|
|
6562
6564
|
const ids = /* @__PURE__ */ new Set();
|
|
6563
|
-
const rows = [...trace.spans].map((
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6565
|
+
const rows = [...trace.spans].map((recorded, i) => {
|
|
6566
|
+
const state = runStatus(recorded.status ?? "unknown");
|
|
6567
|
+
const outcome = {
|
|
6568
|
+
label: statusMeta(state).label,
|
|
6569
|
+
tone: state === "running" ? "live" : state === "done" ? "ok" : state === "needs-you" ? "warn" : state === "failed" ? "failed" : "neutral",
|
|
6570
|
+
...state === "unknown" ? { ring: true } : {}
|
|
6571
|
+
};
|
|
6572
|
+
const span = recorded.outcome ? recorded : {
|
|
6573
|
+
...recorded,
|
|
6574
|
+
outcome
|
|
6575
|
+
};
|
|
6576
|
+
return {
|
|
6577
|
+
span,
|
|
6578
|
+
key: span.id ?? `span-${i}`
|
|
6579
|
+
};
|
|
6580
|
+
}).sort((a, b) => a.span.startMs - b.span.startMs).map(({ span, key }, index) => {
|
|
6567
6581
|
let id = key;
|
|
6568
6582
|
let suffix = index;
|
|
6569
6583
|
while (ids.has(id)) id = `${key}:${suffix++}`;
|
|
@@ -6634,9 +6648,9 @@ function flowWaterfallRows(trace, resolutionMs) {
|
|
|
6634
6648
|
}
|
|
6635
6649
|
Object.assign(row, stepStats(kids));
|
|
6636
6650
|
const tokens = own ? {
|
|
6637
|
-
prompt: row.span.promptTokens,
|
|
6638
|
-
completion: row.span.completionTokens,
|
|
6639
|
-
cached: row.span.cachedTokens
|
|
6651
|
+
prompt: reportedCount(row.span.promptTokens),
|
|
6652
|
+
completion: reportedCount(row.span.completionTokens),
|
|
6653
|
+
cached: reportedCount(row.span.cachedTokens)
|
|
6640
6654
|
} : sumTokens(kids);
|
|
6641
6655
|
if (tokens) row.tokens = tokens;
|
|
6642
6656
|
if (row.hasChildren && costKnown) row.rollupCostUsd = cost;
|
|
@@ -6709,13 +6723,14 @@ function mergeStats(rows) {
|
|
|
6709
6723
|
}
|
|
6710
6724
|
function sumTokens(rows) {
|
|
6711
6725
|
let out;
|
|
6712
|
-
|
|
6726
|
+
const sources = rows.filter((row) => row.kind === "model" || row.tokens !== void 0);
|
|
6727
|
+
for (const key of [
|
|
6713
6728
|
"prompt",
|
|
6714
6729
|
"completion",
|
|
6715
6730
|
"cached"
|
|
6716
6731
|
]) {
|
|
6717
|
-
const
|
|
6718
|
-
if (
|
|
6732
|
+
const total = sumReportedCounts(sources.map((row) => row.tokens?.[key]));
|
|
6733
|
+
if (total !== void 0) (out ??= {})[key] = total;
|
|
6719
6734
|
}
|
|
6720
6735
|
return out;
|
|
6721
6736
|
}
|
|
@@ -6839,39 +6854,40 @@ function busyMs(row, clockMs) {
|
|
|
6839
6854
|
}
|
|
6840
6855
|
return total;
|
|
6841
6856
|
}
|
|
6842
|
-
/**
|
|
6857
|
+
/** Complete per-span usage drives playback; authoritative call/error counts remain trace totals. */
|
|
6843
6858
|
function totalsAt(trace, clockMs) {
|
|
6844
|
-
let cost = 0
|
|
6859
|
+
let cost = 0;
|
|
6845
6860
|
let costs = false, counted = false;
|
|
6861
|
+
const tokens = [];
|
|
6846
6862
|
for (const span of trace.spans) {
|
|
6847
6863
|
if (span.startMs > clockMs) continue;
|
|
6848
|
-
|
|
6849
|
-
if (span.kind === "tool") tools++;
|
|
6864
|
+
const aggregate = span.meta?.["cli_bridge.usage.scope"] === "sum_of_children";
|
|
6850
6865
|
if (span.endMs > clockMs) continue;
|
|
6851
|
-
if (spanFailed(span)) errors++;
|
|
6852
6866
|
if (span.costUsd !== void 0) {
|
|
6853
6867
|
cost += span.costUsd;
|
|
6854
6868
|
costs = true;
|
|
6855
6869
|
}
|
|
6856
|
-
if (span.promptTokens !== void 0 || span.completionTokens !== void 0) {
|
|
6857
|
-
tokens
|
|
6858
|
-
counted
|
|
6870
|
+
if (!aggregate && (span.kind === "model" || span.promptTokens !== void 0 || span.completionTokens !== void 0)) {
|
|
6871
|
+
tokens.push(sumReportedCounts([span.promptTokens, span.completionTokens]));
|
|
6872
|
+
counted ||= span.promptTokens !== void 0 || span.completionTokens !== void 0;
|
|
6859
6873
|
}
|
|
6860
6874
|
}
|
|
6861
6875
|
return {
|
|
6862
6876
|
costUsd: trace.costUsd === void 0 ? void 0 : costs ? cost : trace.costUsd,
|
|
6863
|
-
tokens: counted ? tokens : trace.promptTokens
|
|
6864
|
-
modelCalls: trace.modelCalls
|
|
6865
|
-
toolCalls: trace.toolCalls
|
|
6866
|
-
errors
|
|
6877
|
+
tokens: counted ? sumReportedCounts(tokens) : sumReportedCounts([trace.promptTokens, trace.completionTokens]),
|
|
6878
|
+
modelCalls: reportedCount(trace.modelCalls),
|
|
6879
|
+
toolCalls: reportedCount(trace.toolCalls),
|
|
6880
|
+
errors: reportedCount(trace.errors)
|
|
6867
6881
|
};
|
|
6868
6882
|
}
|
|
6869
6883
|
/** The cost a reader can rely on: a lower bound when some calls are unpriced, a dash only when nothing is priced. */
|
|
6870
6884
|
function costSummary(costUsd, trace) {
|
|
6885
|
+
costUsd = finiteNumber(costUsd);
|
|
6886
|
+
const billedUsd = finiteNumber(trace.billedUsd);
|
|
6871
6887
|
const list = trace.costBasis === "list";
|
|
6872
6888
|
const label = list ? "Cost · list price" : "Cost";
|
|
6873
|
-
if (costUsd === void 0) return
|
|
6874
|
-
value: `${formatCost(
|
|
6889
|
+
if (costUsd === void 0) return billedUsd !== void 0 ? {
|
|
6890
|
+
value: `${formatCost(billedUsd)} billed`,
|
|
6875
6891
|
title: "No list price for these calls; billed is the seat charge",
|
|
6876
6892
|
label
|
|
6877
6893
|
} : {
|
|
@@ -6883,13 +6899,13 @@ function costSummary(costUsd, trace) {
|
|
|
6883
6899
|
const partial = coverage !== void 0 && coverage.priced < coverage.total;
|
|
6884
6900
|
const upper = !partial && trace.costBound === "upper";
|
|
6885
6901
|
return {
|
|
6886
|
-
value: `${partial || trace.costBound === "lower" ? "≥" : upper ? "≤" : list ? "≈" : ""}${formatCost(costUsd)}${
|
|
6902
|
+
value: `${partial || trace.costBound === "lower" ? "≥" : upper ? "≤" : list ? "≈" : ""}${formatCost(costUsd)}${billedUsd !== void 0 ? ` · ${formatCost(billedUsd)} billed` : ""}`,
|
|
6887
6903
|
title: [
|
|
6888
6904
|
partial ? `${coverage.priced} of ${coverage.total} model calls priced` : coverage ? `all ${coverage.total} model calls priced` : "",
|
|
6889
6905
|
list ? "at the model catalog list price; not a bill" : "",
|
|
6890
6906
|
upper ? "cached input is not split out, so it is priced as fresh input: at most this" : "",
|
|
6891
6907
|
trace.costBound === "lower" ? "a floor: the producer reports at least this" : "",
|
|
6892
|
-
|
|
6908
|
+
billedUsd !== void 0 ? `billed ${formatCost(billedUsd)} (the seat charge)` : ""
|
|
6893
6909
|
].filter(Boolean).join("; "),
|
|
6894
6910
|
label
|
|
6895
6911
|
};
|
|
@@ -7106,17 +7122,17 @@ const rowCostText = (row, cost) => `${row.costFloor ? "≥" : ""}${formatCost(co
|
|
|
7106
7122
|
/** Quiet is elapsed silence beyond both five minutes and three median event gaps. */
|
|
7107
7123
|
function quietActivity(row, now) {
|
|
7108
7124
|
const times = [...row.span.activityAt ?? row.own.flatMap((mark) => [mark.startMs, mark.endMs])].filter(Number.isFinite).sort((a, b) => a - b);
|
|
7109
|
-
const last = times.at(-1) ??
|
|
7110
|
-
const gaps = times.slice(1).map((at, i) => at - times[i]).
|
|
7125
|
+
const last = times.at(-1) ?? NaN;
|
|
7126
|
+
const gaps = times.slice(1).map((at, i) => at - times[i]).sort((a, b) => a - b);
|
|
7111
7127
|
const middle = Math.floor(gaps.length / 2);
|
|
7112
7128
|
const observedMedian = gaps.length ? gaps.length % 2 ? gaps[middle] : (gaps[middle - 1] + gaps[middle]) / 2 : 0;
|
|
7113
7129
|
const supplied = row.span.medianEventGapMs;
|
|
7114
7130
|
const median = typeof supplied === "number" && Number.isFinite(supplied) && supplied >= 0 ? supplied : observedMedian;
|
|
7115
|
-
const quietMs = Math.max(0, now - last);
|
|
7131
|
+
const quietMs = Number.isFinite(last) ? Math.max(0, now - last) : 0;
|
|
7116
7132
|
return {
|
|
7117
7133
|
last,
|
|
7118
7134
|
quietMs,
|
|
7119
|
-
quiet: spanRunning(row.span) && quietMs > Math.max(3e5, 3 * median)
|
|
7135
|
+
quiet: spanRunning(row.span) && Number.isFinite(last) && quietMs > Math.max(3e5, 3 * median)
|
|
7120
7136
|
};
|
|
7121
7137
|
}
|
|
7122
7138
|
//#endregion
|
|
@@ -7303,9 +7319,12 @@ function metrics(row, clockMs, full) {
|
|
|
7303
7319
|
["completion", "out"],
|
|
7304
7320
|
["cached", "cached"]
|
|
7305
7321
|
].flatMap(([key, unit]) => tokens[key] === void 0 ? [] : [count(tokens[key], unit)]);
|
|
7306
|
-
out.push(["Tokens", /* @__PURE__ */ jsx("span", {
|
|
7322
|
+
out.push(["Tokens", parts.length ? /* @__PURE__ */ jsx("span", {
|
|
7307
7323
|
className: `fw-card-parts${parts.length > 2 ? " fw-card-stack" : ""}`,
|
|
7308
7324
|
children: parts.map((part, i) => /* @__PURE__ */ jsx("span", { children: part }, i))
|
|
7325
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
7326
|
+
title: "Tokens not reported",
|
|
7327
|
+
children: "—"
|
|
7309
7328
|
})]);
|
|
7310
7329
|
}
|
|
7311
7330
|
const latency = row.modelMs;
|
|
@@ -7561,6 +7580,8 @@ function endLabel(row, clockMs) {
|
|
|
7561
7580
|
const FlowRow = memo(function FlowRow(props) {
|
|
7562
7581
|
const { row, view, top, collapsed, searching, slowest, costliest, critical, matched, active, focusable, selected, sticky, clockMs, renderIcon, fillByAgent, motion } = props;
|
|
7563
7582
|
const span = row.span;
|
|
7583
|
+
const state = runStatus(span.status ?? "unknown");
|
|
7584
|
+
const meta = statusMeta(state);
|
|
7564
7585
|
const running = spanRunning(span);
|
|
7565
7586
|
const agentView = view === "agents";
|
|
7566
7587
|
const folded = row.hasChildren && collapsed && !searching;
|
|
@@ -7687,6 +7708,11 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
7687
7708
|
className: "fw-name-text",
|
|
7688
7709
|
children: row.name
|
|
7689
7710
|
}),
|
|
7711
|
+
(state === "failed" || state === "needs-you") && /* @__PURE__ */ jsx("span", {
|
|
7712
|
+
className: "fw-row-state",
|
|
7713
|
+
style: { color: `var(${meta.token})` },
|
|
7714
|
+
children: meta.label
|
|
7715
|
+
}),
|
|
7690
7716
|
activity.quiet && /* @__PURE__ */ jsxs("span", {
|
|
7691
7717
|
className: "fw-quiet",
|
|
7692
7718
|
children: ["Quiet ", formatSpanDuration(activity.quietMs)]
|
|
@@ -8697,36 +8723,42 @@ function usual(values) {
|
|
|
8697
8723
|
/** The summary strip: as of the playhead while scrubbing, the producer's totals otherwise. */
|
|
8698
8724
|
function summaryCells(trace, at, durationMs) {
|
|
8699
8725
|
const cost = costSummary(at ? at.costUsd : trace.costUsd, trace);
|
|
8700
|
-
const tokens = at ? at.tokens : trace.promptTokens
|
|
8701
|
-
const modelCalls = at ? at.modelCalls : trace.modelCalls;
|
|
8702
|
-
const
|
|
8726
|
+
const tokens = at ? at.tokens : sumReportedCounts([trace.promptTokens, trace.completionTokens]);
|
|
8727
|
+
const modelCalls = reportedCount(at ? at.modelCalls : trace.modelCalls);
|
|
8728
|
+
const toolCalls = reportedCount(at ? at.toolCalls : trace.toolCalls);
|
|
8729
|
+
const observedErrors = trace.spans.filter((span) => spanFailed(span) && span.kind !== "pipeline").length || trace.spans.filter(spanFailed).length;
|
|
8730
|
+
const errors = at ? at.errors : trace.errors === void 0 ? observedErrors || void 0 : reportedCount(trace.errors);
|
|
8703
8731
|
return {
|
|
8704
8732
|
cost,
|
|
8705
8733
|
tokens,
|
|
8706
8734
|
errors,
|
|
8707
8735
|
summary: [
|
|
8708
8736
|
["Duration", formatSpanDuration(durationMs)],
|
|
8709
|
-
|
|
8737
|
+
[
|
|
8710
8738
|
cost.label,
|
|
8711
8739
|
cost.value,
|
|
8712
8740
|
cost.title
|
|
8713
|
-
]
|
|
8741
|
+
],
|
|
8714
8742
|
[
|
|
8715
8743
|
"Tokens",
|
|
8716
|
-
formatCount(tokens),
|
|
8717
|
-
`${tokens.toLocaleString()} tokens`
|
|
8744
|
+
tokens === void 0 ? "—" : formatCount(tokens),
|
|
8745
|
+
tokens === void 0 ? "Tokens not reported" : `${tokens.toLocaleString()} tokens`
|
|
8718
8746
|
],
|
|
8719
8747
|
[
|
|
8720
8748
|
"Model calls",
|
|
8721
8749
|
modelCalls === void 0 ? "—" : modelCalls.toLocaleString(),
|
|
8722
|
-
modelCalls === void 0 ? "Model
|
|
8750
|
+
modelCalls === void 0 ? "Model calls not reported" : at ? "Reported trace total" : void 0
|
|
8723
8751
|
],
|
|
8724
8752
|
[
|
|
8725
8753
|
"Tool calls",
|
|
8726
|
-
|
|
8727
|
-
|
|
8754
|
+
toolCalls?.toLocaleString() ?? "—",
|
|
8755
|
+
toolCalls === void 0 ? "Tool calls not reported" : at ? "Reported trace total" : void 0
|
|
8728
8756
|
],
|
|
8729
|
-
[
|
|
8757
|
+
[
|
|
8758
|
+
"Errors",
|
|
8759
|
+
errors?.toLocaleString() ?? "—",
|
|
8760
|
+
errors === void 0 ? "Errors not reported" : at ? "Reported trace total" : void 0
|
|
8761
|
+
]
|
|
8730
8762
|
]
|
|
8731
8763
|
};
|
|
8732
8764
|
}
|
|
@@ -9204,7 +9236,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
9204
9236
|
const quietAgents = agents.filter((row) => quietActivity(row, clockMs).quiet).length;
|
|
9205
9237
|
const recordedErrors = trace.spans.filter((span) => spanFailed(span) && span.kind !== "pipeline");
|
|
9206
9238
|
const errorSpans = recordedErrors.length ? recordedErrors : trace.spans.filter(spanFailed);
|
|
9207
|
-
const errorTimes = Array.from({ length: errors }, (_, index) => errorSpans[index]?.endMs);
|
|
9239
|
+
const errorTimes = Array.from({ length: errors ?? 0 }, (_, index) => errorSpans[index]?.endMs);
|
|
9208
9240
|
const done = !isLive && !trace.spans.some(spanRunning);
|
|
9209
9241
|
const shown = (row) => row && drawnRow(row, visible);
|
|
9210
9242
|
const cardRow = shown(card ? byId.get(card.target) : void 0);
|
|
@@ -9322,7 +9354,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
9322
9354
|
}), /* @__PURE__ */ jsxs("details", {
|
|
9323
9355
|
className: "fw-key",
|
|
9324
9356
|
open: root.current ? root.current.clientWidth >= 600 : true,
|
|
9325
|
-
children: [/* @__PURE__ */
|
|
9357
|
+
children: [/* @__PURE__ */ jsxs("summary", { children: ["Key", /* @__PURE__ */ jsx(ChevronGlyph, {})] }), /* @__PURE__ */ jsxs("div", {
|
|
9326
9358
|
className: "fw-legends",
|
|
9327
9359
|
children: [
|
|
9328
9360
|
/* @__PURE__ */ jsx(OutcomeLegend, {
|
|
@@ -9391,7 +9423,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
9391
9423
|
showSummary && /* @__PURE__ */ jsx("dl", {
|
|
9392
9424
|
className: "fw-summary",
|
|
9393
9425
|
children: summary.map(([name, value, title]) => /* @__PURE__ */ jsxs("div", {
|
|
9394
|
-
className: name === "Errors" && errors > 0 ? "fw-has-errors" : void 0,
|
|
9426
|
+
className: name === "Errors" && errors !== void 0 && errors > 0 ? "fw-has-errors" : void 0,
|
|
9395
9427
|
children: [/* @__PURE__ */ jsx("dt", { children: name }), /* @__PURE__ */ jsx("dd", {
|
|
9396
9428
|
className: "fw-value",
|
|
9397
9429
|
title,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/charts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Static SVG and HTML figures for benchmarks and operational timing. Draws from producer data and refuses what it cannot draw honestly.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"check:run-tokens": "node scripts/check-run-tokens.mjs"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@tangle-network/brand": "1.31.
|
|
55
|
+
"@tangle-network/brand": "^1.31.1",
|
|
56
56
|
"@testing-library/react": "^16.3.3",
|
|
57
57
|
"@testing-library/user-event": "^14.6.7",
|
|
58
58
|
"@types/node": "^26.6.3",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"tsdown": "^0.23.0",
|
|
67
67
|
"typescript": "^7.0.2",
|
|
68
68
|
"vitest": "^5.0.2",
|
|
69
|
-
"@tangle-network/ui": "11.
|
|
69
|
+
"@tangle-network/ui": "^11.51.1"
|
|
70
70
|
},
|
|
71
71
|
"keywords": [
|
|
72
72
|
"tangle",
|