import type { ThreadStructure, ThreadStructureTurnItem } from "@t3tools/client-runtime/state/shell"; import type { OrchestrationV2ProviderTurn, OrchestrationV2ProviderTurnTokenUsage, OrchestrationV2ProviderThread, ThreadTokenUsageSnapshot, } from "@t3tools/contracts"; import * as DateTime from "effect/DateTime"; function asFiniteNumber(value: unknown): number | null { return typeof value === "number" && Number.isFinite(value) ? value : null; } type NullableContextWindowUsage = { readonly [Key in keyof ThreadTokenUsageSnapshot]: undefined extends ThreadTokenUsageSnapshot[Key] ? Exclude | null : ThreadTokenUsageSnapshot[Key]; }; export type ContextWindowSnapshot = NullableContextWindowUsage & { readonly remainingTokens: number | null; readonly usedPercentage: number | null; readonly remainingPercentage: number | null; readonly updatedAt: string; }; /** Newest report by timestamp; snapshots order turns by provider thread. */ export function latestProviderTurnTokenUsage( turns: ReadonlyArray>, projection?: Pick | null, providerThread?: Pick | null, ): OrchestrationV2ProviderTurnTokenUsage | null { let latest: OrchestrationV2ProviderTurnTokenUsage | null = null; for (const { tokenUsage } of turns) { if (tokenUsage === undefined) continue; // Ties go to the later entry, since live upserts append. if (latest === null || Date.parse(tokenUsage.updatedAt) >= Date.parse(latest.updatedAt)) { latest = tokenUsage; } } if (latest !== null || projection == null || providerThread?.contextUsage != null) return latest; const forkedFrom = projection.thread.forkedFrom; if (forkedFrom?.type !== "run") return null; return ( projection.contextTransfers.find( (transfer) => transfer.type === "fork" && transfer.targetThreadId === projection.thread.id && transfer.sourceThreadId === forkedFrom.threadId && transfer.sourcePoint.runId === forkedFrom.runId && transfer.sourceProviderInstanceId === projection.thread.modelSelection.instanceId && (transfer.status === "pending" || transfer.resolution?.strategy === "native_fork"), )?.sourceTokenUsage ?? null ); } export function deriveLatestContextWindowSnapshot( entries: ReadonlyArray<{ readonly item: ThreadStructureTurnItem; }>, liveUsage?: OrchestrationV2ProviderTurnTokenUsage | null, providerThread?: Pick | null, ): ContextWindowSnapshot | null { if (liveUsage != null) { const usedTokens = Math.max(0, liveUsage.usedTokens); const maxTokens = liveUsage.maxTokens ?? null; const usedPercentage = maxTokens !== null && maxTokens > 0 ? Math.min(100, (usedTokens / maxTokens) * 100) : null; const remainingTokens = maxTokens !== null ? Math.max(0, Math.round(maxTokens - usedTokens)) : null; const remainingPercentage = usedPercentage !== null ? Math.max(0, 100 - usedPercentage) : null; return { usedTokens, totalProcessedTokens: null, maxTokens, remainingTokens, usedPercentage, remainingPercentage, inputTokens: liveUsage.inputTokens ?? null, cachedInputTokens: liveUsage.cachedInputTokens ?? null, outputTokens: liveUsage.outputTokens ?? null, reasoningOutputTokens: liveUsage.reasoningOutputTokens ?? null, lastUsedTokens: null, lastInputTokens: null, lastCachedInputTokens: null, lastOutputTokens: null, lastReasoningOutputTokens: null, toolUses: null, durationMs: null, compactsAutomatically: true, autoCompactThreshold: liveUsage.autoCompactThreshold ?? null, cost: null, updatedAt: liveUsage.updatedAt, }; } const providerUsage = providerThread?.contextUsage; const providerUsageUpdatedAt = providerThread?.updatedAt; if ( providerUsage !== null && providerUsage !== undefined && providerUsageUpdatedAt !== undefined ) { const maxTokens = asFiniteNumber(providerUsage.maxTokens); const usedTokens = providerUsage.usedTokens; const usedPercentage = maxTokens !== null && maxTokens > 0 ? Math.min(100, (usedTokens / maxTokens) * 100) : null; return { usedTokens, totalProcessedTokens: asFiniteNumber(providerUsage.totalProcessedTokens), maxTokens, remainingTokens: maxTokens === null ? null : Math.max(0, Math.round(maxTokens - usedTokens)), usedPercentage, remainingPercentage: usedPercentage === null ? null : Math.max(0, 100 - usedPercentage), inputTokens: asFiniteNumber(providerUsage.inputTokens), cachedInputTokens: asFiniteNumber(providerUsage.cachedInputTokens), outputTokens: asFiniteNumber(providerUsage.outputTokens), reasoningOutputTokens: asFiniteNumber(providerUsage.reasoningOutputTokens), lastUsedTokens: asFiniteNumber(providerUsage.lastUsedTokens), lastInputTokens: asFiniteNumber(providerUsage.lastInputTokens), lastCachedInputTokens: asFiniteNumber(providerUsage.lastCachedInputTokens), lastOutputTokens: asFiniteNumber(providerUsage.lastOutputTokens), lastReasoningOutputTokens: asFiniteNumber(providerUsage.lastReasoningOutputTokens), toolUses: asFiniteNumber(providerUsage.toolUses), durationMs: asFiniteNumber(providerUsage.durationMs), compactsAutomatically: providerUsage.compactsAutomatically ?? null, cost: providerUsage.cost ?? null, autoCompactThreshold: providerUsage.autoCompactThreshold ?? null, updatedAt: DateTime.formatIso(providerUsageUpdatedAt), }; } for (let index = entries.length - 1; index >= 0; index -= 1) { const entry = entries[index]; if (!entry || entry.item.type !== "compaction") { continue; } const payload = entry.item; const usedTokens = asFiniteNumber(payload.afterTokenCount); if (usedTokens === null || usedTokens < 0) { continue; } const maxTokens = null; const usedPercentage = maxTokens !== null && maxTokens > 0 ? Math.min(100, (usedTokens / maxTokens) * 100) : null; const remainingTokens = maxTokens !== null ? Math.max(0, Math.round(maxTokens - usedTokens)) : null; const remainingPercentage = usedPercentage !== null ? Math.max(0, 100 - usedPercentage) : null; return { usedTokens, totalProcessedTokens: asFiniteNumber(payload.beforeTokenCount), maxTokens, remainingTokens, usedPercentage, remainingPercentage, inputTokens: null, cachedInputTokens: null, outputTokens: null, reasoningOutputTokens: null, lastUsedTokens: null, lastInputTokens: null, lastCachedInputTokens: null, lastOutputTokens: null, lastReasoningOutputTokens: null, toolUses: null, durationMs: null, compactsAutomatically: true, autoCompactThreshold: null, cost: null, updatedAt: DateTime.formatIso(payload.startedAt ?? payload.updatedAt), }; } return null; } export function formatContextWindowTokens(value: number | null): string { if (value === null || !Number.isFinite(value)) { return "0"; } if (value < 1_000) { return `${Math.round(value)}`; } if (value < 10_000) { return `${(value / 1_000).toFixed(1).replace(/\.0$/, "")}k`; } if (value < 999_500) { return `${Math.round(value / 1_000)}k`; } return `${(value / 1_000_000).toFixed(1).replace(/\.0$/, "")}m`; }