@browserbasehq/orca 3.2.0-preview.2 → 3.2.0-preview.4
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/v3/agent/AnthropicCUAClient.js +5 -5
- package/dist/cjs/lib/v3/agent/AnthropicCUAClient.js.map +1 -1
- package/dist/cjs/lib/v3/agent/GoogleCUAClient.js +5 -5
- package/dist/cjs/lib/v3/agent/GoogleCUAClient.js.map +1 -1
- package/dist/cjs/lib/v3/agent/OpenAICUAClient.js +5 -5
- package/dist/cjs/lib/v3/agent/OpenAICUAClient.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/act.js +1 -10
- package/dist/cjs/lib/v3/agent/tools/act.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/ariaTree.js +1 -12
- package/dist/cjs/lib/v3/agent/tools/ariaTree.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/braveSearch.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/browserbaseSearch.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/click.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/clickAndHold.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/dragAndDrop.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/extract.js +1 -10
- package/dist/cjs/lib/v3/agent/tools/extract.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/fillFormVision.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/fillform.js +1 -10
- package/dist/cjs/lib/v3/agent/tools/fillform.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/index.d.ts +2 -2
- package/dist/cjs/lib/v3/agent/tools/index.js +53 -5
- package/dist/cjs/lib/v3/agent/tools/index.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/keys.d.ts +1 -1
- package/dist/cjs/lib/v3/agent/tools/keys.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/type.js.map +1 -1
- package/dist/cjs/lib/v3/api.js +9 -2
- package/dist/cjs/lib/v3/api.js.map +1 -1
- package/dist/cjs/lib/v3/flowlogger/EventEmitter.d.ts +7 -0
- package/dist/cjs/lib/v3/flowlogger/EventEmitter.js +30 -0
- package/dist/cjs/lib/v3/flowlogger/EventEmitter.js.map +1 -0
- package/dist/cjs/lib/v3/flowlogger/EventSink.d.ts +44 -0
- package/dist/cjs/lib/v3/flowlogger/EventSink.js +217 -0
- package/dist/cjs/lib/v3/flowlogger/EventSink.js.map +1 -0
- package/dist/cjs/lib/v3/flowlogger/EventStore.d.ts +26 -0
- package/dist/cjs/lib/v3/flowlogger/EventStore.js +135 -0
- package/dist/cjs/lib/v3/flowlogger/EventStore.js.map +1 -0
- package/dist/{esm/lib/v3/flowLogger.d.ts → cjs/lib/v3/flowlogger/FlowLogger.d.ts} +32 -31
- package/dist/cjs/lib/v3/flowlogger/FlowLogger.js +591 -0
- package/dist/cjs/lib/v3/flowlogger/FlowLogger.js.map +1 -0
- package/dist/cjs/lib/v3/flowlogger/prettify.d.ts +6 -0
- package/dist/cjs/lib/v3/flowlogger/prettify.js +395 -0
- package/dist/cjs/lib/v3/flowlogger/prettify.js.map +1 -0
- package/dist/cjs/lib/v3/handlers/handlerUtils/actHandlerUtils.js +26 -28
- package/dist/cjs/lib/v3/handlers/handlerUtils/actHandlerUtils.js.map +1 -1
- package/dist/cjs/lib/v3/handlers/v3AgentHandler.js +2 -2
- package/dist/cjs/lib/v3/handlers/v3AgentHandler.js.map +1 -1
- package/dist/cjs/lib/v3/handlers/v3CuaAgentHandler.js +3 -5
- package/dist/cjs/lib/v3/handlers/v3CuaAgentHandler.js.map +1 -1
- package/dist/cjs/lib/v3/llm/aisdk.js +9 -9
- package/dist/cjs/lib/v3/llm/aisdk.js.map +1 -1
- package/dist/cjs/lib/v3/types/public/options.d.ts +2 -0
- package/dist/cjs/lib/v3/types/public/options.js.map +1 -1
- package/dist/cjs/lib/v3/understudy/cdp.d.ts +1 -1
- package/dist/cjs/lib/v3/understudy/cdp.js +83 -43
- package/dist/cjs/lib/v3/understudy/cdp.js.map +1 -1
- package/dist/cjs/lib/v3/understudy/page.js +18 -23
- package/dist/cjs/lib/v3/understudy/page.js.map +1 -1
- package/dist/cjs/lib/v3/v3.d.ts +5 -5
- package/dist/cjs/lib/v3/v3.js +48 -46
- package/dist/cjs/lib/v3/v3.js.map +1 -1
- package/dist/cjs/tests/integration/flowLogger.spec.d.ts +1 -0
- package/dist/cjs/tests/integration/flowLogger.spec.js +714 -0
- package/dist/cjs/tests/integration/flowLogger.spec.js.map +1 -0
- package/dist/cjs/tests/integration/testUtils.d.ts +33 -0
- package/dist/cjs/tests/integration/testUtils.js +144 -0
- package/dist/cjs/tests/integration/testUtils.js.map +1 -1
- package/dist/cjs/tests/integration/timeouts.spec.js +112 -2
- package/dist/cjs/tests/integration/timeouts.spec.js.map +1 -1
- package/dist/cjs/tests/unit/flowlogger-capturing-cdp.test.d.ts +1 -0
- package/dist/cjs/tests/unit/flowlogger-capturing-cdp.test.js +95 -0
- package/dist/cjs/tests/unit/flowlogger-capturing-cdp.test.js.map +1 -0
- package/dist/cjs/tests/unit/flowlogger-capturing-llm.test.d.ts +1 -0
- package/dist/cjs/tests/unit/flowlogger-capturing-llm.test.js +43 -0
- package/dist/cjs/tests/unit/flowlogger-capturing-llm.test.js.map +1 -0
- package/dist/cjs/tests/unit/flowlogger-eventstore.test.d.ts +1 -0
- package/dist/cjs/tests/unit/flowlogger-eventstore.test.js +250 -0
- package/dist/cjs/tests/unit/flowlogger-eventstore.test.js.map +1 -0
- package/dist/esm/lib/v3/agent/AnthropicCUAClient.js +1 -1
- package/dist/esm/lib/v3/agent/AnthropicCUAClient.js.map +1 -1
- package/dist/esm/lib/v3/agent/GoogleCUAClient.js +1 -1
- package/dist/esm/lib/v3/agent/GoogleCUAClient.js.map +1 -1
- package/dist/esm/lib/v3/agent/OpenAICUAClient.js +1 -1
- package/dist/esm/lib/v3/agent/OpenAICUAClient.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/act.js +1 -10
- package/dist/esm/lib/v3/agent/tools/act.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/ariaTree.js +1 -12
- package/dist/esm/lib/v3/agent/tools/ariaTree.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/braveSearch.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/browserbaseSearch.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/click.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/clickAndHold.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/dragAndDrop.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/extract.js +1 -10
- package/dist/esm/lib/v3/agent/tools/extract.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/fillFormVision.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/fillform.js +1 -10
- package/dist/esm/lib/v3/agent/tools/fillform.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/index.d.ts +2 -2
- package/dist/esm/lib/v3/agent/tools/index.js +53 -5
- package/dist/esm/lib/v3/agent/tools/index.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/keys.d.ts +1 -1
- package/dist/esm/lib/v3/agent/tools/keys.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/type.js.map +1 -1
- package/dist/esm/lib/v3/api.js +9 -2
- package/dist/esm/lib/v3/api.js.map +1 -1
- package/dist/esm/lib/v3/flowlogger/EventEmitter.d.ts +7 -0
- package/dist/esm/lib/v3/flowlogger/EventEmitter.js +26 -0
- package/dist/esm/lib/v3/flowlogger/EventEmitter.js.map +1 -0
- package/dist/esm/lib/v3/flowlogger/EventSink.d.ts +44 -0
- package/dist/esm/lib/v3/flowlogger/EventSink.js +206 -0
- package/dist/esm/lib/v3/flowlogger/EventSink.js.map +1 -0
- package/dist/esm/lib/v3/flowlogger/EventStore.d.ts +26 -0
- package/dist/esm/lib/v3/flowlogger/EventStore.js +127 -0
- package/dist/esm/lib/v3/flowlogger/EventStore.js.map +1 -0
- package/dist/{cjs/lib/v3/flowLogger.d.ts → esm/lib/v3/flowlogger/FlowLogger.d.ts} +32 -31
- package/dist/esm/lib/v3/flowlogger/FlowLogger.js +583 -0
- package/dist/esm/lib/v3/flowlogger/FlowLogger.js.map +1 -0
- package/dist/esm/lib/v3/flowlogger/prettify.d.ts +6 -0
- package/dist/esm/lib/v3/flowlogger/prettify.js +389 -0
- package/dist/esm/lib/v3/flowlogger/prettify.js.map +1 -0
- package/dist/esm/lib/v3/handlers/handlerUtils/actHandlerUtils.js +25 -27
- package/dist/esm/lib/v3/handlers/handlerUtils/actHandlerUtils.js.map +1 -1
- package/dist/esm/lib/v3/handlers/v3AgentHandler.js +1 -1
- package/dist/esm/lib/v3/handlers/v3AgentHandler.js.map +1 -1
- package/dist/esm/lib/v3/handlers/v3CuaAgentHandler.js +2 -4
- package/dist/esm/lib/v3/handlers/v3CuaAgentHandler.js.map +1 -1
- package/dist/esm/lib/v3/llm/aisdk.js +1 -1
- package/dist/esm/lib/v3/llm/aisdk.js.map +1 -1
- package/dist/esm/lib/v3/types/public/options.d.ts +2 -0
- package/dist/esm/lib/v3/types/public/options.js.map +1 -1
- package/dist/esm/lib/v3/understudy/cdp.d.ts +1 -1
- package/dist/esm/lib/v3/understudy/cdp.js +78 -38
- package/dist/esm/lib/v3/understudy/cdp.js.map +1 -1
- package/dist/esm/lib/v3/understudy/page.js +13 -18
- package/dist/esm/lib/v3/understudy/page.js.map +1 -1
- package/dist/esm/lib/v3/v3.d.ts +5 -5
- package/dist/esm/lib/v3/v3.js +43 -41
- package/dist/esm/lib/v3/v3.js.map +1 -1
- package/dist/esm/tests/integration/flowLogger.spec.d.ts +1 -0
- package/dist/esm/tests/integration/flowLogger.spec.js +712 -0
- package/dist/esm/tests/integration/flowLogger.spec.js.map +1 -0
- package/dist/esm/tests/integration/testUtils.d.ts +33 -0
- package/dist/esm/tests/integration/testUtils.js +138 -0
- package/dist/esm/tests/integration/testUtils.js.map +1 -1
- package/dist/esm/tests/integration/timeouts.spec.js +112 -2
- package/dist/esm/tests/integration/timeouts.spec.js.map +1 -1
- package/dist/esm/tests/unit/flowlogger-capturing-cdp.test.d.ts +1 -0
- package/dist/esm/tests/unit/flowlogger-capturing-cdp.test.js +93 -0
- package/dist/esm/tests/unit/flowlogger-capturing-cdp.test.js.map +1 -0
- package/dist/esm/tests/unit/flowlogger-capturing-llm.test.d.ts +1 -0
- package/dist/esm/tests/unit/flowlogger-capturing-llm.test.js +41 -0
- package/dist/esm/tests/unit/flowlogger-capturing-llm.test.js.map +1 -0
- package/dist/esm/tests/unit/flowlogger-eventstore.test.d.ts +1 -0
- package/dist/esm/tests/unit/flowlogger-eventstore.test.js +248 -0
- package/dist/esm/tests/unit/flowlogger-eventstore.test.js.map +1 -0
- package/package.json +3 -1
- package/dist/cjs/lib/v3/eventStore.d.ts +0 -41
- package/dist/cjs/lib/v3/eventStore.js +0 -375
- package/dist/cjs/lib/v3/eventStore.js.map +0 -1
- package/dist/cjs/lib/v3/flowLogger.js +0 -470
- package/dist/cjs/lib/v3/flowLogger.js.map +0 -1
- package/dist/esm/lib/v3/eventStore.d.ts +0 -41
- package/dist/esm/lib/v3/eventStore.js +0 -363
- package/dist/esm/lib/v3/eventStore.js.map +0 -1
- package/dist/esm/lib/v3/flowLogger.js +0 -462
- package/dist/esm/lib/v3/flowLogger.js.map +0 -1
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { v7 as uuidv7 } from "uuid";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
// =============================================================================
|
|
5
|
+
// Flow Event Model
|
|
6
|
+
// =============================================================================
|
|
7
|
+
export const FlowEventDataSchema = z.record(z.string(), z.unknown());
|
|
8
|
+
export const FlowEventInputSchema = z.object({
|
|
9
|
+
eventType: z.string(),
|
|
10
|
+
eventId: z.string().optional(),
|
|
11
|
+
eventParentIds: z.array(z.string()).optional(),
|
|
12
|
+
eventCreatedAt: z.string().optional(),
|
|
13
|
+
sessionId: z.string().optional(),
|
|
14
|
+
data: FlowEventDataSchema.optional(),
|
|
15
|
+
});
|
|
16
|
+
export class FlowEvent {
|
|
17
|
+
// "ModuleMethodSomethingEvent" -> hashToSmallInt("Modu) -> 5. eventId = "...5"
|
|
18
|
+
static deriveEventIdSuffix(eventType) {
|
|
19
|
+
const prefixMatch = eventType.match(/^[A-Z][a-z0-9]*/);
|
|
20
|
+
const prefix = prefixMatch?.[0] ?? eventType.slice(0, 4);
|
|
21
|
+
let hash = 0;
|
|
22
|
+
for (const ch of prefix.slice(0, 4)) {
|
|
23
|
+
hash = (hash * 31 + ch.charCodeAt(0)) % 10;
|
|
24
|
+
}
|
|
25
|
+
return String(hash); // e.g. "0" or "9"
|
|
26
|
+
}
|
|
27
|
+
// Builds a sortable UUID-like event id while preserving a stable, human-friendly suffix derived from the event family.
|
|
28
|
+
static createEventId(eventType) {
|
|
29
|
+
const rawEventId = uuidv7();
|
|
30
|
+
return `${rawEventId.slice(0, -1)}${FlowEvent.deriveEventIdSuffix(eventType)}`;
|
|
31
|
+
}
|
|
32
|
+
// Base required fields for all events:
|
|
33
|
+
eventType;
|
|
34
|
+
eventId;
|
|
35
|
+
eventParentIds;
|
|
36
|
+
eventCreatedAt;
|
|
37
|
+
// `sessionId` usually matches `browserbaseSessionId` today, but FlowLogger treats it as a generic Stagehand session identifier because those may diverge in the future.
|
|
38
|
+
sessionId;
|
|
39
|
+
data; // event payload (e.g. params, action, result, error, etc.)
|
|
40
|
+
// Normalizes the event shape used everywhere in the flow logger pipeline. This is called at emission time right before an event is attached to the event bus and any sinks.
|
|
41
|
+
constructor(input) {
|
|
42
|
+
if (!input.sessionId) {
|
|
43
|
+
throw new Error("FlowEvent.sessionId is required.");
|
|
44
|
+
}
|
|
45
|
+
if (input.eventType.endsWith("Event")) {
|
|
46
|
+
this.eventType = input.eventType;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
this.eventType = `${input.eventType}Event`;
|
|
50
|
+
}
|
|
51
|
+
this.eventId = input.eventId ?? FlowEvent.createEventId(this.eventType);
|
|
52
|
+
this.eventParentIds = input.eventParentIds ?? [];
|
|
53
|
+
this.eventCreatedAt = input.eventCreatedAt ?? new Date().toISOString();
|
|
54
|
+
this.sessionId = input.sessionId;
|
|
55
|
+
this.data = input.data ?? {};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// AsyncLocalStorage is the authoritative source for the active flow parent stack inside a single async call-chain.
|
|
59
|
+
const loggerContext = new AsyncLocalStorage();
|
|
60
|
+
// Converts raw inline image/base64 payload lengths into a compact kb string for LLM prompt summaries.
|
|
61
|
+
function dataToKb(data) {
|
|
62
|
+
return ((data.length * 0.75) / 1024).toFixed(1);
|
|
63
|
+
}
|
|
64
|
+
const CDP_EVENT_NAMES = {
|
|
65
|
+
call: "CdpCallEvent",
|
|
66
|
+
response: "CdpResponseEvent",
|
|
67
|
+
responseError: "CdpResponseErrorEvent",
|
|
68
|
+
message: "CdpMessageEvent",
|
|
69
|
+
};
|
|
70
|
+
export class FlowLogger {
|
|
71
|
+
// Copies the mutable parts of a context before it is re-entered in a later async callback. This prevents later parent-stack mutations from leaking backward into stored snapshots.
|
|
72
|
+
static cloneContext(ctx) {
|
|
73
|
+
return {
|
|
74
|
+
...ctx,
|
|
75
|
+
parentEvents: ctx.parentEvents.map((event) => ({
|
|
76
|
+
...event,
|
|
77
|
+
eventParentIds: [...event.eventParentIds],
|
|
78
|
+
})),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// Chooses the safest context to re-enter when callers already have a stored context
|
|
82
|
+
// and ALS may or may not already contain one for the same session.
|
|
83
|
+
// If the current ALS stack extends the stored stack, we keep the richer ALS view.
|
|
84
|
+
// If the stored stack is deeper, we preserve that instead.
|
|
85
|
+
// If they diverge, we prefer the current ALS view because it reflects the currently executing call-chain.
|
|
86
|
+
static resolveReentryContext(context) {
|
|
87
|
+
const currentContext = loggerContext.getStore() ?? null;
|
|
88
|
+
// If ALS is empty or belongs to another session, the caller's stored
|
|
89
|
+
// snapshot is the only safe context we can re-enter.
|
|
90
|
+
if (!currentContext || currentContext.sessionId !== context.sessionId) {
|
|
91
|
+
return FlowLogger.cloneContext(context);
|
|
92
|
+
}
|
|
93
|
+
const providedParentIds = context.parentEvents.map((event) => event.eventId);
|
|
94
|
+
const currentParentIds = currentContext.parentEvents.map((event) => event.eventId);
|
|
95
|
+
const currentExtendsProvided = providedParentIds.every((eventId, index) => currentParentIds[index] === eventId);
|
|
96
|
+
// ALS already has the provided chain as a prefix, so we keep the richer
|
|
97
|
+
// currently-executing stack instead of truncating it.
|
|
98
|
+
if (currentExtendsProvided) {
|
|
99
|
+
return FlowLogger.cloneContext(currentContext);
|
|
100
|
+
}
|
|
101
|
+
const providedExtendsCurrent = currentParentIds.every((eventId, index) => providedParentIds[index] === eventId);
|
|
102
|
+
// The stored snapshot is deeper than the current ALS stack, which usually
|
|
103
|
+
// means we are re-entering from a later async callback and need to restore
|
|
104
|
+
// the missing parent chain.
|
|
105
|
+
if (providedExtendsCurrent) {
|
|
106
|
+
return FlowLogger.cloneContext(context);
|
|
107
|
+
}
|
|
108
|
+
// If the two chains diverged, prefer the live ALS chain because it reflects
|
|
109
|
+
// the work currently executing on this async path.
|
|
110
|
+
return FlowLogger.cloneContext(currentContext);
|
|
111
|
+
}
|
|
112
|
+
// Materializes and emits a single flow event on the active ALS context.
|
|
113
|
+
// This is the lowest-level write path used by all higher-level logging helpers
|
|
114
|
+
// after they have decided which parent chain and session the event belongs to.
|
|
115
|
+
static emit(event) {
|
|
116
|
+
const ctx = FlowLogger.currentContext;
|
|
117
|
+
const emittedEvent = new FlowEvent({
|
|
118
|
+
...event,
|
|
119
|
+
eventParentIds: event.eventParentIds ??
|
|
120
|
+
ctx.parentEvents.map((parent) => parent.eventId),
|
|
121
|
+
sessionId: ctx.sessionId,
|
|
122
|
+
});
|
|
123
|
+
ctx.eventBus.emit(emittedEvent.eventType, emittedEvent);
|
|
124
|
+
return emittedEvent;
|
|
125
|
+
}
|
|
126
|
+
// Wraps a unit of async work with started/completed/error events while maintaining
|
|
127
|
+
// the parent stack inside the active context.
|
|
128
|
+
static async runWithAutoStatusEventLogging(options, originalMethod) {
|
|
129
|
+
const ctx = FlowLogger.currentContext;
|
|
130
|
+
const { data, eventParentIds, eventType } = options;
|
|
131
|
+
let caughtError = null;
|
|
132
|
+
// if eventParentIds is explicitly [], this is a root event, clear the parent events in context
|
|
133
|
+
if (eventParentIds && eventParentIds.length === 0) {
|
|
134
|
+
ctx.parentEvents = [];
|
|
135
|
+
}
|
|
136
|
+
const startedEvent = FlowLogger.emit({
|
|
137
|
+
eventType,
|
|
138
|
+
data,
|
|
139
|
+
eventParentIds,
|
|
140
|
+
});
|
|
141
|
+
// Push after emitting so nested work sees this event as its direct parent
|
|
142
|
+
// for the rest of the wrapped method's lifetime.
|
|
143
|
+
ctx.parentEvents.push(startedEvent);
|
|
144
|
+
try {
|
|
145
|
+
return await originalMethod();
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
caughtError = error;
|
|
149
|
+
// Error events attach directly under the started event even though the
|
|
150
|
+
// stack is still live, so the failure edge is explicit in the tree.
|
|
151
|
+
FlowLogger.emit({
|
|
152
|
+
eventType: `${eventType}ErrorEvent`,
|
|
153
|
+
eventParentIds: [...startedEvent.eventParentIds, startedEvent.eventId],
|
|
154
|
+
data: {
|
|
155
|
+
error: error instanceof Error ? error.message : String(error),
|
|
156
|
+
durationMs: Date.now() - new Date(startedEvent.eventCreatedAt).getTime(),
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
// Pop only the frame owned by this wrapper. If nested code has already
|
|
163
|
+
// mutated the stack unexpectedly, we skip the completed event rather than
|
|
164
|
+
// emitting a misleading lifecycle edge.
|
|
165
|
+
const parentEvent = ctx.parentEvents.pop();
|
|
166
|
+
if (parentEvent?.eventId === startedEvent.eventId && !caughtError) {
|
|
167
|
+
FlowLogger.emit({
|
|
168
|
+
eventType: `${eventType}CompletedEvent`,
|
|
169
|
+
eventParentIds: [
|
|
170
|
+
...startedEvent.eventParentIds,
|
|
171
|
+
startedEvent.eventId,
|
|
172
|
+
],
|
|
173
|
+
data: {
|
|
174
|
+
durationMs: Date.now() - new Date(startedEvent.eventCreatedAt).getTime(),
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Emits a CDP event under a caller-supplied context. CDP transport code uses this
|
|
181
|
+
// instead of `runWithLogging()` because request/response/message events
|
|
182
|
+
// are separate lifecycle edges with explicit parent ids.
|
|
183
|
+
static logCdpEvent(context, eventType, { method, params, result, error, targetId }, eventParentIds) {
|
|
184
|
+
if (method.endsWith(".enable") || method === "enable") {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
if (eventType === "message" && FlowLogger.NOISY_CDP_EVENTS.has(method)) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
return loggerContext.run(FlowLogger.cloneContext(context), () => FlowLogger.emit({
|
|
191
|
+
eventType: CDP_EVENT_NAMES[eventType],
|
|
192
|
+
eventParentIds,
|
|
193
|
+
data: {
|
|
194
|
+
method,
|
|
195
|
+
params,
|
|
196
|
+
result,
|
|
197
|
+
error,
|
|
198
|
+
targetId,
|
|
199
|
+
},
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
// Emits an LLM request/response event only when a flow context is active.
|
|
203
|
+
// LLM logging is best-effort, so callers should not fail if it is invoked outside a tracked async chain.
|
|
204
|
+
static emitLlmEvent(event) {
|
|
205
|
+
const context = FlowLogger.resolveContext();
|
|
206
|
+
if (!context) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
loggerContext.run(context, () => {
|
|
210
|
+
FlowLogger.emit(event);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
// Builds the one-line prompt summary used in LLM request events for AI SDK middleware calls.
|
|
214
|
+
static buildMiddlewarePromptSummary(params) {
|
|
215
|
+
const toolCount = Array.isArray(params.tools) ? params.tools.length : 0;
|
|
216
|
+
const messages = (params.prompt ?? []);
|
|
217
|
+
const lastMsg = messages
|
|
218
|
+
.filter((message) => message.role !== "system")
|
|
219
|
+
.pop();
|
|
220
|
+
let rolePrefix = lastMsg?.role ?? "?";
|
|
221
|
+
let promptSummary = `(no text) +{${toolCount} tools}`;
|
|
222
|
+
if (!lastMsg) {
|
|
223
|
+
return `?: ${promptSummary}`;
|
|
224
|
+
}
|
|
225
|
+
if (typeof lastMsg.content === "string") {
|
|
226
|
+
promptSummary = `${lastMsg.content} +{${toolCount} tools}`;
|
|
227
|
+
}
|
|
228
|
+
else if (Array.isArray(lastMsg.content)) {
|
|
229
|
+
const toolResult = lastMsg.content.find((part) => part.type === "tool-result");
|
|
230
|
+
if (toolResult) {
|
|
231
|
+
rolePrefix = `tool result: ${toolResult.toolName}()`;
|
|
232
|
+
if (toolResult.output?.type === "json" && toolResult.output.value) {
|
|
233
|
+
promptSummary = `${JSON.stringify(toolResult.output.value)} +{${toolCount} tools}`;
|
|
234
|
+
}
|
|
235
|
+
else if (Array.isArray(toolResult.output?.value)) {
|
|
236
|
+
promptSummary = `${extractLlmMessageSummary({
|
|
237
|
+
content: toolResult.output.value,
|
|
238
|
+
}) ?? "(no text)"} +{${toolCount} tools}`;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
promptSummary = `${extractLlmMessageSummary({ content: lastMsg.content }) ?? "(no text)"} +{${toolCount} tools}`;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return `${rolePrefix}: ${promptSummary}`;
|
|
246
|
+
}
|
|
247
|
+
// Builds the one-line output summary used in LLM response events for AI SDK middleware calls.
|
|
248
|
+
static buildMiddlewareOutputSummary(result) {
|
|
249
|
+
let outputSummary = result.text || "";
|
|
250
|
+
if (!outputSummary && result.content) {
|
|
251
|
+
if (typeof result.content === "string") {
|
|
252
|
+
outputSummary = result.content;
|
|
253
|
+
}
|
|
254
|
+
else if (Array.isArray(result.content)) {
|
|
255
|
+
outputSummary = result.content
|
|
256
|
+
.map((contentPart) => {
|
|
257
|
+
if (contentPart.text) {
|
|
258
|
+
return contentPart.text;
|
|
259
|
+
}
|
|
260
|
+
if (contentPart.type === "tool-call") {
|
|
261
|
+
return `tool call: ${contentPart.toolName}()`;
|
|
262
|
+
}
|
|
263
|
+
return `[${contentPart.type}]`;
|
|
264
|
+
})
|
|
265
|
+
.join(" ");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (!outputSummary && result.toolCalls?.length) {
|
|
269
|
+
return `[${result.toolCalls.length} tool calls]`;
|
|
270
|
+
}
|
|
271
|
+
return outputSummary || "[empty]";
|
|
272
|
+
}
|
|
273
|
+
// =============================================================================
|
|
274
|
+
// Flow Logger Public Lifecycle API
|
|
275
|
+
// =============================================================================
|
|
276
|
+
// Initialize a new logging context. Call this at the start of a session.
|
|
277
|
+
static init(sessionId, eventBus) {
|
|
278
|
+
const ctx = {
|
|
279
|
+
sessionId,
|
|
280
|
+
eventBus,
|
|
281
|
+
parentEvents: [],
|
|
282
|
+
};
|
|
283
|
+
loggerContext.enterWith(ctx);
|
|
284
|
+
return ctx;
|
|
285
|
+
}
|
|
286
|
+
// Clears the parent stack for a session when a V3 instance shuts down.
|
|
287
|
+
// This does not emit a final event; it just tears down in-memory context.
|
|
288
|
+
static async close(context) {
|
|
289
|
+
const ctx = context ?? loggerContext.getStore() ?? null;
|
|
290
|
+
if (!ctx)
|
|
291
|
+
return;
|
|
292
|
+
ctx.parentEvents = [];
|
|
293
|
+
}
|
|
294
|
+
// Returns the current ALS-backed flow context and throws when code
|
|
295
|
+
// executes outside a tracked flow. Use `resolveContext()` for best-effort lookups.
|
|
296
|
+
static get currentContext() {
|
|
297
|
+
const ctx = loggerContext.getStore() ?? null;
|
|
298
|
+
if (!ctx) {
|
|
299
|
+
throw new Error("FlowLogger context is missing.");
|
|
300
|
+
}
|
|
301
|
+
return ctx;
|
|
302
|
+
}
|
|
303
|
+
// Returns a cloned FlowLogger context for the current async call-chain when one exists,
|
|
304
|
+
// otherwise falls back to the provided instance-owned context.
|
|
305
|
+
// This is the non-throwing lookup for callers that can continue without ALS.
|
|
306
|
+
static resolveContext(fallbackContext) {
|
|
307
|
+
const currentContext = loggerContext.getStore() ?? null;
|
|
308
|
+
if (currentContext) {
|
|
309
|
+
return FlowLogger.cloneContext(currentContext);
|
|
310
|
+
}
|
|
311
|
+
return fallbackContext ? FlowLogger.cloneContext(fallbackContext) : null;
|
|
312
|
+
}
|
|
313
|
+
// Decorator-style wrapper used on class methods that should emit their own started/completed/error envelope.
|
|
314
|
+
// It resolves the flow context from either the decorator options or `this.flowLoggerContext`,
|
|
315
|
+
// then delegates the actual lifecycle handling to `runWithLogging()`.
|
|
316
|
+
static wrapWithLogging(options) {
|
|
317
|
+
return function (originalMethod) {
|
|
318
|
+
const wrappedMethod = async function (...args) {
|
|
319
|
+
let context = options.context;
|
|
320
|
+
if (!context) {
|
|
321
|
+
context = this?.flowLoggerContext;
|
|
322
|
+
}
|
|
323
|
+
return await FlowLogger.runWithLogging({
|
|
324
|
+
...options,
|
|
325
|
+
context,
|
|
326
|
+
}, (...boundArgs) => originalMethod.apply(this, boundArgs), args);
|
|
327
|
+
};
|
|
328
|
+
return wrappedMethod;
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
static runWithLogging(options, originalMethod, params) {
|
|
332
|
+
const eventData = {
|
|
333
|
+
...(options.data ?? {}),
|
|
334
|
+
params: [...params],
|
|
335
|
+
};
|
|
336
|
+
const execute = () => FlowLogger.runWithAutoStatusEventLogging({
|
|
337
|
+
...options,
|
|
338
|
+
data: eventData,
|
|
339
|
+
}, () => originalMethod(...params));
|
|
340
|
+
// No explicit context and no active ALS means there is nothing to attach
|
|
341
|
+
// this work to, so we leave execution untouched instead of fabricating a
|
|
342
|
+
// root event.
|
|
343
|
+
if (!options.context && !(loggerContext.getStore() ?? null)) {
|
|
344
|
+
return originalMethod(...params);
|
|
345
|
+
}
|
|
346
|
+
if (options.context) {
|
|
347
|
+
// Re-enter the caller-owned context so wrapper events land under the same
|
|
348
|
+
// session tree even when this code executes outside the original ALS
|
|
349
|
+
// chain.
|
|
350
|
+
return loggerContext.run(FlowLogger.resolveReentryContext(options.context), execute);
|
|
351
|
+
}
|
|
352
|
+
return execute();
|
|
353
|
+
}
|
|
354
|
+
// Re-enters an existing FlowLogger context without emitting wrapper events.
|
|
355
|
+
// Use this when work already belongs to a known parent and needs AsyncLocalStorage set manually.
|
|
356
|
+
static withContext(context, fn) {
|
|
357
|
+
return loggerContext.run(FlowLogger.resolveReentryContext(context), fn);
|
|
358
|
+
}
|
|
359
|
+
// ===========================================================================
|
|
360
|
+
// CDP Events
|
|
361
|
+
// ===========================================================================
|
|
362
|
+
static NOISY_CDP_EVENTS = new Set([
|
|
363
|
+
"Target.targetInfoChanged",
|
|
364
|
+
"Runtime.executionContextCreated",
|
|
365
|
+
"Runtime.executionContextDestroyed",
|
|
366
|
+
"Runtime.executionContextsCleared",
|
|
367
|
+
"Page.lifecycleEvent",
|
|
368
|
+
"Network.dataReceived",
|
|
369
|
+
"Network.loadingFinished",
|
|
370
|
+
"Network.requestWillBeSentExtraInfo",
|
|
371
|
+
"Network.responseReceivedExtraInfo",
|
|
372
|
+
"Network.requestWillBeSent",
|
|
373
|
+
"Network.responseReceived",
|
|
374
|
+
]);
|
|
375
|
+
// Logs the start of a CDP command. CDP transport calls this before sending a
|
|
376
|
+
// message over the websocket so the eventual response can attach to it.
|
|
377
|
+
static logCdpCallEvent(context, data) {
|
|
378
|
+
return FlowLogger.logCdpEvent(context, "call", data);
|
|
379
|
+
}
|
|
380
|
+
// Logs the terminal response for a previously emitted CDP call event.
|
|
381
|
+
static logCdpResponseEvent(context, parentEvent, data) {
|
|
382
|
+
FlowLogger.logCdpEvent(context, data.error ? "responseError" : "response", data, [...parentEvent.eventParentIds, parentEvent.eventId]);
|
|
383
|
+
}
|
|
384
|
+
// Logs an unsolicited CDP message under the most recent related call event.
|
|
385
|
+
static logCdpMessageEvent(context, parentEvent, data) {
|
|
386
|
+
FlowLogger.logCdpEvent(context, "message", data, [
|
|
387
|
+
...parentEvent.eventParentIds,
|
|
388
|
+
parentEvent.eventId,
|
|
389
|
+
]);
|
|
390
|
+
}
|
|
391
|
+
// ===========================================================================
|
|
392
|
+
// LLM Events
|
|
393
|
+
// ===========================================================================
|
|
394
|
+
// Emits a best-effort LLM request event when logging occurs inside an active flow context.
|
|
395
|
+
static logLlmRequest({ requestId, model, prompt, }) {
|
|
396
|
+
FlowLogger.emitLlmEvent({
|
|
397
|
+
eventType: "LlmRequestEvent",
|
|
398
|
+
data: {
|
|
399
|
+
requestId,
|
|
400
|
+
model,
|
|
401
|
+
prompt,
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
// Emits a best-effort LLM response event when logging occurs inside an active flow context.
|
|
406
|
+
static logLlmResponse({ requestId, model, output, inputTokens, outputTokens, }) {
|
|
407
|
+
FlowLogger.emitLlmEvent({
|
|
408
|
+
eventType: "LlmResponseEvent",
|
|
409
|
+
data: {
|
|
410
|
+
requestId,
|
|
411
|
+
model,
|
|
412
|
+
output,
|
|
413
|
+
inputTokens,
|
|
414
|
+
outputTokens,
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
// ===========================================================================
|
|
419
|
+
// LLM Logging Middleware
|
|
420
|
+
// ===========================================================================
|
|
421
|
+
// Creates AI SDK middleware that wraps a generate call with FlowLogger LLM request/response events
|
|
422
|
+
// while leaving model execution behavior unchanged.
|
|
423
|
+
static createLlmLoggingMiddleware(modelId) {
|
|
424
|
+
return {
|
|
425
|
+
wrapGenerate: async ({ doGenerate, params }) => {
|
|
426
|
+
const llmRequestId = uuidv7();
|
|
427
|
+
FlowLogger.logLlmRequest({
|
|
428
|
+
requestId: llmRequestId,
|
|
429
|
+
model: modelId,
|
|
430
|
+
prompt: FlowLogger.buildMiddlewarePromptSummary(params),
|
|
431
|
+
});
|
|
432
|
+
const result = await doGenerate();
|
|
433
|
+
const res = result;
|
|
434
|
+
FlowLogger.logLlmResponse({
|
|
435
|
+
requestId: llmRequestId,
|
|
436
|
+
model: modelId,
|
|
437
|
+
output: FlowLogger.buildMiddlewareOutputSummary(res),
|
|
438
|
+
inputTokens: result.usage?.inputTokens,
|
|
439
|
+
outputTokens: result.usage?.outputTokens,
|
|
440
|
+
});
|
|
441
|
+
return result;
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// Extracts text and image markers from an LLM content array.
|
|
447
|
+
// This is shared by the request-summary helpers below so different provider message
|
|
448
|
+
// shapes render consistently in the flow log.
|
|
449
|
+
function extractLlmMessageContent(content) {
|
|
450
|
+
const result = {
|
|
451
|
+
text: undefined,
|
|
452
|
+
extras: [],
|
|
453
|
+
};
|
|
454
|
+
for (const part of content) {
|
|
455
|
+
const p = part;
|
|
456
|
+
// Text
|
|
457
|
+
if (!result.text && p.text) {
|
|
458
|
+
result.text = p.type === "text" || !p.type ? p.text : undefined;
|
|
459
|
+
}
|
|
460
|
+
// Images - various formats
|
|
461
|
+
if (p.type === "image" || p.type === "image_url") {
|
|
462
|
+
const url = p.image_url?.url;
|
|
463
|
+
if (url?.startsWith("data:"))
|
|
464
|
+
result.extras.push(`${dataToKb(url)}kb image`);
|
|
465
|
+
else if (p.source?.data)
|
|
466
|
+
result.extras.push(`${dataToKb(p.source.data)}kb image`);
|
|
467
|
+
else
|
|
468
|
+
result.extras.push("image");
|
|
469
|
+
}
|
|
470
|
+
else if (p.source?.data) {
|
|
471
|
+
result.extras.push(`${dataToKb(p.source.data)}kb image`);
|
|
472
|
+
}
|
|
473
|
+
else if (p.inlineData?.data) {
|
|
474
|
+
result.extras.push(`${dataToKb(p.inlineData.data)}kb image`);
|
|
475
|
+
}
|
|
476
|
+
// Recurse into tool_result content
|
|
477
|
+
if (p.type === "tool_result" && Array.isArray(p.content)) {
|
|
478
|
+
const nested = extractLlmMessageContent(p.content);
|
|
479
|
+
if (!result.text && nested.text) {
|
|
480
|
+
result.text = nested.text;
|
|
481
|
+
}
|
|
482
|
+
result.extras.push(...nested.extras);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return result;
|
|
486
|
+
}
|
|
487
|
+
// Produces a single compact summary from a provider-specific message payload
|
|
488
|
+
// so request and tool-result logs stay readable.
|
|
489
|
+
function extractLlmMessageSummary(input, options) {
|
|
490
|
+
const result = {
|
|
491
|
+
text: undefined,
|
|
492
|
+
extras: [...(options?.extras ?? [])],
|
|
493
|
+
};
|
|
494
|
+
if (typeof input.content === "string") {
|
|
495
|
+
result.text = input.content;
|
|
496
|
+
}
|
|
497
|
+
else if (typeof input.text === "string") {
|
|
498
|
+
result.text = input.text;
|
|
499
|
+
}
|
|
500
|
+
else if (Array.isArray(input.parts)) {
|
|
501
|
+
const summary = extractLlmMessageContent(input.parts);
|
|
502
|
+
result.text = summary.text;
|
|
503
|
+
result.extras.push(...summary.extras);
|
|
504
|
+
}
|
|
505
|
+
else if (Array.isArray(input.content)) {
|
|
506
|
+
const summary = extractLlmMessageContent(input.content);
|
|
507
|
+
result.text = summary.text;
|
|
508
|
+
result.extras.push(...summary.extras);
|
|
509
|
+
}
|
|
510
|
+
if (options?.trimInstructionPrefix && result.text) {
|
|
511
|
+
result.text = result.text.replace(/^[Ii]nstruction: /, "");
|
|
512
|
+
}
|
|
513
|
+
const text = result.text;
|
|
514
|
+
if (!text && result.extras.length === 0)
|
|
515
|
+
return undefined;
|
|
516
|
+
let summary = text || "";
|
|
517
|
+
if (result.extras.length > 0) {
|
|
518
|
+
const extrasStr = result.extras.map((e) => `+{${e}}`).join(" ");
|
|
519
|
+
summary = summary ? `${summary} ${extrasStr}` : extrasStr;
|
|
520
|
+
}
|
|
521
|
+
return summary || undefined;
|
|
522
|
+
}
|
|
523
|
+
// Formats the last user-facing prompt into the one-line form used by standard LLM request logs,
|
|
524
|
+
// for example: `some text +{5.8kb image} +{schema}`.
|
|
525
|
+
export function extractLlmPromptSummary(messages, options) {
|
|
526
|
+
try {
|
|
527
|
+
const lastUserMsg = messages.filter((m) => m.role === "user").pop();
|
|
528
|
+
if (!lastUserMsg)
|
|
529
|
+
return undefined;
|
|
530
|
+
return extractLlmMessageSummary(lastUserMsg, {
|
|
531
|
+
trimInstructionPrefix: true,
|
|
532
|
+
extras: [
|
|
533
|
+
...(options?.hasSchema ? ["schema"] : []),
|
|
534
|
+
...(options?.toolCount ? [`${options.toolCount} tools`] : []),
|
|
535
|
+
],
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
return undefined;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// Extract a text summary from CUA-style messages. This accepts Anthropic, OpenAI, and Google-style payloads.
|
|
543
|
+
export function extractLlmCuaPromptSummary(messages) {
|
|
544
|
+
try {
|
|
545
|
+
const lastMsg = messages
|
|
546
|
+
.filter((m) => {
|
|
547
|
+
const msg = m;
|
|
548
|
+
return msg.role === "user" || msg.type === "tool_result";
|
|
549
|
+
})
|
|
550
|
+
.pop();
|
|
551
|
+
if (!lastMsg)
|
|
552
|
+
return undefined;
|
|
553
|
+
return extractLlmMessageSummary(lastMsg);
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
return undefined;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
// Formats the response side of a CUA exchange into a single short log line.
|
|
560
|
+
export function extractLlmCuaResponseSummary(output) {
|
|
561
|
+
try {
|
|
562
|
+
const items = output
|
|
563
|
+
?.candidates?.[0]?.content?.parts ??
|
|
564
|
+
(Array.isArray(output) ? output : []);
|
|
565
|
+
const summary = items
|
|
566
|
+
.map((item) => {
|
|
567
|
+
const i = item;
|
|
568
|
+
if (i.text)
|
|
569
|
+
return i.text;
|
|
570
|
+
if (i.functionCall?.name)
|
|
571
|
+
return i.functionCall.name;
|
|
572
|
+
if (i.type === "tool_use" && i.name)
|
|
573
|
+
return i.name;
|
|
574
|
+
return i.type ?? "[item]";
|
|
575
|
+
})
|
|
576
|
+
.join(" ");
|
|
577
|
+
return summary;
|
|
578
|
+
}
|
|
579
|
+
catch {
|
|
580
|
+
return "[error]";
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
//# sourceMappingURL=FlowLogger.js.map
|