TestTurnEndsWhenAssistantEmitsNoToolCalls pins the turn-end contract: a final assistant message with no tool calls returns to phaseIdle and hands control back: no nudge, no forced tool, no extra message appended to history.
(t *testing.T)
| 1122 | // assistant message with no tool calls returns to phaseIdle and hands control |
| 1123 | // back: no nudge, no forced tool, no extra message appended to history. |
| 1124 | func TestTurnEndsWhenAssistantEmitsNoToolCalls(t *testing.T) { |
| 1125 | m := newTestModel(t, func(w http.ResponseWriter, _ *http.Request) { |
| 1126 | w.Header().Set("Content-Type", "text/event-stream") |
| 1127 | fmt.Fprintf(w, "data: %s\n\n", `{"choices":[{"delta":{"content":"all done, nothing to run"}}]}`) |
| 1128 | fmt.Fprintf(w, "data: %s\n\n", `{"choices":[{"delta":{},"finish_reason":"stop"}],"usage":{"completion_tokens":4}}`) |
| 1129 | fmt.Fprint(w, "data: [DONE]\n\n") |
| 1130 | }) |
| 1131 | mm, cmd := m.submit("just answer", "just answer", promptEntry{display: "just answer"}) |
| 1132 | out, _ := drain(mm, cmd) |
| 1133 | final := out.(Model) |
| 1134 | |
| 1135 | if final.phase.active() { |
| 1136 | t.Fatalf("no-tool-call turn must return to idle, phase=%v", final.phase) |
| 1137 | } |
| 1138 | // History should be exactly: user prompt + assistant reply. No nudge |
| 1139 | // message of any role appended. |
| 1140 | if len(final.history) != 2 { |
| 1141 | t.Fatalf("expected 2 history messages (user + assistant), got %d: %+v", |
| 1142 | len(final.history), final.history) |
| 1143 | } |
| 1144 | if final.history[0].Role != chmctx.RoleUser { |
| 1145 | t.Fatalf("history[0] should be the user prompt, got %+v", final.history[0]) |
| 1146 | } |
| 1147 | if final.history[1].Role != chmctx.RoleAssistant { |
| 1148 | t.Fatalf("history[1] should be the assistant reply, got %+v", final.history[1]) |
| 1149 | } |
| 1150 | if !strings.Contains(stripANSI(final.scroll.String()), "all done, nothing to run") { |
| 1151 | t.Fatalf("assistant content missing from scroll:\n%s", final.scroll.String()) |
| 1152 | } |
| 1153 | } |
| 1154 | |
| 1155 | // TestHandleStreamClosedEndsTurnWithNoPending: handleStreamClosed with an empty |
| 1156 | // pending queue finalizes the turn, returns to idle, and returns no follow-up |