Skip to content

Commit c86ddd5

Browse files
author
SentienceDEV
committed
Merge pull request #68 from SentienceAPI/report_size
file size reporting
2 parents de5b6c6 + 5b50a7e commit c86ddd5

File tree

3 files changed

+128
-8
lines changed

3 files changed

+128
-8
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sentienceapi",
3-
"version": "0.90.3",
3+
"version": "0.90.5",
44
"description": "TypeScript SDK for Sentience AI Agent Browser Automation",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/tracing/cloud-sink.ts

Lines changed: 120 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*/
1111

1212
import * as fs from 'fs';
13+
import { promises as fsPromises } from 'fs';
1314
import * as os from 'os';
1415
import * as path from 'path';
1516
import * as zlib from 'zlib';
@@ -18,6 +19,15 @@ import * as http from 'http';
1819
import { URL } from 'url';
1920
import { 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
*/

src/tracing/tracer-factory.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import * as http from 'http';
1616
import { URL } from 'url';
1717
import { randomUUID } from 'crypto';
1818
import { Tracer } from './tracer';
19-
import { CloudTraceSink } from './cloud-sink';
19+
import { CloudTraceSink, SentienceLogger } from './cloud-sink';
2020
import { JsonlTraceSink } from './jsonl-sink';
2121

2222
/**
@@ -172,6 +172,7 @@ function httpPost(url: string, data: any, headers: Record<string, string>): Prom
172172
* @param options.apiKey - Sentience API key (e.g., "sk_pro_xxxxx")
173173
* @param options.runId - Unique identifier for this agent run (generates UUID if not provided)
174174
* @param options.apiUrl - Sentience API base URL (default: https://api.sentienceapi.com)
175+
* @param options.logger - Optional logger instance for logging file sizes and errors
175176
* @returns Tracer configured with appropriate sink
176177
*
177178
* @example
@@ -194,6 +195,7 @@ export async function createTracer(options: {
194195
apiKey?: string;
195196
runId?: string;
196197
apiUrl?: string;
198+
logger?: SentienceLogger;
197199
}): Promise<Tracer> {
198200
const runId = options.runId || randomUUID();
199201
const apiUrl = options.apiUrl || SENTIENCE_API_URL;
@@ -223,7 +225,10 @@ export async function createTracer(options: {
223225

224226
console.log('☁️ [Sentience] Cloud tracing enabled (Pro tier)');
225227
// PRODUCTION FIX: Pass runId for persistent cache naming
226-
return new Tracer(runId, new CloudTraceSink(uploadUrl, runId));
228+
return new Tracer(
229+
runId,
230+
new CloudTraceSink(uploadUrl, runId, options.apiKey, apiUrl, options.logger)
231+
);
227232
} else if (response.status === 403) {
228233
console.log('⚠️ [Sentience] Cloud tracing requires Pro tier');
229234
console.log(' Falling back to local-only tracing');

0 commit comments

Comments
 (0)