Draw a model's thinking, and what a reply cost to produce
A llama.cpp session's `reasoning_content` becomes `Event::Thinking` deltas closed by an `Event::ThinkingDone` carrying the span the driver measured, and the phone draws it as a card of its own: "Thinking" with the spinner a running command has, then "Thought for 12.4s". Deliberately not a tool call, so a run of calls cannot collapse the reasoning into "Called 6 tools"; the reasoning is also kept out of the next prompt, which `conversation` already ignored. `UsageDelta` gains `tokensPerSecond`, the provider's own figure or nothing -- llama.cpp reports `timings.predicted_per_second` and the coding CLIs report no such thing -- and a finished reply carries a small line under it saying when it was sent and, where there is one, how fast it came out: "3:00 PM · 149 tok/s". The compact usage bar drops the provider's name for the window and puts its length after the time left instead: "42% · 3h 20m left / 5h". Three things that had to come with it: the transcript coalesces runs of thinking deltas as it does reply deltas, so one block is one row of a page rather than a page of its own; `joinPages` welds a block cut by a page boundary (`healSplitThinking`), since the half with no ending spun for ever; and `UsageDelta` now reaches the fold, which is what carries the rate to the reply. Verified on the emulator against a real Qwen3-0.6B session and the echo rig's new `/think [seconds]`: the spinner while it runs, "Thought for 1.4s" and "2:54 PM · 149 tok/s" after, the reasoning on tapping the card, and the usage bar reading "42% · 3h 19m left / 5h". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
45f249ae91
commit
bb5ac1a242
18 files changed
+900
-101
No files matched your search
@@ -135,6 +135,19 @@ seq N", so there is no separate history path to drift from the live one.
|
||||
`Patch { diff }`. Patch success boilerplate is omitted and failures remain
|
||||
as output. This normalization belongs in the drivers, before persistence;
|
||||
the phone never decodes a provider's tool schema.
|
||||
- `Thinking { delta }` / `ThinkingDone { ms }` (2026-09-19) — the model's
|
||||
working, streamed the way its reply is, and its own kind because it is not
|
||||
what the session *said*: the phone draws it as a card of its own, shut, and
|
||||
no driver folds it back into the next prompt. Only a provider that actually
|
||||
streams its reasoning sends it — llama.cpp does, as `reasoning_content`;
|
||||
nothing is inferred for one that does not, since a card that appeared
|
||||
whenever a turn was slow would be a guess wearing a measurement's clothes.
|
||||
The duration is **measured by the driver**, because a reader only knows when
|
||||
an event arrived: the last fragment of a block followed by a slow tool call
|
||||
is indistinguishable from thinking that went on that long. A block with no
|
||||
`ThinkingDone` is one still being thought, which is what the card's spinner
|
||||
says; one closed by the turn ending without a duration says "Thought" and
|
||||
names no span rather than inventing one.
|
||||
- `Image { ref }` — saved under the session dir, fetched by URL.
|
||||
- `Question { id, prompt, options }` — anything needing a human. Claude's
|
||||
AskUserQuestion and permission requests (canUseTool) are the same shape;
|
||||
@@ -149,8 +162,13 @@ seq N", so there is no separate history path to drift from the live one.
|
||||
waiting for itself and will speak again with nobody having typed anything.
|
||||
Reporting it as idle sent a "finished" notification at the one moment that
|
||||
was untrue.
|
||||
- `UsageDelta { tokens, context }` — what a turn cost and how much the model
|
||||
was holding when it ended. `context` is prompt plus both cache figures,
|
||||
- `UsageDelta { tokens, context, tokensPerSecond }` — what a turn cost, how
|
||||
much the model was holding when it ended, and how fast it was generated.
|
||||
`tokensPerSecond` (2026-09-19) is the provider's own measurement or nothing:
|
||||
llama.cpp reports `timings.predicted_per_second`, and the coding CLIs report
|
||||
no such figure, so dividing what this server watched a reply arrive over
|
||||
would count the network, the tool calls and the reader's own permission
|
||||
answers as generation. The phone draws it under the reply it measured. `context` is prompt plus both cache figures,
|
||||
taken from the **last assistant message** rather than the turn's `result`:
|
||||
measured 2026-08-30 against CLI 2.1.237, the result adds a turn's messages
|
||||
up, so its cache read of 40,211 was the same conversation counted twice.
|
||||
@@ -779,6 +797,11 @@ nothing at all for it — not a zero, and not "unknown".
|
||||
The session's usage dialog applies the same machine-and-provider match and
|
||||
shows every billing pool for that provider; it does not turn opening one
|
||||
session into a comparison with the other providers on that machine.
|
||||
The compact bar names no window (2026-09-19): the provider's own name for it
|
||||
("5-hour window") became a denominator after the span instead — "42% · 3h 20m
|
||||
left / 5h" — which says in one reading both how much of the cycle is to come
|
||||
and which cycle it is. Where the provider reported no duration there is
|
||||
nothing after the span, since the name it gave is not a measurement of one.
|
||||
For a provider with several pools, the compact bar selects the pool named by
|
||||
the session's model (including Luna's `gpt-reserve` name), falling back to the
|
||||
provider's generic pool, and shows the shortest cycle that pool actually
|
||||
@@ -1237,8 +1260,25 @@ dev-updater (Kotlin 2.4.x, CMP 1.11.x, JDK 21).
|
||||
decision (`groupToolRuns`) and a cut run's pieces are keyed there — the
|
||||
first piece keeps the run's name, since that name is what survives a
|
||||
page of history landing in front of it.
|
||||
- **The model's working is a card of its own** (2026-09-19) — "Thinking"
|
||||
with the same spinner a running command has while it goes, and "Thought
|
||||
for 12.4s" once it is over. Deliberately not a tool call, because a run
|
||||
of tool calls collapses into "Called 6 tools" and the reasoning would be
|
||||
filed as one of them; it therefore also breaks a run, which is right —
|
||||
the model stopped to think in the middle of it. A block cut by a page
|
||||
boundary is welded like a reply is (`healSplitThinking`), since the half
|
||||
with no ending would otherwise spin for the rest of the conversation.
|
||||
- **A finished reply carries a line under it saying when it was sent and,
|
||||
where the provider measured one, how fast it was generated** (2026-09-19)
|
||||
— "3:00 PM · 149 tok/s", small and set back, right-aligned because it
|
||||
closes the message rather than opening one. The time is the transcript's
|
||||
own timestamp, so every device draws the same one; the rate is the
|
||||
provider's own figure or nothing at all. It is a list unit of its own
|
||||
(`ReplyFoot`), because a settled reply *is* its blocks and there is no
|
||||
row left to hang it on.
|
||||
- **Anything that is a note *about* the conversation rather than a turn in
|
||||
it is closed by default** — a tool call, a peer message, a memory note.
|
||||
it is closed by default** — a tool call, a peer message, a memory note,
|
||||
a thinking block.
|
||||
Open-ness is the screen's, never the card's: a card that remembered for
|
||||
itself forgets the moment the lazy list stops composing it, so a note
|
||||
opened and scrolled past would shut behind the reader.
|
||||
|
||||
Reference in new issue
Block a user