feat: improve session status handling and SSE batching (#360)
* refactor: improve event stream session-id parsing and SSE batching * feat: improve event streaming and server session polling
This commit is contained in:
committed by
GitHub
parent
3a3604881e
commit
681918a089
@@ -639,7 +639,7 @@ export const useEventStream = () => {
|
||||
|
||||
case 'session.status':
|
||||
{
|
||||
const sessionId = typeof props.sessionID === 'string' ? props.sessionID : null;
|
||||
const sessionId = readStringProp(props, ['sessionID', 'sessionId']);
|
||||
const statusObj = (typeof props.status === 'object' && props.status !== null) ? props.status as Record<string, unknown> : null;
|
||||
const statusType = typeof statusObj?.type === 'string' ? statusObj.type : null;
|
||||
const statusInfo = statusObj ?? {};
|
||||
@@ -664,7 +664,7 @@ export const useEventStream = () => {
|
||||
|
||||
case 'openchamber:session-status':
|
||||
{
|
||||
const sessionId = typeof props.sessionId === 'string' ? props.sessionId : null;
|
||||
const sessionId = readStringProp(props, ['sessionId', 'sessionID']);
|
||||
const status = typeof props.status === 'string' ? props.status : null;
|
||||
const needsAttention = typeof props.needsAttention === 'boolean' ? props.needsAttention : false;
|
||||
const timestamp = typeof props.timestamp === 'number' ? props.timestamp : Date.now();
|
||||
@@ -784,17 +784,38 @@ export const useEventStream = () => {
|
||||
// Fallback: if we see assistant parts but session.status hasn't arrived yet, mark busy.
|
||||
if (roleInfo === 'assistant') {
|
||||
const partType = (messagePart as { type?: unknown }).type;
|
||||
const isStreamingPart =
|
||||
partType === 'step-start' ||
|
||||
partType === 'text' ||
|
||||
partType === 'tool' ||
|
||||
partType === 'reasoning' ||
|
||||
partType === 'file' ||
|
||||
partType === 'patch';
|
||||
const partTime = (messagePart as { time?: { end?: unknown } }).time;
|
||||
const partHasEnded = typeof partTime?.end === 'number';
|
||||
const toolState = (messagePart as { state?: { status?: unknown } }).state?.status;
|
||||
const textContent = (messagePart as { text?: unknown }).text;
|
||||
|
||||
const isStreamingPart = (() => {
|
||||
if (partType === 'tool') {
|
||||
return toolState === 'running' || toolState === 'pending';
|
||||
}
|
||||
if (partType === 'reasoning') {
|
||||
return !partHasEnded;
|
||||
}
|
||||
if (partType === 'text') {
|
||||
const hasText = typeof textContent === 'string' && textContent.trim().length > 0;
|
||||
return hasText && !partHasEnded;
|
||||
}
|
||||
if (partType === 'step-start') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})();
|
||||
|
||||
if (isStreamingPart) {
|
||||
const currentStatus = useSessionStore.getState().sessionStatus?.get(sessionId);
|
||||
const recentlyConfirmedIdle =
|
||||
currentStatus?.type === 'idle' &&
|
||||
typeof currentStatus.confirmedAt === 'number' &&
|
||||
Date.now() - currentStatus.confirmedAt < 1200;
|
||||
if (!currentStatus || currentStatus.type === 'idle') {
|
||||
if (recentlyConfirmedIdle) {
|
||||
break;
|
||||
}
|
||||
updateSessionStatus(sessionId, { type: 'busy' }, 'sse:message.part.updated');
|
||||
}
|
||||
}
|
||||
@@ -1057,13 +1078,15 @@ export const useEventStream = () => {
|
||||
const hasParts = partsArray.length > 0;
|
||||
const timeObj = (messageExt as { time?: { completed?: number } }).time || {};
|
||||
const completedFromServer = typeof timeObj?.completed === 'number';
|
||||
|
||||
if (!hasParts && !completedFromServer) break;
|
||||
|
||||
const rawStatus = (message as { status?: unknown }).status;
|
||||
const status = typeof rawStatus === 'string' ? rawStatus.toLowerCase() : null;
|
||||
const hasCompletedStatus = status === 'completed' || status === 'complete';
|
||||
const finishCandidate = (message as { finish?: unknown }).finish;
|
||||
const finish = typeof finishCandidate === 'string' ? finishCandidate : null;
|
||||
const eventHasStopFinish = finish === 'stop';
|
||||
|
||||
if (!hasParts && !completedFromServer && !hasCompletedStatus && !eventHasStopFinish) break;
|
||||
|
||||
if ((messageExt as { role?: unknown }).role === 'assistant' && hasParts) {
|
||||
const incomingLen = computeTextLength(partsArray);
|
||||
const wouldShrink = existingLen > 0 && incomingLen + TEXT_SHRINK_TOLERANCE < existingLen;
|
||||
@@ -1111,7 +1134,7 @@ export const useEventStream = () => {
|
||||
|
||||
const shouldFinalizeAssistantMessage =
|
||||
(message as { role?: string }).role === 'assistant' &&
|
||||
(hasCompletedTimestamp || stopMarkerPresent);
|
||||
(hasCompletedTimestamp || hasCompletedStatus || stopMarkerPresent);
|
||||
|
||||
if (shouldFinalizeAssistantMessage && (message as { role?: string }).role === 'assistant') {
|
||||
|
||||
@@ -1741,6 +1764,7 @@ export const useEventStream = () => {
|
||||
|
||||
clearPauseTimeout();
|
||||
maybeBootstrapIfStale('visibility_restore');
|
||||
triggerSessionStatusPoll();
|
||||
|
||||
const isStalled = Date.now() - lastEventTimestampRef.current > 45000;
|
||||
if (isStalled) {
|
||||
@@ -1769,6 +1793,7 @@ export const useEventStream = () => {
|
||||
if (visibilityStateRef.current === 'visible') {
|
||||
clearPauseTimeout();
|
||||
maybeBootstrapIfStale('window_focus');
|
||||
triggerSessionStatusPoll();
|
||||
|
||||
const isStalled = Date.now() - lastEventTimestampRef.current > 45000;
|
||||
if (isStalled) {
|
||||
@@ -1859,7 +1884,7 @@ export const useEventStream = () => {
|
||||
);
|
||||
|
||||
if (hasBusySessions) {
|
||||
// Removed: void refreshSessionStatus();
|
||||
triggerSessionStatusPoll();
|
||||
}
|
||||
if (now - lastEventTimestampRef.current > 45000) {
|
||||
Promise.resolve().then(async () => {
|
||||
|
||||
Reference in New Issue
Block a user