Merge branch 'dev' into sqlite2

This commit is contained in:
Dax Raad
2026-01-29 13:23:48 -05:00
76 changed files with 1148 additions and 678 deletions
+28 -4
View File
@@ -28,6 +28,10 @@ interface SessionStats {
tokens: {
input: number
output: number
cache: {
read: number
write: number
}
}
cost: number
}
@@ -175,6 +179,10 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
tokens: {
input: number
output: number
cache: {
read: number
write: number
}
}
cost: number
}
@@ -188,7 +196,7 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
if (!sessionModelUsage[modelKey]) {
sessionModelUsage[modelKey] = {
messages: 0,
tokens: { input: 0, output: 0 },
tokens: { input: 0, output: 0, cache: { read: 0, write: 0 } },
cost: 0,
}
}
@@ -205,6 +213,8 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
sessionModelUsage[modelKey].tokens.input += message.info.tokens.input || 0
sessionModelUsage[modelKey].tokens.output +=
(message.info.tokens.output || 0) + (message.info.tokens.reasoning || 0)
sessionModelUsage[modelKey].tokens.cache.read += message.info.tokens.cache?.read || 0
sessionModelUsage[modelKey].tokens.cache.write += message.info.tokens.cache?.write || 0
}
}
@@ -219,7 +229,12 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
messageCount: messages.length,
sessionCost,
sessionTokens,
sessionTotalTokens: sessionTokens.input + sessionTokens.output + sessionTokens.reasoning,
sessionTotalTokens:
sessionTokens.input +
sessionTokens.output +
sessionTokens.reasoning +
sessionTokens.cache.read +
sessionTokens.cache.write,
sessionToolUsage,
sessionModelUsage,
earliestTime: cutoffTime > 0 ? session.time.updated : session.time.created,
@@ -250,13 +265,15 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
if (!stats.modelUsage[model]) {
stats.modelUsage[model] = {
messages: 0,
tokens: { input: 0, output: 0 },
tokens: { input: 0, output: 0, cache: { read: 0, write: 0 } },
cost: 0,
}
}
stats.modelUsage[model].messages += usage.messages
stats.modelUsage[model].tokens.input += usage.tokens.input
stats.modelUsage[model].tokens.output += usage.tokens.output
stats.modelUsage[model].tokens.cache.read += usage.tokens.cache.read
stats.modelUsage[model].tokens.cache.write += usage.tokens.cache.write
stats.modelUsage[model].cost += usage.cost
}
}
@@ -270,7 +287,12 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
}
stats.days = effectiveDays
stats.costPerDay = stats.totalCost / effectiveDays
const totalTokens = stats.totalTokens.input + stats.totalTokens.output + stats.totalTokens.reasoning
const totalTokens =
stats.totalTokens.input +
stats.totalTokens.output +
stats.totalTokens.reasoning +
stats.totalTokens.cache.read +
stats.totalTokens.cache.write
stats.tokensPerSession = filteredSessions.length > 0 ? totalTokens / filteredSessions.length : 0
sessionTotalTokens.sort((a, b) => a - b)
const mid = Math.floor(sessionTotalTokens.length / 2)
@@ -337,6 +359,8 @@ export function displayStats(stats: SessionStats, toolLimit?: number, modelLimit
console.log(renderRow(" Messages", usage.messages.toLocaleString()))
console.log(renderRow(" Input Tokens", formatNumber(usage.tokens.input)))
console.log(renderRow(" Output Tokens", formatNumber(usage.tokens.output)))
console.log(renderRow(" Cache Read", formatNumber(usage.tokens.cache.read)))
console.log(renderRow(" Cache Write", formatNumber(usage.tokens.cache.write)))
console.log(renderRow(" Cost", `$${usage.cost.toFixed(4)}`))
console.log("├────────────────────────────────────────────────────────┤")
}
@@ -93,8 +93,11 @@ export function Prompt(props: PromptProps) {
let promptPartTypeId = 0
sdk.event.on(TuiEvent.PromptAppend.type, (evt) => {
if (!input || input.isDestroyed) return
input.insertText(evt.properties.text)
setTimeout(() => {
// setTimeout is a workaround and needs to be addressed properly
if (!input || input.isDestroyed) return
input.getLayoutNode().markDirty()
input.gotoBufferEnd()
renderer.requestRender()
@@ -924,6 +927,8 @@ export function Prompt(props: PromptProps) {
// Force layout update and render for the pasted content
setTimeout(() => {
// setTimeout is a workaround and needs to be addressed properly
if (!input || input.isDestroyed) return
input.getLayoutNode().markDirty()
renderer.requestRender()
}, 0)
@@ -935,6 +940,8 @@ export function Prompt(props: PromptProps) {
}
props.ref?.(ref)
setTimeout(() => {
// setTimeout is a workaround and needs to be addressed properly
if (!input || input.isDestroyed) return
input.cursorColor = theme.text
}, 0)
}}
@@ -34,9 +34,8 @@ export const { use: useKeybind, provider: KeybindProvider } = createSimpleContex
timeout = setTimeout(() => {
if (!store.leader) return
leader(false)
if (focus) {
focus.focus()
}
if (!focus || focus.isDestroyed) return
focus.focus()
}, 2000)
return
}
@@ -275,7 +275,8 @@ export function Session() {
function toBottom() {
setTimeout(() => {
if (scroll) scroll.scrollTo(scroll.scrollHeight)
if (!scroll || scroll.isDestroyed) return
scroll.scrollTo(scroll.scrollHeight)
}, 50)
}
@@ -68,6 +68,7 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
onMount(() => {
dialog.setSize("medium")
setTimeout(() => {
if (!textarea || textarea.isDestroyed) return
textarea.focus()
}, 1)
textarea.gotoLineEnd()
@@ -27,6 +27,7 @@ export function DialogPrompt(props: DialogPromptProps) {
onMount(() => {
dialog.setSize("medium")
setTimeout(() => {
if (!textarea || textarea.isDestroyed) return
textarea.focus()
}, 1)
textarea.gotoLineEnd()
@@ -241,7 +241,11 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
focusedTextColor={theme.textMuted}
ref={(r) => {
input = r
setTimeout(() => input.focus(), 1)
setTimeout(() => {
if (!input) return
if (input.isDestroyed) return
input.focus()
}, 1)
}}
placeholder={props.placeholder ?? "Search"}
/>