This commit is contained in:
oogway 2026-01-31 00:05:45 +08:00 committed by GitHub
commit 18e9a43d17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 56 additions and 2 deletions

View File

@ -54,8 +54,57 @@ export function handleMessageUpdate(
return; return;
} }
const delta = typeof assistantRecord?.delta === "string" ? assistantRecord.delta : ""; // Explicit null/undefined detection (Issue #4143)
const content = typeof assistantRecord?.content === "string" ? assistantRecord.content : ""; // Some providers (e.g., Kimi 2.5) may return null deltas, causing silent hangs
const deltaRaw = assistantRecord?.delta;
const contentRaw = assistantRecord?.content;
const MAX_CONSECUTIVE_NULLS = 5;
// Check for null/undefined delta in streaming events
if (evtType === "text_delta" && (deltaRaw === null || deltaRaw === undefined)) {
ctx.state.consecutiveNullDeltas++;
ctx.log.warn(
`Received null delta in text_delta event (count: ${ctx.state.consecutiveNullDeltas}/${MAX_CONSECUTIVE_NULLS}). ` +
`This may indicate an API issue with the model provider.`,
);
if (ctx.state.consecutiveNullDeltas >= MAX_CONSECUTIVE_NULLS) {
const error = new Error(
`Model provider returned ${ctx.state.consecutiveNullDeltas} consecutive null deltas. ` +
`This typically indicates an API issue or incompatibility. ` +
`Try a different model or contact the provider.`,
);
error.name = "NullDeltaError";
// Emit error event for better user feedback
emitAgentEvent({
runId: ctx.params.runId,
stream: "error",
data: {
error: error.message,
},
});
throw error;
}
return; // Skip processing this null delta
}
// Reset counter on valid delta
if (evtType === "text_delta" && typeof deltaRaw === "string" && deltaRaw.length > 0) {
ctx.state.consecutiveNullDeltas = 0;
}
// Check for null content in text_end
if (evtType === "text_end" && contentRaw === null && !deltaRaw) {
ctx.log.warn(
`Received null content in text_end event with no delta. ` +
`The model may have failed to generate a response.`,
);
}
const delta = typeof deltaRaw === "string" ? deltaRaw : "";
const content = typeof contentRaw === "string" ? contentRaw : "";
appendRawStream({ appendRawStream({
ts: Date.now(), ts: Date.now(),

View File

@ -58,6 +58,9 @@ export type EmbeddedPiSubscribeState = {
messagingToolSentTargets: MessagingToolSend[]; messagingToolSentTargets: MessagingToolSend[];
pendingMessagingTexts: Map<string, string>; pendingMessagingTexts: Map<string, string>;
pendingMessagingTargets: Map<string, MessagingToolSend>; pendingMessagingTargets: Map<string, MessagingToolSend>;
// Track consecutive null deltas for hang detection (Issue #4143)
consecutiveNullDeltas: number;
}; };
export type EmbeddedPiSubscribeContext = { export type EmbeddedPiSubscribeContext = {

View File

@ -65,6 +65,7 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
messagingToolSentTargets: [], messagingToolSentTargets: [],
pendingMessagingTexts: new Map(), pendingMessagingTexts: new Map(),
pendingMessagingTargets: new Map(), pendingMessagingTargets: new Map(),
consecutiveNullDeltas: 0,
}; };
const assistantTexts = state.assistantTexts; const assistantTexts = state.assistantTexts;
@ -96,6 +97,7 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
state.lastAssistantTextNormalized = undefined; state.lastAssistantTextNormalized = undefined;
state.lastAssistantTextTrimmed = undefined; state.lastAssistantTextTrimmed = undefined;
state.assistantTextBaseline = nextAssistantTextBaseline; state.assistantTextBaseline = nextAssistantTextBaseline;
state.consecutiveNullDeltas = 0; // Reset null delta counter (Issue #4143)
}; };
const rememberAssistantText = (text: string) => { const rememberAssistantText = (text: string) => {