TestEmptyReplyNudgeRePromptsThenRecovers: a turn whose first round comes back with no content and no tool call (the dominant silent-death: a thinking model's tool call swallowed into the reasoning channel) must be re-prompted once, not ended silently. Here the second round produces a real summary, s
(t *testing.T)
| 3239 | }, true}, |
| 3240 | {"assistant with summary", []chmctx.Message{ |
| 3241 | {Role: chmctx.RoleAssistant, Content: "done"}, |
| 3242 | }, false}, |
| 3243 | {"assistant with tool call", []chmctx.Message{ |
| 3244 | {Role: chmctx.RoleAssistant, ToolCalls: []chmctx.ToolCall{{Name: "bash"}}}, |
| 3245 | }, false}, |
| 3246 | {"newest is tool result, prior assistant empty", []chmctx.Message{ |
| 3247 | {Role: chmctx.RoleAssistant, Content: ""}, |
| 3248 | {Role: chmctx.RoleTool, Content: "out"}, |
| 3249 | }, true}, |
| 3250 | {"no assistant at all", []chmctx.Message{ |
| 3251 | {Role: chmctx.RoleUser, Content: "go"}, |
| 3252 | }, false}, |
| 3253 | } |
| 3254 | for _, tc := range cases { |
| 3255 | if got := newestAssistantEmpty(tc.hist); got != tc.want { |
| 3256 | t.Errorf("%s: newestAssistantEmpty = %v, want %v", tc.name, got, tc.want) |
| 3257 | } |
| 3258 | } |
| 3259 | } |
| 3260 | |
| 3261 | // TestEmptyReplyNudgeRePromptsThenRecovers: a turn whose first round comes back |
| 3262 | // with no content and no tool call (the dominant silent-death: a thinking |
| 3263 | // model's tool call swallowed into the reasoning channel) must be re-prompted |
| 3264 | // once, not ended silently. Here the second round produces a real summary, so |
| 3265 | // the run self-heals. |
| 3266 | func TestEmptyReplyNudgeRePromptsThenRecovers(t *testing.T) { |
| 3267 | var round int |
| 3268 | handler := func(w http.ResponseWriter, _ *http.Request) { |
| 3269 | w.Header().Set("Content-Type", "text/event-stream") |
| 3270 | round++ |
| 3271 | if round == 1 { |
| 3272 | // Empty assistant message: stop with no content delta, no tool calls. |
| 3273 | fmt.Fprintf(w, "data: %s\n\n", `{"choices":[{"delta":{},"finish_reason":"stop"}],"usage":{"completion_tokens":1}}`) |
| 3274 | fmt.Fprint(w, "data: [DONE]\n\n") |
| 3275 | return |
| 3276 | } |
| 3277 | fmt.Fprintf(w, "data: %s\n\n", `{"choices":[{"delta":{"content":"fixed and verified"}}]}`) |
| 3278 | fmt.Fprintf(w, "data: %s\n\n", `{"choices":[{"delta":{},"finish_reason":"stop"}],"usage":{"completion_tokens":3}}`) |
| 3279 | fmt.Fprint(w, "data: [DONE]\n\n") |
| 3280 | } |
| 3281 | |
| 3282 | m := newTestModel(t, handler) |
| 3283 | final := drainFinal(t, m, "build it") |
| 3284 | |
| 3285 | if round != 2 { |
nothing calls this directly
no test coverage detected