@browserbasehq/orca 3.2.0-preview.2 → 3.2.0-preview.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/lib/utils.d.ts +0 -1
- package/dist/cjs/lib/utils.js +0 -4
- package/dist/cjs/lib/utils.js.map +1 -1
- package/dist/cjs/lib/v3/agent/AnthropicCUAClient.js +6 -4
- package/dist/cjs/lib/v3/agent/AnthropicCUAClient.js.map +1 -1
- package/dist/cjs/lib/v3/agent/GoogleCUAClient.js +6 -4
- package/dist/cjs/lib/v3/agent/GoogleCUAClient.js.map +1 -1
- package/dist/cjs/lib/v3/agent/OpenAICUAClient.js +6 -4
- package/dist/cjs/lib/v3/agent/OpenAICUAClient.js.map +1 -1
- package/dist/cjs/lib/v3/flowLogger.d.ts +103 -62
- package/dist/cjs/lib/v3/flowLogger.js +773 -362
- package/dist/cjs/lib/v3/flowLogger.js.map +1 -1
- package/dist/cjs/lib/v3/handlers/handlerUtils/actHandlerUtils.js +33 -21
- package/dist/cjs/lib/v3/handlers/handlerUtils/actHandlerUtils.js.map +1 -1
- package/dist/cjs/lib/v3/handlers/v3AgentHandler.d.ts +4 -0
- package/dist/cjs/lib/v3/handlers/v3AgentHandler.js +31 -1
- package/dist/cjs/lib/v3/handlers/v3AgentHandler.js.map +1 -1
- package/dist/cjs/lib/v3/handlers/v3CuaAgentHandler.js +12 -10
- package/dist/cjs/lib/v3/handlers/v3CuaAgentHandler.js.map +1 -1
- package/dist/cjs/lib/v3/llm/aisdk.js +16 -10
- package/dist/cjs/lib/v3/llm/aisdk.js.map +1 -1
- package/dist/cjs/lib/v3/types/public/options.d.ts +0 -5
- package/dist/cjs/lib/v3/types/public/options.js.map +1 -1
- package/dist/cjs/lib/v3/understudy/cdp.d.ts +12 -3
- package/dist/cjs/lib/v3/understudy/cdp.js +10 -83
- package/dist/cjs/lib/v3/understudy/cdp.js.map +1 -1
- package/dist/cjs/lib/v3/understudy/page.js +17 -32
- package/dist/cjs/lib/v3/understudy/page.js.map +1 -1
- package/dist/cjs/lib/v3/v3.d.ts +0 -5
- package/dist/cjs/lib/v3/v3.js +157 -174
- package/dist/cjs/lib/v3/v3.js.map +1 -1
- package/dist/esm/lib/utils.d.ts +0 -1
- package/dist/esm/lib/utils.js +0 -3
- package/dist/esm/lib/utils.js.map +1 -1
- package/dist/esm/lib/v3/agent/AnthropicCUAClient.js +7 -5
- package/dist/esm/lib/v3/agent/AnthropicCUAClient.js.map +1 -1
- package/dist/esm/lib/v3/agent/GoogleCUAClient.js +7 -5
- package/dist/esm/lib/v3/agent/GoogleCUAClient.js.map +1 -1
- package/dist/esm/lib/v3/agent/OpenAICUAClient.js +7 -5
- package/dist/esm/lib/v3/agent/OpenAICUAClient.js.map +1 -1
- package/dist/esm/lib/v3/flowLogger.d.ts +103 -62
- package/dist/esm/lib/v3/flowLogger.js +762 -356
- package/dist/esm/lib/v3/flowLogger.js.map +1 -1
- package/dist/esm/lib/v3/handlers/handlerUtils/actHandlerUtils.js +34 -22
- package/dist/esm/lib/v3/handlers/handlerUtils/actHandlerUtils.js.map +1 -1
- package/dist/esm/lib/v3/handlers/v3AgentHandler.d.ts +4 -0
- package/dist/esm/lib/v3/handlers/v3AgentHandler.js +32 -2
- package/dist/esm/lib/v3/handlers/v3AgentHandler.js.map +1 -1
- package/dist/esm/lib/v3/handlers/v3CuaAgentHandler.js +13 -11
- package/dist/esm/lib/v3/handlers/v3CuaAgentHandler.js.map +1 -1
- package/dist/esm/lib/v3/llm/aisdk.js +17 -11
- package/dist/esm/lib/v3/llm/aisdk.js.map +1 -1
- package/dist/esm/lib/v3/types/public/options.d.ts +0 -5
- package/dist/esm/lib/v3/types/public/options.js.map +1 -1
- package/dist/esm/lib/v3/understudy/cdp.d.ts +12 -3
- package/dist/esm/lib/v3/understudy/cdp.js +10 -83
- package/dist/esm/lib/v3/understudy/cdp.js.map +1 -1
- package/dist/esm/lib/v3/understudy/page.js +18 -33
- package/dist/esm/lib/v3/understudy/page.js.map +1 -1
- package/dist/esm/lib/v3/v3.d.ts +0 -5
- package/dist/esm/lib/v3/v3.js +158 -175
- package/dist/esm/lib/v3/v3.js.map +1 -1
- package/package.json +1 -1
- package/dist/cjs/lib/v3/eventStore.d.ts +0 -41
- package/dist/cjs/lib/v3/eventStore.js +0 -375
- package/dist/cjs/lib/v3/eventStore.js.map +0 -1
- package/dist/esm/lib/v3/eventStore.d.ts +0 -41
- package/dist/esm/lib/v3/eventStore.js +0 -363
- package/dist/esm/lib/v3/eventStore.js.map +0 -1
|
@@ -1,249 +1,736 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.
|
|
5
|
-
exports.
|
|
6
|
-
exports.
|
|
6
|
+
exports.SessionFileLogger = void 0;
|
|
7
|
+
exports.getConfigDir = getConfigDir;
|
|
8
|
+
exports.formatLlmPromptPreview = formatLlmPromptPreview;
|
|
9
|
+
exports.formatCuaPromptPreview = formatCuaPromptPreview;
|
|
10
|
+
exports.formatCuaResponsePreview = formatCuaResponsePreview;
|
|
11
|
+
exports.logAction = logAction;
|
|
12
|
+
exports.logStagehandStep = logStagehandStep;
|
|
7
13
|
const node_async_hooks_1 = require("node:async_hooks");
|
|
14
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
15
|
+
const node_stream_1 = require("node:stream");
|
|
8
16
|
const uuid_1 = require("uuid");
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
18
|
+
const pino_1 = __importDefault(require("pino"));
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Constants
|
|
21
|
+
// =============================================================================
|
|
22
|
+
const MAX_LINE_LENGTH = 160;
|
|
23
|
+
// Flow logging config dir - empty string disables logging entirely
|
|
24
|
+
const CONFIG_DIR = process.env.BROWSERBASE_CONFIG_DIR || "";
|
|
25
|
+
const NOISY_CDP_EVENTS = new Set([
|
|
26
|
+
"Target.targetInfoChanged",
|
|
27
|
+
"Runtime.executionContextCreated",
|
|
28
|
+
"Runtime.executionContextDestroyed",
|
|
29
|
+
"Runtime.executionContextsCleared",
|
|
30
|
+
"Page.lifecycleEvent",
|
|
31
|
+
"Network.dataReceived",
|
|
32
|
+
"Network.loadingFinished",
|
|
33
|
+
"Network.requestWillBeSentExtraInfo",
|
|
34
|
+
"Network.responseReceivedExtraInfo",
|
|
35
|
+
"Network.requestWillBeSent",
|
|
36
|
+
"Network.responseReceived",
|
|
37
|
+
]);
|
|
38
|
+
const loggerContext = new node_async_hooks_1.AsyncLocalStorage();
|
|
39
|
+
// =============================================================================
|
|
40
|
+
// Formatting Utilities (used by pretty streams)
|
|
41
|
+
// =============================================================================
|
|
42
|
+
/** Calculate base64 data size in KB */
|
|
43
|
+
const dataToKb = (data) => ((data.length * 0.75) / 1024).toFixed(1);
|
|
44
|
+
/** Truncate CDP IDs: frameId:363F03EB...EF8 → frameId:363F…5EF8 */
|
|
45
|
+
function truncateCdpIds(value) {
|
|
46
|
+
return value.replace(/([iI]d:?"?)([0-9A-F]{32})(?="?[,})\s]|$)/g, (_, pre, id) => `${pre}${id.slice(0, 4)}…${id.slice(-4)}`);
|
|
47
|
+
}
|
|
48
|
+
/** Truncate line showing start...end */
|
|
49
|
+
function truncateLine(value, maxLen) {
|
|
50
|
+
const collapsed = value.replace(/\s+/g, " ");
|
|
51
|
+
if (collapsed.length <= maxLen)
|
|
52
|
+
return collapsed;
|
|
53
|
+
const endLen = Math.floor(maxLen * 0.3);
|
|
54
|
+
const startLen = maxLen - endLen - 1;
|
|
55
|
+
return `${collapsed.slice(0, startLen)}…${collapsed.slice(-endLen)}`;
|
|
56
|
+
}
|
|
57
|
+
function formatValue(value) {
|
|
58
|
+
if (typeof value === "string")
|
|
59
|
+
return `'${value}'`;
|
|
60
|
+
if (value == null || typeof value !== "object")
|
|
61
|
+
return String(value);
|
|
62
|
+
try {
|
|
63
|
+
return JSON.stringify(value);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return "[unserializable]";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function formatArgs(args) {
|
|
70
|
+
if (args === undefined)
|
|
71
|
+
return "";
|
|
72
|
+
return (Array.isArray(args) ? args : [args])
|
|
73
|
+
.filter((e) => e !== undefined)
|
|
74
|
+
.map(formatValue)
|
|
75
|
+
.filter((e) => e.length > 0)
|
|
76
|
+
.join(", ");
|
|
77
|
+
}
|
|
78
|
+
const shortId = (id) => id ? id.slice(-4) : "-";
|
|
79
|
+
function formatTag(label, id, icon) {
|
|
80
|
+
return id ? `[${icon} #${shortId(id)}${label ? " " + label : ""}]` : "⤑";
|
|
81
|
+
}
|
|
82
|
+
let nonce = 0;
|
|
83
|
+
function formatTimestamp() {
|
|
84
|
+
const d = new Date();
|
|
85
|
+
const pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
86
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}${pad(nonce++ % 100)}`;
|
|
87
|
+
}
|
|
88
|
+
const SENSITIVE_KEYS = /apikey|api_key|key|secret|token|password|passwd|pwd|credential|auth/i;
|
|
89
|
+
function sanitizeOptions(options) {
|
|
90
|
+
const sanitize = (obj) => {
|
|
91
|
+
if (typeof obj !== "object" || obj === null)
|
|
92
|
+
return obj;
|
|
93
|
+
if (Array.isArray(obj))
|
|
94
|
+
return obj.map(sanitize);
|
|
95
|
+
const result = {};
|
|
96
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
97
|
+
result[key] = SENSITIVE_KEYS.test(key) ? "******" : sanitize(value);
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
};
|
|
101
|
+
return sanitize({ ...options });
|
|
102
|
+
}
|
|
103
|
+
/** Remove unescaped quotes for cleaner log output */
|
|
104
|
+
function removeQuotes(str) {
|
|
105
|
+
return str
|
|
106
|
+
.replace(/([^\\])["']/g, "$1")
|
|
107
|
+
.replace(/^["']|["']$/g, "")
|
|
108
|
+
.trim();
|
|
109
|
+
}
|
|
110
|
+
// =============================================================================
|
|
111
|
+
// Pretty Formatting (converts FlowEvent to human-readable log line)
|
|
112
|
+
// =============================================================================
|
|
113
|
+
function prettifyEvent(event) {
|
|
114
|
+
const parts = [];
|
|
115
|
+
// Build context tags - always add parent span tags (formatTag returns ⤑ for null IDs)
|
|
116
|
+
if (event.category === "AgentTask") {
|
|
117
|
+
parts.push(formatTag("", event.taskId, "🅰"));
|
|
118
|
+
}
|
|
119
|
+
else if (event.category === "StagehandStep") {
|
|
120
|
+
parts.push(formatTag("", event.taskId, "🅰"));
|
|
121
|
+
parts.push(formatTag(event.stepLabel, event.stepId, "🆂"));
|
|
122
|
+
}
|
|
123
|
+
else if (event.category === "UnderstudyAction") {
|
|
124
|
+
parts.push(formatTag("", event.taskId, "🅰"));
|
|
125
|
+
parts.push(formatTag(event.stepLabel, event.stepId, "🆂"));
|
|
126
|
+
parts.push(formatTag(event.actionLabel, event.actionId, "🆄"));
|
|
127
|
+
}
|
|
128
|
+
else if (event.category === "CDP") {
|
|
129
|
+
parts.push(formatTag("", event.taskId, "🅰"));
|
|
130
|
+
parts.push(formatTag(event.stepLabel, event.stepId, "🆂"));
|
|
131
|
+
parts.push(formatTag(event.actionLabel, event.actionId, "🆄"));
|
|
132
|
+
parts.push(formatTag("CDP", event.targetId, "🅲"));
|
|
133
|
+
}
|
|
134
|
+
else if (event.category === "LLM") {
|
|
135
|
+
parts.push(formatTag("", event.taskId, "🅰"));
|
|
136
|
+
parts.push(formatTag(event.stepLabel, event.stepId, "🆂"));
|
|
137
|
+
parts.push(formatTag("LLM", event.requestId, "🧠"));
|
|
138
|
+
}
|
|
139
|
+
// Build details based on event type
|
|
140
|
+
let details = "";
|
|
141
|
+
const argsStr = event.params ? formatArgs(event.params) : "";
|
|
142
|
+
if (event.category === "AgentTask") {
|
|
143
|
+
if (event.event === "started") {
|
|
144
|
+
details = `▷ ${event.method}(${argsStr})`;
|
|
145
|
+
}
|
|
146
|
+
else if (event.event === "completed") {
|
|
147
|
+
const m = event.metrics;
|
|
148
|
+
const durationSec = m?.durationMs
|
|
149
|
+
? (m.durationMs / 1000).toFixed(1)
|
|
150
|
+
: "?";
|
|
151
|
+
const llmStats = `${m?.llmRequests ?? 0} LLM calls ꜛ${m?.inputTokens ?? 0} ꜜ${m?.outputTokens ?? 0} tokens`;
|
|
152
|
+
const cdpStats = `${m?.cdpEvents ?? 0} CDP msgs`;
|
|
153
|
+
details = `✓ Agent.execute() DONE in ${durationSec}s | ${llmStats} | ${cdpStats}`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else if (event.category === "StagehandStep") {
|
|
157
|
+
if (event.event === "started") {
|
|
158
|
+
details = `▷ ${event.method}(${argsStr})`;
|
|
159
|
+
}
|
|
160
|
+
else if (event.event === "completed") {
|
|
161
|
+
const durationSec = event.metrics?.durationMs
|
|
162
|
+
? (event.metrics.durationMs / 1000).toFixed(2)
|
|
163
|
+
: "?";
|
|
164
|
+
details = `✓ ${event.stepLabel || "STEP"} completed in ${durationSec}s`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else if (event.category === "UnderstudyAction") {
|
|
168
|
+
if (event.event === "started") {
|
|
169
|
+
details = `▷ ${event.method}(${argsStr})`;
|
|
170
|
+
}
|
|
171
|
+
else if (event.event === "completed") {
|
|
172
|
+
const durationSec = event.metrics?.durationMs
|
|
173
|
+
? (event.metrics.durationMs / 1000).toFixed(2)
|
|
174
|
+
: "?";
|
|
175
|
+
details = `✓ ${event.actionLabel || "ACTION"} completed in ${durationSec}s`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else if (event.category === "CDP") {
|
|
179
|
+
const icon = event.event === "call" ? "⏵" : "⏴";
|
|
180
|
+
details = `${icon} ${event.method}(${argsStr})`;
|
|
181
|
+
}
|
|
182
|
+
else if (event.category === "LLM") {
|
|
183
|
+
if (event.event === "request") {
|
|
184
|
+
const promptStr = event.prompt ? " " + String(event.prompt) : "";
|
|
185
|
+
details = `${event.model} ⏴${promptStr}`;
|
|
24
186
|
}
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
187
|
+
else if (event.event === "response") {
|
|
188
|
+
const hasTokens = event.inputTokens !== undefined || event.outputTokens !== undefined;
|
|
189
|
+
const tokenStr = hasTokens
|
|
190
|
+
? ` ꜛ${event.inputTokens ?? 0} ꜜ${event.outputTokens ?? 0} |`
|
|
191
|
+
: "";
|
|
192
|
+
const outputStr = event.output ? " " + String(event.output) : "";
|
|
193
|
+
details = `${event.model} ↳${tokenStr}${outputStr}`;
|
|
29
194
|
}
|
|
30
|
-
this.eventType = input.eventType.endsWith("Event")
|
|
31
|
-
? input.eventType
|
|
32
|
-
: `${input.eventType}Event`;
|
|
33
|
-
this.eventId =
|
|
34
|
-
input.eventId ?? FlowEvent.createEventId(input.eventIdSuffix ?? "0");
|
|
35
|
-
this.eventParentIds = input.eventParentIds ?? [];
|
|
36
|
-
this.createdAt = input.createdAt ?? new Date().toISOString();
|
|
37
|
-
this.sessionId = input.sessionId;
|
|
38
|
-
this.data = input.data ?? {};
|
|
39
195
|
}
|
|
196
|
+
if (!details)
|
|
197
|
+
return null;
|
|
198
|
+
// Assemble line and apply final truncation
|
|
199
|
+
const fullLine = `${formatTimestamp()} ${parts.join(" ")} ${details}`;
|
|
200
|
+
const cleaned = removeQuotes(fullLine);
|
|
201
|
+
const processed = event.category === "CDP" ? truncateCdpIds(cleaned) : cleaned;
|
|
202
|
+
return truncateLine(processed, MAX_LINE_LENGTH);
|
|
40
203
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
204
|
+
/** Check if a CDP event should be filtered from pretty output */
|
|
205
|
+
function shouldFilterCdpEvent(event) {
|
|
206
|
+
if (event.category !== "CDP")
|
|
207
|
+
return false;
|
|
208
|
+
if (event.method?.endsWith(".enable") || event.method === "enable")
|
|
209
|
+
return true;
|
|
210
|
+
return event.event === "message" && NOISY_CDP_EVENTS.has(event.method);
|
|
45
211
|
}
|
|
46
212
|
// =============================================================================
|
|
47
|
-
//
|
|
213
|
+
// Stream Creation
|
|
48
214
|
// =============================================================================
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
215
|
+
const isWritable = (s) => !!(s && !s.destroyed && s.writable);
|
|
216
|
+
function createJsonlStream(ctx) {
|
|
217
|
+
return new node_stream_1.Writable({
|
|
218
|
+
objectMode: true,
|
|
219
|
+
write(chunk, _, cb) {
|
|
220
|
+
if (ctx.initialized && isWritable(ctx.fileStreams.jsonl)) {
|
|
221
|
+
ctx.fileStreams.jsonl.write(chunk, cb);
|
|
222
|
+
}
|
|
223
|
+
else
|
|
224
|
+
cb();
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function createPrettyStream(ctx, category, streamKey) {
|
|
229
|
+
return new node_stream_1.Writable({
|
|
230
|
+
objectMode: true,
|
|
231
|
+
write(chunk, _, cb) {
|
|
232
|
+
const stream = ctx.fileStreams[streamKey];
|
|
233
|
+
if (!ctx.initialized || !isWritable(stream))
|
|
234
|
+
return cb();
|
|
235
|
+
try {
|
|
236
|
+
const event = JSON.parse(chunk);
|
|
237
|
+
if (event.category !== category || shouldFilterCdpEvent(event))
|
|
238
|
+
return cb();
|
|
239
|
+
const line = prettifyEvent(event);
|
|
240
|
+
if (line)
|
|
241
|
+
stream.write(line + "\n", cb);
|
|
242
|
+
else
|
|
243
|
+
cb();
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
cb();
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
// =============================================================================
|
|
252
|
+
// Public Helpers (used by external callers)
|
|
253
|
+
// =============================================================================
|
|
254
|
+
/**
|
|
255
|
+
* Get the config directory. Returns empty string if logging is disabled.
|
|
256
|
+
*/
|
|
257
|
+
function getConfigDir() {
|
|
258
|
+
return CONFIG_DIR ? node_path_1.default.resolve(CONFIG_DIR) : "";
|
|
259
|
+
}
|
|
260
|
+
/** Extract text and image info from a content array (handles nested tool_result) */
|
|
261
|
+
function extractFromContent(content, result) {
|
|
262
|
+
for (const part of content) {
|
|
263
|
+
const p = part;
|
|
264
|
+
// Text
|
|
265
|
+
if (!result.text && p.text) {
|
|
266
|
+
result.text = p.type === "text" || !p.type ? p.text : undefined;
|
|
267
|
+
}
|
|
268
|
+
// Images - various formats
|
|
269
|
+
if (p.type === "image" || p.type === "image_url") {
|
|
270
|
+
const url = p.image_url?.url;
|
|
271
|
+
if (url?.startsWith("data:"))
|
|
272
|
+
result.extras.push(`${dataToKb(url)}kb image`);
|
|
273
|
+
else if (p.source?.data)
|
|
274
|
+
result.extras.push(`${dataToKb(p.source.data)}kb image`);
|
|
275
|
+
else
|
|
276
|
+
result.extras.push("image");
|
|
277
|
+
}
|
|
278
|
+
else if (p.source?.data) {
|
|
279
|
+
result.extras.push(`${dataToKb(p.source.data)}kb image`);
|
|
280
|
+
}
|
|
281
|
+
else if (p.inlineData?.data) {
|
|
282
|
+
result.extras.push(`${dataToKb(p.inlineData.data)}kb image`);
|
|
283
|
+
}
|
|
284
|
+
// Recurse into tool_result content
|
|
285
|
+
if (p.type === "tool_result" && Array.isArray(p.content)) {
|
|
286
|
+
extractFromContent(p.content, result);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/** Build final preview string with extras */
|
|
291
|
+
function buildPreview(text, extras, maxLen) {
|
|
292
|
+
if (!text && extras.length === 0)
|
|
293
|
+
return undefined;
|
|
294
|
+
let result = text || "";
|
|
295
|
+
if (maxLen && result.length > maxLen)
|
|
296
|
+
result = result.slice(0, maxLen) + "...";
|
|
297
|
+
if (extras.length > 0) {
|
|
298
|
+
const extrasStr = extras.map((e) => `+{${e}}`).join(" ");
|
|
299
|
+
result = result ? `${result} ${extrasStr}` : extrasStr;
|
|
300
|
+
}
|
|
301
|
+
return result || undefined;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Format a prompt preview from LLM messages for logging.
|
|
305
|
+
* Returns format like: "some text... +{5.8kb image} +{schema} +{12 tools}"
|
|
306
|
+
*/
|
|
307
|
+
function formatLlmPromptPreview(messages, options) {
|
|
308
|
+
try {
|
|
309
|
+
const lastUserMsg = messages.filter((m) => m.role === "user").pop();
|
|
310
|
+
if (!lastUserMsg)
|
|
311
|
+
return undefined;
|
|
312
|
+
const result = {
|
|
313
|
+
text: undefined,
|
|
314
|
+
extras: [],
|
|
57
315
|
};
|
|
316
|
+
if (typeof lastUserMsg.content === "string") {
|
|
317
|
+
result.text = lastUserMsg.content;
|
|
318
|
+
}
|
|
319
|
+
else if (Array.isArray(lastUserMsg.content)) {
|
|
320
|
+
extractFromContent(lastUserMsg.content, result);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
return undefined;
|
|
324
|
+
}
|
|
325
|
+
// Clean instruction prefix
|
|
326
|
+
if (result.text) {
|
|
327
|
+
result.text = result.text.replace(/^[Ii]nstruction: /, "");
|
|
328
|
+
}
|
|
329
|
+
if (options?.hasSchema)
|
|
330
|
+
result.extras.push("schema");
|
|
331
|
+
if (options?.toolCount)
|
|
332
|
+
result.extras.push(`${options.toolCount} tools`);
|
|
333
|
+
return buildPreview(result.text, result.extras);
|
|
58
334
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
335
|
+
catch {
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Extract a text preview from CUA-style messages.
|
|
341
|
+
* Accepts various message formats (Anthropic, OpenAI, Google).
|
|
342
|
+
*/
|
|
343
|
+
function formatCuaPromptPreview(messages, maxLen = 100) {
|
|
344
|
+
try {
|
|
345
|
+
const lastMsg = messages
|
|
346
|
+
.filter((m) => {
|
|
347
|
+
const msg = m;
|
|
348
|
+
return msg.role === "user" || msg.type === "tool_result";
|
|
349
|
+
})
|
|
350
|
+
.pop();
|
|
351
|
+
if (!lastMsg)
|
|
352
|
+
return undefined;
|
|
353
|
+
const result = {
|
|
354
|
+
text: undefined,
|
|
355
|
+
extras: [],
|
|
356
|
+
};
|
|
357
|
+
if (typeof lastMsg.content === "string") {
|
|
358
|
+
result.text = lastMsg.content;
|
|
77
359
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
eventType,
|
|
81
|
-
data,
|
|
82
|
-
eventParentIds,
|
|
83
|
-
});
|
|
84
|
-
ctx.parentEvents.push(startedEvent);
|
|
85
|
-
try {
|
|
86
|
-
return await originalMethod();
|
|
360
|
+
else if (typeof lastMsg.text === "string") {
|
|
361
|
+
result.text = lastMsg.text;
|
|
87
362
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
FlowLogger.emit({
|
|
91
|
-
eventIdSuffix,
|
|
92
|
-
eventType: `${eventType}ErrorEvent`,
|
|
93
|
-
eventParentIds: [...startedEvent.eventParentIds, startedEvent.eventId],
|
|
94
|
-
data: {
|
|
95
|
-
error: error instanceof Error ? error.message : String(error),
|
|
96
|
-
durationMs: Date.now() - new Date(startedEvent.createdAt).getTime(),
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
throw error;
|
|
363
|
+
else if (Array.isArray(lastMsg.parts)) {
|
|
364
|
+
extractFromContent(lastMsg.parts, result);
|
|
100
365
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (parentEvent?.eventId === startedEvent.eventId && !caughtError) {
|
|
104
|
-
FlowLogger.emit({
|
|
105
|
-
eventIdSuffix,
|
|
106
|
-
eventType: `${eventType}CompletedEvent`,
|
|
107
|
-
eventParentIds: [
|
|
108
|
-
...startedEvent.eventParentIds,
|
|
109
|
-
startedEvent.eventId,
|
|
110
|
-
],
|
|
111
|
-
data: {
|
|
112
|
-
durationMs: Date.now() - new Date(startedEvent.createdAt).getTime(),
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
}
|
|
366
|
+
else if (Array.isArray(lastMsg.content)) {
|
|
367
|
+
extractFromContent(lastMsg.content, result);
|
|
116
368
|
}
|
|
369
|
+
return buildPreview(result.text, result.extras, maxLen);
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/** Format CUA response output for logging */
|
|
376
|
+
function formatCuaResponsePreview(output, maxLen = 100) {
|
|
377
|
+
try {
|
|
378
|
+
// Handle Google format or array
|
|
379
|
+
const items = output
|
|
380
|
+
?.candidates?.[0]?.content?.parts ??
|
|
381
|
+
(Array.isArray(output) ? output : []);
|
|
382
|
+
const preview = items
|
|
383
|
+
.map((item) => {
|
|
384
|
+
const i = item;
|
|
385
|
+
if (i.text)
|
|
386
|
+
return i.text.slice(0, 50);
|
|
387
|
+
if (i.functionCall?.name)
|
|
388
|
+
return `fn:${i.functionCall.name}`;
|
|
389
|
+
if (i.type === "tool_use" && i.name)
|
|
390
|
+
return `tool_use:${i.name}`;
|
|
391
|
+
return i.type ? `[${i.type}]` : "[item]";
|
|
392
|
+
})
|
|
393
|
+
.join(" ");
|
|
394
|
+
return preview.slice(0, maxLen);
|
|
117
395
|
}
|
|
396
|
+
catch {
|
|
397
|
+
return "[error]";
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// =============================================================================
|
|
401
|
+
// SessionFileLogger - Main API
|
|
402
|
+
// =============================================================================
|
|
403
|
+
class SessionFileLogger {
|
|
118
404
|
/**
|
|
119
405
|
* Initialize a new logging context. Call this at the start of a session.
|
|
406
|
+
* If BROWSERBASE_CONFIG_DIR is not set, logging is disabled.
|
|
120
407
|
*/
|
|
121
|
-
static init(sessionId,
|
|
408
|
+
static init(sessionId, v3Options) {
|
|
409
|
+
const configDir = getConfigDir();
|
|
410
|
+
if (!configDir)
|
|
411
|
+
return; // Logging disabled
|
|
412
|
+
const sessionDir = node_path_1.default.join(configDir, "sessions", sessionId);
|
|
413
|
+
// Create context with placeholder logger (will be replaced after streams init)
|
|
122
414
|
const ctx = {
|
|
415
|
+
logger: (0, pino_1.default)({ level: "silent" }), // Placeholder, replaced below
|
|
416
|
+
metrics: {
|
|
417
|
+
llmRequests: 0,
|
|
418
|
+
llmInputTokens: 0,
|
|
419
|
+
llmOutputTokens: 0,
|
|
420
|
+
cdpEvents: 0,
|
|
421
|
+
},
|
|
123
422
|
sessionId,
|
|
124
|
-
|
|
125
|
-
|
|
423
|
+
sessionDir,
|
|
424
|
+
configDir,
|
|
425
|
+
initPromise: Promise.resolve(),
|
|
426
|
+
initialized: false,
|
|
427
|
+
// Span context - mutable, injected into every log via mixin
|
|
428
|
+
taskId: null,
|
|
429
|
+
stepId: null,
|
|
430
|
+
stepLabel: null,
|
|
431
|
+
actionId: null,
|
|
432
|
+
actionLabel: null,
|
|
433
|
+
fileStreams: {
|
|
434
|
+
agent: null,
|
|
435
|
+
stagehand: null,
|
|
436
|
+
understudy: null,
|
|
437
|
+
cdp: null,
|
|
438
|
+
llm: null,
|
|
439
|
+
jsonl: null,
|
|
440
|
+
},
|
|
126
441
|
};
|
|
442
|
+
// Store init promise for awaiting in log methods
|
|
443
|
+
ctx.initPromise = SessionFileLogger.initAsync(ctx, v3Options);
|
|
127
444
|
loggerContext.enterWith(ctx);
|
|
128
|
-
return ctx;
|
|
129
445
|
}
|
|
130
|
-
static async
|
|
131
|
-
|
|
446
|
+
static async initAsync(ctx, v3Options) {
|
|
447
|
+
try {
|
|
448
|
+
await node_fs_1.default.promises.mkdir(ctx.sessionDir, { recursive: true });
|
|
449
|
+
if (v3Options) {
|
|
450
|
+
const sanitizedOptions = sanitizeOptions(v3Options);
|
|
451
|
+
const sessionJsonPath = node_path_1.default.join(ctx.sessionDir, "session.json");
|
|
452
|
+
await node_fs_1.default.promises.writeFile(sessionJsonPath, JSON.stringify(sanitizedOptions, null, 2), "utf-8");
|
|
453
|
+
}
|
|
454
|
+
// Create symlink to latest session
|
|
455
|
+
const latestLink = node_path_1.default.join(ctx.configDir, "sessions", "latest");
|
|
456
|
+
try {
|
|
457
|
+
try {
|
|
458
|
+
await node_fs_1.default.promises.unlink(latestLink);
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
// Ignore if doesn't exist
|
|
462
|
+
}
|
|
463
|
+
await node_fs_1.default.promises.symlink(ctx.sessionId, latestLink, "dir");
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
// Symlink creation can fail on Windows or due to permissions
|
|
467
|
+
}
|
|
468
|
+
// Create file streams
|
|
469
|
+
const dir = ctx.sessionDir;
|
|
470
|
+
ctx.fileStreams.agent = node_fs_1.default.createWriteStream(node_path_1.default.join(dir, "agent_events.log"), { flags: "a" });
|
|
471
|
+
ctx.fileStreams.stagehand = node_fs_1.default.createWriteStream(node_path_1.default.join(dir, "stagehand_events.log"), { flags: "a" });
|
|
472
|
+
ctx.fileStreams.understudy = node_fs_1.default.createWriteStream(node_path_1.default.join(dir, "understudy_events.log"), { flags: "a" });
|
|
473
|
+
ctx.fileStreams.cdp = node_fs_1.default.createWriteStream(node_path_1.default.join(dir, "cdp_events.log"), { flags: "a" });
|
|
474
|
+
ctx.fileStreams.llm = node_fs_1.default.createWriteStream(node_path_1.default.join(dir, "llm_events.log"), { flags: "a" });
|
|
475
|
+
ctx.fileStreams.jsonl = node_fs_1.default.createWriteStream(node_path_1.default.join(dir, "session_events.jsonl"), { flags: "a" });
|
|
476
|
+
ctx.initialized = true;
|
|
477
|
+
// Create pino multistream: JSONL + pretty streams per category
|
|
478
|
+
const streams = [
|
|
479
|
+
{ stream: createJsonlStream(ctx) },
|
|
480
|
+
{ stream: createPrettyStream(ctx, "AgentTask", "agent") },
|
|
481
|
+
{ stream: createPrettyStream(ctx, "StagehandStep", "stagehand") },
|
|
482
|
+
{ stream: createPrettyStream(ctx, "UnderstudyAction", "understudy") },
|
|
483
|
+
{ stream: createPrettyStream(ctx, "CDP", "cdp") },
|
|
484
|
+
{ stream: createPrettyStream(ctx, "LLM", "llm") },
|
|
485
|
+
];
|
|
486
|
+
// Create logger with mixin that injects span context from AsyncLocalStorage
|
|
487
|
+
ctx.logger = (0, pino_1.default)({
|
|
488
|
+
level: "info",
|
|
489
|
+
// Mixin adds eventId and current span context to every log
|
|
490
|
+
mixin() {
|
|
491
|
+
const store = loggerContext.getStore();
|
|
492
|
+
return {
|
|
493
|
+
eventId: (0, uuid_1.v7)(),
|
|
494
|
+
sessionId: store?.sessionId,
|
|
495
|
+
taskId: store?.taskId,
|
|
496
|
+
stepId: store?.stepId,
|
|
497
|
+
stepLabel: store?.stepLabel,
|
|
498
|
+
actionId: store?.actionId,
|
|
499
|
+
actionLabel: store?.actionLabel,
|
|
500
|
+
};
|
|
501
|
+
},
|
|
502
|
+
}, pino_1.default.multistream(streams));
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
// Fail silently
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
static async close() {
|
|
509
|
+
const ctx = loggerContext.getStore();
|
|
132
510
|
if (!ctx)
|
|
133
511
|
return;
|
|
134
|
-
ctx.
|
|
512
|
+
await ctx.initPromise;
|
|
513
|
+
SessionFileLogger.logAgentTaskCompleted();
|
|
514
|
+
await Promise.all(Object.values(ctx.fileStreams)
|
|
515
|
+
.filter(Boolean)
|
|
516
|
+
.map((s) => new Promise((r) => s.end(r)))).catch(() => { });
|
|
135
517
|
}
|
|
136
|
-
static get
|
|
137
|
-
|
|
138
|
-
if (!ctx) {
|
|
139
|
-
throw new Error("FlowLogger context is missing.");
|
|
140
|
-
}
|
|
141
|
-
return ctx;
|
|
518
|
+
static get sessionId() {
|
|
519
|
+
return loggerContext.getStore()?.sessionId ?? null;
|
|
142
520
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
521
|
+
static get sessionDir() {
|
|
522
|
+
return loggerContext.getStore()?.sessionDir ?? null;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Get the current logger context object.
|
|
526
|
+
*/
|
|
527
|
+
static getContext() {
|
|
528
|
+
return loggerContext.getStore() ?? null;
|
|
529
|
+
}
|
|
530
|
+
// ===========================================================================
|
|
531
|
+
// Agent Task Events
|
|
532
|
+
// ===========================================================================
|
|
533
|
+
/**
|
|
534
|
+
* Start a new task and log it.
|
|
535
|
+
*/
|
|
536
|
+
static logAgentTaskStarted({ invocation, args, }) {
|
|
537
|
+
const ctx = loggerContext.getStore();
|
|
538
|
+
if (!ctx)
|
|
539
|
+
return;
|
|
540
|
+
// Set up task context
|
|
541
|
+
ctx.taskId = (0, uuid_1.v7)();
|
|
542
|
+
ctx.stepId = null;
|
|
543
|
+
ctx.stepLabel = null;
|
|
544
|
+
ctx.actionId = null;
|
|
545
|
+
ctx.actionLabel = null;
|
|
546
|
+
// Reset metrics for new task
|
|
547
|
+
ctx.metrics = {
|
|
548
|
+
taskStartTime: Date.now(),
|
|
549
|
+
llmRequests: 0,
|
|
550
|
+
llmInputTokens: 0,
|
|
551
|
+
llmOutputTokens: 0,
|
|
552
|
+
cdpEvents: 0,
|
|
150
553
|
};
|
|
554
|
+
ctx.logger.info({
|
|
555
|
+
category: "AgentTask",
|
|
556
|
+
event: "started",
|
|
557
|
+
method: invocation,
|
|
558
|
+
params: args,
|
|
559
|
+
});
|
|
151
560
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
561
|
+
/**
|
|
562
|
+
* Log task completion with metrics summary.
|
|
563
|
+
*/
|
|
564
|
+
static logAgentTaskCompleted(options) {
|
|
565
|
+
const ctx = loggerContext.getStore();
|
|
566
|
+
if (!ctx || !ctx.metrics.taskStartTime)
|
|
567
|
+
return;
|
|
568
|
+
const durationMs = Date.now() - ctx.metrics.taskStartTime;
|
|
569
|
+
const event = {
|
|
570
|
+
category: "AgentTask",
|
|
571
|
+
event: "completed",
|
|
572
|
+
method: "Agent.execute",
|
|
573
|
+
metrics: {
|
|
574
|
+
durationMs,
|
|
575
|
+
llmRequests: ctx.metrics.llmRequests,
|
|
576
|
+
inputTokens: ctx.metrics.llmInputTokens,
|
|
577
|
+
outputTokens: ctx.metrics.llmOutputTokens,
|
|
578
|
+
cdpEvents: ctx.metrics.cdpEvents,
|
|
579
|
+
},
|
|
156
580
|
};
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
data: eventData,
|
|
160
|
-
}, () => originalMethod(...params));
|
|
161
|
-
if (!options.context && !(loggerContext.getStore() ?? null)) {
|
|
162
|
-
return originalMethod(...params);
|
|
581
|
+
if (options?.cacheHit) {
|
|
582
|
+
event.msg = "CACHE HIT, NO LLM NEEDED";
|
|
163
583
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
584
|
+
ctx.logger.info(event);
|
|
585
|
+
// Clear task context
|
|
586
|
+
ctx.taskId = null;
|
|
587
|
+
ctx.stepId = null;
|
|
588
|
+
ctx.stepLabel = null;
|
|
589
|
+
ctx.actionId = null;
|
|
590
|
+
ctx.actionLabel = null;
|
|
591
|
+
ctx.metrics.taskStartTime = undefined;
|
|
167
592
|
}
|
|
168
593
|
// ===========================================================================
|
|
169
|
-
//
|
|
594
|
+
// Stagehand Step Events
|
|
170
595
|
// ===========================================================================
|
|
171
|
-
static
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
596
|
+
static logStagehandStepEvent({ invocation, args, label, }) {
|
|
597
|
+
const ctx = loggerContext.getStore();
|
|
598
|
+
if (!ctx)
|
|
599
|
+
return (0, uuid_1.v7)();
|
|
600
|
+
// Set up step context
|
|
601
|
+
ctx.stepId = (0, uuid_1.v7)();
|
|
602
|
+
ctx.stepLabel = label.toUpperCase();
|
|
603
|
+
ctx.actionId = null;
|
|
604
|
+
ctx.actionLabel = null;
|
|
605
|
+
ctx.metrics.stepStartTime = Date.now();
|
|
606
|
+
ctx.logger.info({
|
|
607
|
+
category: "StagehandStep",
|
|
608
|
+
event: "started",
|
|
609
|
+
method: invocation,
|
|
610
|
+
params: args,
|
|
611
|
+
});
|
|
612
|
+
return ctx.stepId;
|
|
613
|
+
}
|
|
614
|
+
static logStagehandStepCompleted() {
|
|
615
|
+
const ctx = loggerContext.getStore();
|
|
616
|
+
if (!ctx || !ctx.stepId)
|
|
617
|
+
return;
|
|
618
|
+
const durationMs = ctx.metrics.stepStartTime
|
|
619
|
+
? Date.now() - ctx.metrics.stepStartTime
|
|
620
|
+
: 0;
|
|
621
|
+
ctx.logger.info({
|
|
622
|
+
category: "StagehandStep",
|
|
623
|
+
event: "completed",
|
|
624
|
+
metrics: { durationMs },
|
|
625
|
+
});
|
|
626
|
+
// Clear step context
|
|
627
|
+
ctx.stepId = null;
|
|
628
|
+
ctx.stepLabel = null;
|
|
629
|
+
ctx.actionId = null;
|
|
630
|
+
ctx.actionLabel = null;
|
|
631
|
+
ctx.metrics.stepStartTime = undefined;
|
|
632
|
+
}
|
|
633
|
+
// ===========================================================================
|
|
634
|
+
// Understudy Action Events
|
|
635
|
+
// ===========================================================================
|
|
636
|
+
static logUnderstudyActionEvent({ actionType, target, args, }) {
|
|
637
|
+
const ctx = loggerContext.getStore();
|
|
638
|
+
if (!ctx)
|
|
639
|
+
return (0, uuid_1.v7)();
|
|
640
|
+
// Set up action context
|
|
641
|
+
ctx.actionId = (0, uuid_1.v7)();
|
|
642
|
+
ctx.actionLabel = actionType
|
|
643
|
+
.toUpperCase()
|
|
644
|
+
.replace("UNDERSTUDY.", "")
|
|
645
|
+
.replace("PAGE.", "");
|
|
646
|
+
ctx.metrics.actionStartTime = Date.now();
|
|
647
|
+
const params = {};
|
|
648
|
+
if (target)
|
|
649
|
+
params.target = target;
|
|
650
|
+
if (args)
|
|
651
|
+
params.args = args;
|
|
652
|
+
ctx.logger.info({
|
|
653
|
+
category: "UnderstudyAction",
|
|
654
|
+
event: "started",
|
|
655
|
+
method: actionType,
|
|
656
|
+
params: Object.keys(params).length > 0 ? params : undefined,
|
|
657
|
+
});
|
|
658
|
+
return ctx.actionId;
|
|
209
659
|
}
|
|
210
|
-
static
|
|
211
|
-
|
|
660
|
+
static logUnderstudyActionCompleted() {
|
|
661
|
+
const ctx = loggerContext.getStore();
|
|
662
|
+
if (!ctx || !ctx.actionId)
|
|
663
|
+
return;
|
|
664
|
+
const durationMs = ctx.metrics.actionStartTime
|
|
665
|
+
? Date.now() - ctx.metrics.actionStartTime
|
|
666
|
+
: 0;
|
|
667
|
+
ctx.logger.info({
|
|
668
|
+
category: "UnderstudyAction",
|
|
669
|
+
event: "completed",
|
|
670
|
+
metrics: { durationMs },
|
|
671
|
+
});
|
|
672
|
+
// Clear action context
|
|
673
|
+
ctx.actionId = null;
|
|
674
|
+
ctx.actionLabel = null;
|
|
675
|
+
ctx.metrics.actionStartTime = undefined;
|
|
676
|
+
}
|
|
677
|
+
// ===========================================================================
|
|
678
|
+
// CDP Events
|
|
679
|
+
// ===========================================================================
|
|
680
|
+
static logCdpEvent(eventType, { method, params, targetId, }, explicitCtx) {
|
|
681
|
+
const ctx = explicitCtx ?? loggerContext.getStore();
|
|
682
|
+
if (!ctx)
|
|
683
|
+
return;
|
|
684
|
+
if (eventType === "call")
|
|
685
|
+
ctx.metrics.cdpEvents++;
|
|
686
|
+
ctx.logger.info({
|
|
687
|
+
category: "CDP",
|
|
688
|
+
event: eventType,
|
|
689
|
+
method,
|
|
690
|
+
params,
|
|
691
|
+
targetId,
|
|
692
|
+
});
|
|
212
693
|
}
|
|
213
|
-
static
|
|
214
|
-
|
|
694
|
+
static logCdpCallEvent(data, ctx) {
|
|
695
|
+
SessionFileLogger.logCdpEvent("call", data, ctx);
|
|
215
696
|
}
|
|
216
|
-
static logCdpMessageEvent(
|
|
217
|
-
|
|
218
|
-
...parentEvent.eventParentIds,
|
|
219
|
-
parentEvent.eventId,
|
|
220
|
-
]);
|
|
697
|
+
static logCdpMessageEvent(data, ctx) {
|
|
698
|
+
SessionFileLogger.logCdpEvent("message", data, ctx);
|
|
221
699
|
}
|
|
222
700
|
// ===========================================================================
|
|
223
701
|
// LLM Events
|
|
224
702
|
// ===========================================================================
|
|
225
|
-
static logLlmRequest({ requestId, model, prompt, }) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
703
|
+
static logLlmRequest({ requestId, model, prompt, }, explicitCtx) {
|
|
704
|
+
const ctx = explicitCtx ?? loggerContext.getStore();
|
|
705
|
+
if (!ctx)
|
|
706
|
+
return;
|
|
707
|
+
// Track LLM requests for task metrics
|
|
708
|
+
ctx.metrics.llmRequests++;
|
|
709
|
+
ctx.logger.info({
|
|
710
|
+
category: "LLM",
|
|
711
|
+
event: "request",
|
|
712
|
+
requestId,
|
|
713
|
+
method: "LLM.request",
|
|
714
|
+
model,
|
|
715
|
+
prompt,
|
|
234
716
|
});
|
|
235
717
|
}
|
|
236
|
-
static logLlmResponse({ requestId, model, output, inputTokens, outputTokens, }) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
718
|
+
static logLlmResponse({ requestId, model, output, inputTokens, outputTokens, }, explicitCtx) {
|
|
719
|
+
const ctx = explicitCtx ?? loggerContext.getStore();
|
|
720
|
+
if (!ctx)
|
|
721
|
+
return;
|
|
722
|
+
// Track tokens for task metrics
|
|
723
|
+
ctx.metrics.llmInputTokens += inputTokens ?? 0;
|
|
724
|
+
ctx.metrics.llmOutputTokens += outputTokens ?? 0;
|
|
725
|
+
ctx.logger.info({
|
|
726
|
+
category: "LLM",
|
|
727
|
+
event: "response",
|
|
728
|
+
requestId,
|
|
729
|
+
method: "LLM.response",
|
|
730
|
+
model,
|
|
731
|
+
output,
|
|
732
|
+
inputTokens,
|
|
733
|
+
outputTokens,
|
|
247
734
|
});
|
|
248
735
|
}
|
|
249
736
|
// ===========================================================================
|
|
@@ -254,57 +741,69 @@ class FlowLogger {
|
|
|
254
741
|
* Returns a no-op middleware when logging is disabled.
|
|
255
742
|
*/
|
|
256
743
|
static createLlmLoggingMiddleware(modelId) {
|
|
744
|
+
// No-op middleware when logging is disabled
|
|
745
|
+
if (!CONFIG_DIR) {
|
|
746
|
+
return {
|
|
747
|
+
wrapGenerate: async ({ doGenerate }) => doGenerate(),
|
|
748
|
+
};
|
|
749
|
+
}
|
|
257
750
|
return {
|
|
258
751
|
wrapGenerate: async ({ doGenerate, params }) => {
|
|
752
|
+
const ctx = SessionFileLogger.getContext();
|
|
753
|
+
// Skip logging overhead if no context (shouldn't happen but be safe)
|
|
754
|
+
if (!ctx) {
|
|
755
|
+
return doGenerate();
|
|
756
|
+
}
|
|
259
757
|
const llmRequestId = (0, uuid_1.v7)();
|
|
260
758
|
const toolCount = Array.isArray(params.tools) ? params.tools.length : 0;
|
|
759
|
+
// Extract prompt preview from last non-system message
|
|
261
760
|
const messages = (params.prompt ?? []);
|
|
262
761
|
const lastMsg = messages.filter((m) => m.role !== "system").pop();
|
|
762
|
+
const extracted = {
|
|
763
|
+
text: undefined,
|
|
764
|
+
extras: [],
|
|
765
|
+
};
|
|
263
766
|
let rolePrefix = lastMsg?.role ?? "?";
|
|
264
|
-
let promptSummary = `(no text) +{${toolCount} tools}`;
|
|
265
767
|
if (lastMsg) {
|
|
266
768
|
if (typeof lastMsg.content === "string") {
|
|
267
|
-
|
|
769
|
+
extracted.text = lastMsg.content;
|
|
268
770
|
}
|
|
269
771
|
else if (Array.isArray(lastMsg.content)) {
|
|
270
|
-
|
|
772
|
+
// Check for tool-result first
|
|
773
|
+
const toolResult = lastMsg.content.find((p) => p.type === "tool-result");
|
|
271
774
|
if (toolResult) {
|
|
272
775
|
rolePrefix = `tool result: ${toolResult.toolName}()`;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
776
|
+
const out = toolResult.output;
|
|
777
|
+
if (out?.type === "json" && out.value) {
|
|
778
|
+
extracted.text = JSON.stringify(out.value).slice(0, 150);
|
|
276
779
|
}
|
|
277
|
-
else if (Array.isArray(
|
|
278
|
-
|
|
279
|
-
content: toolResult.output.value,
|
|
280
|
-
}) ?? "(no text)"} +{${toolCount} tools}`;
|
|
780
|
+
else if (Array.isArray(out?.value)) {
|
|
781
|
+
extractFromContent(out.value, extracted);
|
|
281
782
|
}
|
|
282
783
|
}
|
|
283
784
|
else {
|
|
284
|
-
|
|
285
|
-
"(no text)"} +{${toolCount} tools}`;
|
|
785
|
+
extractFromContent(lastMsg.content, extracted);
|
|
286
786
|
}
|
|
287
787
|
}
|
|
288
|
-
promptSummary = `${rolePrefix}: ${promptSummary}`;
|
|
289
788
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
FlowLogger.logLlmRequest({
|
|
789
|
+
const promptText = extracted.text || "(no text)";
|
|
790
|
+
const promptPreview = `${rolePrefix}: ${promptText} +{${toolCount} tools}`;
|
|
791
|
+
SessionFileLogger.logLlmRequest({
|
|
294
792
|
requestId: llmRequestId,
|
|
295
793
|
model: modelId,
|
|
296
|
-
|
|
297
|
-
|
|
794
|
+
operation: "generateText",
|
|
795
|
+
prompt: promptPreview,
|
|
796
|
+
}, ctx);
|
|
298
797
|
const result = await doGenerate();
|
|
299
|
-
// Extract output
|
|
798
|
+
// Extract output preview
|
|
300
799
|
const res = result;
|
|
301
|
-
let
|
|
302
|
-
if (!
|
|
800
|
+
let outputPreview = res.text || "";
|
|
801
|
+
if (!outputPreview && res.content) {
|
|
303
802
|
if (typeof res.content === "string") {
|
|
304
|
-
|
|
803
|
+
outputPreview = res.content;
|
|
305
804
|
}
|
|
306
805
|
else if (Array.isArray(res.content)) {
|
|
307
|
-
|
|
806
|
+
outputPreview = res.content
|
|
308
807
|
.map((c) => c.text ||
|
|
309
808
|
(c.type === "tool-call"
|
|
310
809
|
? `tool call: ${c.toolName}()`
|
|
@@ -312,159 +811,71 @@ class FlowLogger {
|
|
|
312
811
|
.join(" ");
|
|
313
812
|
}
|
|
314
813
|
}
|
|
315
|
-
if (!
|
|
316
|
-
|
|
814
|
+
if (!outputPreview && res.toolCalls?.length) {
|
|
815
|
+
outputPreview = `[${res.toolCalls.length} tool calls]`;
|
|
317
816
|
}
|
|
318
|
-
|
|
817
|
+
SessionFileLogger.logLlmResponse({
|
|
319
818
|
requestId: llmRequestId,
|
|
320
819
|
model: modelId,
|
|
321
|
-
|
|
820
|
+
operation: "generateText",
|
|
821
|
+
output: outputPreview || "[empty]",
|
|
322
822
|
inputTokens: result.usage?.inputTokens,
|
|
323
823
|
outputTokens: result.usage?.outputTokens,
|
|
324
|
-
});
|
|
824
|
+
}, ctx);
|
|
325
825
|
return result;
|
|
326
826
|
},
|
|
327
827
|
};
|
|
328
828
|
}
|
|
329
829
|
}
|
|
330
|
-
exports.
|
|
331
|
-
/**
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (!result.text && p.text) {
|
|
341
|
-
result.text = p.type === "text" || !p.type ? p.text : undefined;
|
|
342
|
-
}
|
|
343
|
-
// Images - various formats
|
|
344
|
-
if (p.type === "image" || p.type === "image_url") {
|
|
345
|
-
const url = p.image_url?.url;
|
|
346
|
-
if (url?.startsWith("data:"))
|
|
347
|
-
result.extras.push(`${dataToKb(url)}kb image`);
|
|
348
|
-
else if (p.source?.data)
|
|
349
|
-
result.extras.push(`${dataToKb(p.source.data)}kb image`);
|
|
350
|
-
else
|
|
351
|
-
result.extras.push("image");
|
|
352
|
-
}
|
|
353
|
-
else if (p.source?.data) {
|
|
354
|
-
result.extras.push(`${dataToKb(p.source.data)}kb image`);
|
|
355
|
-
}
|
|
356
|
-
else if (p.inlineData?.data) {
|
|
357
|
-
result.extras.push(`${dataToKb(p.inlineData.data)}kb image`);
|
|
830
|
+
exports.SessionFileLogger = SessionFileLogger;
|
|
831
|
+
/**
|
|
832
|
+
* Method decorator for logging understudy actions with automatic start/complete.
|
|
833
|
+
* Logs all arguments automatically. No-op when CONFIG_DIR is empty.
|
|
834
|
+
*/
|
|
835
|
+
function logAction(actionType) {
|
|
836
|
+
return function (originalMethod) {
|
|
837
|
+
// No-op when logging is disabled
|
|
838
|
+
if (!CONFIG_DIR) {
|
|
839
|
+
return originalMethod;
|
|
358
840
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
841
|
+
return async function (...args) {
|
|
842
|
+
SessionFileLogger.logUnderstudyActionEvent({
|
|
843
|
+
actionType,
|
|
844
|
+
args: args.length > 0 ? args : undefined,
|
|
845
|
+
});
|
|
846
|
+
try {
|
|
847
|
+
return await originalMethod.apply(this, args);
|
|
364
848
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
function extractLlmMessageSummary(input, options) {
|
|
371
|
-
const result = {
|
|
372
|
-
text: undefined,
|
|
373
|
-
extras: [...(options?.extras ?? [])],
|
|
849
|
+
finally {
|
|
850
|
+
SessionFileLogger.logUnderstudyActionCompleted();
|
|
851
|
+
}
|
|
852
|
+
};
|
|
374
853
|
};
|
|
375
|
-
if (typeof input.content === "string") {
|
|
376
|
-
result.text = input.content;
|
|
377
|
-
}
|
|
378
|
-
else if (typeof input.text === "string") {
|
|
379
|
-
result.text = input.text;
|
|
380
|
-
}
|
|
381
|
-
else if (Array.isArray(input.parts)) {
|
|
382
|
-
const summary = extractLlmMessageContent(input.parts);
|
|
383
|
-
result.text = summary.text;
|
|
384
|
-
result.extras.push(...summary.extras);
|
|
385
|
-
}
|
|
386
|
-
else if (Array.isArray(input.content)) {
|
|
387
|
-
const summary = extractLlmMessageContent(input.content);
|
|
388
|
-
result.text = summary.text;
|
|
389
|
-
result.extras.push(...summary.extras);
|
|
390
|
-
}
|
|
391
|
-
if (options?.trimInstructionPrefix && result.text) {
|
|
392
|
-
result.text = result.text.replace(/^[Ii]nstruction: /, "");
|
|
393
|
-
}
|
|
394
|
-
const text = result.text;
|
|
395
|
-
if (!text && result.extras.length === 0)
|
|
396
|
-
return undefined;
|
|
397
|
-
let summary = text || "";
|
|
398
|
-
if (result.extras.length > 0) {
|
|
399
|
-
const extrasStr = result.extras.map((e) => `+{${e}}`).join(" ");
|
|
400
|
-
summary = summary ? `${summary} ${extrasStr}` : extrasStr;
|
|
401
|
-
}
|
|
402
|
-
return summary || undefined;
|
|
403
|
-
}
|
|
404
|
-
/**
|
|
405
|
-
* Format a prompt summary from LLM messages for logging.
|
|
406
|
-
* Returns format like: "some text +{5.8kb image} +{schema} +{12 tools}"
|
|
407
|
-
*/
|
|
408
|
-
function extractLlmPromptSummary(messages, options) {
|
|
409
|
-
try {
|
|
410
|
-
const lastUserMsg = messages.filter((m) => m.role === "user").pop();
|
|
411
|
-
if (!lastUserMsg)
|
|
412
|
-
return undefined;
|
|
413
|
-
return extractLlmMessageSummary(lastUserMsg, {
|
|
414
|
-
trimInstructionPrefix: true,
|
|
415
|
-
extras: [
|
|
416
|
-
...(options?.hasSchema ? ["schema"] : []),
|
|
417
|
-
...(options?.toolCount ? [`${options.toolCount} tools`] : []),
|
|
418
|
-
],
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
catch {
|
|
422
|
-
return undefined;
|
|
423
|
-
}
|
|
424
854
|
}
|
|
425
855
|
/**
|
|
426
|
-
*
|
|
427
|
-
*
|
|
856
|
+
* Method decorator for logging Stagehand step events (act, extract, observe).
|
|
857
|
+
* Only adds logging - does NOT wrap with withInstanceLogContext (caller handles that).
|
|
858
|
+
* No-op when CONFIG_DIR is empty.
|
|
428
859
|
*/
|
|
429
|
-
function
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
.
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const items = output
|
|
450
|
-
?.candidates?.[0]?.content?.parts ??
|
|
451
|
-
(Array.isArray(output) ? output : []);
|
|
452
|
-
const summary = items
|
|
453
|
-
.map((item) => {
|
|
454
|
-
const i = item;
|
|
455
|
-
if (i.text)
|
|
456
|
-
return i.text;
|
|
457
|
-
if (i.functionCall?.name)
|
|
458
|
-
return i.functionCall.name;
|
|
459
|
-
if (i.type === "tool_use" && i.name)
|
|
460
|
-
return i.name;
|
|
461
|
-
return i.type ?? "[item]";
|
|
462
|
-
})
|
|
463
|
-
.join(" ");
|
|
464
|
-
return summary;
|
|
465
|
-
}
|
|
466
|
-
catch {
|
|
467
|
-
return "[error]";
|
|
468
|
-
}
|
|
860
|
+
function logStagehandStep(invocation, label) {
|
|
861
|
+
return function (originalMethod) {
|
|
862
|
+
// No-op when logging is disabled
|
|
863
|
+
if (!CONFIG_DIR) {
|
|
864
|
+
return originalMethod;
|
|
865
|
+
}
|
|
866
|
+
return async function (...args) {
|
|
867
|
+
SessionFileLogger.logStagehandStepEvent({
|
|
868
|
+
invocation,
|
|
869
|
+
args: args.length > 0 ? args : undefined,
|
|
870
|
+
label,
|
|
871
|
+
});
|
|
872
|
+
try {
|
|
873
|
+
return await originalMethod.apply(this, args);
|
|
874
|
+
}
|
|
875
|
+
finally {
|
|
876
|
+
SessionFileLogger.logStagehandStepCompleted();
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
};
|
|
469
880
|
}
|
|
470
881
|
//# sourceMappingURL=flowLogger.js.map
|