Skip to content

Commit 09cc590

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/general-settings-auto-update-625989
2 parents b7f94c1 + 35155a4 commit 09cc590

File tree

3 files changed

+162
-20
lines changed

3 files changed

+162
-20
lines changed

packages/agents/test/session-inspector.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,4 +220,72 @@ describe("session inspector", () => {
220220

221221
expect(state.sessionTitle).toBe("Fix session title extraction");
222222
});
223+
224+
it("hydrates OpenCode model/agent and reported total tokens", () => {
225+
const startedAt = Date.now();
226+
const state = buildSessionMessageState([
227+
{
228+
timestamp: startedAt,
229+
type: "session.updated",
230+
data: {
231+
payload: {
232+
type: "session.updated",
233+
properties: {
234+
info: {
235+
title: "Inspect stream payload",
236+
},
237+
},
238+
},
239+
},
240+
},
241+
{
242+
timestamp: startedAt + 1,
243+
type: "message.updated",
244+
data: {
245+
payload: {
246+
type: "message.updated",
247+
properties: {
248+
info: {
249+
modelID: "gpt-5.3-codex",
250+
agent: "build",
251+
cost: 0,
252+
tokens: {
253+
total: 41681,
254+
input: 295,
255+
output: 42,
256+
reasoning: 0,
257+
cache: {
258+
read: 41344,
259+
write: 0,
260+
},
261+
},
262+
},
263+
},
264+
},
265+
},
266+
},
267+
]);
268+
269+
expect(state.model).toBe("gpt-5.3-codex");
270+
expect(state.agent).toBe("build");
271+
expect(state.tokenUsage?.total).toBe(41681);
272+
273+
const text = buildLiveStatusMessage(
274+
{
275+
channelId: "C1",
276+
threadId: "T1",
277+
statusMessageTs: "S1",
278+
startedAt,
279+
currentText: "",
280+
},
281+
"/tmp/repo",
282+
state,
283+
"medium"
284+
);
285+
286+
expect(text).toContain("model: gpt-5.3-codex");
287+
expect(text).toContain("42k tokens");
288+
expect(text).toContain("build agent");
289+
expect(text).not.toContain("cost 0");
290+
});
223291
});

