





















@@ -30,6 +30,7 @@ import {
3030STRICT_AGENTIC_BLOCKED_TEXT,
3131resolveReplayInvalidFlag,
3232resolveRunLivenessState,
33+shouldTreatEmptyAssistantReplyAsSilent,
3334} from "./run/incomplete-turn.js";
3435import type { EmbeddedRunAttemptResult } from "./run/types.js";
3536@@ -1151,6 +1152,138 @@ describe("runEmbeddedPiAgent incomplete-turn safety", () => {
11511152expect(DEFAULT_EMPTY_RESPONSE_RETRY_LIMIT).toBe(1);
11521153});
115311541155+it("treats clean empty assistant turns as silent only when the caller allows it", () => {
1156+const attempt = makeAttemptResult({
1157+assistantTexts: [],
1158+lastAssistant: {
1159+role: "assistant",
1160+stopReason: "stop",
1161+provider: "openai-codex",
1162+model: "gpt-5.5",
1163+content: [{ type: "text", text: "" }],
1164+} as unknown as EmbeddedRunAttemptResult["lastAssistant"],
1165+});
1166+1167+expect(
1168+shouldTreatEmptyAssistantReplyAsSilent({
1169+allowEmptyAssistantReplyAsSilent: true,
1170+payloadCount: 0,
1171+aborted: false,
1172+timedOut: false,
1173+ attempt,
1174+}),
1175+).toBe(true);
1176+expect(
1177+shouldTreatEmptyAssistantReplyAsSilent({
1178+allowEmptyAssistantReplyAsSilent: false,
1179+payloadCount: 0,
1180+aborted: false,
1181+timedOut: false,
1182+ attempt,
1183+}),
1184+).toBe(false);
1185+});
1186+1187+it("does not treat error or side-effect empty turns as silent", () => {
1188+const errorAttempt = makeAttemptResult({
1189+assistantTexts: [],
1190+lastAssistant: {
1191+role: "assistant",
1192+stopReason: "error",
1193+provider: "openai-codex",
1194+model: "gpt-5.5",
1195+content: [],
1196+} as unknown as EmbeddedRunAttemptResult["lastAssistant"],
1197+});
1198+const sideEffectAttempt = makeAttemptResult({
1199+assistantTexts: [],
1200+didSendViaMessagingTool: true,
1201+messagingToolSentTexts: ["sent already"],
1202+lastAssistant: {
1203+role: "assistant",
1204+stopReason: "stop",
1205+provider: "openai-codex",
1206+model: "gpt-5.5",
1207+content: [{ type: "text", text: "" }],
1208+} as unknown as EmbeddedRunAttemptResult["lastAssistant"],
1209+});
1210+1211+expect(
1212+shouldTreatEmptyAssistantReplyAsSilent({
1213+allowEmptyAssistantReplyAsSilent: true,
1214+payloadCount: 0,
1215+aborted: false,
1216+timedOut: false,
1217+attempt: errorAttempt,
1218+}),
1219+).toBe(false);
1220+expect(
1221+shouldTreatEmptyAssistantReplyAsSilent({
1222+allowEmptyAssistantReplyAsSilent: true,
1223+payloadCount: 0,
1224+aborted: false,
1225+timedOut: false,
1226+attempt: sideEffectAttempt,
1227+}),
1228+).toBe(false);
1229+});
1230+1231+it("returns NO_REPLY without retrying clean empty assistant turns when silence is allowed", async () => {
1232+mockedClassifyFailoverReason.mockReturnValue(null);
1233+mockedRunEmbeddedAttempt.mockResolvedValue(
1234+makeAttemptResult({
1235+assistantTexts: [],
1236+lastAssistant: {
1237+role: "assistant",
1238+stopReason: "stop",
1239+provider: "openai-codex",
1240+model: "gpt-5.5",
1241+content: [{ type: "text", text: "" }],
1242+} as unknown as EmbeddedRunAttemptResult["lastAssistant"],
1243+}),
1244+);
1245+1246+const result = await runEmbeddedPiAgent({
1247+ ...overflowBaseRunParams,
1248+allowEmptyAssistantReplyAsSilent: true,
1249+provider: "openai-codex",
1250+model: "gpt-5.5",
1251+runId: "run-empty-assistant-silent",
1252+});
1253+1254+expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(1);
1255+expect(result.payloads).toEqual([{ text: "NO_REPLY" }]);
1256+expect(result.meta.terminalReplyKind).toBe("silent-empty");
1257+expect(result.meta.livenessState).toBe("working");
1258+});
1259+1260+it("keeps retrying and surfacing clean empty assistant turns without the silence flag", async () => {
1261+mockedClassifyFailoverReason.mockReturnValue(null);
1262+mockedRunEmbeddedAttempt.mockResolvedValue(
1263+makeAttemptResult({
1264+assistantTexts: [],
1265+lastAssistant: {
1266+role: "assistant",
1267+stopReason: "stop",
1268+provider: "openai",
1269+model: "gpt-5.4",
1270+content: [{ type: "text", text: "" }],
1271+} as unknown as EmbeddedRunAttemptResult["lastAssistant"],
1272+}),
1273+);
1274+1275+const result = await runEmbeddedPiAgent({
1276+ ...overflowBaseRunParams,
1277+provider: "openai",
1278+model: "gpt-5.4",
1279+runId: "run-empty-assistant-error",
1280+});
1281+1282+expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(2);
1283+expect(result.payloads?.[0]?.isError).toBe(true);
1284+expect(result.payloads?.[0]?.text).toContain("couldn't generate a response");
1285+});
1286+11541287it("detects generic empty Gemini turns without visible text", () => {
11551288const retryInstruction = resolveEmptyResponseRetryInstruction({
11561289provider: "google-vertex",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。