Skip to content

Commit ab37ad6

Browse files
committed
refactor(lab): session 类型迁移到 @earendil-works/pi-agent-core
api.ts 手写的 SessionEvent/SessionMessage/AssistantContent/ToolResultContent 类型替换为 pi-agent-core 的标准 Entry/MessageEntry/AgentMessage 类型, 与 pi 的 session JSONL 格式保持同步。 改动: - 安装 @earendil-works/pi-agent-core + @earendil-works/pi-telemetry - api.ts: SessionEvent -> Entry, re-export Entry/MessageEntry - metrics.ts: computeSessionMetrics(events: Entry[]), 用 msgTimestamp() 辅助函数从 AgentMessage 提取 timestamp (所有变体都有此字段) - 新增类型覆盖 compaction/branch_summary/active_tools_change 等, 被 type==='message' 过滤自然忽略 验证: typecheck + build 通过, JS bundle 51.76->51.58KB (仅类型导入, 无运行时依赖被打包), CSS 不变 366KB
1 parent d76ecfa commit ab37ad6

4 files changed

Lines changed: 904 additions & 106 deletions

File tree

lab/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"typecheck": "tsc --noEmit"
1212
},
1313
"dependencies": {
14+
"@earendil-works/pi-agent-core": "^0.84.2",
15+
"@earendil-works/pi-telemetry": "^0.84.2",
1416
"@solidjs/router": "^1.0.0",
1517
"solid-js": "^1.9.15"
1618
},

lab/src/api.ts

Lines changed: 7 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
// API client + types for the artifacts-server.
2+
// Session event types are imported from @earendil-works/pi-agent-core
3+
// instead of hand-written, to stay in sync with pi's session JSONL format.
4+
5+
import type { Entry } from "@earendil-works/pi-agent-core";
6+
7+
export type { Entry, MessageEntry } from "@earendil-works/pi-agent-core";
28