packages/utils/session-inspector.ts

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export type SessionMessageState = {
5151
sessionTitle?: string;
5252
phaseStatus?: string;
5353
thinkingText?: string;
54+
model?: string;
55+
agent?: string;
5456
tokenUsage?: SessionTokenUsage;
5557
currentText: string;
5658
tools: SessionTool[];
@@ -82,7 +84,10 @@ function applySessionUpdatedEvent(state: SessionMessageState, eventProps: Record
8284
function applyMessageUpdatedEvent(state: SessionMessageState, eventProps: Record<string, unknown>): void {
8385
const info = eventProps.info as
8486
| {
87+
modelID?: unknown;
88+
agent?: unknown;
8589
tokens?: {
90+
total?: unknown;
8691
input?: unknown;
8792
output?: unknown;
8893
reasoning?: unknown;
@@ -91,25 +96,37 @@ function applyMessageUpdatedEvent(state: SessionMessageState, eventProps: Record
9196
cost?: unknown;
9297
}
9398
| undefined;
99+
if (typeof info?.modelID === "string" && info.modelID.trim()) {
100+
state.model = info.modelID;
101+
}
102+
103+
if (typeof info?.agent === "string" && info.agent.trim()) {
104+
state.agent = info.agent;
105+
}
106+
94107
const tokens = info?.tokens;
95-
if (!tokens || typeof tokens !== "object") return;
96-
97-
const input = Number(tokens.input ?? 0) || 0;
98-
const output = Number(tokens.output ?? 0) || 0;
99-
const reasoning = Number(tokens.reasoning ?? 0) || 0;
100-
const cacheRead = Number(tokens.cache?.read ?? 0) || 0;
101-
const cacheWrite = Number(tokens.cache?.write ?? 0) || 0;
102-
const total = input + output + reasoning;
103-
const cost = typeof info?.cost === "number" ? info.cost : undefined;
104-
state.tokenUsage = {
105-
input,
106-
output,
107-
reasoning,
108-
cacheRead,
109-
cacheWrite,
110-
total,
111-
cost,
112-
};
108+
if (tokens && typeof tokens === "object") {
109+
const input = Number(tokens.input ?? 0) || 0;
110+
const output = Number(tokens.output ?? 0) || 0;
111+
const reasoning = Number(tokens.reasoning ?? 0) || 0;
112+
const cacheRead = Number(tokens.cache?.read ?? 0) || 0;
113+
const cacheWrite = Number(tokens.cache?.write ?? 0) || 0;
114+
const reportedTotal = Number(tokens.total);
115+
const total = Number.isFinite(reportedTotal)
116+
? reportedTotal
117+
: input + output + reasoning + cacheRead + cacheWrite;
118+
const parsedCost = Number(info?.cost);
119+
const cost = Number.isFinite(parsedCost) ? parsedCost : undefined;
120+
state.tokenUsage = {
121+
input,
122+
output,
123+
reasoning,
124+
cacheRead,
125+
cacheWrite,
126+
total,
127+
cost,
128+
};
129+
}
113130
}
114131

115132
function applySessionStatusEvent(state: SessionMessageState, eventProps: Record<string, unknown>): void {
@@ -275,6 +292,8 @@ export function buildSessionMessageState(
275292
sessionTitle: baseState?.sessionTitle,
276293
phaseStatus: baseState?.phaseStatus,
277294
thinkingText: baseState?.thinkingText,
295+
model: baseState?.model,
296+
agent: baseState?.agent,
278297
tokenUsage: baseState?.tokenUsage,
279298
currentText: baseState?.currentText ?? "",
280299
tools: baseState?.tools ? [...baseState.tools] : [],

packages/utils/status.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,60 @@ export function formatElapsedTime(startedAt: number): string {
4141
return `${minutes}m ${seconds}s`;
4242
}
4343

44+
function formatCompactCount(value: number): string {
45+
if (!Number.isFinite(value)) return "0";
46+
const sign = value < 0 ? "-" : "";
47+
let current = Math.abs(value);
48+
let unitIndex = 0;
49+
const units = ["", "k", "m", "b", "t"];
50+
51+
while (current >= 1000 && unitIndex < units.length - 1) {
52+
current /= 1000;
53+
unitIndex += 1;
54+
}
55+
56+
if (unitIndex === 0) {
57+
return `${sign}${Math.round(current)}`;
58+
}
59+
60+
const rounded = current >= 10
61+
? Math.round(current)
62+
: Math.round(current * 10) / 10;
63+
const numeric = Number.isInteger(rounded) ? String(rounded) : String(rounded);
64+
return `${sign}${numeric}${units[unitIndex]}`;
65+
}
66+
67+
function formatCost(value: number): string {
68+
if (!Number.isFinite(value)) return "0";
69+
if (value >= 1) return String(Math.round(value * 100) / 100);
70+
if (value >= 0.01) return value.toFixed(2);
71+
return value.toFixed(4);
72+
}
73+
74+
function buildHeaderDetails(state: SessionMessageState): string {
75+
const details: string[] = [];
76+
if (state.model) {
77+
details.push(`model: ${state.model}`);
78+
}
79+
80+
const totalTokens = state.tokenUsage?.total;
81+
if (typeof totalTokens === "number" && Number.isFinite(totalTokens) && totalTokens > 0) {
82+
details.push(`${formatCompactCount(totalTokens)} tokens`);
83+
}
84+
85+
if (state.agent) {
86+
details.push(`${state.agent} agent`);
87+
}
88+
89+
const cost = state.tokenUsage?.cost;
90+
if (typeof cost === "number" && Number.isFinite(cost) && cost > 0) {
91+
details.push(`cost ${formatCost(cost)}`);
92+
}
93+
94+
details.push(formatElapsedTime(state.startedAt));
95+
return details.join(", ");
96+
}
97+
4498
export function getToolIcon(status: string): string {
4599
switch (status) {
46100
case "running":
@@ -249,11 +303,12 @@ export function buildLiveStatusMessage(
249303
}
250304

251305
const lines: string[] = [];
306+
const headerDetails = buildHeaderDetails(state);
252307

253308
if (state.sessionTitle) {
254-
lines.push(`*${state.sessionTitle}* (${formatElapsedTime(state.startedAt)})`);
309+
lines.push(`*${state.sessionTitle}* (${headerDetails})`);
255310
} else {
256-
lines.push(`_${formatElapsedTime(state.startedAt)}_`);
311+
lines.push(`_${headerDetails}_`);
257312
}
258313

259314
if (state.phaseStatus) {

0 commit comments

Comments
 (0)