Bug
`GenericProvider._stream` defaults `stop_reason = "stop"` (`pycodeloop/providers/generic.py:386`), only overwritten if a chunk carries `finish_reason` (lines 452-453). The `for raw_line in response:` loop (line 390) just ends normally if the underlying connection drops mid-generation without the socket raising an explicit error (e.g. server closes abruptly, no `[DONE]`, no `finish_reason` chunk).
Impact
Not message loss in the strict sense, but silent corruption: a response truncated by a dropped connection gets persisted to session history labeled `stop_reason="stop"`, indistinguishable from the model finishing on its own. Future turns build on a "complete" assistant message that's actually cut off mid-thought, which can confuse the model or produce incoherent follow-ups without any visible error.
Fix
Track whether the stream loop exited via a `finish_reason`/`[DONE]` marker vs. the iterator simply running out, and surface the difference (e.g. a distinct `stop_reason` like `"connection_lost"`, or raise so the caller's retry logic — the same path already used for `TimeoutError`/`ConnectionError` — can kick in) instead of defaulting to `"stop"`.
Bug
`GenericProvider._stream` defaults `stop_reason = "stop"` (`pycodeloop/providers/generic.py:386`), only overwritten if a chunk carries `finish_reason` (lines 452-453). The `for raw_line in response:` loop (line 390) just ends normally if the underlying connection drops mid-generation without the socket raising an explicit error (e.g. server closes abruptly, no `[DONE]`, no `finish_reason` chunk).
Impact
Not message loss in the strict sense, but silent corruption: a response truncated by a dropped connection gets persisted to session history labeled `stop_reason="stop"`, indistinguishable from the model finishing on its own. Future turns build on a "complete" assistant message that's actually cut off mid-thought, which can confuse the model or produce incoherent follow-ups without any visible error.
Fix
Track whether the stream loop exited via a `finish_reason`/`[DONE]` marker vs. the iterator simply running out, and surface the difference (e.g. a distinct `stop_reason` like `"connection_lost"`, or raise so the caller's retry logic — the same path already used for `TimeoutError`/`ConnectionError` — can kick in) instead of defaulting to `"stop"`.