Use session usage totals in TUI
This commit is contained in:
@@ -337,6 +337,7 @@ export function Prompt(props: PromptProps) {
|
||||
|
||||
const usage = createMemo(() => {
|
||||
if (!props.sessionID) return
|
||||
const session = sync.session.get(props.sessionID)
|
||||
const msg = sync.data.message[props.sessionID] ?? []
|
||||
const last = msg.findLast((item): item is AssistantMessage => item.role === "assistant" && item.tokens.output > 0)
|
||||
if (!last) return
|
||||
@@ -347,7 +348,7 @@ export function Prompt(props: PromptProps) {
|
||||
|
||||
const model = sync.data.provider.find((item) => item.id === last.providerID)?.models[last.modelID]
|
||||
const pct = model?.limit.context ? `${Math.round((tokens / model.limit.context) * 100)}%` : undefined
|
||||
const cost = msg.reduce((sum, item) => sum + (item.role === "assistant" ? item.cost : 0), 0)
|
||||
const cost = session?.cost ?? 0
|
||||
return {
|
||||
context: pct ? `${Locale.number(tokens)} (${pct})` : Locale.number(tokens),
|
||||
cost: cost > 0 ? money.format(cost) : undefined,
|
||||
|
||||
@@ -131,6 +131,34 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
.then((x) => (x.data ?? []).toSorted((a, b) => a.id.localeCompare(b.id)))
|
||||
}
|
||||
|
||||
function partUsage(part: Part) {
|
||||
if (part.type !== "step-finish") return
|
||||
return { cost: part.cost, tokens: part.tokens }
|
||||
}
|
||||
|
||||
function applySessionUsage(sessionID: string, part: Part, sign = 1) {
|
||||
const usage = partUsage(part)
|
||||
if (!usage) return
|
||||
const result = Binary.search(store.session, sessionID, (s) => s.id)
|
||||
if (!result.found) return
|
||||
setStore(
|
||||
"session",
|
||||
result.index,
|
||||
produce((draft) => {
|
||||
draft.cost = (draft.cost ?? 0) + usage.cost * sign
|
||||
draft.tokens = {
|
||||
input: (draft.tokens?.input ?? 0) + usage.tokens.input * sign,
|
||||
output: (draft.tokens?.output ?? 0) + usage.tokens.output * sign,
|
||||
reasoning: (draft.tokens?.reasoning ?? 0) + usage.tokens.reasoning * sign,
|
||||
cache: {
|
||||
read: (draft.tokens?.cache.read ?? 0) + usage.tokens.cache.read * sign,
|
||||
write: (draft.tokens?.cache.write ?? 0) + usage.tokens.cache.write * sign,
|
||||
},
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
event.subscribe((event) => {
|
||||
switch (event.type) {
|
||||
case "server.instance.disposed":
|
||||
@@ -294,6 +322,9 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
const messages = store.message[event.properties.sessionID]
|
||||
const result = Binary.search(messages, event.properties.messageID, (m) => m.id)
|
||||
if (result.found) {
|
||||
for (const part of store.part[event.properties.messageID] ?? []) {
|
||||
applySessionUsage(event.properties.sessionID, part, -1)
|
||||
}
|
||||
setStore(
|
||||
"message",
|
||||
event.properties.sessionID,
|
||||
@@ -307,14 +338,18 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
case "message.part.updated": {
|
||||
const parts = store.part[event.properties.part.messageID]
|
||||
if (!parts) {
|
||||
applySessionUsage(event.properties.part.sessionID, event.properties.part)
|
||||
setStore("part", event.properties.part.messageID, [event.properties.part])
|
||||
break
|
||||
}
|
||||
const result = Binary.search(parts, event.properties.part.id, (p) => p.id)
|
||||
if (result.found) {
|
||||
applySessionUsage(event.properties.part.sessionID, parts[result.index], -1)
|
||||
applySessionUsage(event.properties.part.sessionID, event.properties.part)
|
||||
setStore("part", event.properties.part.messageID, result.index, reconcile(event.properties.part))
|
||||
break
|
||||
}
|
||||
applySessionUsage(event.properties.part.sessionID, event.properties.part)
|
||||
setStore(
|
||||
"part",
|
||||
event.properties.part.messageID,
|
||||
@@ -346,7 +381,8 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
case "message.part.removed": {
|
||||
const parts = store.part[event.properties.messageID]
|
||||
const result = Binary.search(parts, event.properties.partID, (p) => p.id)
|
||||
if (result.found)
|
||||
if (result.found) {
|
||||
applySessionUsage(event.properties.sessionID, parts[result.index], -1)
|
||||
setStore(
|
||||
"part",
|
||||
event.properties.messageID,
|
||||
@@ -354,6 +390,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
draft.splice(result.index, 1)
|
||||
}),
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ const money = new Intl.NumberFormat("en-US", {
|
||||
function View(props: { api: TuiPluginApi; session_id: string }) {
|
||||
const theme = () => props.api.theme.current
|
||||
const msg = createMemo(() => props.api.state.session.messages(props.session_id))
|
||||
const cost = createMemo(() => msg().reduce((sum, item) => sum + (item.role === "assistant" ? item.cost : 0), 0))
|
||||
const session = createMemo(() => props.api.state.session.get(props.session_id))
|
||||
const cost = createMemo(() => session()?.cost ?? 0)
|
||||
|
||||
const state = createMemo(() => {
|
||||
const last = msg().findLast((item): item is AssistantMessage => item.role === "assistant" && item.tokens.output > 0)
|
||||
|
||||
@@ -147,6 +147,9 @@ function stateApi(sync: ReturnType<typeof useSync>): TuiPluginApi["state"] {
|
||||
count() {
|
||||
return sync.data.session.length
|
||||
},
|
||||
get(sessionID) {
|
||||
return sync.session.get(sessionID)
|
||||
},
|
||||
diff(sessionID) {
|
||||
return (sync.data.session_diff[sessionID] ?? []).flatMap((item) =>
|
||||
item.file === undefined ? [] : [{ ...item, file: item.file }],
|
||||
|
||||
@@ -42,7 +42,7 @@ export function SubagentFooter() {
|
||||
|
||||
const model = sync.data.provider.find((item) => item.id === last.providerID)?.models[last.modelID]
|
||||
const pct = model?.limit.context ? `${Math.round((tokens / model.limit.context) * 100)}%` : undefined
|
||||
const cost = msg.reduce((sum, item) => sum + (item.role === "assistant" ? item.cost : 0), 0)
|
||||
const cost = session()?.cost ?? 0
|
||||
|
||||
const money = new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
|
||||
@@ -292,6 +292,7 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
|
||||
},
|
||||
session: {
|
||||
count: opts.state?.session?.count ?? (() => 0),
|
||||
get: opts.state?.session?.get ?? (() => undefined),
|
||||
diff: opts.state?.session?.diff ?? (() => []),
|
||||
todo: opts.state?.session?.todo ?? (() => []),
|
||||
messages: opts.state?.session?.messages ?? (() => []),
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
Provider,
|
||||
PermissionRequest,
|
||||
QuestionRequest,
|
||||
Session,
|
||||
SessionStatus,
|
||||
TextPart,
|
||||
Config as SdkConfig,
|
||||
@@ -310,6 +311,7 @@ export type TuiState = {
|
||||
readonly vcs: { branch?: string } | undefined
|
||||
session: {
|
||||
count: () => number
|
||||
get: (sessionID: string) => Session | undefined
|
||||
diff: (sessionID: string) => ReadonlyArray<TuiSidebarFileItem>
|
||||
todo: (sessionID: string) => ReadonlyArray<TuiSidebarTodoItem>
|
||||
messages: (sessionID: string) => ReadonlyArray<Message>
|
||||
|
||||
Reference in New Issue
Block a user