@ymys4ever/opencode-throughput-v2 4.0.0 → 4.0.2

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/src/types.ts DELETED
@@ -1,44 +0,0 @@
1
- /** frozen on-disk JSONL schema, never rename fields */
2
- export type LogEntry = {
3
- ts: string
4
- /** composite key: providerID/modelID (frozen) */
5
- model: string
6
- providerID: string
7
- modelID: string
8
- sessionID: string
9
- messageID: string
10
- ttft_ms: number | null
11
- tps: number | null
12
- latency_ms: number | null
13
- inputTokens: number
14
- outputTokens: number
15
- reasoningTokens: number
16
- cacheReadTokens: number
17
- cacheWriteTokens: number
18
- cost: number
19
- finish: string
20
- }
21
-
22
- /**
23
- * Aggregated per-model statistics. Metric fields (avg/min/max/maxTPS) are null
24
- * when no entry provided a valid sample; total* fields are plain sums, count counts every entry.
25
- */
26
- export type ModelStats = {
27
- model: string
28
- count: number
29
- avgTTFT: number | null
30
- avgTPS: number | null
31
- avgLatency: number | null
32
- minTTFT: number | null
33
- maxTTFT: number | null
34
- minLatency: number | null
35
- maxLatency: number | null
36
- maxTPS: number | null
37
- totalInputTokens: number
38
- totalOutputTokens: number
39
- totalReasoningTokens: number
40
- totalCacheReadTokens: number
41
- totalCacheWriteTokens: number
42
- totalCost: number
43
- lastUpdated: number
44
- }
package/src/ui/sidebar.ts DELETED
@@ -1,90 +0,0 @@
1
- import type { ModelStats } from "../types.js"
2
-
3
- /** Three-column model key: provider initial, first version digit, final non-version token initial. */
4
- export function abbreviateModel(modelID: string): string {
5
- const tokens = modelID.split(/[^a-zA-Z0-9]+/).filter(Boolean)
6
- const versionIndex = tokens.findIndex((token) => /\d/.test(token))
7
- const provider = (tokens[0]?.[0] ?? "?").toLowerCase()
8
- if (versionIndex < 0) return (provider + (tokens[1]?.[0] ?? " ").toLowerCase() + " ").slice(0, 3).padEnd(3, " ")
9
- const version = tokens[versionIndex].match(/\d/)?.[0] ?? " "
10
- const suffix = tokens.filter((token, index) => index !== 0 && index !== versionIndex && !/^\d+(?:\.\d+)*$/.test(token)).at(-1)
11
- return `${provider}${version}${(suffix?.[0] ?? " ").toLowerCase()}`
12
- }
13
-
14
- /** Sidebar column widths shared by the header and every data row. */
15
- const COL_ID = 3
16
- const COL_TIME = 4
17
- const COL_TOKEN = 5
18
-
19
- /**
20
- * Collision-aware display IDs: the first model keeps the 3-char
21
- * abbreviateModel form; later colliders widen to 4 then 5 chars of
22
- * modelID until unique (a still-colliding 5-char form is kept as-is,
23
- * never widened further). Deterministic for a given input sequence:
24
- * first-seen order wins, so existing IDs stay stable as new models appear.
25
- */
26
- export function buildDisplayIds(modelIDs: readonly string[]): Map<string, string> {
27
- const taken = new Set<string>()
28
- const ids = new Map<string, string>()
29
- for (const modelID of modelIDs) {
30
- if (ids.has(modelID)) continue
31
- const candidates = [abbreviateModel(modelID), modelID.slice(0, COL_ID + 1), modelID.slice(0, COL_TOKEN)]
32
- let chosen = candidates[candidates.length - 1]
33
- for (const candidate of candidates) {
34
- if (!taken.has(candidate)) {
35
- chosen = candidate
36
- break
37
- }
38
- }
39
- taken.add(chosen)
40
- ids.set(modelID, chosen)
41
- }
42
- return ids
43
- }
44
-
45
- /** One sidebar stats row: aggregated metrics plus the structured display modelID
46
- * and its collision-aware display ID (see buildDisplayIds). */
47
- export type StatsRow = { stats: ModelStats; modelID: string; displayID: string }
48
-
49
- /** Sidebar duration in seconds: compact precision without a unit suffix. */
50
- export function formatSidebarSeconds(ms: number | null): string {
51
- if (ms == null) return " N/A"
52
- return fitColumn4(ms / 1000)
53
- }
54
-
55
- /** Sidebar metric without a unit suffix, fitted to a 4-char column. */
56
- export function formatSidebarMetric(value: number | null): string {
57
- if (value == null) return " N/A"
58
- return fitColumn4(value)
59
- }
60
-
61
- /** Fit into 4 chars: one decimal (xxx.x), else the integer as-is. A 5-digit integer overflows by design rather than losing digits. */
62
- function fitColumn4(value: number): string {
63
- const s = value.toFixed(1)
64
- return s.length > COL_TIME ? value.toFixed(0) : s
65
- }
66
-
67
- /**
68
- * Sidebar token volume in a 5-char column: the unit (none / k / m) is
69
- * chosen so the numeric part always fits with rounding-safe boundaries.
70
- * @param value non-negative token count (clamped)
71
- */
72
- export function formatSidebarTokens(value: number): string {
73
- value = Math.max(0, Math.round(value))
74
- if (value < 1_000) return value.toFixed(1) // "892.0"
75
- if (value < 99_950) return (value / 1e3).toFixed(1) + "k" // "1.0k".."99.9k"
76
- if (value < 999_500) return (value / 1e3).toFixed(0) + "k" // "100k".."999k"
77
- if (value < 99_950_000) return (value / 1e6).toFixed(1) + "m" // "1.0m".."99.9m"
78
- if (value < 999_500_000) return (value / 1e6).toFixed(0) + "m"
79
- return "999m" // cap
80
- }
81
-
82
- /** Sidebar data row; `displayID` is a collision-aware display ID from buildDisplayIds (3-5 chars, rendered as-is). */
83
- export function formatSidebarRow(displayID: string, ttft: number | null, tps: number | null, latency: number | null, input: number, output: number): string {
84
- return ` ${displayID.padEnd(COL_ID)} ${formatSidebarSeconds(ttft).padStart(COL_TIME)} ${formatSidebarMetric(tps).padStart(COL_TIME)} ${formatSidebarSeconds(latency).padStart(COL_TIME)} ${formatSidebarTokens(input).padStart(COL_TOKEN)} ${formatSidebarTokens(output).padStart(COL_TOKEN)}`
85
- }
86
-
87
- /** Sidebar header sharing the exact column layout of formatSidebarRow. */
88
- export function formatSidebarHeader(): string {
89
- return ` ${"ID".padEnd(COL_ID)} ${"TTFT".padStart(COL_TIME)} ${"TPS".padStart(COL_TIME)} ${"Lat".padStart(COL_TIME)} ${"↑In".padStart(COL_TOKEN)} ${"↓Out".padStart(COL_TOKEN)}`
90
- }
@@ -1,95 +0,0 @@
1
- import { abbreviateModel, formatSidebarHeader, formatSidebarRow } from "./sidebar.js"
2
- import type { LogEntry } from "../types.js"
3
- import type { StatsRow } from "./sidebar.js"
4
-
5
- // Structural subset of ResolvedTheme from @opencode/theme/tui; kept local so
6
- // the TUI entry does not need a direct @opencode/theme dependency.
7
- export type ThemeTokens = {
8
- readonly text?: {
9
- readonly base?: string | undefined
10
- readonly muted?: string | undefined
11
- }
12
- }
13
-
14
- // Plain JSX expressions only — plugin-local solid may not notify the renderer (see tui/controller.ts header).
15
- export function ThroughputWidget(props: {
16
- readonly theme: ThemeTokens
17
- readonly recent: () => LogEntry[]
18
- readonly stats: () => StatsRow[]
19
- readonly open: () => boolean
20
- readonly onToggle: () => void
21
- }) {
22
- const fg = () => props.theme.text?.base
23
- const muted = () => props.theme.text?.muted
24
- const totalCount = () => props.stats().reduce((sum, row) => sum + row.stats.count, 0)
25
-
26
- // Same-screen consistency: recent rows resolve their display ID through
27
- // the stats rows (built from the controller's collision-aware map), so a
28
- // widened collider renders identically in both sections.
29
- const displayIDFor = (modelID: string): string => {
30
- for (const row of props.stats()) {
31
- if (row.modelID === modelID) return row.displayID
32
- }
33
- // Every recorded model has a row; this fallback only guards hostless
34
- // edge cases and keeps the plain 3-char abbreviation.
35
- return abbreviateModel(modelID)
36
- }
37
-
38
- return (
39
- <box>
40
- <box
41
- flexDirection="row"
42
- gap={1}
43
- onMouseDown={() => totalCount() > 0 && props.onToggle()}
44
- >
45
- {totalCount() > 0 ? <text fg={fg()}>{props.open() ? "▼" : "▶"}</text> : null}
46
- <text fg={fg()}>
47
- <b>Throughput</b>
48
- </text>
49
- {totalCount() > 0 ? (
50
- <text fg={muted()}>{totalCount() + " reqs"}</text>
51
- ) : null}
52
- </box>
53
- {totalCount() === 0 ? (
54
- <text fg={muted()}>{" Waiting for requests..."}</text>
55
- ) : null}
56
- {props.open() && totalCount() > 0 ? (
57
- <box flexDirection="column">
58
- <text fg={muted()}>{formatSidebarHeader()}</text>
59
- {props.stats().map((row) => (
60
- <text fg={muted()}>
61
- {formatSidebarRow(
62
- row.displayID,
63
- row.stats.avgTTFT,
64
- row.stats.avgTPS,
65
- row.stats.avgLatency,
66
- row.stats.totalInputTokens,
67
- row.stats.totalOutputTokens,
68
- )}
69
- </text>
70
- ))}
71
- {props.recent().length > 0 ? (
72
- <box flexDirection="column">
73
- <text fg={fg()}>
74
- {" "}
75
- <b>Recent</b>
76
- </text>
77
- {props.recent().map((entry: LogEntry) => (
78
- <text fg={muted()}>
79
- {formatSidebarRow(
80
- displayIDFor(entry.modelID),
81
- entry.ttft_ms,
82
- entry.tps,
83
- entry.latency_ms,
84
- entry.inputTokens,
85
- entry.outputTokens,
86
- )}
87
- </text>
88
- ))}
89
- </box>
90
- ) : null}
91
- </box>
92
- ) : null}
93
- </box>
94
- )
95
- }