1010 */
1111
1212import * as fs from 'fs' ;
13+ import { promises as fsPromises } from 'fs' ;
1314import * as os from 'os' ;
1415import * as path from 'path' ;
1516import * as zlib from 'zlib' ;
@@ -18,6 +19,15 @@ import * as http from 'http';
1819import { URL } from 'url' ;
1920import { TraceSink } from './sink' ;
2021
22+ /**
23+ * Optional logger interface for SDK users
24+ */
25+ export interface SentienceLogger {
26+ info ( message : string ) : void ;
27+ warn ( message : string ) : void ;
28+ error ( message : string ) : void ;
29+ }
30+
2131/**
2232 * Get persistent cache directory for traces
2333 * Uses ~/.sentience/traces/pending/ (survives process crashes)
@@ -61,17 +71,36 @@ export class CloudTraceSink extends TraceSink {
6171 private runId : string ;
6272 private writeStream : fs . WriteStream | null = null ;
6373 private closed : boolean = false ;
74+ private apiKey ?: string ;
75+ private apiUrl : string ;
76+ private logger ?: SentienceLogger ;
77+
78+ // File size tracking (NEW)
79+ private traceFileSizeBytes : number = 0 ;
80+ private screenshotTotalSizeBytes : number = 0 ;
6481
6582 /**
6683 * Create a new CloudTraceSink
6784 *
6885 * @param uploadUrl - Pre-signed PUT URL from Sentience API
6986 * @param runId - Run ID for persistent cache naming
87+ * @param apiKey - Sentience API key for calling /v1/traces/complete
88+ * @param apiUrl - Sentience API base URL (default: https://api.sentienceapi.com)
89+ * @param logger - Optional logger instance for logging file sizes and errors
7090 */
71- constructor ( uploadUrl : string , runId ?: string ) {
91+ constructor (
92+ uploadUrl : string ,
93+ runId ?: string ,
94+ apiKey ?: string ,
95+ apiUrl ?: string ,
96+ logger ?: SentienceLogger
97+ ) {
7298 super ( ) ;
7399 this . uploadUrl = uploadUrl ;
74100 this . runId = runId || `trace-${ Date . now ( ) } ` ;
101+ this . apiKey = apiKey ;
102+ this . apiUrl = apiUrl || 'https://api.sentienceapi.com' ;
103+ this . logger = logger ;
75104
76105 // PRODUCTION FIX: Use persistent cache directory instead of /tmp
77106 // This ensures traces survive process crashes!
@@ -218,15 +247,30 @@ export class CloudTraceSink extends TraceSink {
218247 } ) ;
219248 }
220249
221- // 2. Read and compress trace data
222- if ( ! fs . existsSync ( this . tempFilePath ) ) {
250+ // 2. Read and compress trace data (using async operations)
251+ try {
252+ await fsPromises . access ( this . tempFilePath ) ;
253+ } catch {
223254 console . warn ( '[CloudTraceSink] Temp file does not exist, skipping upload' ) ;
224255 return ;
225256 }
226257
227- const traceData = fs . readFileSync ( this . tempFilePath ) ;
258+ const traceData = await fsPromises . readFile ( this . tempFilePath ) ;
228259 const compressedData = zlib . gzipSync ( traceData ) ;
229260
261+ // Measure trace file size (NEW)
262+ this . traceFileSizeBytes = compressedData . length ;
263+
264+ // Log file sizes if logger is provided (NEW)
265+ if ( this . logger ) {
266+ this . logger . info (
267+ `Trace file size: ${ ( this . traceFileSizeBytes / 1024 / 1024 ) . toFixed ( 2 ) } MB`
268+ ) ;
269+ this . logger . info (
270+ `Screenshot total: ${ ( this . screenshotTotalSizeBytes / 1024 / 1024 ) . toFixed ( 2 ) } MB`
271+ ) ;
272+ }
273+
230274 // 3. Upload to cloud via pre-signed URL
231275 console . log (
232276 `📤 [Sentience] Uploading trace to cloud (${ compressedData . length } bytes)...`
@@ -237,8 +281,11 @@ export class CloudTraceSink extends TraceSink {
237281 if ( statusCode === 200 ) {
238282 console . log ( '✅ [Sentience] Trace uploaded successfully' ) ;
239283
284+ // Call /v1/traces/complete to report file sizes (NEW)
285+ await this . _completeTrace ( ) ;
286+
240287 // 4. Delete temp file on success
241- fs . unlinkSync ( this . tempFilePath ) ;
288+ await fsPromises . unlink ( this . tempFilePath ) ;
242289 } else {
243290 console . error ( `❌ [Sentience] Upload failed: HTTP ${ statusCode } ` ) ;
244291 console . error ( ` Local trace preserved at: ${ this . tempFilePath } ` ) ;
@@ -250,6 +297,74 @@ export class CloudTraceSink extends TraceSink {
250297 }
251298 }
252299
300+ /**
301+ * Call /v1/traces/complete to report file sizes to gateway.
302+ *
303+ * This is a best-effort call - failures are logged but don't affect upload success.
304+ */
305+ private async _completeTrace ( ) : Promise < void > {
306+ if ( ! this . apiKey ) {
307+ // No API key - skip complete call
308+ return ;
309+ }
310+
311+ return new Promise ( ( resolve ) => {
312+ const url = new URL ( `${ this . apiUrl } /v1/traces/complete` ) ;
313+ const protocol = url . protocol === 'https:' ? https : http ;
314+
315+ const body = JSON . stringify ( {
316+ run_id : this . runId ,
317+ stats : {
318+ trace_file_size_bytes : this . traceFileSizeBytes ,
319+ screenshot_total_size_bytes : this . screenshotTotalSizeBytes ,
320+ } ,
321+ } ) ;
322+
323+ const options = {
324+ hostname : url . hostname ,
325+ port : url . port || ( url . protocol === 'https:' ? 443 : 80 ) ,
326+ path : url . pathname + url . search ,
327+ method : 'POST' ,
328+ headers : {
329+ 'Content-Type' : 'application/json' ,
330+ 'Content-Length' : Buffer . byteLength ( body ) ,
331+ Authorization : `Bearer ${ this . apiKey } ` ,
332+ } ,
333+ timeout : 10000 , // 10 second timeout
334+ } ;
335+
336+ const req = protocol . request ( options , ( res ) => {
337+ // Consume response data
338+ res . on ( 'data' , ( ) => { } ) ;
339+ res . on ( 'end' , ( ) => {
340+ if ( res . statusCode === 200 ) {
341+ this . logger ?. info ( 'Trace completion reported to gateway' ) ;
342+ } else {
343+ this . logger ?. warn (
344+ `Failed to report trace completion: HTTP ${ res . statusCode } `
345+ ) ;
346+ }
347+ resolve ( ) ;
348+ } ) ;
349+ } ) ;
350+
351+ req . on ( 'error' , ( error ) => {
352+ // Best-effort - log but don't fail
353+ this . logger ?. warn ( `Error reporting trace completion: ${ error . message } ` ) ;
354+ resolve ( ) ;
355+ } ) ;
356+
357+ req . on ( 'timeout' , ( ) => {
358+ req . destroy ( ) ;
359+ this . logger ?. warn ( 'Trace completion request timeout' ) ;
360+ resolve ( ) ;
361+ } ) ;
362+
363+ req . write ( body ) ;
364+ req . end ( ) ;
365+ } ) ;
366+ }
367+
253368 /**
254369 * Get unique identifier for this sink
255370 */
0 commit comments