Files
fusion/packages/dashboard/app/utils/chatContextUsage.ts
Fusion Agent 9d8c14b4e8 FN-165: use session context usage for chat indicator
Use provider-reported pi session context usage to drive the Chat context indicator.

- Track measured and pending session context usage in the dashboard.
- Expose bounded context metadata from chat sessions and update token estimation.
- Add regression coverage, localized labels, documentation, and a changeset.

Files changed:
 .changeset/fn-165-chat-context-usage.md            |  7 ++
 docs/dashboard-guide.md                            |  4 +-
 packages/dashboard/app/components/ChatView.tsx     | 49 ++++++++----
 .../__tests__/ChatView.context-window.test.tsx     | 84 +++++++++++++++++++-
 .../app/utils/__tests__/chatContextUsage.test.ts   | 66 ++++++++++++++++
 .../app/utils/__tests__/estimateChatTokens.test.ts | 10 +++
 packages/dashboard/app/utils/chatContextUsage.ts   | 73 ++++++++++++++++++
 packages/dashboard/app/utils/estimateChatTokens.ts | 39 ++++++++--
 .../dashboard/src/__tests__/chat-manager.test.ts   | 89 +++++++++++++++++++++
 packages/dashboard/src/chat.ts                     | 65 +++++++++++++---
 packages/i18n/locales/en/app.json                  |  3 +
 packages/i18n/locales/es/app.json                  |  3 +
 packages/i18n/locales/fr/app.json                  |  3 +
 packages/i18n/locales/ko/app.json                  |  3 +
 packages/i18n/locales/pt-BR/app.json               |  3 +
 packages/i18n/locales/zh-CN/app.json               |  3 +
 packages/i18n/locales/zh-TW/app.json               |  3 +
 packages/i18n/src/resources.d.ts                   | 19 +++--
 18 files changed, 483 insertions(+), 43 deletions(-)

Fusion-Task-Id: FN-165

Fusion-Task-Lineage: a95a9c7f-80aa-41be-8807-1fbd383f0f1d

Co-authored-by: Fusion <noreply@runfusion.ai>
2026-08-23 01:37:58 +00:00

74 lines
2.5 KiB
TypeScript

import { estimateChatTokens, type ChatTokenEstimateMessage } from "./estimateChatTokens";
export interface ChatContextUsageRecord {
tokens: number | null;
contextWindow: number;
percent: number | null;
}
export type ChatContextUsageSource = "measured" | "pending" | "estimated";
export interface ChatContextUsageMessage extends ChatTokenEstimateMessage {
metadata?: unknown;
}
export interface ResolvedChatContextUsage {
source: ChatContextUsageSource;
used: number | null;
total: number;
approximate: boolean;
percent: number | null;
}
export function parseChatContextUsage(metadata: unknown): ChatContextUsageRecord | null {
if (!metadata || typeof metadata !== "object") return null;
const contextUsage = (metadata as Record<string, unknown>).contextUsage;
if (!contextUsage || typeof contextUsage !== "object") return null;
const { tokens, contextWindow, percent } = contextUsage as Record<string, unknown>;
if (typeof contextWindow !== "number" || !Number.isFinite(contextWindow) || contextWindow <= 0) return null;
if (tokens !== null && (typeof tokens !== "number" || !Number.isFinite(tokens))) return null;
return {
tokens: tokens === null ? null : Math.max(0, Math.trunc(tokens)),
contextWindow: Math.max(1, Math.trunc(contextWindow)),
percent: typeof percent === "number" && Number.isFinite(percent) ? percent : null,
};
}
export function resolveChatContextUsage({
messages,
streamingText,
fallbackContextWindow,
}: {
messages: ChatContextUsageMessage[];
streamingText?: string | null;
fallbackContextWindow: number | null | undefined;
}): ResolvedChatContextUsage | null {
for (let index = messages.length - 1; index >= 0; index -= 1) {
const record = parseChatContextUsage(messages[index].metadata);
if (!record) continue;
if (record.tokens === null) {
return { source: "pending", used: null, total: record.contextWindow, approximate: false, percent: null };
}
const trailing = estimateChatTokens(messages.slice(index + 1), streamingText);
const used = record.tokens + trailing;
return {
source: "measured",
used,
total: record.contextWindow,
approximate: trailing > 0,
percent: (used / record.contextWindow) * 100,
};
}
if (typeof fallbackContextWindow !== "number" || !Number.isFinite(fallbackContextWindow) || fallbackContextWindow <= 0) {
return null;
}
return {
source: "estimated",
used: estimateChatTokens(messages, streamingText),
total: fallbackContextWindow,
approximate: true,
percent: null,
};
}