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