mirror of
https://github.com/yaklang/yaklang-chrome-extension.git
synced 2026-09-26 13:11:53 +08:00
feat: advance browser agent integration workflows
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
import type { BrowserRecordingEvent } from '@/types/models';
|
||||
|
||||
export interface RecordingTraceContext {
|
||||
traceId: string;
|
||||
interactionId?: string;
|
||||
}
|
||||
|
||||
export type RecordingTraceEventInput = Omit<BrowserRecordingEvent,
|
||||
| 'id'
|
||||
| 'sequence'
|
||||
| 'timestamp'
|
||||
| 'recordingId'
|
||||
| 'traceId'
|
||||
| 'interactionId'
|
||||
| 'parentEventId'
|
||||
| 'sensitiveCaptured'
|
||||
| 'inputs'
|
||||
| 'outputs'
|
||||
> & {
|
||||
inputs?: BrowserRecordingEvent['inputs'];
|
||||
outputs?: BrowserRecordingEvent['outputs'];
|
||||
};
|
||||
|
||||
export interface RecordingTraceHost {
|
||||
active(): boolean;
|
||||
recordingId(): string | undefined;
|
||||
captureValues(): boolean;
|
||||
maxEntries(): number;
|
||||
parentEventId(): string | undefined;
|
||||
unique(prefix: string): string;
|
||||
}
|
||||
|
||||
export interface RecordingTraceSnapshot {
|
||||
count: number;
|
||||
droppedCount: number;
|
||||
sequence: number;
|
||||
events: BrowserRecordingEvent[];
|
||||
}
|
||||
|
||||
export interface RecordingTraceRuntime {
|
||||
context(): RecordingTraceContext;
|
||||
currentContext(): RecordingTraceContext | undefined;
|
||||
bindContext(context: RecordingTraceContext): void;
|
||||
releaseContext(): void;
|
||||
record(input: RecordingTraceEventInput, context?: RecordingTraceContext): BrowserRecordingEvent | undefined;
|
||||
observe(
|
||||
factory: () => RecordingTraceEventInput,
|
||||
context?: RecordingTraceContext,
|
||||
): BrowserRecordingEvent | undefined;
|
||||
reset(sequenceStart?: number): void;
|
||||
advanceSequenceStart(sequenceStart: number): void;
|
||||
snapshot(limit: number): RecordingTraceSnapshot;
|
||||
}
|
||||
|
||||
const TRACE_IDLE_MS = 5_000;
|
||||
|
||||
export function createRecordingTraceRuntime(
|
||||
host: RecordingTraceHost,
|
||||
now = () => performance.now(),
|
||||
): RecordingTraceRuntime {
|
||||
let sequence = 0;
|
||||
let droppedCount = 0;
|
||||
let events: BrowserRecordingEvent[] = [];
|
||||
let currentTrace: (RecordingTraceContext & { expiresAt: number }) | undefined;
|
||||
|
||||
const bindContext = (context: RecordingTraceContext): void => {
|
||||
currentTrace = { ...context, expiresAt: now() + TRACE_IDLE_MS };
|
||||
};
|
||||
|
||||
const currentContext = (): RecordingTraceContext | undefined => {
|
||||
const currentTime = now();
|
||||
if (!currentTrace || currentTrace.expiresAt < currentTime) return undefined;
|
||||
currentTrace.expiresAt = currentTime + TRACE_IDLE_MS;
|
||||
return {
|
||||
traceId: currentTrace.traceId,
|
||||
interactionId: currentTrace.interactionId,
|
||||
};
|
||||
};
|
||||
|
||||
const context = (): RecordingTraceContext => {
|
||||
const existing = currentContext();
|
||||
if (existing) return existing;
|
||||
const created = { traceId: host.unique('trace') };
|
||||
bindContext(created);
|
||||
return created;
|
||||
};
|
||||
|
||||
const record = (
|
||||
input: RecordingTraceEventInput,
|
||||
explicitContext?: RecordingTraceContext,
|
||||
): BrowserRecordingEvent | undefined => {
|
||||
const recordingId = host.recordingId();
|
||||
if (!host.active() || !recordingId) return undefined;
|
||||
const eventContext = explicitContext || context();
|
||||
sequence += 1;
|
||||
const item: BrowserRecordingEvent = {
|
||||
id: host.unique('event'),
|
||||
sequence,
|
||||
timestamp: Date.now(),
|
||||
recordingId,
|
||||
traceId: eventContext.traceId,
|
||||
interactionId: eventContext.interactionId,
|
||||
parentEventId: host.parentEventId(),
|
||||
source: 'page',
|
||||
sensitiveCaptured: host.captureValues(),
|
||||
inputs: input.inputs || [],
|
||||
outputs: input.outputs || [],
|
||||
...input,
|
||||
};
|
||||
events.push(item);
|
||||
const configuredMaxEntries = host.maxEntries();
|
||||
const maxEntries = Number.isSafeInteger(configuredMaxEntries) && configuredMaxEntries > 0
|
||||
? configuredMaxEntries
|
||||
: 1;
|
||||
while (events.length > maxEntries) {
|
||||
events.shift();
|
||||
droppedCount += 1;
|
||||
}
|
||||
return item;
|
||||
};
|
||||
|
||||
return {
|
||||
context,
|
||||
currentContext,
|
||||
bindContext,
|
||||
releaseContext() {
|
||||
currentTrace = undefined;
|
||||
},
|
||||
record,
|
||||
observe(factory, explicitContext) {
|
||||
if (!host.active() || !host.recordingId()) return undefined;
|
||||
try {
|
||||
return record(factory(), explicitContext);
|
||||
} catch {
|
||||
droppedCount += 1;
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
reset(sequenceStart = 0) {
|
||||
events = [];
|
||||
droppedCount = 0;
|
||||
sequence = Number.isSafeInteger(sequenceStart) && sequenceStart >= 0 ? sequenceStart : 0;
|
||||
currentTrace = undefined;
|
||||
},
|
||||
advanceSequenceStart(sequenceStart) {
|
||||
if (Number.isSafeInteger(sequenceStart) && sequenceStart >= sequence) sequence = sequenceStart;
|
||||
},
|
||||
snapshot(limit) {
|
||||
const configuredMaxEntries = host.maxEntries();
|
||||
const maxEntries = Number.isSafeInteger(configuredMaxEntries) && configuredMaxEntries > 0
|
||||
? configuredMaxEntries
|
||||
: 1;
|
||||
const normalizedLimit = Number.isSafeInteger(limit) && limit >= 0
|
||||
? Math.min(limit, maxEntries)
|
||||
: maxEntries;
|
||||
return {
|
||||
count: events.length,
|
||||
droppedCount,
|
||||
sequence,
|
||||
events: normalizedLimit === 0 ? [] : events.slice(-normalizedLimit),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user