Declare provider settings, and give the context figure a denominator
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>
This commit is contained in:
1 parent
ac476ab0c9
commit
81ab564a09
18 files changed
+831
-115
No files matched your search
@@ -233,6 +233,8 @@ fun SessionScreen(
|
||||
// that was measured.
|
||||
var contextTokens by
|
||||
remember(address) { mutableStateOf(if (isSubagent) null else summary.contextTokens) }
|
||||
var contextLimit by
|
||||
remember(address) { mutableStateOf(if (isSubagent) null else summary.contextLimit) }
|
||||
// When the current compaction started. The moment comes off the `compacting` status event
|
||||
// itself -- the server timestamps every transcript line -- rather than off this device noticing
|
||||
// one, which is what makes it survive leaving the session and reopening it.
|
||||
@@ -304,6 +306,14 @@ fun SessionScreen(
|
||||
// hardcoded list is a claim about a machine.
|
||||
var offeredModels by remember { mutableStateOf<List<OfferedModel>>(emptyList()) }
|
||||
var offeredPermissionModes by remember { mutableStateOf<List<String>>(emptyList()) }
|
||||
// The settings this session's provider takes, and what they are set to. The specs come from
|
||||
// the provider and the values from the session, because "what can be set" and "what is set"
|
||||
// are different questions with different answers.
|
||||
var paramSpecs by remember { mutableStateOf<List<ParamSpec>>(emptyList()) }
|
||||
var params by remember(summary.id) { mutableStateOf(summary.params) }
|
||||
// What was last successfully saved, so the debounce below knows whether there is anything to
|
||||
// send -- and so a failed save can put the controls back to what the server actually holds.
|
||||
var savedParams by remember(summary.id) { mutableStateOf(summary.params) }
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
// The resume cursor, written from the stream's IO thread.
|
||||
val lastSeq = remember { AtomicLong(0) }
|
||||
@@ -489,6 +499,7 @@ fun SessionScreen(
|
||||
// Before the rest, and for every event rather than only the usage ones: a compaction and a
|
||||
// clear move this as much as a turn does. See `contextAfter`.
|
||||
contextTokens = contextAfter(contextTokens, entry.event)
|
||||
contextLimit = contextLimitAfter(contextLimit, entry.event)
|
||||
when (val event = entry.event) {
|
||||
// Nothing further: what it carries was folded into the context above.
|
||||
is SessionEvent.UsageDelta -> {}
|
||||
@@ -1205,6 +1216,30 @@ fun SessionScreen(
|
||||
fun label(id: String?): String =
|
||||
offeredModels.firstOrNull { it.id == id }?.label ?: modelLabel(id)
|
||||
|
||||
/**
|
||||
* Saves edited provider settings once the typing stops.
|
||||
*
|
||||
* Debounced rather than sent per keystroke, because a text field over the tunnel would be a
|
||||
* round trip and a config write per character. Here rather than in the settings dialog for the
|
||||
* reason the delay exists at all: the dialog can be dismissed mid-edit, and this screen
|
||||
* outlives it, so the last value typed is still saved.
|
||||
*/
|
||||
LaunchedEffect(params) {
|
||||
if (params == savedParams) return@LaunchedEffect
|
||||
delay(PARAM_SAVE_DELAY_MS)
|
||||
val wanted = params
|
||||
try {
|
||||
withContext(Dispatchers.IO) { setSessionParams(settings, summary.id, wanted) }
|
||||
savedParams = wanted
|
||||
actionError = null
|
||||
} catch (e: ApiException) {
|
||||
// Back to what the server holds. A control left showing a value that was refused is
|
||||
// stating something untrue about the session.
|
||||
params = savedParams
|
||||
actionError = e.message
|
||||
}
|
||||
}
|
||||
|
||||
// Only for the model picker, which a subagent does not have.
|
||||
if (!isSubagent) {
|
||||
LaunchedEffect(summary.machine, summary.provider) {
|
||||
@@ -1218,6 +1253,7 @@ fun SessionScreen(
|
||||
}
|
||||
.getOrNull()
|
||||
offeredPermissionModes = provider?.permissionModes.orEmpty()
|
||||
paramSpecs = provider?.params.orEmpty()
|
||||
offeredModels =
|
||||
provider
|
||||
?.let {
|
||||
@@ -1916,6 +1952,7 @@ fun SessionScreen(
|
||||
status = status,
|
||||
compactingFor = compactingFor,
|
||||
contextTokens = contextTokens,
|
||||
contextLimit = contextLimit,
|
||||
backgroundTasks = backgroundTasks,
|
||||
subagent = isSubagent,
|
||||
)
|
||||
@@ -2197,6 +2234,9 @@ fun SessionScreen(
|
||||
effort = effort.takeIf { summary.takesEffort },
|
||||
takesEffort = summary.takesEffort,
|
||||
onEffortChanged = { effort = it },
|
||||
paramSpecs = paramSpecs,
|
||||
params = params,
|
||||
onParamsChanged = { params = it },
|
||||
cachedBytes = cachedBytes,
|
||||
// The purge finishes before the epoch moves, because the relaunched opening effect
|
||||
// reads the same directory and would otherwise draw what is about to be deleted. The
|
||||
@@ -2473,6 +2513,8 @@ private fun SessionStatusRow(
|
||||
compactingFor: Long?,
|
||||
/** Context the session is holding, or null where nothing has measured it. */
|
||||
contextTokens: Long?,
|
||||
/** What that is out of, or null where the provider does not say. */
|
||||
contextLimit: Long?,
|
||||
/** Provider-reported live background work; zero is deliberately not drawn. */
|
||||
backgroundTasks: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -2577,7 +2619,7 @@ private fun SessionStatusRow(
|
||||
// reports usage, and one that has not run a turn all showed nothing at all, which reads as
|
||||
// a conversation with room to spare.
|
||||
Text(
|
||||
contextTokens?.let { "context ${tokens(it)}" } ?: "context unknown",
|
||||
contextLabel(contextTokens, contextLimit),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user