@oh-my-pi-zen/omp-stats 16.3.6-zen.1
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/CHANGELOG.md +197 -0
- package/README.md +82 -0
- package/build.ts +95 -0
- package/dist/client/index.css +1 -0
- package/dist/client/index.html +24 -0
- package/dist/client/index.js +257 -0
- package/dist/client/styles.css +1656 -0
- package/dist/types/aggregator.d.ts +87 -0
- package/dist/types/client/App.d.ts +1 -0
- package/dist/types/client/api.d.ts +21 -0
- package/dist/types/client/app/AppLayout.d.ts +16 -0
- package/dist/types/client/app/NavRail.d.ts +7 -0
- package/dist/types/client/app/RangeControl.d.ts +7 -0
- package/dist/types/client/app/SyncButton.d.ts +14 -0
- package/dist/types/client/app/ThemeToggle.d.ts +1 -0
- package/dist/types/client/app/TopBar.d.ts +15 -0
- package/dist/types/client/app/routes.d.ts +12 -0
- package/dist/types/client/components/AgentTokenShare.d.ts +5 -0
- package/dist/types/client/components/chart-shared.d.ts +173 -0
- package/dist/types/client/components/models-table-shared.d.ts +175 -0
- package/dist/types/client/components/range-meta.d.ts +21 -0
- package/dist/types/client/data/charts.d.ts +1 -0
- package/dist/types/client/data/formatters.d.ts +8 -0
- package/dist/types/client/data/useHashRoute.d.ts +8 -0
- package/dist/types/client/data/useResource.d.ts +13 -0
- package/dist/types/client/data/view-models.d.ts +67 -0
- package/dist/types/client/index.d.ts +2 -0
- package/dist/types/client/routes/BehaviorRoute.d.ts +7 -0
- package/dist/types/client/routes/CostsRoute.d.ts +7 -0
- package/dist/types/client/routes/ErrorsRoute.d.ts +8 -0
- package/dist/types/client/routes/GainRoute.d.ts +7 -0
- package/dist/types/client/routes/ModelsRoute.d.ts +7 -0
- package/dist/types/client/routes/OverviewRoute.d.ts +8 -0
- package/dist/types/client/routes/ProjectsRoute.d.ts +7 -0
- package/dist/types/client/routes/RequestsRoute.d.ts +8 -0
- package/dist/types/client/routes/ToolsRoute.d.ts +7 -0
- package/dist/types/client/routes/index.d.ts +9 -0
- package/dist/types/client/types.d.ts +63 -0
- package/dist/types/client/ui/AsyncBoundary.d.ts +12 -0
- package/dist/types/client/ui/DataTable.d.ts +17 -0
- package/dist/types/client/ui/EmptyState.d.ts +7 -0
- package/dist/types/client/ui/ErrorState.d.ts +6 -0
- package/dist/types/client/ui/JsonBlock.d.ts +7 -0
- package/dist/types/client/ui/MetricCluster.d.ts +5 -0
- package/dist/types/client/ui/Panel.d.ts +7 -0
- package/dist/types/client/ui/RequestDrawer.d.ts +5 -0
- package/dist/types/client/ui/SegmentedControl.d.ts +12 -0
- package/dist/types/client/ui/Skeleton.d.ts +8 -0
- package/dist/types/client/ui/StatusPill.d.ts +7 -0
- package/dist/types/client/ui/index.d.ts +11 -0
- package/dist/types/client/useSystemTheme.d.ts +11 -0
- package/dist/types/db.d.ts +144 -0
- package/dist/types/embedded-client.d.ts +18 -0
- package/dist/types/gain-aggregator.d.ts +26 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/parser.d.ts +53 -0
- package/dist/types/server.d.ts +7 -0
- package/dist/types/shared-types.d.ts +301 -0
- package/dist/types/sync-worker.d.ts +31 -0
- package/dist/types/types.d.ts +164 -0
- package/dist/types/user-metrics.d.ts +72 -0
- package/package.json +95 -0
- package/src/aggregator.ts +501 -0
- package/src/client/App.tsx +109 -0
- package/src/client/api.ts +102 -0
- package/src/client/app/AppLayout.tsx +93 -0
- package/src/client/app/NavRail.tsx +44 -0
- package/src/client/app/RangeControl.tsx +39 -0
- package/src/client/app/SyncButton.tsx +75 -0
- package/src/client/app/ThemeToggle.tsx +37 -0
- package/src/client/app/TopBar.tsx +73 -0
- package/src/client/app/routes.ts +69 -0
- package/src/client/components/AgentTokenShare.tsx +68 -0
- package/src/client/components/chart-shared.tsx +257 -0
- package/src/client/components/models-table-shared.tsx +255 -0
- package/src/client/components/range-meta.ts +73 -0
- package/src/client/css.d.ts +1 -0
- package/src/client/data/charts.ts +14 -0
- package/src/client/data/formatters.ts +45 -0
- package/src/client/data/useHashRoute.ts +87 -0
- package/src/client/data/useResource.ts +154 -0
- package/src/client/data/view-models.ts +251 -0
- package/src/client/index.tsx +10 -0
- package/src/client/routes/BehaviorRoute.tsx +623 -0
- package/src/client/routes/CostsRoute.tsx +234 -0
- package/src/client/routes/ErrorsRoute.tsx +118 -0
- package/src/client/routes/GainRoute.tsx +226 -0
- package/src/client/routes/ModelsRoute.tsx +430 -0
- package/src/client/routes/OverviewRoute.tsx +342 -0
- package/src/client/routes/ProjectsRoute.tsx +163 -0
- package/src/client/routes/RequestsRoute.tsx +123 -0
- package/src/client/routes/ToolsRoute.tsx +463 -0
- package/src/client/routes/index.ts +9 -0
- package/src/client/styles.css +1330 -0
- package/src/client/types.ts +80 -0
- package/src/client/ui/AsyncBoundary.tsx +54 -0
- package/src/client/ui/DataTable.tsx +122 -0
- package/src/client/ui/EmptyState.tsx +16 -0
- package/src/client/ui/ErrorState.tsx +25 -0
- package/src/client/ui/JsonBlock.tsx +75 -0
- package/src/client/ui/MetricCluster.tsx +67 -0
- package/src/client/ui/Panel.tsx +24 -0
- package/src/client/ui/RequestDrawer.tsx +208 -0
- package/src/client/ui/SegmentedControl.tsx +36 -0
- package/src/client/ui/Skeleton.tsx +17 -0
- package/src/client/ui/StatusPill.tsx +15 -0
- package/src/client/ui/index.ts +11 -0
- package/src/client/useSystemTheme.ts +87 -0
- package/src/db.ts +1530 -0
- package/src/embedded-client.generated.txt +0 -0
- package/src/embedded-client.ts +26 -0
- package/src/gain-aggregator.ts +281 -0
- package/src/index.ts +194 -0
- package/src/parser.ts +450 -0
- package/src/server.ts +352 -0
- package/src/shared-types.ts +323 -0
- package/src/sync-worker.ts +40 -0
- package/src/types.ts +171 -0
- package/src/user-metrics.ts +685 -0
- package/tailwind.config.js +40 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type definitions consumed by both the server-side stats code and the
|
|
3
|
+
* standalone client bundle. Keep this file free of any imports from server-only
|
|
4
|
+
* packages (e.g. `@oh-my-pi-zen/pi-ai`, `bun:sqlite`) so the client can import it
|
|
5
|
+
* without dragging server dependencies into its bundle.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Aggregated stats for a model or folder.
|
|
9
|
+
*/
|
|
10
|
+
export interface AggregatedStats {
|
|
11
|
+
/** Total number of requests */
|
|
12
|
+
totalRequests: number;
|
|
13
|
+
/** Number of successful requests */
|
|
14
|
+
successfulRequests: number;
|
|
15
|
+
/** Number of failed requests */
|
|
16
|
+
failedRequests: number;
|
|
17
|
+
/** Error rate (0-1) */
|
|
18
|
+
errorRate: number;
|
|
19
|
+
/** Total input tokens */
|
|
20
|
+
totalInputTokens: number;
|
|
21
|
+
/** Total output tokens */
|
|
22
|
+
totalOutputTokens: number;
|
|
23
|
+
/** Total cache read tokens */
|
|
24
|
+
totalCacheReadTokens: number;
|
|
25
|
+
/** Total cache write tokens */
|
|
26
|
+
totalCacheWriteTokens: number;
|
|
27
|
+
/** Cache hit rate (0-1) */
|
|
28
|
+
cacheRate: number;
|
|
29
|
+
/** Total cost */
|
|
30
|
+
totalCost: number;
|
|
31
|
+
/** Total premium requests */
|
|
32
|
+
totalPremiumRequests: number;
|
|
33
|
+
/** Average duration in ms */
|
|
34
|
+
avgDuration: number | null;
|
|
35
|
+
/** Average TTFT in ms */
|
|
36
|
+
avgTtft: number | null;
|
|
37
|
+
/** Average tokens per second (output tokens / duration) */
|
|
38
|
+
avgTokensPerSecond: number | null;
|
|
39
|
+
/** Time range */
|
|
40
|
+
firstTimestamp: number;
|
|
41
|
+
lastTimestamp: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Stats grouped by model.
|
|
45
|
+
*/
|
|
46
|
+
export interface ModelStats extends AggregatedStats {
|
|
47
|
+
model: string;
|
|
48
|
+
provider: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Stats grouped by folder.
|
|
52
|
+
*/
|
|
53
|
+
export interface FolderStats extends AggregatedStats {
|
|
54
|
+
folder: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Time series data point.
|
|
58
|
+
*/
|
|
59
|
+
export interface TimeSeriesPoint {
|
|
60
|
+
/** Bucket timestamp (start of hour/day) */
|
|
61
|
+
timestamp: number;
|
|
62
|
+
/** Request count */
|
|
63
|
+
requests: number;
|
|
64
|
+
/** Error count */
|
|
65
|
+
errors: number;
|
|
66
|
+
/** Total tokens */
|
|
67
|
+
tokens: number;
|
|
68
|
+
/** Total cost */
|
|
69
|
+
cost: number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Model usage time series data point (daily buckets).
|
|
73
|
+
*/
|
|
74
|
+
export interface ModelTimeSeriesPoint {
|
|
75
|
+
/** Bucket timestamp (start of day) */
|
|
76
|
+
timestamp: number;
|
|
77
|
+
/** Model name */
|
|
78
|
+
model: string;
|
|
79
|
+
/** Provider name */
|
|
80
|
+
provider: string;
|
|
81
|
+
/** Request count */
|
|
82
|
+
requests: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Model performance time series data point (daily buckets).
|
|
86
|
+
*/
|
|
87
|
+
export interface ModelPerformancePoint {
|
|
88
|
+
/** Bucket timestamp (start of day) */
|
|
89
|
+
timestamp: number;
|
|
90
|
+
/** Model name */
|
|
91
|
+
model: string;
|
|
92
|
+
/** Provider name */
|
|
93
|
+
provider: string;
|
|
94
|
+
/** Request count */
|
|
95
|
+
requests: number;
|
|
96
|
+
/** Average TTFT in ms */
|
|
97
|
+
avgTtft: number | null;
|
|
98
|
+
/** Average tokens per second */
|
|
99
|
+
avgTokensPerSecond: number | null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Cost time series data point (daily buckets).
|
|
103
|
+
*/
|
|
104
|
+
export interface CostTimeSeriesPoint {
|
|
105
|
+
/** Bucket timestamp (start of day) */
|
|
106
|
+
timestamp: number;
|
|
107
|
+
/** Model name */
|
|
108
|
+
model: string;
|
|
109
|
+
/** Provider name */
|
|
110
|
+
provider: string;
|
|
111
|
+
/** Total cost for this bucket */
|
|
112
|
+
cost: number;
|
|
113
|
+
/** Cost breakdown */
|
|
114
|
+
costInput: number;
|
|
115
|
+
costOutput: number;
|
|
116
|
+
costCacheRead: number;
|
|
117
|
+
costCacheWrite: number;
|
|
118
|
+
/** Request count */
|
|
119
|
+
requests: number;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Overall dashboard stats.
|
|
123
|
+
*/
|
|
124
|
+
export interface DashboardStats {
|
|
125
|
+
overall: AggregatedStats;
|
|
126
|
+
byModel: ModelStats[];
|
|
127
|
+
byFolder: FolderStats[];
|
|
128
|
+
byAgentType: AgentTypeStats[];
|
|
129
|
+
timeSeries: TimeSeriesPoint[];
|
|
130
|
+
modelSeries: ModelTimeSeriesPoint[];
|
|
131
|
+
modelPerformanceSeries: ModelPerformancePoint[];
|
|
132
|
+
costSeries: CostTimeSeriesPoint[];
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Which agent produced a message, derived from its transcript file location
|
|
136
|
+
* inside the session directory: the top-level `<project>/<file>.jsonl` is the
|
|
137
|
+
* `main` agent, an `__advisor.jsonl` is the passive `advisor`, and any other
|
|
138
|
+
* nested transcript is a task `subagent`.
|
|
139
|
+
*/
|
|
140
|
+
export type AgentType = "main" | "subagent" | "advisor";
|
|
141
|
+
/**
|
|
142
|
+
* Token usage aggregated by {@link AgentType} over the active range. Token
|
|
143
|
+
* columns are explicit so the dashboard's share denominator matches the
|
|
144
|
+
* counts it renders (input + output + cache read + cache write).
|
|
145
|
+
*/
|
|
146
|
+
export interface AgentTypeStats {
|
|
147
|
+
agentType: AgentType;
|
|
148
|
+
/** Total number of requests */
|
|
149
|
+
totalRequests: number;
|
|
150
|
+
/** Total input tokens */
|
|
151
|
+
totalInputTokens: number;
|
|
152
|
+
/** Total output tokens */
|
|
153
|
+
totalOutputTokens: number;
|
|
154
|
+
/** Total cache read tokens */
|
|
155
|
+
totalCacheReadTokens: number;
|
|
156
|
+
/** Total cache write tokens */
|
|
157
|
+
totalCacheWriteTokens: number;
|
|
158
|
+
/** Total cost */
|
|
159
|
+
totalCost: number;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Behavior time-series point (daily bucket, per responding model).
|
|
163
|
+
*/
|
|
164
|
+
export interface BehaviorTimeSeriesPoint {
|
|
165
|
+
/** Bucket timestamp (start of day) */
|
|
166
|
+
timestamp: number;
|
|
167
|
+
/** Responding model ("unknown" if user msg never got a reply) */
|
|
168
|
+
model: string;
|
|
169
|
+
/** Responding provider */
|
|
170
|
+
provider: string;
|
|
171
|
+
/** Number of user messages in bucket */
|
|
172
|
+
messages: number;
|
|
173
|
+
/** Total yelling sentences in bucket */
|
|
174
|
+
yelling: number;
|
|
175
|
+
/** Total profanity hits in bucket */
|
|
176
|
+
profanity: number;
|
|
177
|
+
/** Total anguish signal in bucket */
|
|
178
|
+
anguish: number;
|
|
179
|
+
/** Total corrective-negation hits in bucket */
|
|
180
|
+
negation: number;
|
|
181
|
+
/** Total user-repeating-themselves hits in bucket */
|
|
182
|
+
repetition: number;
|
|
183
|
+
/** Total second-person blame hits in bucket */
|
|
184
|
+
blame: number;
|
|
185
|
+
/** Total characters in bucket */
|
|
186
|
+
chars: number;
|
|
187
|
+
}
|
|
188
|
+
export interface BehaviorOverallStats {
|
|
189
|
+
totalMessages: number;
|
|
190
|
+
totalYelling: number;
|
|
191
|
+
totalProfanity: number;
|
|
192
|
+
totalAnguish: number;
|
|
193
|
+
totalNegation: number;
|
|
194
|
+
totalRepetition: number;
|
|
195
|
+
totalBlame: number;
|
|
196
|
+
totalChars: number;
|
|
197
|
+
firstTimestamp: number;
|
|
198
|
+
lastTimestamp: number;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Per-model behavioral aggregate over the active range.
|
|
202
|
+
*/
|
|
203
|
+
export interface BehaviorModelStats {
|
|
204
|
+
model: string;
|
|
205
|
+
provider: string;
|
|
206
|
+
totalMessages: number;
|
|
207
|
+
totalYelling: number;
|
|
208
|
+
totalProfanity: number;
|
|
209
|
+
totalAnguish: number;
|
|
210
|
+
totalNegation: number;
|
|
211
|
+
totalRepetition: number;
|
|
212
|
+
totalBlame: number;
|
|
213
|
+
totalChars: number;
|
|
214
|
+
lastTimestamp: number;
|
|
215
|
+
}
|
|
216
|
+
export interface BehaviorDashboardStats {
|
|
217
|
+
overall: BehaviorOverallStats;
|
|
218
|
+
byModel: BehaviorModelStats[];
|
|
219
|
+
behaviorSeries: BehaviorTimeSeriesPoint[];
|
|
220
|
+
}
|
|
221
|
+
/** Token savings from a single source type. */
|
|
222
|
+
export interface GainSourceTotals {
|
|
223
|
+
savedTokens: number;
|
|
224
|
+
savedBytes: number;
|
|
225
|
+
hits: number;
|
|
226
|
+
/** originalBytes - savedBytes, when original is known */
|
|
227
|
+
outputBytes: number;
|
|
228
|
+
/** Total original bytes before compression, when known */
|
|
229
|
+
originalBytes: number;
|
|
230
|
+
/** savedBytes / originalBytes when both are known, else null */
|
|
231
|
+
reductionPercent: number | null;
|
|
232
|
+
}
|
|
233
|
+
/** Per-source breakdown. */
|
|
234
|
+
export type GainSource = "snapcompact";
|
|
235
|
+
/** Time-series point for gain (daily bucket). */
|
|
236
|
+
export interface GainTimeSeriesPoint {
|
|
237
|
+
date: string;
|
|
238
|
+
snapcompact: number;
|
|
239
|
+
total: number;
|
|
240
|
+
}
|
|
241
|
+
/** Complete gain dashboard payload. */
|
|
242
|
+
export interface GainDashboardStats {
|
|
243
|
+
/** Aggregate across all sources for the active range. */
|
|
244
|
+
overall: GainSourceTotals;
|
|
245
|
+
/** Per-source breakdown. */
|
|
246
|
+
bySource: Record<GainSource, GainSourceTotals>;
|
|
247
|
+
/** Daily time series. */
|
|
248
|
+
timeSeries: GainTimeSeriesPoint[];
|
|
249
|
+
/** Active project filter (cwd prefix), or null for all projects. */
|
|
250
|
+
project: string | null;
|
|
251
|
+
/** All distinct projects seen in the data, for the selector. */
|
|
252
|
+
projects: string[];
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Aggregated usage for a single tool over the active range.
|
|
256
|
+
*
|
|
257
|
+
* Token/cost fields are the *real* provider usage of the assistant turns that
|
|
258
|
+
* invoked the tool, split evenly across that turn's tool calls so the numbers
|
|
259
|
+
* stay additive (a turn with 3 calls contributes a third of its usage to each
|
|
260
|
+
* tool). Payload fields (`argsChars`/`resultChars`) are raw character counts
|
|
261
|
+
* of the serialized arguments and the text fed back into context — a size
|
|
262
|
+
* proxy, not provider-counted tokens.
|
|
263
|
+
*/
|
|
264
|
+
export interface ToolUsageStats {
|
|
265
|
+
/** Tool name as recorded on the tool call. */
|
|
266
|
+
tool: string;
|
|
267
|
+
/** Number of tool calls. */
|
|
268
|
+
calls: number;
|
|
269
|
+
/** Calls whose result came back with `isError`. */
|
|
270
|
+
errors: number;
|
|
271
|
+
/** Serialized tool-call argument characters. */
|
|
272
|
+
argsChars: number;
|
|
273
|
+
/** Text characters of tool results fed back into context. */
|
|
274
|
+
resultChars: number;
|
|
275
|
+
/** Total provider tokens of invoking turns, attributed per call share. */
|
|
276
|
+
totalTokensShare: number;
|
|
277
|
+
/** Output tokens of invoking turns, attributed per call share. */
|
|
278
|
+
outputTokensShare: number;
|
|
279
|
+
/** Cost (USD) of invoking turns, attributed per call share. */
|
|
280
|
+
costShare: number;
|
|
281
|
+
/** Unix ms of the most recent call in range. */
|
|
282
|
+
lastUsed: number;
|
|
283
|
+
}
|
|
284
|
+
/** Per-(tool, model) breakdown with the same attribution as {@link ToolUsageStats}. */
|
|
285
|
+
export interface ToolModelStats extends ToolUsageStats {
|
|
286
|
+
model: string;
|
|
287
|
+
provider: string;
|
|
288
|
+
}
|
|
289
|
+
/** Tool-call time-series point (one bucket per tool). */
|
|
290
|
+
export interface ToolTimeSeriesPoint {
|
|
291
|
+
timestamp: number;
|
|
292
|
+
tool: string;
|
|
293
|
+
calls: number;
|
|
294
|
+
errors: number;
|
|
295
|
+
}
|
|
296
|
+
/** Complete tools dashboard payload. */
|
|
297
|
+
export interface ToolDashboardStats {
|
|
298
|
+
byTool: ToolUsageStats[];
|
|
299
|
+
byToolModel: ToolModelStats[];
|
|
300
|
+
series: ToolTimeSeriesPoint[];
|
|
301
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stateless parse worker for `syncAllSessions`. The main thread owns the
|
|
3
|
+
* SQLite handle; workers receive `{ sessionFile, fromOffset }`, run
|
|
4
|
+
* `parseSessionFile` (which is pure I/O + CPU, no DB), and post the
|
|
5
|
+
* structured-clone-safe result back. One in-flight request per worker so
|
|
6
|
+
* the main thread can fan jobs out 1:1 with the pool size.
|
|
7
|
+
*
|
|
8
|
+
* A `{ kind: "ping" }` request is also accepted and replies with
|
|
9
|
+
* `{ ok: true, kind: "pong" }` — used by `smokeTestSyncWorker` to prove the
|
|
10
|
+
* worker actually spawns and runs in compiled binaries (regression coverage
|
|
11
|
+
* for issue #1011 / PR #1027, where the worker silently failed to load).
|
|
12
|
+
*/
|
|
13
|
+
import { type ParseSessionResult } from "./parser";
|
|
14
|
+
export type SyncWorkerRequest = {
|
|
15
|
+
kind?: "parse";
|
|
16
|
+
sessionFile: string;
|
|
17
|
+
fromOffset: number;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "ping";
|
|
20
|
+
};
|
|
21
|
+
export type SyncWorkerResponse = {
|
|
22
|
+
ok: true;
|
|
23
|
+
kind?: "parse";
|
|
24
|
+
result: ParseSessionResult;
|
|
25
|
+
} | {
|
|
26
|
+
ok: true;
|
|
27
|
+
kind: "pong";
|
|
28
|
+
} | {
|
|
29
|
+
ok: false;
|
|
30
|
+
error: string;
|
|
31
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { AssistantMessage, ServiceTier, ServiceTierByFamily, StopReason, Usage } from "@oh-my-pi-zen/pi-ai";
|
|
2
|
+
import type { AgentType } from "./shared-types";
|
|
3
|
+
export * from "./shared-types";
|
|
4
|
+
/**
|
|
5
|
+
* Extracted stats from an assistant message.
|
|
6
|
+
*/
|
|
7
|
+
export interface MessageStats {
|
|
8
|
+
/** Database ID */
|
|
9
|
+
id?: number;
|
|
10
|
+
/** Session file path */
|
|
11
|
+
sessionFile: string;
|
|
12
|
+
/** Entry ID within the session */
|
|
13
|
+
entryId: string;
|
|
14
|
+
/** Folder/project path (extracted from session filename) */
|
|
15
|
+
folder: string;
|
|
16
|
+
/** Model ID */
|
|
17
|
+
model: string;
|
|
18
|
+
/** Provider name */
|
|
19
|
+
provider: string;
|
|
20
|
+
/** API type */
|
|
21
|
+
api: string;
|
|
22
|
+
/** Unix timestamp in milliseconds */
|
|
23
|
+
timestamp: number;
|
|
24
|
+
/** Request duration in milliseconds */
|
|
25
|
+
duration: number | null;
|
|
26
|
+
/** Time to first token in milliseconds */
|
|
27
|
+
ttft: number | null;
|
|
28
|
+
/** Stop reason */
|
|
29
|
+
stopReason: StopReason;
|
|
30
|
+
/** Error message if stopReason is error */
|
|
31
|
+
errorMessage: string | null;
|
|
32
|
+
/** Token usage */
|
|
33
|
+
usage: Usage;
|
|
34
|
+
/** Which agent produced this message (main agent, task subagent, advisor) */
|
|
35
|
+
agentType: AgentType;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Full details of a request, including content.
|
|
39
|
+
*/
|
|
40
|
+
export interface RequestDetails extends MessageStats {
|
|
41
|
+
/** The full conversation history or just the last turn. */
|
|
42
|
+
messages: unknown[];
|
|
43
|
+
/** The model's response. */
|
|
44
|
+
output: unknown;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Session log entry types.
|
|
48
|
+
*/
|
|
49
|
+
export interface SessionHeader {
|
|
50
|
+
type: "session";
|
|
51
|
+
version: number;
|
|
52
|
+
id: string;
|
|
53
|
+
timestamp: string;
|
|
54
|
+
cwd: string;
|
|
55
|
+
title?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface SessionMessageEntry {
|
|
58
|
+
type: "message";
|
|
59
|
+
id: string;
|
|
60
|
+
parentId: string | null;
|
|
61
|
+
timestamp: string;
|
|
62
|
+
message: AssistantMessage | {
|
|
63
|
+
role: "user" | "toolResult";
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export interface SessionServiceTierChangeEntry {
|
|
67
|
+
type: "service_tier_change";
|
|
68
|
+
id: string;
|
|
69
|
+
parentId?: string | null;
|
|
70
|
+
timestamp: string;
|
|
71
|
+
serviceTier: ServiceTierByFamily | ServiceTier | null;
|
|
72
|
+
}
|
|
73
|
+
export type SessionEntry = SessionHeader | SessionMessageEntry | SessionServiceTierChangeEntry | {
|
|
74
|
+
type: string;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Behavioral stats extracted from a single user message.
|
|
78
|
+
*/
|
|
79
|
+
export interface UserMessageStats {
|
|
80
|
+
/** Database ID */
|
|
81
|
+
id?: number;
|
|
82
|
+
/** Session file path */
|
|
83
|
+
sessionFile: string;
|
|
84
|
+
/** Entry ID within the session */
|
|
85
|
+
entryId: string;
|
|
86
|
+
/** Folder/project path */
|
|
87
|
+
folder: string;
|
|
88
|
+
/** Unix timestamp in ms */
|
|
89
|
+
timestamp: number;
|
|
90
|
+
/** Model that responded to this user message, if linked */
|
|
91
|
+
model: string | null;
|
|
92
|
+
/** Provider that responded to this user message, if linked */
|
|
93
|
+
provider: string | null;
|
|
94
|
+
/** Total characters of message text */
|
|
95
|
+
chars: number;
|
|
96
|
+
/** Whitespace-delimited word count */
|
|
97
|
+
words: number;
|
|
98
|
+
/** Yelling sentences (> 50% uppercase letters) */
|
|
99
|
+
yelling: number;
|
|
100
|
+
/** Profanity hits */
|
|
101
|
+
profanity: number;
|
|
102
|
+
/** Catch-all upset signal: drama runs + `noooo`/`ughh`/... + `dude` + `..` */
|
|
103
|
+
anguish: number;
|
|
104
|
+
/** Corrective negation ("no", "nope", "thats not what i meant") */
|
|
105
|
+
negation: number;
|
|
106
|
+
/** User repeating themselves ("i meant", "still doesnt work", "like i said") */
|
|
107
|
+
repetition: number;
|
|
108
|
+
/** Second-person reproach ("you didnt", "you broke", "stop X-ing") */
|
|
109
|
+
blame: number;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Pair emitted by the parser when it sees an assistant message whose
|
|
113
|
+
* `parentId` points to a user message that wasn't parsed in the same pass
|
|
114
|
+
* (e.g. user prompt landed in an earlier incremental sync). The aggregator
|
|
115
|
+
* applies the link to the persisted `user_messages` row so it stops showing
|
|
116
|
+
* up in the "unknown" model bucket.
|
|
117
|
+
*/
|
|
118
|
+
export interface UserMessageLink {
|
|
119
|
+
sessionFile: string;
|
|
120
|
+
entryId: string;
|
|
121
|
+
model: string;
|
|
122
|
+
provider: string;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* One tool call extracted from an assistant message's `toolCall` content
|
|
126
|
+
* blocks. `callsInTurn` records how many calls that assistant turn contained
|
|
127
|
+
* so aggregation can split the turn's real provider usage evenly per call.
|
|
128
|
+
*/
|
|
129
|
+
export interface ToolCallStats {
|
|
130
|
+
/** Session file path */
|
|
131
|
+
sessionFile: string;
|
|
132
|
+
/** Assistant-message entry ID that emitted the call */
|
|
133
|
+
entryId: string;
|
|
134
|
+
/** Provider-assigned tool call ID (unique within a session) */
|
|
135
|
+
toolCallId: string;
|
|
136
|
+
/** Folder/project path (extracted from session filename) */
|
|
137
|
+
folder: string;
|
|
138
|
+
/** Tool name */
|
|
139
|
+
toolName: string;
|
|
140
|
+
/** Model that emitted the call */
|
|
141
|
+
model: string;
|
|
142
|
+
/** Provider name */
|
|
143
|
+
provider: string;
|
|
144
|
+
/** Assistant-message timestamp (Unix ms) */
|
|
145
|
+
timestamp: number;
|
|
146
|
+
/** Which agent produced the call */
|
|
147
|
+
agentType: AgentType;
|
|
148
|
+
/** Total tool calls in the same assistant turn (>= 1) */
|
|
149
|
+
callsInTurn: number;
|
|
150
|
+
/** Serialized argument characters */
|
|
151
|
+
argsChars: number;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Result linkage emitted when the parser sees a `toolResult` message entry.
|
|
155
|
+
* Applied as an UPDATE on the persisted tool-call row — results can land in a
|
|
156
|
+
* later incremental sync pass than the call that produced them.
|
|
157
|
+
*/
|
|
158
|
+
export interface ToolResultLink {
|
|
159
|
+
sessionFile: string;
|
|
160
|
+
toolCallId: string;
|
|
161
|
+
/** Text characters fed back into context */
|
|
162
|
+
resultChars: number;
|
|
163
|
+
isError: boolean;
|
|
164
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavioral metrics extracted from a single user message.
|
|
3
|
+
*
|
|
4
|
+
* Pure, side-effect free. Designed for batch use during session ingestion
|
|
5
|
+
* and standalone testing.
|
|
6
|
+
*/
|
|
7
|
+
export interface UserMessageMetrics {
|
|
8
|
+
/** Total characters of analyzed text. */
|
|
9
|
+
chars: number;
|
|
10
|
+
/** Whitespace-delimited word count. */
|
|
11
|
+
words: number;
|
|
12
|
+
/**
|
|
13
|
+
* Number of "yelling" sentences: sentences where more than half of the
|
|
14
|
+
* alphabetic characters are uppercase (and there are enough letters to
|
|
15
|
+
* make the ratio meaningful - short acronyms like "OK" don't count).
|
|
16
|
+
*/
|
|
17
|
+
yelling: number;
|
|
18
|
+
/** Profanity hits (word-boundary, case-insensitive). */
|
|
19
|
+
profanity: number;
|
|
20
|
+
/**
|
|
21
|
+
* Catch-all "obviously upset" signal computed on a *prose-only* body
|
|
22
|
+
* (code fences, XML/HTML tags, URLs, file mentions, and quoted lines
|
|
23
|
+
* are stripped first; messages whose remaining prose is >=3 lines score
|
|
24
|
+
* zero because formatted prompts aren't tantrums).
|
|
25
|
+
*
|
|
26
|
+
* Sum of:
|
|
27
|
+
* - drama runs: 3+ `!` / `?` (with `1`-mishit fallout)
|
|
28
|
+
* - elongated interjections: `noooo`, `ahhhh`, `ughhh`, `argh`, `stooop`,
|
|
29
|
+
* `whyyy`, `fuuu(ck)`, `shiiit`, `wtfff`, `omggg`, `yessss`, `helpp`,
|
|
30
|
+
* `goddd`, `dammm`, `bruhh`
|
|
31
|
+
* - standalone `dude`
|
|
32
|
+
* - dot runs: `..`, `...`, `....+`
|
|
33
|
+
*/
|
|
34
|
+
anguish: number;
|
|
35
|
+
/**
|
|
36
|
+
* Corrective negation: the user is telling us we got it wrong.
|
|
37
|
+
*
|
|
38
|
+
* Counted on the same prose-only body as {@link anguish}.
|
|
39
|
+
*
|
|
40
|
+
* - line-leading `no` / `nope` / `nah` / `nvm` / `wrong` / `incorrect`
|
|
41
|
+
* (word-bounded, so `now`, `nobody`, `north` don't match)
|
|
42
|
+
* - `that(?:'s)? not (what|right|it)` and `not what i (meant|asked|said|wanted)`
|
|
43
|
+
*/
|
|
44
|
+
negation: number;
|
|
45
|
+
/**
|
|
46
|
+
* The user is repeating themselves - strong signal the previous turn
|
|
47
|
+
* missed the ask. Counts hits for:
|
|
48
|
+
*
|
|
49
|
+
* - `i (meant|said|asked|told you|already (said|told|did|asked|wrote))`
|
|
50
|
+
* - `(like|as) i (said|told you|asked)`
|
|
51
|
+
* - `still (doesn't|isn't|not|broken|wrong|fails|failing|the same|same)`
|
|
52
|
+
*
|
|
53
|
+
* Bare `still` / `again` are too ambiguous to count alone (they show up
|
|
54
|
+
* in normal speech like "try again" or "still works").
|
|
55
|
+
*/
|
|
56
|
+
repetition: number;
|
|
57
|
+
/**
|
|
58
|
+
* Direct second-person reproach pinned on the agent:
|
|
59
|
+
*
|
|
60
|
+
* - `you (didn't|did not|broke|missed|forgot|keep|always|never|still|ignored)`
|
|
61
|
+
* - sentence-leading `stop <verb>ing` imperatives
|
|
62
|
+
*/
|
|
63
|
+
blame: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Compute behavioral metrics for a user message.
|
|
67
|
+
*
|
|
68
|
+
* `text` may be empty or whitespace; in that case every metric is 0.
|
|
69
|
+
*/
|
|
70
|
+
export declare function computeUserMessageMetrics(text: string): UserMessageMetrics;
|
|
71
|
+
/** Empty metrics constant for callers that need a default. */
|
|
72
|
+
export declare const EMPTY_USER_METRICS: UserMessageMetrics;
|
package/package.json
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "module",
|
|
3
|
+
"name": "@oh-my-pi-zen/omp-stats",
|
|
4
|
+
"version": "16.3.6-zen.1",
|
|
5
|
+
"description": "Local observability dashboard for pi AI usage statistics",
|
|
6
|
+
"homepage": "https://omp.sh",
|
|
7
|
+
"author": "Can Boluk",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/cagedbird043/oh-my-pi-zen.git",
|
|
12
|
+
"directory": "packages/stats"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/cagedbird043/oh-my-pi-zen/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"ai",
|
|
19
|
+
"observability",
|
|
20
|
+
"metrics",
|
|
21
|
+
"dashboard",
|
|
22
|
+
"llm",
|
|
23
|
+
"statistics"
|
|
24
|
+
],
|
|
25
|
+
"main": "./src/index.ts",
|
|
26
|
+
"types": "./dist/types/index.d.ts",
|
|
27
|
+
"bin": {
|
|
28
|
+
"omp-stats": "./src/index.ts"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"gen:stats": "bun scripts/generate-client-bundle.ts --generate",
|
|
32
|
+
"gen:stats:reset": "bun scripts/generate-client-bundle.ts --reset",
|
|
33
|
+
"build": "bun run build.ts",
|
|
34
|
+
"dev": "bun run src/index.ts",
|
|
35
|
+
"check": "biome check . && bun run check:types",
|
|
36
|
+
"check:types": "tsgo -p tsconfig.json --noEmit && tsgo -p tsconfig.client.json --noEmit",
|
|
37
|
+
"lint": "biome lint .",
|
|
38
|
+
"fix": "biome check --write --unsafe .",
|
|
39
|
+
"fmt": "biome format --write ."
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@oh-my-pi-zen/pi-ai": "16.3.6-zen.1",
|
|
43
|
+
"@oh-my-pi-zen/pi-catalog": "16.3.6-zen.1",
|
|
44
|
+
"@oh-my-pi-zen/pi-utils": "16.3.6-zen.1",
|
|
45
|
+
"@tailwindcss/node": "^4.3.0",
|
|
46
|
+
"chart.js": "^4.5.1",
|
|
47
|
+
"date-fns": "^4.4.0",
|
|
48
|
+
"lucide-react": "^1.17.0",
|
|
49
|
+
"react": "19.2.7",
|
|
50
|
+
"react-chartjs-2": "^5.3.1",
|
|
51
|
+
"react-dom": "19.2.7",
|
|
52
|
+
"tailwindcss": "^4.3.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/bun": "^1.3.14",
|
|
56
|
+
"@types/react": "^19.2.17",
|
|
57
|
+
"@types/react-dom": "^19.2.3",
|
|
58
|
+
"postcss": "^8.5.15"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"bun": ">=1.3.14"
|
|
62
|
+
},
|
|
63
|
+
"files": [
|
|
64
|
+
"src",
|
|
65
|
+
"build.ts",
|
|
66
|
+
"tailwind.config.js",
|
|
67
|
+
"README.md",
|
|
68
|
+
"CHANGELOG.md",
|
|
69
|
+
"dist/types",
|
|
70
|
+
"dist/client"
|
|
71
|
+
],
|
|
72
|
+
"exports": {
|
|
73
|
+
".": {
|
|
74
|
+
"types": "./dist/types/index.d.ts",
|
|
75
|
+
"import": "./src/index.ts"
|
|
76
|
+
},
|
|
77
|
+
"./*": {
|
|
78
|
+
"types": "./dist/types/*.d.ts",
|
|
79
|
+
"import": "./src/*.ts"
|
|
80
|
+
},
|
|
81
|
+
"./client": {
|
|
82
|
+
"types": "./dist/types/client/index.d.ts",
|
|
83
|
+
"import": "./src/client/index.tsx"
|
|
84
|
+
},
|
|
85
|
+
"./client/*": {
|
|
86
|
+
"types": "./dist/types/client/*.d.ts",
|
|
87
|
+
"import": "./src/client/*.ts"
|
|
88
|
+
},
|
|
89
|
+
"./client/components/*": {
|
|
90
|
+
"types": "./dist/types/client/components/*.d.ts",
|
|
91
|
+
"import": "./src/client/components/*.ts"
|
|
92
|
+
},
|
|
93
|
+
"./*.js": "./src/*.ts"
|
|
94
|
+
}
|
|
95
|
+
}
|