@oh-my-pi/omp-stats 16.1.14 → 16.1.16

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/parser.ts CHANGED
@@ -3,6 +3,7 @@ import * as path from "node:path";
3
3
  import { type AssistantMessage, getPriorityPremiumRequests, type ServiceTier } from "@oh-my-pi/pi-ai";
4
4
  import { getSessionsDir, isEnoent } from "@oh-my-pi/pi-utils";
5
5
  import type {
6
+ AgentType,
6
7
  MessageStats,
7
8
  SessionEntry,
8
9
  SessionMessageEntry,
@@ -12,6 +13,25 @@ import type {
12
13
  } from "./types";
13
14
  import { computeUserMessageMetrics } from "./user-metrics";
14
15
 
16
+ /** Basename of an advisor agent's transcript inside a session artifacts dir. */
17
+ const ADVISOR_TRANSCRIPT_BASENAME = "__advisor.jsonl";
18
+
19
+ /**
20
+ * Classify which agent produced a transcript from its path within the sessions
21
+ * directory. Layout: `<sessionsDir>/<project>/<file>.jsonl` is the `main`
22
+ * agent; subagent and advisor transcripts live nested one level deeper inside
23
+ * the session's artifacts dir (`<project>/<session>/<id>.jsonl`,
24
+ * `<project>/<session>/__advisor.jsonl`). Any `__advisor.jsonl` — at any depth,
25
+ * including a subagent's own advisor — counts as `advisor`; every other nested
26
+ * transcript is a task `subagent`.
27
+ */
28
+ export function classifyAgentType(sessionPath: string): AgentType {
29
+ if (path.basename(sessionPath) === ADVISOR_TRANSCRIPT_BASENAME) return "advisor";
30
+ const rel = path.relative(getSessionsDir(), sessionPath);
31
+ // `<project>/<file>.jsonl` -> 2 segments. Deeper nesting is a subagent.
32
+ return rel.split(path.sep).length <= 2 ? "main" : "subagent";
33
+ }
34
+
15
35
  /**
16
36
  * Extract folder name from session filename.
17
37
  * Session files are named like: --work--pi--/timestamp_uuid.jsonl
@@ -107,6 +127,7 @@ function extractStats(
107
127
  folder: string,
108
128
  entry: SessionMessageEntry,
109
129
  currentServiceTier: ServiceTier | undefined,
130
+ agentType: AgentType,
110
131
  ): MessageStats | null {
111
132
  const msg = entry.message as AssistantMessage;
112
133
  if (msg?.role !== "assistant") return null;
@@ -134,6 +155,7 @@ function extractStats(
134
155
  stopReason: msg.stopReason,
135
156
  errorMessage: msg.errorMessage ?? null,
136
157
  usage,
158
+ agentType,
137
159
  };
138
160
  }
139
161
 
@@ -220,6 +242,7 @@ export async function parseSessionFile(sessionPath: string, fromOffset = 0): Pro
220
242
  }
221
243
 
222
244
  const folder = extractFolderFromPath(sessionPath);
245
+ const agentType = classifyAgentType(sessionPath);
223
246
  const stats: MessageStats[] = [];
224
247
  const userStats: UserMessageStats[] = [];
225
248
  const userLinks: UserMessageLink[] = [];
@@ -245,7 +268,7 @@ export async function parseSessionFile(sessionPath: string, fromOffset = 0): Pro
245
268
  continue;
246
269
  }
247
270
  if (isAssistantMessage(entry)) {
248
- const msgStats = extractStats(sessionPath, folder, entry, currentServiceTier);
271
+ const msgStats = extractStats(sessionPath, folder, entry, currentServiceTier, agentType);
249
272
  if (msgStats) stats.push(msgStats);
250
273
  // Link assistant's responding model back to the user message it answered.
251
274
  const parentId = (entry as SessionMessageEntry).parentId;
@@ -133,12 +133,42 @@ export interface DashboardStats {
133
133
  overall: AggregatedStats;
134
134
  byModel: ModelStats[];
135
135
  byFolder: FolderStats[];
136
+ byAgentType: AgentTypeStats[];
136
137
  timeSeries: TimeSeriesPoint[];
137
138
  modelSeries: ModelTimeSeriesPoint[];
138
139
  modelPerformanceSeries: ModelPerformancePoint[];
139
140
  costSeries: CostTimeSeriesPoint[];
140
141
  }
141
142
 
143
+ /**
144
+ * Which agent produced a message, derived from its transcript file location
145
+ * inside the session directory: the top-level `<project>/<file>.jsonl` is the
146
+ * `main` agent, an `__advisor.jsonl` is the passive `advisor`, and any other
147
+ * nested transcript is a task `subagent`.
148
+ */
149
+ export type AgentType = "main" | "subagent" | "advisor";
150
+
151
+ /**
152
+ * Token usage aggregated by {@link AgentType} over the active range. Token
153
+ * columns are explicit so the dashboard's share denominator matches the
154
+ * counts it renders (input + output + cache read + cache write).
155
+ */
156
+ export interface AgentTypeStats {
157
+ agentType: AgentType;
158
+ /** Total number of requests */
159
+ totalRequests: number;
160
+ /** Total input tokens */
161
+ totalInputTokens: number;
162
+ /** Total output tokens */
163
+ totalOutputTokens: number;
164
+ /** Total cache read tokens */
165
+ totalCacheReadTokens: number;
166
+ /** Total cache write tokens */
167
+ totalCacheWriteTokens: number;
168
+ /** Total cost */
169
+ totalCost: number;
170
+ }
171
+
142
172
  /**
143
173
  * Behavior time-series point (daily bucket, per responding model).
144
174
  */
package/src/types.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { AssistantMessage, ServiceTier, StopReason, Usage } from "@oh-my-pi/pi-ai";
2
+ import type { AgentType } from "./shared-types";
2
3
 
3
4
  export * from "./shared-types";
4
5
 
@@ -32,6 +33,8 @@ export interface MessageStats {
32
33
  errorMessage: string | null;
33
34
  /** Token usage */
34
35
  usage: Usage;
36
+ /** Which agent produced this message (main agent, task subagent, advisor) */
37
+ agentType: AgentType;
35
38
  }
36
39
 
37
40
  /**