Propagate session context usage percent
This commit is contained in:
13
src/App.tsx
13
src/App.tsx
@@ -302,19 +302,14 @@ const ContextUsagePanel: Component<{ instanceId: string; sessionId: string }> =
|
|||||||
contextWindow: 0,
|
contextWindow: 0,
|
||||||
isSubscriptionModel: false,
|
isSubscriptionModel: false,
|
||||||
contextUsageTokens: 0,
|
contextUsageTokens: 0,
|
||||||
|
contextUsagePercent: null,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const tokens = createMemo(() => info().tokens)
|
const tokens = createMemo(() => info().tokens)
|
||||||
const contextUsageTokens = createMemo(() => info().contextUsageTokens ?? 0)
|
const contextUsageTokens = createMemo(() => info().contextUsageTokens ?? 0)
|
||||||
const contextWindow = createMemo(() => info().contextWindow)
|
const contextWindow = createMemo(() => info().contextWindow)
|
||||||
const percentage = createMemo(() => {
|
const contextUsagePercent = createMemo(() => info().contextUsagePercent)
|
||||||
const windowSize = contextWindow()
|
|
||||||
if (!windowSize || windowSize <= 0) return null
|
|
||||||
const usage = contextUsageTokens()
|
|
||||||
const percent = Math.round((usage / windowSize) * 100)
|
|
||||||
return Math.min(100, Math.max(0, percent))
|
|
||||||
})
|
|
||||||
|
|
||||||
const costLabel = createMemo(() => {
|
const costLabel = createMemo(() => {
|
||||||
if (info().isSubscriptionModel || info().cost <= 0) return "Included in plan"
|
if (info().isSubscriptionModel || info().cost <= 0) return "Included in plan"
|
||||||
@@ -333,7 +328,7 @@ const ContextUsagePanel: Component<{ instanceId: string; sessionId: string }> =
|
|||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<div class="flex items-center justify-between mb-1">
|
<div class="flex items-center justify-between mb-1">
|
||||||
<div class="text-xs font-semibold text-primary/70 uppercase tracking-wide">Context window usage</div>
|
<div class="text-xs font-semibold text-primary/70 uppercase tracking-wide">Context window usage</div>
|
||||||
<div class="text-sm font-medium text-primary">{percentage() !== null ? `${percentage()}%` : "--"}</div>
|
<div class="text-sm font-medium text-primary">{contextUsagePercent() !== null ? `${contextUsagePercent()}%` : "--"}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-primary/90">
|
<div class="text-sm text-primary/90">
|
||||||
{contextWindow()
|
{contextWindow()
|
||||||
@@ -344,7 +339,7 @@ const ContextUsagePanel: Component<{ instanceId: string; sessionId: string }> =
|
|||||||
<div class="mt-3 h-1.5 rounded-full bg-base relative overflow-hidden">
|
<div class="mt-3 h-1.5 rounded-full bg-base relative overflow-hidden">
|
||||||
<div
|
<div
|
||||||
class="absolute inset-y-0 left-0 rounded-full bg-accent-primary transition-[width]"
|
class="absolute inset-y-0 left-0 rounded-full bg-accent-primary transition-[width]"
|
||||||
style={{ width: percentage() === null ? "0%" : `${percentage()}%` }}
|
style={{ width: contextUsagePercent() === null ? "0%" : `${contextUsagePercent()}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,14 +31,7 @@ import ToolCall from "./tool-call"
|
|||||||
import { sseManager } from "../lib/sse-manager"
|
import { sseManager } from "../lib/sse-manager"
|
||||||
import Kbd from "./kbd"
|
import Kbd from "./kbd"
|
||||||
import { preferences } from "../stores/preferences"
|
import { preferences } from "../stores/preferences"
|
||||||
import {
|
import { getSessionInfo, computeDisplayParts, sessions, setActiveSession, setActiveParentSession } from "../stores/sessions"
|
||||||
providers,
|
|
||||||
getSessionInfo,
|
|
||||||
computeDisplayParts,
|
|
||||||
sessions,
|
|
||||||
setActiveSession,
|
|
||||||
setActiveParentSession,
|
|
||||||
} from "../stores/sessions"
|
|
||||||
import { setActiveInstanceId } from "../stores/instances"
|
import { setActiveInstanceId } from "../stores/instances"
|
||||||
|
|
||||||
const SCROLL_OFFSET = 64
|
const SCROLL_OFFSET = 64
|
||||||
@@ -76,75 +69,6 @@ function navigateToTaskSession(location: TaskSessionLocation) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate session tokens and cost from messagesInfo (matches TUI logic)
|
|
||||||
function calculateSessionInfo(messagesInfo?: Map<string, MessageInfo>, instanceId?: string) {
|
|
||||||
if (!messagesInfo || messagesInfo.size === 0)
|
|
||||||
return { tokens: 0, cost: 0, contextWindow: 0, isSubscriptionModel: false }
|
|
||||||
|
|
||||||
let tokens = 0
|
|
||||||
let cost = 0
|
|
||||||
let contextWindow = 0
|
|
||||||
let modelID = ""
|
|
||||||
let providerID = ""
|
|
||||||
let isSubscriptionModel = false
|
|
||||||
|
|
||||||
// Go backwards through messages to find the last relevant assistant message (like TUI)
|
|
||||||
const messageArray = Array.from(messagesInfo.values()).reverse()
|
|
||||||
|
|
||||||
for (const info of messageArray) {
|
|
||||||
if (!info) continue
|
|
||||||
if (info.role === "assistant" && info.tokens) {
|
|
||||||
const usage = info.tokens
|
|
||||||
|
|
||||||
if (usage.output && usage.output > 0) {
|
|
||||||
if (info.summary) {
|
|
||||||
// If summary message, only count output tokens and stop (like TUI)
|
|
||||||
tokens = usage.output || 0
|
|
||||||
cost = info.cost || 0
|
|
||||||
} else {
|
|
||||||
// Regular message - count all token types (like TUI)
|
|
||||||
tokens =
|
|
||||||
(usage.input || 0) +
|
|
||||||
(usage.cache?.read || 0) +
|
|
||||||
(usage.cache?.write || 0) +
|
|
||||||
(usage.output || 0) +
|
|
||||||
(usage.reasoning || 0)
|
|
||||||
cost = info.cost || 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get model info for context window and subscription check
|
|
||||||
modelID = info.modelID || ""
|
|
||||||
providerID = info.providerID || ""
|
|
||||||
isSubscriptionModel = cost === 0
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to get context window from providers
|
|
||||||
if (instanceId && modelID && providerID) {
|
|
||||||
const instanceProviders = providers().get(instanceId) || []
|
|
||||||
console.log("[calculateSessionInfo] instanceProviders:", instanceProviders)
|
|
||||||
console.log("[calculateSessionInfo] looking for providerID:", providerID, "modelID:", modelID)
|
|
||||||
const provider = instanceProviders.find((p) => p.id === providerID)
|
|
||||||
console.log("[calculateSessionInfo] found provider:", provider)
|
|
||||||
if (provider) {
|
|
||||||
const model = provider.models.find((m) => m.id === modelID)
|
|
||||||
console.log("[calculateSessionInfo] found model:", model)
|
|
||||||
if (model?.limit?.context) {
|
|
||||||
contextWindow = model.limit.context
|
|
||||||
}
|
|
||||||
// Check if it's a subscription model (cost is 0 for both input and output)
|
|
||||||
if (model?.cost?.input === 0 && model?.cost?.output === 0) {
|
|
||||||
isSubscriptionModel = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { tokens, cost, contextWindow, isSubscriptionModel }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format tokens like TUI (e.g., "110K", "1.2M")
|
// Format tokens like TUI (e.g., "110K", "1.2M")
|
||||||
function formatTokens(tokens: number): string {
|
function formatTokens(tokens: number): string {
|
||||||
if (tokens >= 1000000) {
|
if (tokens >= 1000000) {
|
||||||
@@ -156,16 +80,15 @@ function formatTokens(tokens: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Format session info for the session view header
|
// Format session info for the session view header
|
||||||
function formatSessionInfo(tokens: number, _cost: number, contextWindow: number, _isSubscriptionModel: boolean): string {
|
function formatSessionInfo(usageTokens: number, contextWindow: number, usagePercent: number | null): string {
|
||||||
const tokensStr = formatTokens(tokens)
|
|
||||||
|
|
||||||
if (contextWindow > 0) {
|
if (contextWindow > 0) {
|
||||||
const windowStr = formatTokens(contextWindow)
|
const windowStr = formatTokens(contextWindow)
|
||||||
const percentage = Math.min(100, Math.max(0, Math.round((tokens / contextWindow) * 100)))
|
const usageStr = formatTokens(usageTokens)
|
||||||
return `${tokensStr} of ${windowStr} (${percentage}%)`
|
const percent = usagePercent ?? Math.min(100, Math.max(0, Math.round((usageTokens / contextWindow) * 100)))
|
||||||
|
return `${usageStr} of ${windowStr} (${percent}%)`
|
||||||
}
|
}
|
||||||
|
|
||||||
return tokensStr
|
return formatTokens(usageTokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MessageStreamProps {
|
interface MessageStreamProps {
|
||||||
@@ -274,30 +197,20 @@ export default function MessageStream(props: MessageStreamProps) {
|
|||||||
return `${toolPart.id}:${version}:${status}`
|
return `${toolPart.id}:${version}:${status}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionInfo = createMemo(() => {
|
const sessionInfo = createMemo(() =>
|
||||||
return (
|
getSessionInfo(props.instanceId, props.sessionId) ?? {
|
||||||
getSessionInfo(props.instanceId, props.sessionId) || {
|
|
||||||
tokens: 0,
|
|
||||||
cost: 0,
|
|
||||||
contextWindow: 0,
|
|
||||||
isSubscriptionModel: false,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const formattedSessionInfo = createMemo(() => {
|
|
||||||
const sessionInfo = getSessionInfo(props.instanceId, props.sessionId) || {
|
|
||||||
tokens: 0,
|
tokens: 0,
|
||||||
cost: 0,
|
cost: 0,
|
||||||
contextWindow: 0,
|
contextWindow: 0,
|
||||||
isSubscriptionModel: false,
|
isSubscriptionModel: false,
|
||||||
}
|
contextUsageTokens: 0,
|
||||||
return formatSessionInfo(
|
contextUsagePercent: null,
|
||||||
sessionInfo.tokens,
|
},
|
||||||
sessionInfo.cost,
|
)
|
||||||
sessionInfo.contextWindow,
|
|
||||||
sessionInfo.isSubscriptionModel,
|
const formattedSessionInfo = createMemo(() => {
|
||||||
)
|
const info = sessionInfo()
|
||||||
|
return formatSessionInfo(info.contextUsageTokens, info.contextWindow, info.contextUsagePercent)
|
||||||
})
|
})
|
||||||
|
|
||||||
function isNearBottom(element: HTMLDivElement, offset = SCROLL_OFFSET) {
|
function isNearBottom(element: HTMLDivElement, offset = SCROLL_OFFSET) {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ interface SessionInfo {
|
|||||||
contextWindow: number
|
contextWindow: number
|
||||||
isSubscriptionModel: boolean
|
isSubscriptionModel: boolean
|
||||||
contextUsageTokens: number
|
contextUsageTokens: number
|
||||||
|
contextUsagePercent: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SessionForkResponse {
|
interface SessionForkResponse {
|
||||||
@@ -552,8 +553,9 @@ function updateSessionInfo(instanceId: string, sessionId: string) {
|
|||||||
let isSubscriptionModel = false
|
let isSubscriptionModel = false
|
||||||
let modelID = ""
|
let modelID = ""
|
||||||
let providerID = ""
|
let providerID = ""
|
||||||
let inputTokensForUsage = 0
|
let actualUsageTokens = 0
|
||||||
let cacheReadTokensForUsage = 0
|
let contextUsagePercent: number | null = null
|
||||||
|
let hasContextUsage = false
|
||||||
|
|
||||||
// Calculate from last assistant message in this session only
|
// Calculate from last assistant message in this session only
|
||||||
if (session.messagesInfo.size > 0) {
|
if (session.messagesInfo.size > 0) {
|
||||||
@@ -565,23 +567,23 @@ function updateSessionInfo(instanceId: string, sessionId: string) {
|
|||||||
const usage = info.tokens
|
const usage = info.tokens
|
||||||
|
|
||||||
if (usage.output > 0) {
|
if (usage.output > 0) {
|
||||||
|
const inputTokens = usage.input || 0
|
||||||
|
const reasoningTokens = usage.reasoning || 0
|
||||||
|
const cacheReadTokens = usage.cache?.read || 0
|
||||||
|
const cacheWriteTokens = usage.cache?.write || 0
|
||||||
|
const outputTokens = usage.output || 0
|
||||||
|
|
||||||
if (info.summary) {
|
if (info.summary) {
|
||||||
// If summary message, only count output tokens and stop (like TUI)
|
// If summary message, only count output tokens and stop (like TUI)
|
||||||
tokens = usage.output || 0
|
tokens = outputTokens
|
||||||
cost = info.cost || 0
|
|
||||||
} else {
|
} else {
|
||||||
// Regular message - count all token types (like TUI)
|
// Regular message - count all token types (like TUI)
|
||||||
tokens =
|
tokens = inputTokens + cacheReadTokens + cacheWriteTokens + outputTokens + reasoningTokens
|
||||||
(usage.input || 0) +
|
|
||||||
(usage.cache?.read || 0) +
|
|
||||||
(usage.cache?.write || 0) +
|
|
||||||
(usage.output || 0) +
|
|
||||||
(usage.reasoning || 0)
|
|
||||||
cost = info.cost || 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inputTokensForUsage = usage.input || 0
|
cost = info.cost || 0
|
||||||
cacheReadTokensForUsage = usage.cache?.read || 0
|
actualUsageTokens = tokens
|
||||||
|
hasContextUsage = inputTokens + cacheReadTokens + cacheWriteTokens > 0
|
||||||
|
|
||||||
// Get model info identifiers for context lookups
|
// Get model info identifiers for context lookups
|
||||||
modelID = info.modelID || ""
|
modelID = info.modelID || ""
|
||||||
@@ -625,7 +627,20 @@ function updateSessionInfo(instanceId: string, sessionId: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextUsageTokens = inputTokensForUsage + cacheReadTokensForUsage + modelOutputLimit
|
const outputBudget = Math.min(modelOutputLimit, DEFAULT_MODEL_OUTPUT_LIMIT)
|
||||||
|
let contextUsageTokens = 0
|
||||||
|
|
||||||
|
if (hasContextUsage && actualUsageTokens > 0) {
|
||||||
|
contextUsageTokens = actualUsageTokens + outputBudget
|
||||||
|
if (contextWindow > 0) {
|
||||||
|
const percent = Math.round((contextUsageTokens / contextWindow) * 100)
|
||||||
|
contextUsagePercent = Math.min(100, Math.max(0, percent))
|
||||||
|
} else {
|
||||||
|
contextUsagePercent = null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
contextUsagePercent = contextWindow > 0 ? 0 : null
|
||||||
|
}
|
||||||
|
|
||||||
setSessionInfoByInstance((prev) => {
|
setSessionInfoByInstance((prev) => {
|
||||||
const next = new Map(prev)
|
const next = new Map(prev)
|
||||||
@@ -636,6 +651,7 @@ function updateSessionInfo(instanceId: string, sessionId: string) {
|
|||||||
contextWindow,
|
contextWindow,
|
||||||
isSubscriptionModel,
|
isSubscriptionModel,
|
||||||
contextUsageTokens,
|
contextUsageTokens,
|
||||||
|
contextUsagePercent,
|
||||||
})
|
})
|
||||||
next.set(instanceId, instanceInfo)
|
next.set(instanceId, instanceInfo)
|
||||||
return next
|
return next
|
||||||
@@ -708,11 +724,8 @@ async function createSession(instanceId: string, agent?: string): Promise<Sessio
|
|||||||
const initialProvider = instanceProviders.find((p) => p.id === session.model.providerId)
|
const initialProvider = instanceProviders.find((p) => p.id === session.model.providerId)
|
||||||
const initialModel = initialProvider?.models.find((m) => m.id === session.model.modelId)
|
const initialModel = initialProvider?.models.find((m) => m.id === session.model.modelId)
|
||||||
const initialContextWindow = initialModel?.limit?.context ?? 0
|
const initialContextWindow = initialModel?.limit?.context ?? 0
|
||||||
const initialOutputLimit =
|
|
||||||
initialModel?.limit?.output && initialModel.limit.output > 0
|
|
||||||
? initialModel.limit.output
|
|
||||||
: DEFAULT_MODEL_OUTPUT_LIMIT
|
|
||||||
const initialSubscriptionModel = initialModel?.cost?.input === 0 && initialModel?.cost?.output === 0
|
const initialSubscriptionModel = initialModel?.cost?.input === 0 && initialModel?.cost?.output === 0
|
||||||
|
const initialContextPercent = initialContextWindow > 0 ? 0 : null
|
||||||
|
|
||||||
setSessionInfoByInstance((prev) => {
|
setSessionInfoByInstance((prev) => {
|
||||||
const next = new Map(prev)
|
const next = new Map(prev)
|
||||||
@@ -722,7 +735,8 @@ async function createSession(instanceId: string, agent?: string): Promise<Sessio
|
|||||||
cost: 0,
|
cost: 0,
|
||||||
contextWindow: initialContextWindow,
|
contextWindow: initialContextWindow,
|
||||||
isSubscriptionModel: Boolean(initialSubscriptionModel),
|
isSubscriptionModel: Boolean(initialSubscriptionModel),
|
||||||
contextUsageTokens: initialOutputLimit,
|
contextUsageTokens: 0,
|
||||||
|
contextUsagePercent: initialContextPercent,
|
||||||
})
|
})
|
||||||
next.set(instanceId, instanceInfo)
|
next.set(instanceId, instanceInfo)
|
||||||
return next
|
return next
|
||||||
@@ -810,9 +824,8 @@ async function forkSession(
|
|||||||
const forkProvider = instanceProviders.find((p) => p.id === forkedSession.model.providerId)
|
const forkProvider = instanceProviders.find((p) => p.id === forkedSession.model.providerId)
|
||||||
const forkModel = forkProvider?.models.find((m) => m.id === forkedSession.model.modelId)
|
const forkModel = forkProvider?.models.find((m) => m.id === forkedSession.model.modelId)
|
||||||
const forkContextWindow = forkModel?.limit?.context ?? 0
|
const forkContextWindow = forkModel?.limit?.context ?? 0
|
||||||
const forkOutputLimit =
|
|
||||||
forkModel?.limit?.output && forkModel.limit.output > 0 ? forkModel.limit.output : DEFAULT_MODEL_OUTPUT_LIMIT
|
|
||||||
const forkSubscriptionModel = forkModel?.cost?.input === 0 && forkModel?.cost?.output === 0
|
const forkSubscriptionModel = forkModel?.cost?.input === 0 && forkModel?.cost?.output === 0
|
||||||
|
const forkContextPercent = forkContextWindow > 0 ? 0 : null
|
||||||
|
|
||||||
setSessionInfoByInstance((prev) => {
|
setSessionInfoByInstance((prev) => {
|
||||||
const next = new Map(prev)
|
const next = new Map(prev)
|
||||||
@@ -822,7 +835,8 @@ async function forkSession(
|
|||||||
cost: 0,
|
cost: 0,
|
||||||
contextWindow: forkContextWindow,
|
contextWindow: forkContextWindow,
|
||||||
isSubscriptionModel: Boolean(forkSubscriptionModel),
|
isSubscriptionModel: Boolean(forkSubscriptionModel),
|
||||||
contextUsageTokens: forkOutputLimit,
|
contextUsageTokens: 0,
|
||||||
|
contextUsagePercent: forkContextPercent,
|
||||||
})
|
})
|
||||||
next.set(instanceId, instanceInfo)
|
next.set(instanceId, instanceInfo)
|
||||||
return next
|
return next
|
||||||
|
|||||||
Reference in New Issue
Block a user