67 lines
2.2 KiB
TypeScript
67 lines
2.2 KiB
TypeScript
import type { ClientPart } from "../types/message"
|
|
|
|
/**
|
|
* Count the total character content of a message part.
|
|
*
|
|
* Used by both the xray histogram overlay (message-timeline) and the
|
|
* bulk-delete toolbar token pills (message-section) so both surfaces
|
|
* derive token estimates from the same logic.
|
|
*
|
|
* Note: For tool parts we intentionally only count `state.input` and
|
|
* `state.output`. We exclude `state.metadata` from token estimation since
|
|
* metadata can contain large or verbose diagnostic payloads that are not
|
|
* representative of model context.
|
|
*/
|
|
export function getPartCharCount(part: ClientPart): number {
|
|
if (!part) return 0
|
|
let count = 0
|
|
|
|
if (typeof (part as any).text === "string") {
|
|
count += (part as any).text.length
|
|
}
|
|
|
|
if (part.type === "tool") {
|
|
const state = (part as any).state
|
|
// Tool calls may be compacted server-side. When that happens we treat the
|
|
// tool payload as effectively absent from context for token estimation.
|
|
const compacted = (state as any)?.time?.compacted
|
|
if (compacted !== undefined && compacted !== null) {
|
|
return 0
|
|
}
|
|
if (state) {
|
|
if (state.input) {
|
|
try {
|
|
count += JSON.stringify(state.input).length
|
|
} catch {}
|
|
}
|
|
if (state.output) {
|
|
if (typeof state.output === "string") {
|
|
count += state.output.length
|
|
} else {
|
|
try {
|
|
count += JSON.stringify(state.output).length
|
|
} catch {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Array.isArray((part as any).content)) {
|
|
count += (part as any).content.reduce((acc: number, entry: unknown) => {
|
|
if (typeof entry === "string") return acc + entry.length
|
|
if (entry && typeof entry === "object") {
|
|
let entryCount = (String((entry as any).text || "")).length + (String((entry as any).value || "")).length
|
|
if (Array.isArray((entry as any).content)) {
|
|
entryCount += (entry as any).content.reduce((innerAcc: number, sub: unknown) => {
|
|
if (typeof sub === "string") return innerAcc + sub.length
|
|
return innerAcc + (String((sub as any)?.text || "")).length
|
|
}, 0)
|
|
}
|
|
return acc + entryCount
|
|
}
|
|
return acc
|
|
}, 0)
|
|
}
|
|
return count
|
|
}
|