fix(webchat): stream assistant events and correlate runId
This commit is contained in:
@@ -324,6 +324,13 @@ export async function agentCommand(
|
|||||||
thinkLevel: resolvedThinkLevel,
|
thinkLevel: resolvedThinkLevel,
|
||||||
verboseLevel: resolvedVerboseLevel,
|
verboseLevel: resolvedVerboseLevel,
|
||||||
runId: sessionId,
|
runId: sessionId,
|
||||||
|
onAgentEvent: (evt) => {
|
||||||
|
emitAgentEvent({
|
||||||
|
runId: sessionId,
|
||||||
|
stream: evt.stream,
|
||||||
|
data: evt.data,
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
emitAgentEvent({
|
emitAgentEvent({
|
||||||
runId: sessionId,
|
runId: sessionId,
|
||||||
|
|||||||
@@ -260,8 +260,11 @@ type DedupeEntry = {
|
|||||||
error?: ErrorShape;
|
error?: ErrorShape;
|
||||||
};
|
};
|
||||||
const dedupe = new Map<string, DedupeEntry>();
|
const dedupe = new Map<string, DedupeEntry>();
|
||||||
// Map runId -> sessionKey for chat events (WS WebChat clients).
|
// Map agent sessionId -> {sessionKey, clientRunId} for chat events (WS WebChat clients).
|
||||||
const chatRunSessions = new Map<string, string>();
|
const chatRunSessions = new Map<
|
||||||
|
string,
|
||||||
|
{ sessionKey: string; clientRunId: string }
|
||||||
|
>();
|
||||||
const chatRunBuffers = new Map<string, string[]>();
|
const chatRunBuffers = new Map<string, string[]>();
|
||||||
|
|
||||||
const getGatewayToken = () => process.env.CLAWDIS_GATEWAY_TOKEN;
|
const getGatewayToken = () => process.env.CLAWDIS_GATEWAY_TOKEN;
|
||||||
@@ -972,25 +975,27 @@ export async function startGatewayServer(
|
|||||||
agentRunSeq.set(evt.runId, evt.seq);
|
agentRunSeq.set(evt.runId, evt.seq);
|
||||||
broadcast("agent", evt);
|
broadcast("agent", evt);
|
||||||
|
|
||||||
const sessionKey = chatRunSessions.get(evt.runId);
|
const chatLink = chatRunSessions.get(evt.runId);
|
||||||
if (sessionKey) {
|
if (chatLink) {
|
||||||
// Map agent bus events to chat events for WS WebChat clients.
|
// Map agent bus events to chat events for WS WebChat clients.
|
||||||
|
// Use clientRunId so the webchat can correlate with its pending promise.
|
||||||
|
const { sessionKey, clientRunId } = chatLink;
|
||||||
const base = {
|
const base = {
|
||||||
runId: evt.runId,
|
runId: clientRunId,
|
||||||
sessionKey,
|
sessionKey,
|
||||||
seq: evt.seq,
|
seq: evt.seq,
|
||||||
};
|
};
|
||||||
if (evt.stream === "assistant" && typeof evt.data?.text === "string") {
|
if (evt.stream === "assistant" && typeof evt.data?.text === "string") {
|
||||||
const buf = chatRunBuffers.get(evt.runId) ?? [];
|
const buf = chatRunBuffers.get(clientRunId) ?? [];
|
||||||
buf.push(evt.data.text);
|
buf.push(evt.data.text);
|
||||||
chatRunBuffers.set(evt.runId, buf);
|
chatRunBuffers.set(clientRunId, buf);
|
||||||
} else if (
|
} else if (
|
||||||
evt.stream === "job" &&
|
evt.stream === "job" &&
|
||||||
typeof evt.data?.state === "string" &&
|
typeof evt.data?.state === "string" &&
|
||||||
(evt.data.state === "done" || evt.data.state === "error")
|
(evt.data.state === "done" || evt.data.state === "error")
|
||||||
) {
|
) {
|
||||||
const text = chatRunBuffers.get(evt.runId)?.join("\n").trim() ?? "";
|
const text = chatRunBuffers.get(clientRunId)?.join("\n").trim() ?? "";
|
||||||
chatRunBuffers.delete(evt.runId);
|
chatRunBuffers.delete(clientRunId);
|
||||||
if (evt.data.state === "done") {
|
if (evt.data.state === "done") {
|
||||||
broadcast("chat", {
|
broadcast("chat", {
|
||||||
...base,
|
...base,
|
||||||
@@ -1448,10 +1453,13 @@ export async function startGatewayServer(
|
|||||||
await saveSessionStore(storePath, store);
|
await saveSessionStore(storePath, store);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chatRunSessions.set(sessionId, p.sessionKey);
|
const clientRunId = p.idempotencyKey;
|
||||||
|
chatRunSessions.set(sessionId, {
|
||||||
|
sessionKey: p.sessionKey,
|
||||||
|
clientRunId,
|
||||||
|
});
|
||||||
|
|
||||||
const idem = p.idempotencyKey;
|
const cached = dedupe.get(`chat:${clientRunId}`);
|
||||||
const cached = dedupe.get(`chat:${idem}`);
|
|
||||||
if (cached) {
|
if (cached) {
|
||||||
respond(cached.ok, cached.payload, cached.error, {
|
respond(cached.ok, cached.payload, cached.error, {
|
||||||
cached: true,
|
cached: true,
|
||||||
@@ -1473,26 +1481,30 @@ export async function startGatewayServer(
|
|||||||
deps,
|
deps,
|
||||||
);
|
);
|
||||||
const payload = {
|
const payload = {
|
||||||
runId: sessionId,
|
runId: clientRunId,
|
||||||
status: "ok" as const,
|
status: "ok" as const,
|
||||||
};
|
};
|
||||||
dedupe.set(`chat:${idem}`, { ts: Date.now(), ok: true, payload });
|
dedupe.set(`chat:${clientRunId}`, {
|
||||||
respond(true, payload, undefined, { runId: sessionId });
|
ts: Date.now(),
|
||||||
|
ok: true,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
respond(true, payload, undefined, { runId: clientRunId });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = errorShape(ErrorCodes.UNAVAILABLE, String(err));
|
const error = errorShape(ErrorCodes.UNAVAILABLE, String(err));
|
||||||
const payload = {
|
const payload = {
|
||||||
runId: sessionId,
|
runId: clientRunId,
|
||||||
status: "error" as const,
|
status: "error" as const,
|
||||||
summary: String(err),
|
summary: String(err),
|
||||||
};
|
};
|
||||||
dedupe.set(`chat:${idem}`, {
|
dedupe.set(`chat:${clientRunId}`, {
|
||||||
ts: Date.now(),
|
ts: Date.now(),
|
||||||
ok: false,
|
ok: false,
|
||||||
payload,
|
payload,
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
respond(false, payload, error, {
|
respond(false, payload, error, {
|
||||||
runId: sessionId,
|
runId: clientRunId,
|
||||||
error: formatForLog(err),
|
error: formatForLog(err),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2342,7 +2354,10 @@ export async function startGatewayServer(
|
|||||||
(cfg.inbound?.reply?.session?.mainKey ?? "main").trim() ||
|
(cfg.inbound?.reply?.session?.mainKey ?? "main").trim() ||
|
||||||
"main";
|
"main";
|
||||||
if (requestedSessionKey === mainKey) {
|
if (requestedSessionKey === mainKey) {
|
||||||
chatRunSessions.set(sessionId, requestedSessionKey);
|
chatRunSessions.set(sessionId, {
|
||||||
|
sessionKey: requestedSessionKey,
|
||||||
|
clientRunId: idem,
|
||||||
|
});
|
||||||
bestEffortDeliver = true;
|
bestEffortDeliver = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user