167 lines
4.9 KiB
TypeScript
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"
|
|
}
|