Add used/full context meter to chat screen (#717)

* Adopt /api/health REST route (issue #713)

Cheap liveness probe on the connection screen: GET /api/health surfaces
backend version + auth mode (Token vs Gated/OAuth) without the heavier
/api/status payload. Added HermesApiService.getHealth + HealthStatus model;
ConnectViewModel.loadHealth probes on open (best-effort, silent on failure);
ConnectScreen shows a read-only health card when reachable.

Verified: ktlint clean, assembleDebug BUILD SUCCESSFUL locally.

* Add used/full context meter to chat screen

Shows session prompt tokens vs model context window above the composer,
polled every 5s. Color-coded bar warns at 70%/90%.

* fix(chat): fix unit tests for context usage meter

Format >=100k tokens cleanly in ContextUsageChip and stub AuthManager profile ID in ChatViewModelTest.

* Fix context meter: source used tokens from input_tokens

Gateway /api/sessions/{id} has no last_prompt_tokens column; use
input_tokens instead. Show meter as soon as full context is known.

* Add tappable context breakdown sheet

Chip opens a bottom sheet with input/output/cache/read/write/reasoning
token rows + message count. Source breakdown from /api/sessions/{id}.

* Refresh context meter on streaming complete

Trigger fetchContextUsage immediately when a message finishes streaming
instead of waiting for the 5s session-sync poll.

* Fix context meter: source used tokens from live WS session.info

The REST /api/sessions/{id} endpoint reads the DB input_tokens column,
which is stale/0 for active sessions — that's why the meter was stuck at 0.

Switch the meter to the live values the gateway pushes over the WS
session.info payload after each turn:
- used = last_prompt_tokens (real used context for the turn)
- full = context_length (model-accurate window; keep /api/model/info as a
  fallback before the first turn completes)

fetchContextUsage now only tops up the denominator from /api/model/info and
no longer overwrites the live WS value with the stale REST input_tokens.
The 5s poll remains as a fallback.

* Revert "Fix context meter: source used tokens from live WS session.info"

This reverts commit 932393fd80993d57e66fe93552a949460d646674.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-07-26 01:52:22 +03:00
1 parent c181d1d593
commit 0180fb12c2
15 files changed
+641 -2

No files matched your search

@@ -29,6 +29,7 @@ import com.m57.hermescontrol.data.model.EnvVarDeleteRequest
import com.m57.hermescontrol.data.model.EnvVarRevealRequest
import com.m57.hermescontrol.data.model.EnvVarRevealResponse
import com.m57.hermescontrol.data.model.EnvVarUpdate
import com.m57.hermescontrol.data.model.HealthStatus
import com.m57.hermescontrol.data.model.HookResponse
import com.m57.hermescontrol.data.model.KanbanBoardResponse
import com.m57.hermescontrol.data.model.KanbanBoardsResponse
@@ -46,6 +47,7 @@ import com.m57.hermescontrol.data.model.MessagingPlatformUpdate
import com.m57.hermescontrol.data.model.MoaConfigResponse
import com.m57.hermescontrol.data.model.ModelAssignmentRequest
import com.m57.hermescontrol.data.model.ModelAssignmentResponse
import com.m57.hermescontrol.data.model.ModelInfoResponse
import com.m57.hermescontrol.data.model.ModelOptionsResponse
import com.m57.hermescontrol.data.model.ModelsAnalyticsResponse
import com.m57.hermescontrol.data.model.OAuthCancelResponse
@@ -64,6 +66,7 @@ import com.m57.hermescontrol.data.model.RawConfigResponse
import com.m57.hermescontrol.data.model.RecentUnlock
import com.m57.hermescontrol.data.model.SaveSkillContentRequest
import com.m57.hermescontrol.data.model.ScanStatus
import com.m57.hermescontrol.data.model.SessionDetailResponse
import com.m57.hermescontrol.data.model.SessionListResponse
import com.m57.hermescontrol.data.model.SessionMessagesResponse
import com.m57.hermescontrol.data.model.SessionPromptResponse
@@ -123,6 +126,9 @@ interface HermesApiService {
@GET("api/status")
suspend fun getStatus(): Response<StatusResponse>
@GET("api/health")
suspend fun getHealth(): Response<HealthStatus>
@GET("api/sessions")
suspend fun getSessions(
@Query("limit") limit: Int = 20,
@@ -181,6 +187,17 @@ interface HermesApiService {
@Path("id", encoded = true) sessionId: String,
): Response<SessionPromptResponse>
@GET("api/sessions/{id}")
suspend fun getSessionDetail(
// Preserve slashes in session IDs — backend generates IDs containing '/' characters (issue #468).
// Contract: The server-generated sessionId must only contain URL-safe characters (no ?, #, or spaces).
@Path("id", encoded = true) sessionId: String,
@Query("profile") profile: String? = null,
): Response<SessionDetailResponse>
@GET("api/model/info")
suspend fun getModelInfo(): Response<ModelInfoResponse>
@GET("api/system/stats")
suspend fun getSystemStats(): Response<SystemStatsResponse>