Skip to content

Commit 5367e62

Browse files
committed
add stuff
1 parent 460a9c2 commit 5367e62

File tree

1 file changed

+126
-0
lines changed

1 file changed

+126
-0
lines changed

src/tools/session.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,44 @@ import { createUIResource } from "@mcp-ui/server";
77
import type { BrowserSession } from "../types/types.js";
88
import { TextContent } from "@modelcontextprotocol/sdk/types.js";
99

10+
// Types for Stagehand replay response payload
11+
interface StagehandReplayTokenUsage {
12+
inputTokens: number;
13+
outputTokens: number;
14+
timeMs?: number;
15+
}
16+
17+
interface StagehandReplayAction {
18+
method: string;
19+
parameters?: unknown;
20+
result?: unknown;
21+
timestamp: number;
22+
endTime?: number;
23+
tokenUsage?: StagehandReplayTokenUsage;
24+
}
25+
26+
interface StagehandReplayPage {
27+
url: string;
28+
timestamp: number;
29+
duration: number;
30+
actions: StagehandReplayAction[];
31+
}
32+
33+
interface StagehandReplayData {
34+
pages: StagehandReplayPage[];
35+
}
36+
37+
interface StagehandReplayResponse {
38+
success: boolean;
39+
data: StagehandReplayData;
40+
}
41+
42+
interface StagehandReplayTokenUsageTotals {
43+
totalInputTokens: number;
44+
totalOutputTokens: number;
45+
totalTimeMs: number;
46+
}
47+
1048
// --- Tool: Create Session ---
1149
const CreateSessionInputSchema = z.object({
1250
// Keep sessionId optional
@@ -141,6 +179,79 @@ const closeSessionSchema: ToolSchema<typeof CloseSessionInputSchema> = {
141179
inputSchema: CloseSessionInputSchema,
142180
};
143181

182+
/**
183+
* Fetch token usage metrics from the Stagehand replay endpoint for a given
184+
* Browserbase session and return aggregated totals.
185+
*/
186+
async function fetchStagehandTokenUsageSummary(
187+
config: Context["config"],
188+
browserbaseSessionId: string,
189+
): Promise<StagehandReplayTokenUsageTotals | null> {
190+
const apiKey = config.browserbaseApiKey;
191+
const projectId = config.browserbaseProjectId;
192+
const modelApiKey =
193+
config.modelApiKey ||
194+
process.env.GEMINI_API_KEY ||
195+
process.env.GOOGLE_API_KEY;
196+
197+
if (!apiKey || !projectId || !modelApiKey) {
198+
process.stderr.write(
199+
"[tool.closeSession] Skipping Stagehand replay call due to missing API keys or session ID.\n",
200+
);
201+
return null;
202+
}
203+
204+
const replayResponse = await fetch(
205+
`https://api.stagehand.browserbase.com/v1/sessions/${browserbaseSessionId}/replay`,
206+
{
207+
method: "GET",
208+
headers: {
209+
"x-bb-api-key": apiKey,
210+
"x-bb-project-id": projectId,
211+
"x-bb-session-id": browserbaseSessionId,
212+
"x-stream-response": "true",
213+
"x-model-api-key": modelApiKey,
214+
"x-sent-at": new Date().toISOString(),
215+
"x-language": "typescript",
216+
"x-sdk-version": "3.0.1",
217+
},
218+
},
219+
);
220+
221+
try {
222+
// This JSON should contain tokenUsage with inputTokens/outputTokens per action
223+
const replayJson = (await replayResponse.json()) as StagehandReplayResponse;
224+
225+
// Aggregate token usage across all pages/actions into a single totals object
226+
const totals: StagehandReplayTokenUsageTotals = {
227+
totalInputTokens: 0,
228+
totalOutputTokens: 0,
229+
totalTimeMs: 0,
230+
};
231+
232+
for (const page of replayJson.data.pages) {
233+
for (const action of page.actions) {
234+
if (action.tokenUsage) {
235+
totals.totalInputTokens += action.tokenUsage.inputTokens;
236+
totals.totalOutputTokens += action.tokenUsage.outputTokens;
237+
if (typeof action.tokenUsage.timeMs === "number") {
238+
totals.totalTimeMs += action.tokenUsage.timeMs;
239+
}
240+
}
241+
}
242+
}
243+
244+
return totals;
245+
} catch (parseError) {
246+
const message =
247+
parseError instanceof Error ? parseError.message : String(parseError);
248+
process.stderr.write(
249+
`[tool.closeSession] Failed to parse Stagehand replay response: ${message}\n`,
250+
);
251+
return null;
252+
}
253+
}
254+
144255
async function handleCloseSession(context: Context): Promise<ToolResult> {
145256
const action = async (): Promise<ToolActionResult> => {
146257
// Store the current session ID before cleanup
@@ -166,6 +277,20 @@ async function handleCloseSession(context: Context): Promise<ToolResult> {
166277
// cleanupSession handles both closing Stagehand and cleanup (idempotent)
167278
await sessionManager.cleanupSession(previousSessionId);
168279
cleanupSuccessful = true;
280+
281+
// Fetch Stagehand token usage metrics via shared util and log aggregate totals
282+
if (browserbaseSessionId) {
283+
const tokenUsageTotals = await fetchStagehandTokenUsageSummary(
284+
context.config,
285+
browserbaseSessionId,
286+
);
287+
288+
if (tokenUsageTotals) {
289+
process.stdout.write(
290+
`Total token usage: ${tokenUsageTotals.totalInputTokens} input tokens, ${tokenUsageTotals.totalOutputTokens} output tokens\n`,
291+
);
292+
}
293+
}
169294
} else {
170295
process.stderr.write(
171296
`[tool.closeSession] No session found for ID: ${previousSessionId || "default/unknown"}\n`,
@@ -208,6 +333,7 @@ async function handleCloseSession(context: Context): Promise<ToolResult> {
208333
if (previousSessionId && previousSessionId !== defaultSessionId) {
209334
infoMessage = `No active session found for session ID '${previousSessionId}'. The context has been reset to default.`;
210335
}
336+
211337
return { content: [{ type: "text", text: infoMessage }] };
212338
};
213339

0 commit comments

Comments
 (0)