Setting the file. One moment.
Unify Trace · Autobrowse · browserbase/skills · Skills Docs
ContentsBack to the top of the page Bundled file
scripts/ unify-trace.mjs
JavaScript · 189 lines · 7 KB
15 // noisy/redundant methods (Network.dataReceived, frame Started/Stopped
16 // Loading, ExtraInfo events, Runtime.executionContextCreated). The full
17 // firehose is one drill-down away in cdp/raw.ndjson.
18 //
19 // Usage:
20 // node unify-trace.mjs --trace-dir <run-root> --o11y-dir <.o11y/<run-id>>
21
22 import fs from "node:fs" ;
23 import path from "node:path" ;
24
25 function getArg ( name ) {
26 const i = process.argv. indexOf ( `--${ name }` );
27 return i !== - 1 && process.argv[i + 1 ] ? process.argv[i + 1 ] : null ;
28 }
29
30 const traceDir = getArg ( "trace-dir" );
31 const o11yDir = getArg ( "o11y-dir" );
32 if ( ! traceDir || ! o11yDir) {
33 console. error ( "usage: unify-trace.mjs --trace-dir <run-root> --o11y-dir <.o11y/<run-id>>" );
34 process. exit ( 2 );
35 }
36
37 const tracePath = path. join (traceDir, "trace.json" );
38 const rawPath = path. join (o11yDir, "cdp" , "raw.ndjson" );
39 const manifestPath = path. join (o11yDir, "manifest.json" );
40
41 for ( const p of [tracePath, rawPath, manifestPath]) {
42 if ( ! fs. existsSync (p)) {
43 console. error ( `missing input: ${ p }` );
44 process. exit ( 1 );
45 }
46 }
47
48 const trace = JSON . parse (fs. readFileSync (tracePath, "utf-8" ));
49 const manifest = JSON . parse (fs. readFileSync (manifestPath, "utf-8" ));
50 const rawLines = fs. readFileSync (rawPath, "utf-8" ). trim (). split ( " \n " ). filter (Boolean);
51 const cdpEvents = rawLines. map (( l ) => { try { return JSON . parse (l); } catch { return null ; } }). filter (Boolean);
52
53 const startedMs = manifest.started_at ? new Date (manifest.started_at). getTime () : null ;
54
55 const isMonotonic = ( ts ) => ts != null && ts < 1e9 ;
56 const anchorCdp = cdpEvents. map (( e ) => e?.params?.timestamp). find (isMonotonic) ?? null ;
57
58 function cdpTsToMs ( ev ) {
59 const ts = ev?.params?.timestamp;
60 if (ts == null ) return null ;
61 if ( isMonotonic (ts)) {
62 if (anchorCdp == null || startedMs == null ) return null ;
63 return Math. floor ((ts - anchorCdp) * 1000 + startedMs);
64 }
65 return Math. floor (ts);
66 }
67
68 function topNavUrl ( ev ) {
69 return ev?.method === "Page.frameNavigated" && ! ev?.params?.frame?.parentId
70 ? ev.params.frame.url ?? null
71 : null ;
72 }
73 let pid = - 1 ;
74 const pageIdByIndex = [];
75 for ( let i = 0 ; i < cdpEvents. length ; i ++ ) {
76 if ( topNavUrl (cdpEvents[i]) != null ) pid += 1 ;
77 pageIdByIndex[i] = pid < 0 ? 0 : pid;
78 }
79
80 const SKIP_METHODS = new Set ([
81 "Network.dataReceived" ,
82 "Network.loadingFinished" ,
83 "Network.requestWillBeSentExtraInfo" ,
84 "Network.responseReceivedExtraInfo" ,
85 "Network.resourceChangedPriority" ,
86 "Network.policyUpdated" ,
87 "Page.frameStartedLoading" ,
88 "Page.frameStoppedLoading" ,
89 "Page.frameRequestedNavigation" ,
90 "Page.javascriptDialogOpening" ,
91 "Page.javascriptDialogClosed" ,
92 "Runtime.executionContextCreated" ,
93 "Runtime.executionContextDestroyed" ,
94 "Runtime.executionContextsCleared" ,
95 "Target.targetInfoChanged" ,
96 "Target.detachedFromTarget" ,
97 "Log.entryAdded" ,
98 ]);
99
100 function truncate ( s , n = 500 ) {
101 if ( typeof s !== "string" ) return s;
102 return s. length > n ? s. slice ( 0 , n) + "…" : s;
103 }
104
105 function summarizeCdp ( ev , page_id ) {
106 const m = ev.method;
107 const p = ev.params || {};
108 const base = { source: "browser" , method: m, page_id };
109 switch (m) {
110 case "Network.requestWillBeSent" :
111 return { ... base, url: p.request?.url, request_method: p.request?.method, type: p.type, redirect_response_status: p.redirectResponse?.status };
112 case "Network.responseReceived" :
113 return { ... base, url: p.response?.url, status: p.response?.status, mime: p.response?.mimeType, type: p.type };
114 case "Network.loadingFailed" :
115 return { ... base, type: p.type, error: p.errorText, canceled: p.canceled };
116 case "Network.webSocketCreated" :
117 return { ... base, url: p.url };
118 case "Page.frameStartedNavigating" :
119 return { ... base, url: p.url };
120 case "Page.frameNavigated" :
121 return { ... base, url: p.frame?.url, parent_id: p.frame?.parentId || null };
122 case "Page.lifecycleEvent" :
123 return { ... base, name: p.name };
124 case "Page.domContentEventFired" :
125 case "Page.loadEventFired" :
126 return base;
127 case "Page.navigatedWithinDocument" :
128 return { ... base, url: p.url };
129 case "Page.fileChooserOpened" :
130 return { ... base, mode: p.mode };
131 case "Console.messageAdded" :
132 return { ... base, level: p.message?.level, text: truncate (p.message?.text) };
133 case "Runtime.consoleAPICalled" :
134 return { ... base, level: p.type, text: truncate ((p.args || []). map (( a ) => a.value ?? a.description ?? "" ). join ( " " )) };
135 case "Runtime.exceptionThrown" :
136 return { ... base, text: truncate (p.exceptionDetails?.text || p.exceptionDetails?.exception?.description || "" ), url: p.exceptionDetails?.url, line: p.exceptionDetails?.lineNumber };
137 case "Target.attachedToTarget" :
138 case "Target.targetCreated" :
139 return { ... base, target_id: p.targetInfo?.targetId || p.targetId, type: p.targetInfo?.type, url: p.targetInfo?.url };
140 default :
141 return base;
142 }
143 }
144
145 const browserRows = [];
146 for ( let i = 0 ; i < cdpEvents. length ; i ++ ) {
147 const ev = cdpEvents[i];
148 if ( ! ev.method || SKIP_METHODS . has (ev.method)) continue ;
149 const ts_ms = cdpTsToMs (ev);
150 if (ts_ms == null ) continue ;
151 const row = summarizeCdp (ev, pageIdByIndex[i]);
152 browserRows. push ({ _ts_ms: ts_ms, ts: new Date (ts_ms). toISOString (), ... row });
153 }
154
155 const agentRows = [];
156 for ( const entry of trace) {
157 const ts_ms = entry.timestamp ? new Date (entry.timestamp). getTime () : null ;
158 if (ts_ms == null ) continue ;
159 const base = { source: "agent" , turn: entry.turn, role: null };
160 if (entry.role === "assistant" && entry.reasoning) {
161 base.role = "reasoning" ;
162 base.text = truncate (entry.reasoning);
163 } else if (entry.role === "assistant" && entry.tool_name) {
164 base.role = "tool_call" ;
165 base.tool = entry.tool_name;
166 base.command = entry.tool_input?.command;
167 } else if (entry.role === "tool_result" ) {
168 base.role = "tool_result" ;
169 base.command = entry.command;
170 base.ok = ! entry.error;
171 base.duration_ms = entry.duration_ms;
172 base.output_preview = truncate (entry.output);
173 } else {
174 continue ;
175 }
176 agentRows. push ({ _ts_ms: ts_ms, ts: new Date (ts_ms). toISOString (), ... base });
177 }
178
179 const all = [ ... browserRows, ... agentRows]. sort (( a , b ) => a._ts_ms - b._ts_ms);
180
181 const outPath = path. join (traceDir, "unified-events.jsonl" );
182 const out = fs. openSync (outPath, "w" );
183 for ( const row of all) {
184 const { _ts_ms , ... rest } = row;
185 fs. writeSync (out, JSON . stringify (rest) + " \n " );
186 }
187 fs. closeSync (out);
188
189 console. log ( `unified: ${ all . length } events (${ browserRows . length } browser, ${ agentRows . length } agent) → ${ outPath }` );