Files
CodeNomad/src/stores/session-status.ts
2025-11-12 23:11:23 +00:00

167 lines
4.9 KiB
TypeScript

import type { Session, SessionStatus } from "../types/session"
import type { Message, MessageInfo } from "../types/message"
import { sessions } from "./sessions"
import { isSessionCompactionActive } from "./session-compaction"
function getSession(instanceId: string, sessionId: string): Session | null {
const instanceSessions = sessions().get(instanceId)
return instanceSessions?.get(sessionId) ?? null
}
function isSessionCompacting(session: Session): boolean {
const time = (session.time as (Session["time"] & { compacting?: number }) | undefined)
const compactingFlag = time?.compacting
if (typeof compactingFlag === "number") {
return compactingFlag > 0
}
return Boolean(compactingFlag)
}
function getMessageTimestamp(session: Session, message?: Message): number {
if (!message) return Number.NEGATIVE_INFINITY
if (typeof message.timestamp === "number" && Number.isFinite(message.timestamp)) {
return message.timestamp
}
const info = session.messagesInfo.get(message.id)
return info?.time?.created ?? Number.NEGATIVE_INFINITY
}
function getLastMessage(session: Session): Message | undefined {
let latest: Message | undefined
let latestTimestamp = Number.NEGATIVE_INFINITY
for (const message of session.messages) {
if (!message) continue
const timestamp = getMessageTimestamp(session, message)
if (timestamp >= latestTimestamp) {
latest = message
latestTimestamp = timestamp
}
}
return latest
}
function getLastMessageInfo(session: Session, role?: MessageInfo["role"]): MessageInfo | undefined {
if (session.messagesInfo.size === 0) {
return undefined
}
let latest: MessageInfo | undefined
let latestTimestamp = Number.NEGATIVE_INFINITY
for (const info of session.messagesInfo.values()) {
if (!info) continue
if (role && info.role !== role) continue
const timestamp = info.time?.created ?? 0
if (timestamp >= latestTimestamp) {
latest = info
latestTimestamp = timestamp
}
}
return latest
}
function getInfoCreatedTimestamp(info?: MessageInfo): number {
if (!info) {
return Number.NEGATIVE_INFINITY
}
const created = info.time?.created
if (typeof created === "number" && Number.isFinite(created)) {
return created
}
return Number.NEGATIVE_INFINITY
}
function getAssistantCompletionTimestamp(info?: MessageInfo): number {
if (!info) {
return Number.NEGATIVE_INFINITY
}
const completed = (info.time as { completed?: number } | undefined)?.completed
if (typeof completed === "number" && Number.isFinite(completed)) {
return completed
}
return Number.NEGATIVE_INFINITY
}
function isAssistantInfoPending(info?: MessageInfo): boolean {
if (!info) {
return false
}
const completed = (info.time as { completed?: number } | undefined)?.completed
if (completed === undefined || completed === null) {
return true
}
const created = getInfoCreatedTimestamp(info)
return completed < created
}
function isAssistantStillGenerating(message: Message, info?: MessageInfo): boolean {
if (message.type !== "assistant") {
return false
}
if (message.status === "error") {
return false
}
if (message.status === "streaming" || message.status === "sending") {
return true
}
const completedAt = (info?.time as { completed?: number } | undefined)?.completed
if (completedAt !== undefined && completedAt !== null) {
return false
}
return !(message.status === "complete" || message.status === "sent")
}
export function getSessionStatus(instanceId: string, sessionId: string): SessionStatus {
const session = getSession(instanceId, sessionId)
if (!session) {
return "idle"
}
if (isSessionCompactionActive(instanceId, sessionId) || isSessionCompacting(session)) {
return "compacting"
}
const latestUserInfo = getLastMessageInfo(session, "user")
const latestAssistantInfo = getLastMessageInfo(session, "assistant")
const lastMessage = getLastMessage(session)
if (!lastMessage) {
const latestInfo = getLastMessageInfo(session)
if (!latestInfo) {
return "idle"
}
if (latestInfo.role === "user") {
return "working"
}
const infoCompleted = latestInfo.time?.completed
return infoCompleted ? "idle" : "working"
}
if (lastMessage.type === "user") {
return "working"
}
const infoForMessage = session.messagesInfo.get(lastMessage.id) ?? latestAssistantInfo
if (isAssistantStillGenerating(lastMessage, infoForMessage)) {
return "working"
}
if (isAssistantInfoPending(latestAssistantInfo)) {
return "working"
}
const userTimestamp = getInfoCreatedTimestamp(latestUserInfo)
const assistantCompletedAt = getAssistantCompletionTimestamp(latestAssistantInfo)
if (userTimestamp > assistantCompletedAt) {
return "working"
}
return "idle"
}
export function isSessionBusy(instanceId: string, sessionId: string): boolean {
const status = getSessionStatus(instanceId, sessionId)
return status === "working" || status === "compacting"
}