import type { ModelInfo, SessionMessageAssistant, SessionMessageInfo } from "@opencode/client" import { Locale } from "./locale" type SessionNode = { id: string parentID?: string | null } export function sessionFamily(sessions: readonly T[], sessionID: string) { const byID = new Map(sessions.map((session) => [session.id, session])) const current = byID.get(sessionID) if (!current) return [] const children = new Map() sessions.forEach((session) => { if (!session.parentID) return const group = children.get(session.parentID) if (group) group.push(session) else children.set(session.parentID, [session]) }) function root(session: T): T { const parent = session.parentID ? byID.get(session.parentID) : undefined return parent ? root(parent) : session } function walk(parentID: string, ancestors: boolean[]): Array<{ session: T; prefix: string }> { const group = children.get(parentID) ?? [] return group.flatMap((session, index) => { const last = index === group.length - 1 const prefix = ancestors.length === 0 ? "" : ancestors .slice(1) .map((ancestor) => (ancestor ? " " : "│ ")) .join("") + (last ? "└─ " : "├─ ") return [{ session, prefix }, ...walk(session.id, [...ancestors, last])] }) } return walk(root(current).id, []) } export function lastAssistantWithUsage(messages: ReadonlyArray, boundary?: string) { const boundaryIndex = boundary ? messages.findIndex((message) => message.id === boundary) : -1 if (boundary && boundaryIndex === -1) return undefined const end = boundaryIndex === -1 ? messages.length : boundaryIndex const compactionIndex = messages.findLastIndex( (message, index) => message.type === "compaction" && message.status === "completed" && index < end, ) return messages.findLast( (message, index): message is SessionMessageAssistant & { tokens: NonNullable } => message.type === "assistant" && message.tokens !== undefined && index > compactionIndex && index < end, ) } export function contextUsage( messages: ReadonlyArray, models: ReadonlyArray | undefined, boundary?: string, ) { const last = lastAssistantWithUsage(messages, boundary) if (!last) return const tokens = last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write if (tokens <= 0) return const model = models?.find((model) => model.providerID === last.model.providerID && model.id === last.model.id) return { tokens, percent: model?.limit.context ? Math.round((tokens / model.limit.context) * 100) : undefined, } } export function formatContextUsage(tokens: number, percent?: number) { const value = Locale.number(tokens) return percent === undefined ? value : `${value} (${percent}%)` }