@oh-my-pi/omp-stats 15.1.0 → 15.1.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/dist/client/index.css +1 -0
- package/dist/client/index.html +13 -0
- package/dist/client/index.js +257 -0
- package/dist/client/styles.css +1159 -0
- package/dist/types/aggregator.d.ts +65 -0
- package/dist/types/client/App.d.ts +1 -0
- package/dist/types/client/api.d.ts +10 -0
- package/dist/types/client/components/BehaviorChart.d.ts +6 -0
- package/dist/types/client/components/BehaviorModelsTable.d.ts +7 -0
- package/dist/types/client/components/BehaviorSummary.d.ts +7 -0
- package/dist/types/client/components/ChartsContainer.d.ts +6 -0
- package/dist/types/client/components/CostChart.d.ts +6 -0
- package/dist/types/client/components/CostSummary.d.ts +6 -0
- package/dist/types/client/components/Header.d.ts +12 -0
- package/dist/types/client/components/ModelsTable.d.ts +7 -0
- package/dist/types/client/components/RequestDetail.d.ts +6 -0
- package/dist/types/client/components/RequestList.d.ts +8 -0
- package/dist/types/client/components/StatsGrid.d.ts +6 -0
- package/dist/types/client/components/chart-shared.d.ts +192 -0
- package/dist/types/client/components/models-table-shared.d.ts +195 -0
- package/dist/types/client/index.d.ts +1 -0
- package/dist/types/client/types.d.ts +62 -0
- package/dist/types/client/useSystemTheme.d.ts +2 -0
- package/dist/types/db.d.ts +93 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/parser.d.ts +40 -0
- package/dist/types/server.d.ts +7 -0
- package/dist/types/shared-types.d.ts +192 -0
- package/dist/types/sync-worker.d.ts +31 -0
- package/dist/types/types.d.ts +120 -0
- package/dist/types/user-metrics.d.ts +72 -0
- package/package.json +12 -10
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import type { AggregatedStats, BehaviorModelStats, BehaviorOverallStats, BehaviorTimeSeriesPoint, CostTimeSeriesPoint, FolderStats, MessageStats, ModelPerformancePoint, ModelStats, ModelTimeSeriesPoint, TimeSeriesPoint, UserMessageLink, UserMessageStats } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Initialize the database and create tables.
|
|
5
|
+
*/
|
|
6
|
+
export declare function initDb(): Promise<Database>;
|
|
7
|
+
/**
|
|
8
|
+
* Get the stored offset for a session file.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getFileOffset(sessionFile: string): {
|
|
11
|
+
offset: number;
|
|
12
|
+
lastModified: number;
|
|
13
|
+
} | null;
|
|
14
|
+
/**
|
|
15
|
+
* Update the stored offset for a session file.
|
|
16
|
+
*/
|
|
17
|
+
export declare function setFileOffset(sessionFile: string, offset: number, lastModified: number): void;
|
|
18
|
+
/**
|
|
19
|
+
* Insert message stats into the database.
|
|
20
|
+
*/
|
|
21
|
+
export declare function insertMessageStats(stats: MessageStats[]): number;
|
|
22
|
+
/**
|
|
23
|
+
* Get overall aggregated stats.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getOverallStats(cutoff?: number): AggregatedStats;
|
|
26
|
+
/**
|
|
27
|
+
* Get stats grouped by model.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getStatsByModel(cutoff?: number): ModelStats[];
|
|
30
|
+
/**
|
|
31
|
+
* Get stats grouped by folder.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getStatsByFolder(cutoff?: number): FolderStats[];
|
|
34
|
+
/**
|
|
35
|
+
* Get time series data.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getTimeSeries(hours?: number, cutoff?: number | null, bucketMs?: number): TimeSeriesPoint[];
|
|
38
|
+
/**
|
|
39
|
+
* Get daily performance time series data for the last N days.
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* Get daily model usage time series data for the last N days.
|
|
43
|
+
*/
|
|
44
|
+
export declare function getModelTimeSeries(days?: number, cutoff?: number | null): ModelTimeSeriesPoint[];
|
|
45
|
+
/**
|
|
46
|
+
* Get daily model performance time series data for the last N days.
|
|
47
|
+
*/
|
|
48
|
+
export declare function getModelPerformanceSeries(days?: number, cutoff?: number | null): ModelPerformancePoint[];
|
|
49
|
+
/**
|
|
50
|
+
* Get total message count.
|
|
51
|
+
*/
|
|
52
|
+
export declare function getMessageCount(): number;
|
|
53
|
+
/**
|
|
54
|
+
* Close the database connection.
|
|
55
|
+
*/
|
|
56
|
+
export declare function closeDb(): void;
|
|
57
|
+
export declare function getRecentRequests(limit?: number): MessageStats[];
|
|
58
|
+
export declare function getRecentErrors(limit?: number): MessageStats[];
|
|
59
|
+
export declare function getMessageById(id: number): MessageStats | null;
|
|
60
|
+
/**
|
|
61
|
+
* Get daily cost time series data for the last N days, broken down by model.
|
|
62
|
+
*/
|
|
63
|
+
export declare function getCostTimeSeries(days?: number, cutoff?: number | null): CostTimeSeriesPoint[];
|
|
64
|
+
export declare function markPriorityPremiumRequestsBackfillComplete(): void;
|
|
65
|
+
export declare function markUserMessagesBackfillComplete(): void;
|
|
66
|
+
export declare function markUserMessageLinksRepairComplete(): void;
|
|
67
|
+
/**
|
|
68
|
+
* Insert user-message stats. Idempotent via UNIQUE(session_file, entry_id).
|
|
69
|
+
*/
|
|
70
|
+
export declare function insertUserMessageStats(stats: UserMessageStats[]): number;
|
|
71
|
+
/**
|
|
72
|
+
* Backfill the responding `model`/`provider` on user-message rows that were
|
|
73
|
+
* persisted before their assistant reply was parsed (a side effect of
|
|
74
|
+
* incremental `fromOffset` syncing: the `userByEntryId` map in
|
|
75
|
+
* `parseSessionFile` only spans a single pass). Each row is updated at most
|
|
76
|
+
* once because the `model IS NULL` guard short-circuits subsequent passes.
|
|
77
|
+
*
|
|
78
|
+
* Returns the number of rows actually updated.
|
|
79
|
+
*/
|
|
80
|
+
export declare function updateUserMessageLinks(links: UserMessageLink[]): number;
|
|
81
|
+
/**
|
|
82
|
+
* Daily behavioral time series, grouped by responding model+provider.
|
|
83
|
+
*/
|
|
84
|
+
export declare function getBehaviorTimeSeries(cutoff?: number | null): BehaviorTimeSeriesPoint[];
|
|
85
|
+
/**
|
|
86
|
+
* Overall behavioral totals across the cutoff window.
|
|
87
|
+
*/
|
|
88
|
+
export declare function getBehaviorOverall(cutoff?: number | null): BehaviorOverallStats;
|
|
89
|
+
/**
|
|
90
|
+
* Per-model behavioral totals over the cutoff window. "Unknown" represents
|
|
91
|
+
* user messages that never received an assistant reply.
|
|
92
|
+
*/
|
|
93
|
+
export declare function getBehaviorByModel(cutoff?: number | null): BehaviorModelStats[];
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
export { getDashboardStats, getTotalMessageCount, type SyncOptions, type SyncProgress, smokeTestSyncWorker, syncAllSessions, } from "./aggregator";
|
|
3
|
+
export { closeDb } from "./db";
|
|
4
|
+
export { startServer } from "./server";
|
|
5
|
+
export type { AggregatedStats, DashboardStats, FolderStats, MessageStats, ModelPerformancePoint, ModelStats, ModelTimeSeriesPoint, TimeSeriesPoint, } from "./types";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { MessageStats, SessionEntry, UserMessageLink, UserMessageStats } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a session file and extract all assistant message stats.
|
|
4
|
+
* Uses incremental reading with offset tracking.
|
|
5
|
+
*
|
|
6
|
+
* Service-tier carry-over: `currentServiceTier` is a session-scoped piece of
|
|
7
|
+
* state derived from `service_tier_change` entries that affects whether
|
|
8
|
+
* subsequent OpenAI assistant replies count as premium requests. Incremental
|
|
9
|
+
* syncs that resume past the most-recent tier change would otherwise lose
|
|
10
|
+
* that state and silently record `premiumRequests = 0` for priority traffic
|
|
11
|
+
* (the coding-agent stopped folding the tier into `usage.premiumRequests`
|
|
12
|
+
* after 13f59162e — the parser is now the sole source of truth). When
|
|
13
|
+
* `fromOffset > 0` we therefore scan the bytes preceding `fromOffset`
|
|
14
|
+
* for the latest service-tier value before parsing the unprocessed tail.
|
|
15
|
+
* The scan only keeps the current tier and does not materialize prefix
|
|
16
|
+
* entries, preserving offset-based memory behavior for large sessions.
|
|
17
|
+
*/
|
|
18
|
+
export interface ParseSessionResult {
|
|
19
|
+
stats: MessageStats[];
|
|
20
|
+
userStats: UserMessageStats[];
|
|
21
|
+
userLinks: UserMessageLink[];
|
|
22
|
+
newOffset: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function parseSessionFile(sessionPath: string, fromOffset?: number): Promise<ParseSessionResult>;
|
|
25
|
+
/**
|
|
26
|
+
* List all session directories (folders).
|
|
27
|
+
*/
|
|
28
|
+
export declare function listSessionFolders(): Promise<string[]>;
|
|
29
|
+
/**
|
|
30
|
+
* List all session files in a folder.
|
|
31
|
+
*/
|
|
32
|
+
export declare function listSessionFiles(folderPath: string): Promise<string[]>;
|
|
33
|
+
/**
|
|
34
|
+
* List all session files across all folders.
|
|
35
|
+
*/
|
|
36
|
+
export declare function listAllSessionFiles(): Promise<string[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Find a specific entry in a session file.
|
|
39
|
+
*/
|
|
40
|
+
export declare function getSessionEntry(sessionPath: string, entryId: string): Promise<SessionEntry | null>;
|
|
@@ -0,0 +1,192 @@
|
|
|
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/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
|
+
timeSeries: TimeSeriesPoint[];
|
|
129
|
+
modelSeries: ModelTimeSeriesPoint[];
|
|
130
|
+
modelPerformanceSeries: ModelPerformancePoint[];
|
|
131
|
+
costSeries: CostTimeSeriesPoint[];
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Behavior time-series point (daily bucket, per responding model).
|
|
135
|
+
*/
|
|
136
|
+
export interface BehaviorTimeSeriesPoint {
|
|
137
|
+
/** Bucket timestamp (start of day) */
|
|
138
|
+
timestamp: number;
|
|
139
|
+
/** Responding model ("unknown" if user msg never got a reply) */
|
|
140
|
+
model: string;
|
|
141
|
+
/** Responding provider */
|
|
142
|
+
provider: string;
|
|
143
|
+
/** Number of user messages in bucket */
|
|
144
|
+
messages: number;
|
|
145
|
+
/** Total yelling sentences in bucket */
|
|
146
|
+
yelling: number;
|
|
147
|
+
/** Total profanity hits in bucket */
|
|
148
|
+
profanity: number;
|
|
149
|
+
/** Total anguish signal in bucket */
|
|
150
|
+
anguish: number;
|
|
151
|
+
/** Total corrective-negation hits in bucket */
|
|
152
|
+
negation: number;
|
|
153
|
+
/** Total user-repeating-themselves hits in bucket */
|
|
154
|
+
repetition: number;
|
|
155
|
+
/** Total second-person blame hits in bucket */
|
|
156
|
+
blame: number;
|
|
157
|
+
/** Total characters in bucket */
|
|
158
|
+
chars: number;
|
|
159
|
+
}
|
|
160
|
+
export interface BehaviorOverallStats {
|
|
161
|
+
totalMessages: number;
|
|
162
|
+
totalYelling: number;
|
|
163
|
+
totalProfanity: number;
|
|
164
|
+
totalAnguish: number;
|
|
165
|
+
totalNegation: number;
|
|
166
|
+
totalRepetition: number;
|
|
167
|
+
totalBlame: number;
|
|
168
|
+
totalChars: number;
|
|
169
|
+
firstTimestamp: number;
|
|
170
|
+
lastTimestamp: number;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Per-model behavioral aggregate over the active range.
|
|
174
|
+
*/
|
|
175
|
+
export interface BehaviorModelStats {
|
|
176
|
+
model: string;
|
|
177
|
+
provider: string;
|
|
178
|
+
totalMessages: number;
|
|
179
|
+
totalYelling: number;
|
|
180
|
+
totalProfanity: number;
|
|
181
|
+
totalAnguish: number;
|
|
182
|
+
totalNegation: number;
|
|
183
|
+
totalRepetition: number;
|
|
184
|
+
totalBlame: number;
|
|
185
|
+
totalChars: number;
|
|
186
|
+
lastTimestamp: number;
|
|
187
|
+
}
|
|
188
|
+
export interface BehaviorDashboardStats {
|
|
189
|
+
overall: BehaviorOverallStats;
|
|
190
|
+
byModel: BehaviorModelStats[];
|
|
191
|
+
behaviorSeries: BehaviorTimeSeriesPoint[];
|
|
192
|
+
}
|
|
@@ -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,120 @@
|
|
|
1
|
+
import type { AssistantMessage, ServiceTier, StopReason, Usage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
export * from "./shared-types";
|
|
3
|
+
/**
|
|
4
|
+
* Extracted stats from an assistant message.
|
|
5
|
+
*/
|
|
6
|
+
export interface MessageStats {
|
|
7
|
+
/** Database ID */
|
|
8
|
+
id?: number;
|
|
9
|
+
/** Session file path */
|
|
10
|
+
sessionFile: string;
|
|
11
|
+
/** Entry ID within the session */
|
|
12
|
+
entryId: string;
|
|
13
|
+
/** Folder/project path (extracted from session filename) */
|
|
14
|
+
folder: string;
|
|
15
|
+
/** Model ID */
|
|
16
|
+
model: string;
|
|
17
|
+
/** Provider name */
|
|
18
|
+
provider: string;
|
|
19
|
+
/** API type */
|
|
20
|
+
api: string;
|
|
21
|
+
/** Unix timestamp in milliseconds */
|
|
22
|
+
timestamp: number;
|
|
23
|
+
/** Request duration in milliseconds */
|
|
24
|
+
duration: number | null;
|
|
25
|
+
/** Time to first token in milliseconds */
|
|
26
|
+
ttft: number | null;
|
|
27
|
+
/** Stop reason */
|
|
28
|
+
stopReason: StopReason;
|
|
29
|
+
/** Error message if stopReason is error */
|
|
30
|
+
errorMessage: string | null;
|
|
31
|
+
/** Token usage */
|
|
32
|
+
usage: Usage;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Full details of a request, including content.
|
|
36
|
+
*/
|
|
37
|
+
export interface RequestDetails extends MessageStats {
|
|
38
|
+
/** The full conversation history or just the last turn. */
|
|
39
|
+
messages: unknown[];
|
|
40
|
+
/** The model's response. */
|
|
41
|
+
output: unknown;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Session log entry types.
|
|
45
|
+
*/
|
|
46
|
+
export interface SessionHeader {
|
|
47
|
+
type: "session";
|
|
48
|
+
version: number;
|
|
49
|
+
id: string;
|
|
50
|
+
timestamp: string;
|
|
51
|
+
cwd: string;
|
|
52
|
+
title?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface SessionMessageEntry {
|
|
55
|
+
type: "message";
|
|
56
|
+
id: string;
|
|
57
|
+
parentId: string | null;
|
|
58
|
+
timestamp: string;
|
|
59
|
+
message: AssistantMessage | {
|
|
60
|
+
role: "user" | "toolResult";
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export interface SessionServiceTierChangeEntry {
|
|
64
|
+
type: "service_tier_change";
|
|
65
|
+
id: string;
|
|
66
|
+
parentId?: string | null;
|
|
67
|
+
timestamp: string;
|
|
68
|
+
serviceTier: ServiceTier | null;
|
|
69
|
+
}
|
|
70
|
+
export type SessionEntry = SessionHeader | SessionMessageEntry | SessionServiceTierChangeEntry | {
|
|
71
|
+
type: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Behavioral stats extracted from a single user message.
|
|
75
|
+
*/
|
|
76
|
+
export interface UserMessageStats {
|
|
77
|
+
/** Database ID */
|
|
78
|
+
id?: number;
|
|
79
|
+
/** Session file path */
|
|
80
|
+
sessionFile: string;
|
|
81
|
+
/** Entry ID within the session */
|
|
82
|
+
entryId: string;
|
|
83
|
+
/** Folder/project path */
|
|
84
|
+
folder: string;
|
|
85
|
+
/** Unix timestamp in ms */
|
|
86
|
+
timestamp: number;
|
|
87
|
+
/** Model that responded to this user message, if linked */
|
|
88
|
+
model: string | null;
|
|
89
|
+
/** Provider that responded to this user message, if linked */
|
|
90
|
+
provider: string | null;
|
|
91
|
+
/** Total characters of message text */
|
|
92
|
+
chars: number;
|
|
93
|
+
/** Whitespace-delimited word count */
|
|
94
|
+
words: number;
|
|
95
|
+
/** Yelling sentences (> 50% uppercase letters) */
|
|
96
|
+
yelling: number;
|
|
97
|
+
/** Profanity hits */
|
|
98
|
+
profanity: number;
|
|
99
|
+
/** Catch-all upset signal: drama runs + `noooo`/`ughh`/... + `dude` + `..` */
|
|
100
|
+
anguish: number;
|
|
101
|
+
/** Corrective negation ("no", "nope", "thats not what i meant") */
|
|
102
|
+
negation: number;
|
|
103
|
+
/** User repeating themselves ("i meant", "still doesnt work", "like i said") */
|
|
104
|
+
repetition: number;
|
|
105
|
+
/** Second-person reproach ("you didnt", "you broke", "stop X-ing") */
|
|
106
|
+
blame: number;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Pair emitted by the parser when it sees an assistant message whose
|
|
110
|
+
* `parentId` points to a user message that wasn't parsed in the same pass
|
|
111
|
+
* (e.g. user prompt landed in an earlier incremental sync). The aggregator
|
|
112
|
+
* applies the link to the persisted `user_messages` row so it stops showing
|
|
113
|
+
* up in the "unknown" model bucket.
|
|
114
|
+
*/
|
|
115
|
+
export interface UserMessageLink {
|
|
116
|
+
sessionFile: string;
|
|
117
|
+
entryId: string;
|
|
118
|
+
model: string;
|
|
119
|
+
provider: string;
|
|
120
|
+
}
|
|
@@ -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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/omp-stats",
|
|
4
|
-
"version": "15.1.
|
|
4
|
+
"version": "15.1.2",
|
|
5
5
|
"description": "Local observability dashboard for pi AI usage statistics",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"statistics"
|
|
24
24
|
],
|
|
25
25
|
"main": "./src/index.ts",
|
|
26
|
-
"types": "./
|
|
26
|
+
"types": "./dist/types/index.d.ts",
|
|
27
27
|
"bin": {
|
|
28
28
|
"omp-stats": "./src/index.ts"
|
|
29
29
|
},
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/pi-ai": "15.1.
|
|
41
|
-
"@oh-my-pi/pi-utils": "15.1.
|
|
40
|
+
"@oh-my-pi/pi-ai": "15.1.2",
|
|
41
|
+
"@oh-my-pi/pi-utils": "15.1.2",
|
|
42
42
|
"@tailwindcss/node": "^4.2.4",
|
|
43
43
|
"chart.js": "^4.5.1",
|
|
44
44
|
"date-fns": "^4.1.0",
|
|
@@ -61,27 +61,29 @@
|
|
|
61
61
|
"src",
|
|
62
62
|
"build.ts",
|
|
63
63
|
"tailwind.config.js",
|
|
64
|
-
"README.md"
|
|
64
|
+
"README.md",
|
|
65
|
+
"dist/types",
|
|
66
|
+
"dist/client"
|
|
65
67
|
],
|
|
66
68
|
"exports": {
|
|
67
69
|
".": {
|
|
68
|
-
"types": "./
|
|
70
|
+
"types": "./dist/types/index.d.ts",
|
|
69
71
|
"import": "./src/index.ts"
|
|
70
72
|
},
|
|
71
73
|
"./*": {
|
|
72
|
-
"types": "./
|
|
74
|
+
"types": "./dist/types/*.d.ts",
|
|
73
75
|
"import": "./src/*.ts"
|
|
74
76
|
},
|
|
75
77
|
"./client": {
|
|
76
|
-
"types": "./
|
|
78
|
+
"types": "./dist/types/client/index.d.ts",
|
|
77
79
|
"import": "./src/client/index.tsx"
|
|
78
80
|
},
|
|
79
81
|
"./client/*": {
|
|
80
|
-
"types": "./
|
|
82
|
+
"types": "./dist/types/client/*.d.ts",
|
|
81
83
|
"import": "./src/client/*.ts"
|
|
82
84
|
},
|
|
83
85
|
"./client/components/*": {
|
|
84
|
-
"types": "./
|
|
86
|
+
"types": "./dist/types/client/components/*.d.ts",
|
|
85
87
|
"import": "./src/client/components/*.ts"
|
|
86
88
|
},
|
|
87
89
|
"./*.js": "./src/*.ts"
|