39
export interface RunSummary {
410
id: string;
@@ -41,52 +47,10 @@ async function get<T>(url: string): Promise<T> {
4147
return res.json();
4248
}
4349

44-
// ── Session event types (raw pi session JSONL) ──
45-
46-
/** A single session event as recorded by pi's --session-dir. */
47-
export type SessionEvent =
48-
| { type: "session"; id: string }
49-
| { type: "model_change"; modelId: string; provider: string }
50-
| { type: "thinking_level_change"; level: string | null }
51-
| { type: "message"; message: SessionMessage };
52-
53-
export type SessionMessage =
54-
| { role: "user"; content: string; timestamp: number }
55-
| {
56-
role: "assistant";
57-
content: AssistantContent[];
58-
usage: {
59-
input: number;
60-
output: number;
61-
cacheRead: number;
62-
cacheWrite: number;
63-
reasoning: number;
64-
};
65-
stopReason: string;
66-
model: string;
67-
provider: string;
68-
timestamp: number;
69-
}
70-
| {
71-
role: "toolResult";
72-
content: ToolResultContent[];
73-
toolName: string;
74-
isError: boolean;
75-
timestamp: number;
76-
};
77-
78-
export type AssistantContent =
79-
| { type: "text"; text: string }
80-
| { type: "toolCall"; id: string; name: string; args: unknown };
81-
82-
export type ToolResultContent =
83-
| { type: "text"; text: string }
84-
| { type: "resource"; resource: unknown };
85-
8650
export const api = {
8751
runs: () => get<RunSummary[]>("/api/runs"),
8852
run: (id: string) => get<RunDetail>(`/api/runs/${id}`),
89-
session: (id: string) => get<SessionEvent[]>(`/api/runs/${id}/session`),
53+
session: (id: string) => get<Entry[]>(`/api/runs/${id}/session`),
9054
file: async (id: string, path: string): Promise<string> => {
9155
const res = await fetch(
9256
`/api/runs/${id}/file?path=${encodeURIComponent(path)}`

lab/src/lib/metrics.ts

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import type { SessionEvent, RunDetail } from "../api";
1+
import type { Entry, RunDetail } from "../api";
22
import { fmtSec, fmtNum } from "./format";
3+
import type { AgentMessage } from "@earendil-works/pi-agent-core";
34

45
// ── session metrics (computed client-side from raw session events) ──
56
// NOTE on timestamps: an assistant message's timestamp is when the model request
@@ -37,40 +38,30 @@ export interface SessionMetrics {
3738
rounds: SessionRound[];
3839
}
3940

40-
export function computeSessionMetrics(events: SessionEvent[]): SessionMetrics {
41+
/** Extract timestamp from an AgentMessage (all variants carry timestamp). */
42+
function msgTimestamp(m: AgentMessage): number {
43+
return (m as { timestamp: number }).timestamp;
44+
}
45+
46+
export function computeSessionMetrics(events: Entry[]): SessionMetrics {
4147
const messages = events
4248
.map((event, index) => ({ event, index }))
4349
.filter((x) => x.event.type === "message");
50+
const ts = (x: { event: Entry }) =>
51+
x.event.type === "message" ? msgTimestamp(x.event.message) : NaN;
52+
4453
const timestampAdjusted =
45-
messages.some(
46-
(x) =>
47-
x.event.type === "message" &&
48-
!Number.isFinite(
49-
(x.event as Extract<SessionEvent, { type: "message" }>).message
50-
.timestamp
51-
)
52-
) ||
54+
messages.some((x) => !Number.isFinite(ts(x))) ||
5355
messages.some(
5456
(x, i) =>
5557
i > 0 &&
56-
x.event.type === "message" &&
57-
messages[i - 1].event.type === "message" &&
58-
Number.isFinite(
59-
(x.event as Extract<SessionEvent, { type: "message" }>).message
60-
.timestamp
61-
) &&
62-
Number.isFinite(
63-
(messages[i - 1].event as Extract<SessionEvent, { type: "message" }>)
64-
.message.timestamp
65-
) &&
66-
(x.event as Extract<SessionEvent, { type: "message" }>).message
67-
.timestamp <
68-
(messages[i - 1].event as Extract<SessionEvent, { type: "message" }>)
69-
.message.timestamp
58+
Number.isFinite(ts(x)) &&
59+
Number.isFinite(ts(messages[i - 1])) &&
60+
ts(x) < ts(messages[i - 1])
7061
);
7162
messages.sort((a, b) => {
72-
const at = a.event.type === "message" ? a.event.message.timestamp : NaN;
73-
const bt = b.event.type === "message" ? b.event.message.timestamp : NaN;
63+
const at = ts(a),
64+
bt = ts(b);
7465
if (Number.isFinite(at) && Number.isFinite(bt))
7566
return at - bt || a.index - b.index;
7667
if (Number.isFinite(at)) return -1;
@@ -100,11 +91,11 @@ export function computeSessionMetrics(events: SessionEvent[]): SessionMetrics {
10091
}[] = [];
10192
for (const { event } of messages) {
10293
if (event.type !== "message") continue;
103-
const m = event.message,
104-
ts = m.timestamp;
105-
if (Number.isFinite(ts)) {
106-
firstTs = Math.min(firstTs, ts);
107-
lastTs = Math.max(lastTs, ts);
94+
const m = event.message;
95+
const mts = msgTimestamp(m);
96+
if (Number.isFinite(mts)) {
97+
firstTs = Math.min(firstTs, mts);
98+
lastTs = Math.max(lastTs, mts);
10899
}
109100
if (m.role === "assistant") {
110101
modelRequests++;
@@ -121,7 +112,7 @@ export function computeSessionMetrics(events: SessionEvent[]): SessionMetrics {
121112
tools.push(part.name);
122113
}
123114
assistantMsg.push({
124-
ts,
115+
ts: mts,
125116
model: m.model,
126117
stopReason: m.stopReason,
127118
input: m.usage?.input ?? 0,

0 commit comments

Comments
 (0)