feat: make Kafka projector the Workbench realtime authority

This commit is contained in:
root
2026-07-10 05:23:03 +02:00
parent a685be0b78
commit ce83ffdf49
74 changed files with 7746 additions and 9863 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
export { fetchJson, fetchText, type ActivityRef, type ActivityRefSource, type ApiRequestOptions } from "./client";
export { authAPI, HWLAB_WEB_SESSION_COOKIE } from "./auth";
export { workbenchAPI } from "./workbench";
export { workbenchDebugAPI, connectWorkbenchDebugFakeSse, connectWorkbenchKafkaSseDebug, type WorkbenchDebugFakeSseQueue, type WorkbenchDebugFakeSseResponse, type WorkbenchDebugFakeSseSequence, type WorkbenchDebugFakeSseStream, type WorkbenchKafkaSseDebugEvent, type WorkbenchKafkaSseDebugFilters } from "./workbench-debug";
export { workbenchDebugAPI, connectWorkbenchDebugFakeSse, type WorkbenchDebugFakeSseQueue, type WorkbenchDebugFakeSseResponse, type WorkbenchDebugFakeSseSequence, type WorkbenchDebugFakeSseStream } from "./workbench-debug";
export { connectWorkbenchEvents, type WorkbenchEventStream, type WorkbenchRealtimeEvent } from "./workbench-events";
export { agentAPI } from "./agent";
export { hwpodAPI } from "./hwpod";
@@ -50,39 +50,6 @@ export interface WorkbenchDebugFakeSseStream {
close: () => void;
}
export interface WorkbenchKafkaSseDebugFilters {
traceId?: string;
sessionId?: string;
runId?: string;
commandId?: string;
}
export type WorkbenchKafkaSseDebugStreamName = "stdio" | "agentrun" | "hwlab";
export interface WorkbenchKafkaSseDebugEvent {
ok?: boolean;
contractVersion?: string;
stream?: string;
topic?: string;
partition?: number;
offset?: string;
key?: string | null;
timestamp?: string | null;
value?: Record<string, unknown>;
filters?: WorkbenchKafkaSseDebugFilters;
serverSentAt?: string;
error?: { code?: string; message?: string; [key: string]: unknown };
valuesPrinted?: boolean;
}
export interface WorkbenchKafkaSseDebugStreamOptions extends WorkbenchKafkaSseDebugFilters {
stream?: WorkbenchKafkaSseDebugStreamName;
fromBeginning?: boolean;
onEvent: (event: WorkbenchKafkaSseDebugEvent, eventName: string) => void;
onOpen?: () => void;
onError?: (event: Event) => void;
}
const DEBUG_FAKE_SSE_EVENTS = [
"workbench.connected",
"workbench.trace.snapshot",
@@ -126,39 +93,6 @@ export function connectWorkbenchDebugFakeSse(options: WorkbenchDebugFakeSseStrea
};
}
export function connectWorkbenchKafkaSseDebug(options: WorkbenchKafkaSseDebugStreamOptions): WorkbenchDebugFakeSseStream | null {
if (typeof EventSource === "undefined") return null;
const source = new EventSource(kafkaDebugPath("/events", {
stream: options.stream || "hwlab",
traceId: options.traceId,
sessionId: options.sessionId,
runId: options.runId,
commandId: options.commandId,
fromBeginning: options.fromBeginning ? "true" : undefined
}), { withCredentials: true });
source.onopen = () => options.onOpen?.();
source.onerror = (event) => options.onError?.(event);
const names = ["hwlab.kafka.connected", "hwlab.kafka.event", "hwlab.kafka.error"];
const listeners = names.map((name) => {
const listener = (event: MessageEvent) => {
const payload = parseKafkaEvent(event.data);
if (payload) options.onEvent(payload, name);
};
source.addEventListener(name, listener);
return { name, listener };
});
source.onmessage = (event) => {
const payload = parseKafkaEvent(event.data);
if (payload) options.onEvent(payload, "message");
};
return {
close() {
for (const { name, listener } of listeners) source.removeEventListener(name, listener);
source.close();
}
};
}
function debugPath(suffix: string, params: Record<string, string | null | undefined> = {}): string {
const query = new URLSearchParams();
for (const [key, value] of Object.entries(params)) {
@@ -169,16 +103,6 @@ function debugPath(suffix: string, params: Record<string, string | null | undefi
return `/v1/workbench/debug/fake-sse${suffix}${qs ? `?${qs}` : ""}`;
}
function kafkaDebugPath(suffix: string, params: Record<string, string | null | undefined> = {}): string {
const query = new URLSearchParams();
for (const [key, value] of Object.entries(params)) {
const text = typeof value === "string" ? value.trim() : "";
if (text) query.set(key, text);
}
const qs = query.toString();
return `/v1/workbench/debug/kafka-sse${suffix}${qs ? `?${qs}` : ""}`;
}
function parseRealtimeEvent(raw: string): WorkbenchRealtimeEvent | null {
try {
const value = JSON.parse(raw);
@@ -187,12 +111,3 @@ function parseRealtimeEvent(raw: string): WorkbenchRealtimeEvent | null {
return null;
}
}
function parseKafkaEvent(raw: string): WorkbenchKafkaSseDebugEvent | null {
try {
const value = JSON.parse(raw);
return value && typeof value === "object" ? value as WorkbenchKafkaSseDebugEvent : null;
} catch {
return null;
}
}