@browserbasehq/orca 3.2.0-preview.1 → 3.2.0-preview.2
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 +1 -0
- package/dist/cjs/lib/utils.js +4 -0
- package/dist/cjs/lib/utils.js.map +1 -1
- package/dist/cjs/lib/v3/agent/AnthropicCUAClient.js +4 -6
- package/dist/cjs/lib/v3/agent/AnthropicCUAClient.js.map +1 -1
- package/dist/cjs/lib/v3/agent/GoogleCUAClient.js +4 -6
- package/dist/cjs/lib/v3/agent/GoogleCUAClient.js.map +1 -1
- package/dist/cjs/lib/v3/agent/OpenAICUAClient.js +4 -6
- package/dist/cjs/lib/v3/agent/OpenAICUAClient.js.map +1 -1
- package/dist/cjs/lib/v3/agent/prompts/agentSystemPrompt.d.ts +2 -0
- package/dist/cjs/lib/v3/agent/prompts/agentSystemPrompt.js +2 -2
- package/dist/cjs/lib/v3/agent/prompts/agentSystemPrompt.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/{search.js → braveSearch.js} +1 -1
- package/dist/cjs/lib/v3/agent/tools/braveSearch.js.map +1 -0
- package/dist/cjs/lib/v3/agent/tools/browserbaseSearch.d.ts +13 -0
- package/dist/cjs/lib/v3/agent/tools/browserbaseSearch.js +70 -0
- package/dist/cjs/lib/v3/agent/tools/browserbaseSearch.js.map +1 -0
- package/dist/cjs/lib/v3/agent/tools/index.d.ts +14 -3
- package/dist/cjs/lib/v3/agent/tools/index.js +7 -3
- package/dist/cjs/lib/v3/agent/tools/index.js.map +1 -1
- package/dist/cjs/lib/v3/eventStore.d.ts +41 -0
- package/dist/cjs/lib/v3/eventStore.js +375 -0
- package/dist/cjs/lib/v3/eventStore.js.map +1 -0
- package/dist/cjs/lib/v3/flowLogger.d.ts +62 -103
- package/dist/cjs/lib/v3/flowLogger.js +362 -773
- package/dist/cjs/lib/v3/flowLogger.js.map +1 -1
- package/dist/cjs/lib/v3/handlers/handlerUtils/actHandlerUtils.js +21 -33
- package/dist/cjs/lib/v3/handlers/handlerUtils/actHandlerUtils.js.map +1 -1
- package/dist/cjs/lib/v3/handlers/v3AgentHandler.d.ts +0 -4
- package/dist/cjs/lib/v3/handlers/v3AgentHandler.js +14 -34
- package/dist/cjs/lib/v3/handlers/v3AgentHandler.js.map +1 -1
- package/dist/cjs/lib/v3/handlers/v3CuaAgentHandler.js +10 -12
- package/dist/cjs/lib/v3/handlers/v3CuaAgentHandler.js.map +1 -1
- package/dist/cjs/lib/v3/llm/aisdk.js +10 -16
- package/dist/cjs/lib/v3/llm/aisdk.js.map +1 -1
- package/dist/cjs/lib/v3/types/public/agent.d.ts +16 -2
- package/dist/cjs/lib/v3/types/public/agent.js.map +1 -1
- package/dist/cjs/lib/v3/types/public/options.d.ts +5 -0
- package/dist/cjs/lib/v3/types/public/options.js.map +1 -1
- package/dist/cjs/lib/v3/understudy/cdp.d.ts +3 -12
- package/dist/cjs/lib/v3/understudy/cdp.js +83 -10
- package/dist/cjs/lib/v3/understudy/cdp.js.map +1 -1
- package/dist/cjs/lib/v3/understudy/page.js +32 -17
- package/dist/cjs/lib/v3/understudy/page.js.map +1 -1
- package/dist/cjs/lib/v3/v3.d.ts +10 -0
- package/dist/cjs/lib/v3/v3.js +181 -157
- package/dist/cjs/lib/v3/v3.js.map +1 -1
- package/dist/cjs/tests/unit/public-api/public-types.test.js.map +1 -1
- package/dist/esm/lib/utils.d.ts +1 -0
- package/dist/esm/lib/utils.js +3 -0
- package/dist/esm/lib/utils.js.map +1 -1
- package/dist/esm/lib/v3/agent/AnthropicCUAClient.js +5 -7
- package/dist/esm/lib/v3/agent/AnthropicCUAClient.js.map +1 -1
- package/dist/esm/lib/v3/agent/GoogleCUAClient.js +5 -7
- package/dist/esm/lib/v3/agent/GoogleCUAClient.js.map +1 -1
- package/dist/esm/lib/v3/agent/OpenAICUAClient.js +5 -7
- package/dist/esm/lib/v3/agent/OpenAICUAClient.js.map +1 -1
- package/dist/esm/lib/v3/agent/prompts/agentSystemPrompt.d.ts +2 -0
- package/dist/esm/lib/v3/agent/prompts/agentSystemPrompt.js +2 -2
- package/dist/esm/lib/v3/agent/prompts/agentSystemPrompt.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/{search.js → braveSearch.js} +1 -1
- package/dist/esm/lib/v3/agent/tools/braveSearch.js.map +1 -0
- package/dist/esm/lib/v3/agent/tools/browserbaseSearch.d.ts +13 -0
- package/dist/esm/lib/v3/agent/tools/browserbaseSearch.js +66 -0
- package/dist/esm/lib/v3/agent/tools/browserbaseSearch.js.map +1 -0
- package/dist/esm/lib/v3/agent/tools/index.d.ts +14 -3
- package/dist/esm/lib/v3/agent/tools/index.js +7 -3
- package/dist/esm/lib/v3/agent/tools/index.js.map +1 -1
- package/dist/esm/lib/v3/eventStore.d.ts +41 -0
- package/dist/esm/lib/v3/eventStore.js +363 -0
- package/dist/esm/lib/v3/eventStore.js.map +1 -0
- package/dist/esm/lib/v3/flowLogger.d.ts +62 -103
- package/dist/esm/lib/v3/flowLogger.js +356 -762
- package/dist/esm/lib/v3/flowLogger.js.map +1 -1
- package/dist/esm/lib/v3/handlers/handlerUtils/actHandlerUtils.js +22 -34
- package/dist/esm/lib/v3/handlers/handlerUtils/actHandlerUtils.js.map +1 -1
- package/dist/esm/lib/v3/handlers/v3AgentHandler.d.ts +0 -4
- package/dist/esm/lib/v3/handlers/v3AgentHandler.js +16 -36
- package/dist/esm/lib/v3/handlers/v3AgentHandler.js.map +1 -1
- package/dist/esm/lib/v3/handlers/v3CuaAgentHandler.js +11 -13
- package/dist/esm/lib/v3/handlers/v3CuaAgentHandler.js.map +1 -1
- package/dist/esm/lib/v3/llm/aisdk.js +11 -17
- package/dist/esm/lib/v3/llm/aisdk.js.map +1 -1
- package/dist/esm/lib/v3/types/public/agent.d.ts +16 -2
- package/dist/esm/lib/v3/types/public/agent.js.map +1 -1
- package/dist/esm/lib/v3/types/public/options.d.ts +5 -0
- package/dist/esm/lib/v3/types/public/options.js.map +1 -1
- package/dist/esm/lib/v3/understudy/cdp.d.ts +3 -12
- package/dist/esm/lib/v3/understudy/cdp.js +83 -10
- package/dist/esm/lib/v3/understudy/cdp.js.map +1 -1
- package/dist/esm/lib/v3/understudy/page.js +33 -18
- package/dist/esm/lib/v3/understudy/page.js.map +1 -1
- package/dist/esm/lib/v3/v3.d.ts +10 -0
- package/dist/esm/lib/v3/v3.js +182 -158
- package/dist/esm/lib/v3/v3.js.map +1 -1
- package/dist/esm/tests/unit/public-api/public-types.test.js.map +1 -1
- package/package.json +1 -3
- package/dist/cjs/lib/v3/agent/tools/search.js.map +0 -1
- package/dist/cjs/tests/unit/rerender-missing-shadows.test.d.ts +0 -1
- package/dist/cjs/tests/unit/rerender-missing-shadows.test.js +0 -209
- package/dist/cjs/tests/unit/rerender-missing-shadows.test.js.map +0 -1
- package/dist/esm/lib/v3/agent/tools/search.js.map +0 -1
- package/dist/esm/tests/unit/rerender-missing-shadows.test.d.ts +0 -1
- package/dist/esm/tests/unit/rerender-missing-shadows.test.js +0 -207
- package/dist/esm/tests/unit/rerender-missing-shadows.test.js.map +0 -1
- /package/dist/cjs/lib/v3/agent/tools/{search.d.ts → braveSearch.d.ts} +0 -0
- /package/dist/esm/lib/v3/agent/tools/{search.d.ts → braveSearch.d.ts} +0 -0
|
@@ -1,736 +1,249 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
7
|
-
exports.
|
|
8
|
-
exports.
|
|
9
|
-
exports.
|
|
10
|
-
exports.formatCuaResponsePreview = formatCuaResponsePreview;
|
|
11
|
-
exports.logAction = logAction;
|
|
12
|
-
exports.logStagehandStep = logStagehandStep;
|
|
3
|
+
exports.FlowLogger = exports.FlowEvent = void 0;
|
|
4
|
+
exports.extractLlmPromptSummary = extractLlmPromptSummary;
|
|
5
|
+
exports.extractLlmCuaPromptSummary = extractLlmCuaPromptSummary;
|
|
6
|
+
exports.extractLlmCuaResponseSummary = extractLlmCuaResponseSummary;
|
|
13
7
|
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");
|
|
16
8
|
const uuid_1 = require("uuid");
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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}`;
|
|
9
|
+
class FlowEvent {
|
|
10
|
+
static createEventId(eventIdSuffix) {
|
|
11
|
+
const rawEventId = (0, uuid_1.v7)();
|
|
12
|
+
return `${rawEventId.slice(0, -1)}${eventIdSuffix || "0"}`;
|
|
13
|
+
}
|
|
14
|
+
// base required fields for all events:
|
|
15
|
+
eventType;
|
|
16
|
+
eventId;
|
|
17
|
+
eventParentIds;
|
|
18
|
+
createdAt;
|
|
19
|
+
sessionId;
|
|
20
|
+
data; // event payload (e.g. params, action, result, error, etc.)
|
|
21
|
+
constructor(input) {
|
|
22
|
+
if (!input.sessionId) {
|
|
23
|
+
throw new Error("FlowEvent.sessionId is required.");
|
|
186
24
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
: "";
|
|
192
|
-
const outputStr = event.output ? " " + String(event.output) : "";
|
|
193
|
-
details = `${event.model} ↳${tokenStr}${outputStr}`;
|
|
25
|
+
if (input.eventId &&
|
|
26
|
+
input.eventIdSuffix &&
|
|
27
|
+
!input.eventId.endsWith(input.eventIdSuffix)) {
|
|
28
|
+
throw new Error("FlowEvent cannot take both eventId and eventIdSuffix.");
|
|
194
29
|
}
|
|
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 ?? {};
|
|
195
39
|
}
|
|
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);
|
|
203
40
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (event.method?.endsWith(".enable") || event.method === "enable")
|
|
209
|
-
return true;
|
|
210
|
-
return event.event === "message" && NOISY_CDP_EVENTS.has(event.method);
|
|
211
|
-
}
|
|
212
|
-
// =============================================================================
|
|
213
|
-
// Stream Creation
|
|
214
|
-
// =============================================================================
|
|
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
|
-
});
|
|
41
|
+
exports.FlowEvent = FlowEvent;
|
|
42
|
+
const loggerContext = new node_async_hooks_1.AsyncLocalStorage();
|
|
43
|
+
function dataToKb(data) {
|
|
44
|
+
return ((data.length * 0.75) / 1024).toFixed(1);
|
|
250
45
|
}
|
|
251
46
|
// =============================================================================
|
|
252
|
-
//
|
|
47
|
+
// Flow Logger - Main API
|
|
253
48
|
// =============================================================================
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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: [],
|
|
49
|
+
class FlowLogger {
|
|
50
|
+
static cloneContext(ctx) {
|
|
51
|
+
return {
|
|
52
|
+
...ctx,
|
|
53
|
+
parentEvents: ctx.parentEvents.map((event) => ({
|
|
54
|
+
...event,
|
|
55
|
+
eventParentIds: [...event.eventParentIds],
|
|
56
|
+
})),
|
|
315
57
|
};
|
|
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);
|
|
334
58
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if (
|
|
352
|
-
|
|
353
|
-
const result = {
|
|
354
|
-
text: undefined,
|
|
355
|
-
extras: [],
|
|
356
|
-
};
|
|
357
|
-
if (typeof lastMsg.content === "string") {
|
|
358
|
-
result.text = lastMsg.content;
|
|
59
|
+
static emit(event) {
|
|
60
|
+
const ctx = FlowLogger.currentContext;
|
|
61
|
+
const emittedEvent = new FlowEvent({
|
|
62
|
+
...event,
|
|
63
|
+
eventParentIds: event.eventParentIds ??
|
|
64
|
+
ctx.parentEvents.map((parent) => parent.eventId),
|
|
65
|
+
sessionId: ctx.sessionId,
|
|
66
|
+
});
|
|
67
|
+
ctx.eventBus.emit(emittedEvent.eventType, emittedEvent);
|
|
68
|
+
return emittedEvent;
|
|
69
|
+
}
|
|
70
|
+
static async runWithAutoStatusEventLogging(options, originalMethod) {
|
|
71
|
+
const ctx = FlowLogger.currentContext;
|
|
72
|
+
const { data, eventParentIds, eventType, eventIdSuffix } = options;
|
|
73
|
+
let caughtError = null;
|
|
74
|
+
// if eventParentIds is explicitly [], this is a root event, clear the parent events in context
|
|
75
|
+
if (eventParentIds && eventParentIds.length === 0) {
|
|
76
|
+
ctx.parentEvents = [];
|
|
359
77
|
}
|
|
360
|
-
|
|
361
|
-
|
|
78
|
+
const startedEvent = FlowLogger.emit({
|
|
79
|
+
eventIdSuffix,
|
|
80
|
+
eventType,
|
|
81
|
+
data,
|
|
82
|
+
eventParentIds,
|
|
83
|
+
});
|
|
84
|
+
ctx.parentEvents.push(startedEvent);
|
|
85
|
+
try {
|
|
86
|
+
return await originalMethod();
|
|
362
87
|
}
|
|
363
|
-
|
|
364
|
-
|
|
88
|
+
catch (error) {
|
|
89
|
+
caughtError = error;
|
|
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;
|
|
365
100
|
}
|
|
366
|
-
|
|
367
|
-
|
|
101
|
+
finally {
|
|
102
|
+
const parentEvent = ctx.parentEvents.pop();
|
|
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
|
+
}
|
|
368
116
|
}
|
|
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);
|
|
395
117
|
}
|
|
396
|
-
catch {
|
|
397
|
-
return "[error]";
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
// =============================================================================
|
|
401
|
-
// SessionFileLogger - Main API
|
|
402
|
-
// =============================================================================
|
|
403
|
-
class SessionFileLogger {
|
|
404
118
|
/**
|
|
405
119
|
* Initialize a new logging context. Call this at the start of a session.
|
|
406
|
-
* If BROWSERBASE_CONFIG_DIR is not set, logging is disabled.
|
|
407
120
|
*/
|
|
408
|
-
static init(sessionId,
|
|
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)
|
|
121
|
+
static init(sessionId, eventBus) {
|
|
414
122
|
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
|
-
},
|
|
422
123
|
sessionId,
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
},
|
|
124
|
+
eventBus,
|
|
125
|
+
parentEvents: [],
|
|
441
126
|
};
|
|
442
|
-
// Store init promise for awaiting in log methods
|
|
443
|
-
ctx.initPromise = SessionFileLogger.initAsync(ctx, v3Options);
|
|
444
127
|
loggerContext.enterWith(ctx);
|
|
128
|
+
return ctx;
|
|
445
129
|
}
|
|
446
|
-
static async
|
|
447
|
-
|
|
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();
|
|
130
|
+
static async close(context) {
|
|
131
|
+
const ctx = context ?? loggerContext.getStore() ?? null;
|
|
510
132
|
if (!ctx)
|
|
511
133
|
return;
|
|
512
|
-
|
|
513
|
-
SessionFileLogger.logAgentTaskCompleted();
|
|
514
|
-
await Promise.all(Object.values(ctx.fileStreams)
|
|
515
|
-
.filter(Boolean)
|
|
516
|
-
.map((s) => new Promise((r) => s.end(r)))).catch(() => { });
|
|
134
|
+
ctx.parentEvents = [];
|
|
517
135
|
}
|
|
518
|
-
static get
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* Get the current logger context object.
|
|
526
|
-
*/
|
|
527
|
-
static getContext() {
|
|
528
|
-
return loggerContext.getStore() ?? null;
|
|
136
|
+
static get currentContext() {
|
|
137
|
+
const ctx = loggerContext.getStore() ?? null;
|
|
138
|
+
if (!ctx) {
|
|
139
|
+
throw new Error("FlowLogger context is missing.");
|
|
140
|
+
}
|
|
141
|
+
return ctx;
|
|
529
142
|
}
|
|
530
|
-
//
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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,
|
|
143
|
+
// decorator method to wrap a class method with automatic started/completed/error events
|
|
144
|
+
static wrapWithLogging(options) {
|
|
145
|
+
return function (originalMethod) {
|
|
146
|
+
const wrappedMethod = async function (...args) {
|
|
147
|
+
return await FlowLogger.runWithLogging(options, (...boundArgs) => originalMethod.apply(this, boundArgs), args);
|
|
148
|
+
};
|
|
149
|
+
return wrappedMethod;
|
|
553
150
|
};
|
|
554
|
-
ctx.logger.info({
|
|
555
|
-
category: "AgentTask",
|
|
556
|
-
event: "started",
|
|
557
|
-
method: invocation,
|
|
558
|
-
params: args,
|
|
559
|
-
});
|
|
560
151
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
},
|
|
152
|
+
static runWithLogging(options, originalMethod, params) {
|
|
153
|
+
const eventData = {
|
|
154
|
+
...(options.data ?? {}),
|
|
155
|
+
params: [...params],
|
|
580
156
|
};
|
|
581
|
-
|
|
582
|
-
|
|
157
|
+
const execute = () => FlowLogger.runWithAutoStatusEventLogging({
|
|
158
|
+
...options,
|
|
159
|
+
data: eventData,
|
|
160
|
+
}, () => originalMethod(...params));
|
|
161
|
+
if (!options.context && !(loggerContext.getStore() ?? null)) {
|
|
162
|
+
return originalMethod(...params);
|
|
583
163
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
ctx.stepId = null;
|
|
588
|
-
ctx.stepLabel = null;
|
|
589
|
-
ctx.actionId = null;
|
|
590
|
-
ctx.actionLabel = null;
|
|
591
|
-
ctx.metrics.taskStartTime = undefined;
|
|
592
|
-
}
|
|
593
|
-
// ===========================================================================
|
|
594
|
-
// Stagehand Step Events
|
|
595
|
-
// ===========================================================================
|
|
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;
|
|
659
|
-
}
|
|
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;
|
|
164
|
+
return options.context
|
|
165
|
+
? loggerContext.run(FlowLogger.cloneContext(options.context), execute)
|
|
166
|
+
: execute();
|
|
676
167
|
}
|
|
677
168
|
// ===========================================================================
|
|
678
169
|
// CDP Events
|
|
679
170
|
// ===========================================================================
|
|
680
|
-
static
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
171
|
+
static NOISY_CDP_EVENTS = new Set([
|
|
172
|
+
"Target.targetInfoChanged",
|
|
173
|
+
"Runtime.executionContextCreated",
|
|
174
|
+
"Runtime.executionContextDestroyed",
|
|
175
|
+
"Runtime.executionContextsCleared",
|
|
176
|
+
"Page.lifecycleEvent",
|
|
177
|
+
"Network.dataReceived",
|
|
178
|
+
"Network.loadingFinished",
|
|
179
|
+
"Network.requestWillBeSentExtraInfo",
|
|
180
|
+
"Network.responseReceivedExtraInfo",
|
|
181
|
+
"Network.requestWillBeSent",
|
|
182
|
+
"Network.responseReceived",
|
|
183
|
+
]);
|
|
184
|
+
static logCdpEvent(context, eventType, { method, params, result, error, targetId, }, eventParentIds) {
|
|
185
|
+
if (method.endsWith(".enable") || method === "enable") {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
if (eventType === "message" && FlowLogger.NOISY_CDP_EVENTS.has(method)) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
return loggerContext.run(FlowLogger.cloneContext(context), () => FlowLogger.emit({
|
|
192
|
+
eventIdSuffix: "6",
|
|
193
|
+
eventType: eventType === "call"
|
|
194
|
+
? "CdpCallEvent"
|
|
195
|
+
: eventType === "response"
|
|
196
|
+
? "CdpResponseEvent"
|
|
197
|
+
: eventType === "responseError"
|
|
198
|
+
? "CdpResponseErrorEvent"
|
|
199
|
+
: "CdpMessageEvent",
|
|
200
|
+
eventParentIds,
|
|
201
|
+
data: {
|
|
202
|
+
method,
|
|
203
|
+
params,
|
|
204
|
+
result,
|
|
205
|
+
error,
|
|
206
|
+
targetId,
|
|
207
|
+
},
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
static logCdpCallEvent(context, data) {
|
|
211
|
+
return FlowLogger.logCdpEvent(context, "call", data);
|
|
693
212
|
}
|
|
694
|
-
static
|
|
695
|
-
|
|
213
|
+
static logCdpResponseEvent(context, parentEvent, data) {
|
|
214
|
+
FlowLogger.logCdpEvent(context, data.error ? "responseError" : "response", data, [...parentEvent.eventParentIds, parentEvent.eventId]);
|
|
696
215
|
}
|
|
697
|
-
static logCdpMessageEvent(
|
|
698
|
-
|
|
216
|
+
static logCdpMessageEvent(context, parentEvent, data) {
|
|
217
|
+
FlowLogger.logCdpEvent(context, "message", data, [
|
|
218
|
+
...parentEvent.eventParentIds,
|
|
219
|
+
parentEvent.eventId,
|
|
220
|
+
]);
|
|
699
221
|
}
|
|
700
222
|
// ===========================================================================
|
|
701
223
|
// LLM Events
|
|
702
224
|
// ===========================================================================
|
|
703
|
-
static logLlmRequest({ requestId, model, prompt, }
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
requestId,
|
|
713
|
-
method: "LLM.request",
|
|
714
|
-
model,
|
|
715
|
-
prompt,
|
|
225
|
+
static logLlmRequest({ requestId, model, prompt, }) {
|
|
226
|
+
FlowLogger.emit({
|
|
227
|
+
eventIdSuffix: "7",
|
|
228
|
+
eventType: "LlmRequestEvent",
|
|
229
|
+
data: {
|
|
230
|
+
requestId,
|
|
231
|
+
model,
|
|
232
|
+
prompt,
|
|
233
|
+
},
|
|
716
234
|
});
|
|
717
235
|
}
|
|
718
|
-
static logLlmResponse({ requestId, model, output, inputTokens, outputTokens, }
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
method: "LLM.response",
|
|
730
|
-
model,
|
|
731
|
-
output,
|
|
732
|
-
inputTokens,
|
|
733
|
-
outputTokens,
|
|
236
|
+
static logLlmResponse({ requestId, model, output, inputTokens, outputTokens, }) {
|
|
237
|
+
FlowLogger.emit({
|
|
238
|
+
eventIdSuffix: "7",
|
|
239
|
+
eventType: "LlmResponseEvent",
|
|
240
|
+
data: {
|
|
241
|
+
requestId,
|
|
242
|
+
model,
|
|
243
|
+
output,
|
|
244
|
+
inputTokens,
|
|
245
|
+
outputTokens,
|
|
246
|
+
},
|
|
734
247
|
});
|
|
735
248
|
}
|
|
736
249
|
// ===========================================================================
|
|
@@ -741,69 +254,57 @@ class SessionFileLogger {
|
|
|
741
254
|
* Returns a no-op middleware when logging is disabled.
|
|
742
255
|
*/
|
|
743
256
|
static createLlmLoggingMiddleware(modelId) {
|
|
744
|
-
// No-op middleware when logging is disabled
|
|
745
|
-
if (!CONFIG_DIR) {
|
|
746
|
-
return {
|
|
747
|
-
wrapGenerate: async ({ doGenerate }) => doGenerate(),
|
|
748
|
-
};
|
|
749
|
-
}
|
|
750
257
|
return {
|
|
751
258
|
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
|
-
}
|
|
757
259
|
const llmRequestId = (0, uuid_1.v7)();
|
|
758
260
|
const toolCount = Array.isArray(params.tools) ? params.tools.length : 0;
|
|
759
|
-
// Extract prompt preview from last non-system message
|
|
760
261
|
const messages = (params.prompt ?? []);
|
|
761
262
|
const lastMsg = messages.filter((m) => m.role !== "system").pop();
|
|
762
|
-
const extracted = {
|
|
763
|
-
text: undefined,
|
|
764
|
-
extras: [],
|
|
765
|
-
};
|
|
766
263
|
let rolePrefix = lastMsg?.role ?? "?";
|
|
264
|
+
let promptSummary = `(no text) +{${toolCount} tools}`;
|
|
767
265
|
if (lastMsg) {
|
|
768
266
|
if (typeof lastMsg.content === "string") {
|
|
769
|
-
|
|
267
|
+
promptSummary = `${lastMsg.content} +{${toolCount} tools}`;
|
|
770
268
|
}
|
|
771
269
|
else if (Array.isArray(lastMsg.content)) {
|
|
772
|
-
|
|
773
|
-
const toolResult = lastMsg.content.find((p) => p.type === "tool-result");
|
|
270
|
+
const toolResult = lastMsg.content.find((part) => part.type === "tool-result");
|
|
774
271
|
if (toolResult) {
|
|
775
272
|
rolePrefix = `tool result: ${toolResult.toolName}()`;
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
273
|
+
if (toolResult.output?.type === "json" &&
|
|
274
|
+
toolResult.output.value) {
|
|
275
|
+
promptSummary = `${JSON.stringify(toolResult.output.value)} +{${toolCount} tools}`;
|
|
779
276
|
}
|
|
780
|
-
else if (Array.isArray(
|
|
781
|
-
|
|
277
|
+
else if (Array.isArray(toolResult.output?.value)) {
|
|
278
|
+
promptSummary = `${extractLlmMessageSummary({
|
|
279
|
+
content: toolResult.output.value,
|
|
280
|
+
}) ?? "(no text)"} +{${toolCount} tools}`;
|
|
782
281
|
}
|
|
783
282
|
}
|
|
784
283
|
else {
|
|
785
|
-
|
|
284
|
+
promptSummary = `${extractLlmMessageSummary({ content: lastMsg.content }) ??
|
|
285
|
+
"(no text)"} +{${toolCount} tools}`;
|
|
786
286
|
}
|
|
787
287
|
}
|
|
288
|
+
promptSummary = `${rolePrefix}: ${promptSummary}`;
|
|
788
289
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
290
|
+
else {
|
|
291
|
+
promptSummary = `?: ${promptSummary}`;
|
|
292
|
+
}
|
|
293
|
+
FlowLogger.logLlmRequest({
|
|
792
294
|
requestId: llmRequestId,
|
|
793
295
|
model: modelId,
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
}, ctx);
|
|
296
|
+
prompt: promptSummary,
|
|
297
|
+
});
|
|
797
298
|
const result = await doGenerate();
|
|
798
|
-
// Extract output
|
|
299
|
+
// Extract output summary
|
|
799
300
|
const res = result;
|
|
800
|
-
let
|
|
801
|
-
if (!
|
|
301
|
+
let outputSummary = res.text || "";
|
|
302
|
+
if (!outputSummary && res.content) {
|
|
802
303
|
if (typeof res.content === "string") {
|
|
803
|
-
|
|
304
|
+
outputSummary = res.content;
|
|
804
305
|
}
|
|
805
306
|
else if (Array.isArray(res.content)) {
|
|
806
|
-
|
|
307
|
+
outputSummary = res.content
|
|
807
308
|
.map((c) => c.text ||
|
|
808
309
|
(c.type === "tool-call"
|
|
809
310
|
? `tool call: ${c.toolName}()`
|
|
@@ -811,71 +312,159 @@ class SessionFileLogger {
|
|
|
811
312
|
.join(" ");
|
|
812
313
|
}
|
|
813
314
|
}
|
|
814
|
-
if (!
|
|
815
|
-
|
|
315
|
+
if (!outputSummary && res.toolCalls?.length) {
|
|
316
|
+
outputSummary = `[${res.toolCalls.length} tool calls]`;
|
|
816
317
|
}
|
|
817
|
-
|
|
318
|
+
FlowLogger.logLlmResponse({
|
|
818
319
|
requestId: llmRequestId,
|
|
819
320
|
model: modelId,
|
|
820
|
-
|
|
821
|
-
output: outputPreview || "[empty]",
|
|
321
|
+
output: outputSummary || "[empty]",
|
|
822
322
|
inputTokens: result.usage?.inputTokens,
|
|
823
323
|
outputTokens: result.usage?.outputTokens,
|
|
824
|
-
}
|
|
324
|
+
});
|
|
825
325
|
return result;
|
|
826
326
|
},
|
|
827
327
|
};
|
|
828
328
|
}
|
|
829
329
|
}
|
|
830
|
-
exports.
|
|
831
|
-
/**
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
330
|
+
exports.FlowLogger = FlowLogger;
|
|
331
|
+
/** Extract text and image info from a content array (handles nested tool_result) */
|
|
332
|
+
function extractLlmMessageContent(content) {
|
|
333
|
+
const result = {
|
|
334
|
+
text: undefined,
|
|
335
|
+
extras: [],
|
|
336
|
+
};
|
|
337
|
+
for (const part of content) {
|
|
338
|
+
const p = part;
|
|
339
|
+
// Text
|
|
340
|
+
if (!result.text && p.text) {
|
|
341
|
+
result.text = p.type === "text" || !p.type ? p.text : undefined;
|
|
840
342
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
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`);
|
|
358
|
+
}
|
|
359
|
+
// Recurse into tool_result content
|
|
360
|
+
if (p.type === "tool_result" && Array.isArray(p.content)) {
|
|
361
|
+
const nested = extractLlmMessageContent(p.content);
|
|
362
|
+
if (!result.text && nested.text) {
|
|
363
|
+
result.text = nested.text;
|
|
851
364
|
}
|
|
852
|
-
|
|
365
|
+
result.extras.push(...nested.extras);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
function extractLlmMessageSummary(input, options) {
|
|
371
|
+
const result = {
|
|
372
|
+
text: undefined,
|
|
373
|
+
extras: [...(options?.extras ?? [])],
|
|
853
374
|
};
|
|
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;
|
|
854
403
|
}
|
|
855
404
|
/**
|
|
856
|
-
*
|
|
857
|
-
*
|
|
858
|
-
* No-op when CONFIG_DIR is empty.
|
|
405
|
+
* Format a prompt summary from LLM messages for logging.
|
|
406
|
+
* Returns format like: "some text +{5.8kb image} +{schema} +{12 tools}"
|
|
859
407
|
*/
|
|
860
|
-
function
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
if (!
|
|
864
|
-
return
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Extract a text summary from CUA-style messages.
|
|
427
|
+
* Accepts various message formats (Anthropic, OpenAI, Google).
|
|
428
|
+
*/
|
|
429
|
+
function extractLlmCuaPromptSummary(messages) {
|
|
430
|
+
try {
|
|
431
|
+
const lastMsg = messages
|
|
432
|
+
.filter((m) => {
|
|
433
|
+
const msg = m;
|
|
434
|
+
return msg.role === "user" || msg.type === "tool_result";
|
|
435
|
+
})
|
|
436
|
+
.pop();
|
|
437
|
+
if (!lastMsg)
|
|
438
|
+
return undefined;
|
|
439
|
+
return extractLlmMessageSummary(lastMsg);
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/** Format a CUA response summary for logging */
|
|
446
|
+
function extractLlmCuaResponseSummary(output) {
|
|
447
|
+
try {
|
|
448
|
+
// Handle Google format or array
|
|
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
|
+
}
|
|
880
469
|
}
|
|
881
470
|
//# sourceMappingURL=flowLogger.js.map
|