builder / lib /llm /request-snapshot.ts
Leon4gr45's picture
Upload folder using huggingface_hub (part 2)
391c43e verified
Raw
History Blame Contribute Delete
1.98 kB
/**
* Request snapshot store — debug capture of the exact message history sent to
* the provider on the most recent LLM call.
*
* Deliberately separate from the debug event stream: capture is a single
* assignment per provider call (never per token), is session-only (never
* persisted), and is off by default. The Messages tab in the debug panel
* subscribes via useSyncExternalStore and re-renders only when a new request
* is captured.
*/
import type { Message } from './core/types';
export interface RequestSnapshot {
messages: Message[];
provider: string;
model: string;
timestamp: number;
}
type Listener = () => void;
class RequestSnapshotStore {
private enabled = false;
private snapshot: RequestSnapshot | null = null;
private listeners = new Set<Listener>();
isEnabled(): boolean {
return this.enabled;
}
setEnabled(enabled: boolean): void {
if (this.enabled === enabled) return;
this.enabled = enabled;
this.notify();
}
/** Capture the latest outgoing request. No-op when disabled. */
capture(req: { messages: Message[]; provider: string; model: string }): void {
if (!this.enabled) return;
this.snapshot = {
// Deep copy — the live message objects are mutated by later turns
messages: JSON.parse(JSON.stringify(req.messages)),
provider: req.provider,
model: req.model,
timestamp: Date.now(),
};
this.notify();
}
/** Stable reference between captures (useSyncExternalStore contract). */
getSnapshot(): RequestSnapshot | null {
return this.snapshot;
}
clear(): void {
if (this.snapshot === null) return;
this.snapshot = null;
this.notify();
}
subscribe(listener: Listener): () => void {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
private notify(): void {
for (const listener of this.listeners) listener();
}
}
export const requestSnapshotStore = new RequestSnapshotStore();