mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-01 23:02:26 +00:00
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Frank <frank@anoma.ly>
133 lines
3.9 KiB
TypeScript
133 lines
3.9 KiB
TypeScript
import type { Message, Part } from "@opencode-ai/sdk/v2/client"
|
|
|
|
export type SessionContextBreakdownKey = "system" | "user" | "assistant" | "tool" | "other"
|
|
|
|
export type SessionContextBreakdownSegment = {
|
|
key: SessionContextBreakdownKey
|
|
tokens: number
|
|
width: number
|
|
percent: number
|
|
}
|
|
|
|
const estimateTokens = (chars: number) => Math.ceil(chars / 4)
|
|
const toPercent = (tokens: number, input: number) => (tokens / input) * 100
|
|
const toPercentLabel = (tokens: number, input: number) => Math.round(toPercent(tokens, input) * 10) / 10
|
|
|
|
const charsFromUserPart = (part: Part) => {
|
|
if (part.type === "text") return part.text.length
|
|
if (part.type === "file") return part.source?.text.value.length ?? 0
|
|
if (part.type === "agent") return part.source?.value.length ?? 0
|
|
return 0
|
|
}
|
|
|
|
const charsFromAssistantPart = (part: Part) => {
|
|
if (part.type === "text") return { assistant: part.text.length, tool: 0 }
|
|
if (part.type === "reasoning") return { assistant: part.text.length, tool: 0 }
|
|
if (part.type !== "tool") return { assistant: 0, tool: 0 }
|
|
|
|
const input = Object.keys(part.state.input).length * 16
|
|
if (part.state.status === "pending") return { assistant: 0, tool: input + part.state.raw.length }
|
|
if (part.state.status === "completed") return { assistant: 0, tool: input + part.state.output.length }
|
|
if (part.state.status === "error") return { assistant: 0, tool: input + part.state.error.length }
|
|
return { assistant: 0, tool: input }
|
|
}
|
|
|
|
const build = (
|
|
tokens: { system: number; user: number; assistant: number; tool: number; other: number },
|
|
input: number,
|
|
) => {
|
|
return [
|
|
{
|
|
key: "system",
|
|
tokens: tokens.system,
|
|
},
|
|
{
|
|
key: "user",
|
|
tokens: tokens.user,
|
|
},
|
|
{
|
|
key: "assistant",
|
|
tokens: tokens.assistant,
|
|
},
|
|
{
|
|
key: "tool",
|
|
tokens: tokens.tool,
|
|
},
|
|
{
|
|
key: "other",
|
|
tokens: tokens.other,
|
|
},
|
|
]
|
|
.filter((x) => x.tokens > 0)
|
|
.map((x) => ({
|
|
key: x.key,
|
|
tokens: x.tokens,
|
|
width: toPercent(x.tokens, input),
|
|
percent: toPercentLabel(x.tokens, input),
|
|
})) as SessionContextBreakdownSegment[]
|
|
}
|
|
|
|
export function estimateSessionContextBreakdown(args: {
|
|
messages: Message[]
|
|
parts: Record<string, Part[] | undefined>
|
|
input: number
|
|
systemPrompt?: string
|
|
}) {
|
|
if (!args.input) return []
|
|
|
|
const counts = args.messages.reduce(
|
|
(acc, msg) => {
|
|
const parts = args.parts[msg.id] ?? []
|
|
if (msg.role === "user") {
|
|
const user = parts.reduce((sum, part) => sum + charsFromUserPart(part), 0)
|
|
return { ...acc, user: acc.user + user }
|
|
}
|
|
|
|
if (msg.role !== "assistant") return acc
|
|
const assistant = parts.reduce(
|
|
(sum, part) => {
|
|
const next = charsFromAssistantPart(part)
|
|
return {
|
|
assistant: sum.assistant + next.assistant,
|
|
tool: sum.tool + next.tool,
|
|
}
|
|
},
|
|
{ assistant: 0, tool: 0 },
|
|
)
|
|
return {
|
|
...acc,
|
|
assistant: acc.assistant + assistant.assistant,
|
|
tool: acc.tool + assistant.tool,
|
|
}
|
|
},
|
|
{
|
|
system: args.systemPrompt?.length ?? 0,
|
|
user: 0,
|
|
assistant: 0,
|
|
tool: 0,
|
|
},
|
|
)
|
|
|
|
const tokens = {
|
|
system: estimateTokens(counts.system),
|
|
user: estimateTokens(counts.user),
|
|
assistant: estimateTokens(counts.assistant),
|
|
tool: estimateTokens(counts.tool),
|
|
}
|
|
const estimated = tokens.system + tokens.user + tokens.assistant + tokens.tool
|
|
|
|
if (estimated <= args.input) {
|
|
return build({ ...tokens, other: args.input - estimated }, args.input)
|
|
}
|
|
|
|
const scale = args.input / estimated
|
|
const scaled = {
|
|
system: Math.floor(tokens.system * scale),
|
|
user: Math.floor(tokens.user * scale),
|
|
assistant: Math.floor(tokens.assistant * scale),
|
|
tool: Math.floor(tokens.tool * scale),
|
|
}
|
|
const total = scaled.system + scaled.user + scaled.assistant + scaled.tool
|
|
return build({ ...scaled, other: Math.max(0, args.input - total) }, args.input)
|
|
}
|