@@ -7,6 +7,44 @@ import { createUIResource } from "@mcp-ui/server";
77import type { BrowserSession } from "../types/types.js" ;
88import { 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 ---
1149const 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+
144255async 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