1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! Token-by-token streaming for [`crate::ModelAdapter`].
//!
//! `complete()` is the blocking, whole-reply-at-once call. Streaming is the
//! *other* shape a chat UI needs: on a phone doing a few tokens per second
//! (see `temp_ai_design.md` §10.4), you cannot freeze the screen on
//! `complete()` while the model grinds — you show each token the moment it
//! lands. [`crate::ModelAdapter::stream`] is that shape.
//!
//! # The concurrency discipline — one channel, one background actor
//!
//! `stream` hands back a [`std::sync::mpsc::Receiver`] and the *producing*
//! work runs on a **background thread** that owns its side of the channel —
//! plain std threads + `mpsc`, **no async runtime**. This is exactly the
//! actor shape KOPITIAM already committed to in **AID-0028** (the async LSP
//! session actor): a worker owns a resource, streams results out over a
//! channel, and never blocks the foreground. We reuse that discipline here
//! rather than inventing a second concurrency model — the caller's UI thread
//! stays free to render while the model thread produces.
//!
//! The contract the caller can rely on, for *every* adapter:
//!
//! * chunks arrive **in order**;
//! * a run ends with **exactly one terminal chunk** — either
//! [`StreamChunk::Done`] (clean finish) or [`StreamChunk::Error`] (gave up
//! partway) — and **nothing follows it**;
//! * when the producer thread finishes, its `Sender` drops, so iterating the
//! `Receiver` (`for chunk in rx`) ends naturally after the terminal chunk.
//!
//! Because the terminal chunk is always present, a consumer never has to
//! distinguish "stream ended cleanly" from "sender was dropped mid-reply" by
//! guesswork — the [`StreamChunk::Done`] vs [`StreamChunk::Error`] tells it
//! outright.
use Receiver;
use crate::;
/// One piece of a streamed model reply, delivered in order over the
/// [`Receiver`] that [`ModelAdapter::stream`] returns.
///
/// **This enum is a frozen contract** — every adapter (local, echo, cloud,
/// and anything built later) emits exactly these three variants, and every
/// consumer (the `kopitiam ai chat` loop today, a ratatui pane tomorrow)
/// matches on exactly these three. Don't add or rename variants without
/// treating it as the breaking change it is.
///
/// A well-formed stream is: zero or more [`StreamChunk::Token`], then
/// **one** terminal chunk — [`StreamChunk::Done`] on success or
/// [`StreamChunk::Error`] on failure — then nothing (the sender drops,
/// closing the channel).
/// Runs `adapter.complete(request)` to completion and *then* pushes the whole
/// reply into a fresh channel as a single [`StreamChunk::Token`] followed by
/// [`StreamChunk::Done`] (or a lone [`StreamChunk::Error`] on failure),
/// returning the already-populated [`Receiver`].
///
/// This is the **eager, non-streaming fallback** [`ModelAdapter::stream`]'s
/// default body uses: it honours the [`StreamChunk`] contract exactly (a
/// `Token` then a terminal chunk, in order) so a caller can treat *any*
/// adapter uniformly as a stream — but it does **not** deliver tokens as they
/// are produced and it does **not** run on a background thread, because a
/// blocking-only backend has nothing to stream incrementally. Adapters that
/// *can* produce tokens one at a time (see [`crate::EchoAdapter`],
/// [`crate::LocalAdapter`]) override `stream` with a real background actor
/// instead of leaning on this.
///
/// It's generic over `A: ModelAdapter + ?Sized` so the trait's default
/// `stream` body can pass `self` (a `&Self`, which is not `Sized` inside a
/// default method) straight in, *and* a `&dyn ModelAdapter` still works —
/// while [`ModelAdapter`] stays object-safe (the default body references no
/// type parameter of its own).
pub Sized>