browser-use-js: preserve complete final output

This commit is contained in:
Gregor Žunič
2026-08-14 14:37:39 -07:00
parent 0a61fe1b2c
commit e56a28c78f
3 changed files with 39 additions and 18 deletions
+3 -18
View File
@@ -2,6 +2,7 @@
import { readFile } from "node:fs/promises";
import { Agent } from "./agent.js";
import { eventJsonLine, resultJsonLine } from "./event-json.js";
const args = process.argv.slice(2);
const taskFileIndex = args.indexOf("--task-file");
@@ -23,11 +24,11 @@ try {
reasoningEffort: reasoningEffort(process.env.BROWSER_USE_JS_REASONING_EFFORT),
maxSteps: integer(process.env.BROWSER_USE_JS_MAX_STEPS, 100),
onEvent: (event) => {
process.stdout.write(`${JSON.stringify(sanitize(event))}\n`);
process.stdout.write(eventJsonLine(event));
},
}).run();
const { messages: _messages, ...summary } = result;
process.stdout.write(`${JSON.stringify({ type: "browser_use_js_result", result: sanitize(summary) })}\n`);
process.stdout.write(resultJsonLine(summary));
} catch (error) {
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
process.stdout.write(`${JSON.stringify({ type: "browser_use_js_error", error: message })}\n`);
@@ -35,22 +36,6 @@ try {
process.exitCode = 1;
}
function sanitize(value: unknown): unknown {
if (Array.isArray(value)) return value.map(sanitize);
if (!value || typeof value !== "object") {
return typeof value === "string" && value.length > 30_000
? `${value.slice(0, 30_000)}... <${value.length - 30_000} chars omitted>`
: value;
}
const result: Record<string, unknown> = {};
for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
result[key] = key === "data" && typeof item === "string" && item.length > 10_000
? `<image:${item.length} chars>`
: sanitize(item);
}
return result;
}
function integer(value: string | undefined, fallback: number): number {
const parsed = Number.parseInt(value ?? "", 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
+27
View File
@@ -0,0 +1,27 @@
const MAX_EVENT_STRING_CHARS = 30_000;
/** Encode a bounded Pi event for JSONL logs and telemetry ingestion. */
export function eventJsonLine(event: unknown): string {
return `${JSON.stringify(sanitizeEvent(event))}\n`;
}
/** Encode the public result without clipping the user's final deliverable. */
export function resultJsonLine(result: unknown): string {
return `${JSON.stringify({ type: "browser_use_js_result", result })}\n`;
}
function sanitizeEvent(value: unknown): unknown {
if (Array.isArray(value)) return value.map(sanitizeEvent);
if (!value || typeof value !== "object") {
return typeof value === "string" && value.length > MAX_EVENT_STRING_CHARS
? `${value.slice(0, MAX_EVENT_STRING_CHARS)}... <${value.length - MAX_EVENT_STRING_CHARS} chars omitted>`
: value;
}
const result: Record<string, unknown> = {};
for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
result[key] = key === "data" && typeof item === "string" && item.length > 10_000
? `<image:${item.length} chars>`
: sanitizeEvent(item);
}
return result;
}
+9
View File
@@ -2,6 +2,15 @@ import assert from "node:assert/strict";
import test from "node:test";
import { WebSocketServer } from "ws";
import { CdpSession, createBrowserSession } from "../dist/cdp.js";
import { eventJsonLine, resultJsonLine } from "../dist/event-json.js";
test("CLI clips trace events without clipping the final deliverable", () => {
const output = "x".repeat(40_000);
const event = JSON.parse(eventJsonLine({ output }));
const result = JSON.parse(resultJsonLine({ output }));
assert.match(event.output, /chars omitted/);
assert.equal(result.result.output, output);
});
test("CDP session attaches once and routes page calls through the active session", async () => {
const requests = [];