GCWing / GCWing/OpenBitFun

[Bug]: Incomplete assistant reply is persisted as `complete` when the model output stream ends prematurely

Đang mở
#1,980 0 bình luận 0 reaction 1 người được giao Được @wsp1911 nhận Xem trên GitHub
bug
Ngôn ngữ chính
Rust
Star
2.3k
Fork
231
Merge trung bình
2 giờ 46 phút
Pull request đã merge (30 ngày)
577

Mô tả

### Summary

An assistant reply stopped mid-generation (the text ends at the title of list item 4, with the markdown list left unclosed), but the session persistence layer recorded the turn as `finishReason=complete` / `status=completed` / `hasFinalResponse=true`. The UI shows no "reply may be incomplete" indication, so the user cannot distinguish an intentional stop from a transport/generation failure. This is a silent failure with the same observability gap as session freeze / review freeze issues.

**Expected behavior**: when the model output stream ends prematurely (provider-side interruption, timeout, connection drop, or max_tokens cut), the session pipeline should record a non-`complete` finish reason (`length` / `interrupted` / `error`), persist the turn as incomplete, and let the UI surface a hint (e.g. "reply may be incomplete") instead of silently marking it complete.

### Area

Agent runtime / core (session persistence and `finishReason` determination); AI provider / model adapter (premature stream termination).

### Reproduction or evidence

Hard to reproduce deterministically (it depends on the underlying model stream ending early), but this is a real occurrence with complete local evidence from the persisted turn data (no private data included):

1. **Phenomenon**: the assistant reply was cut mid-sentence — the text stops at `4. **压缩/总结(co`, right after the "能结构性解决" table and list items 1-3 of "解决不了的(方案边界)". Item 4 has a title only, no body; the markdown list is unclosed.

2. **Persisted evidence** (local `sessions//turns/turn-0004.json`):
- `textItems[0].content` is only **865 characters**, ending at `4. **压缩/总结(co`;
- the same modelRound's `thinkingItems` are **complete**, containing the full analysis for items 8, 9, 10 and the conclusion — proving the model finished reasoning and had a full answer ready, but the text stream terminated mid-generation;
- the turn records `finishReason=complete`, `status=completed`, `hasFinalResponse=true` — no truncation or error marker at all;
- `tokenUsage.outputTokens=1776` (including thinking); the text is only a fraction of that, so the output stream stopped far earlier than a normal completion point.

3. **Control group**: other turns in the same session with much longer replies (e.g. 4908 and 2787 characters) completed normally. This shows it is not "long replies always get cut"; it is an intermittent premature stream termination.

### Suggested fix

1. When the response stream ends early (missing stream-end frame, `finish_reason` = `length`/`interrupted`, or visibly unclosed text), mark the round as `interrupted`/`incomplete` instead of `complete`.
2. Keep `finishReason` semantics distinct: `complete` / `length` / `interrupted` / `error`, and retain the termination reason in the round state or `tokenUsage`.
3. Surface a "reply may be incomplete" hint in the UI and allow the user to regenerate or continue.
4. Optionally add a persistence-layer integrity check (e.g. warn when thinking is complete but the text is clearly truncated).

### Environment, if relevant

- BitFun v0.2.15 (2026-07-31 release), Windows desktop app, built-in personal-assistant session.
- Not tied to a specific model/provider; any provider stream that ends early during a long output can trigger this.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.