Two things a session could not say, and one it was saying wrongly.
**Every provider setting is reachable.** `-np 1`, the MTP draft depth, the
tool set, the sampling parameters -- most were hardcoded to what measured
best on this machine, which is right as a default and wrong as a constant:
the next machine has a different GPU and a different core count, and
nobody running this app can edit the source. `DriverKind::params` now
declares what a provider takes -- key, label, shape, what blank means, and
whether a change waits for a restart -- and the phone renders whatever
arrives, on the spawn form and in the session settings dialog. Adding a
setting to a driver is one entry in that table and no app change.
`POST /sessions/{id}/params` takes the whole map, so an absent key is the
instruction to unset; the sampling half applies at once and the session is
told in words which of the rest are waiting for a restart.
`tools` is one of them, because it is the biggest lever on a tight
context: the seven built-in definitions are ~1,300 tokens of every prompt
(2,191 against 887 with none). `"none"` omits the flag rather than passing
it on, since `--tools none` is `unknown tool "none"` and a server that
exits.
**The context figure has a denominator.** `Event::ContextWindow` carries
it, read from `llama-server`'s `/props` once the model is up -- the
measurement rather than the request, since a session that named no context
size gets the model's own. Neither coding CLI states its window, so those
keep the bare figure: "2,042" and "2,042 / 8,192" are deliberately
different-looking, and a missing ceiling is never drawn as a proportion of
an assumed one.
**And the numerator was wrong**, by the length of the last reply: it was
the prompt alone, so a five-word answer reported 2,042 against a slot
holding 2,355. It is the turn's total now, which matches `llama-server`'s
own `n_tokens` to within a token.
Two defects the review found, both of which would have shipped: changing
settings on a *stopped* session reported "no process running, so it can't
take new settings", when a stopped session is exactly when you would set
them for the next start; and `GET /tools` answers **403** rather than an
empty list on a server started without `--tools`, so reading it as a
failure made the no-tools session one that never started.
Verified against real models: settings spawned and changed live, the
restart note, a session with two tools and one with none, and the counter
checked against the server's own slot occupancy each time.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
88 lines
3.1 KiB
Kotlin
88 lines
3.1 KiB
Kotlin
package com.example.aiapp
|
|
|
|
import kotlin.test.Test
|
|
import kotlin.test.assertEquals
|
|
|
|
class SessionOrderTest {
|
|
@Test
|
|
fun `running sessions keep the order their agents were turned on in`() {
|
|
val first = session("first", status = "running", started = 10.0, lastActivity = 900.0)
|
|
val second =
|
|
session("second", status = "awaitingInput", started = 20.0, lastActivity = 20.0)
|
|
val third = session("third", status = "waiting", started = 30.0, lastActivity = 500.0)
|
|
|
|
assertEquals(
|
|
listOf("first", "second", "third"),
|
|
sessionsInListOrder(listOf(third, second, first)).map { it.id },
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `a session started again joins the bottom of the running group`() {
|
|
val old = session("old", status = "idle", started = 10.0, lastActivity = 10.0)
|
|
val restarted = session("restarted", status = "idle", started = 99.0, lastActivity = 99.0)
|
|
|
|
assertEquals(
|
|
listOf("old", "restarted"),
|
|
sessionsInListOrder(listOf(restarted, old)).map { it.id },
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `stopped sessions come after the running ones, most recent first`() {
|
|
val running = session("running", status = "idle", started = 100.0, lastActivity = 100.0)
|
|
val stale = session("stale", status = "exited", started = 1.0, lastActivity = 5.0)
|
|
val recent = session("recent", status = "exited", started = 2.0, lastActivity = 50.0)
|
|
|
|
assertEquals(
|
|
listOf("running", "recent", "stale"),
|
|
sessionsInListOrder(listOf(stale, recent, running)).map { it.id },
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A session whose process nobody could ask about still has one, so it stays where it was rather
|
|
* than dropping into the stopped group the moment a machine goes quiet.
|
|
*/
|
|
@Test
|
|
fun `a session of unknown state is one of the running ones`() {
|
|
val unknown = session("unknown", status = "unknown", started = 10.0, lastActivity = 10.0)
|
|
val stopped = session("stopped", status = "exited", started = 5.0, lastActivity = 999.0)
|
|
|
|
assertEquals(
|
|
listOf("unknown", "stopped"),
|
|
sessionsInListOrder(listOf(stopped, unknown)).map { it.id },
|
|
)
|
|
}
|
|
|
|
private fun session(id: String, status: String, started: Double, lastActivity: Double) =
|
|
SessionSummary(
|
|
id = id,
|
|
machine = "machine",
|
|
machineName = "machine",
|
|
provider = "echo",
|
|
title = id,
|
|
model = null,
|
|
keepsOwnTranscript = false,
|
|
ownTranscriptName = null,
|
|
permissionMode = null,
|
|
effort = null,
|
|
takesEffort = false,
|
|
imported = false,
|
|
notify = true,
|
|
autoResume = false,
|
|
autoResumeMessage = DEFAULT_RESUME_MESSAGE,
|
|
resumeAt = null,
|
|
cwd = null,
|
|
contextTokens = null,
|
|
contextLimit = null,
|
|
params = emptyMap(),
|
|
maxImageEdge = null,
|
|
usageProvider = null,
|
|
status = status,
|
|
lastActivity = lastActivity,
|
|
started = started,
|
|
backgroundTasks = 0,
|
|
)
|
|
}
|