import { pipe } from "effect/Function";import * as Arr from "effect/Array";import * as O from "effect/Order";import type { MessageId, OrchestrationCheckpointSummary, OrchestrationEvent, OrchestrationLatestTurn, OrchestrationMessage, OrchestrationSession, OrchestrationThread, OrchestrationThreadActivity, ThreadPullRequestLink, TurnId,} from "@t3tools/contracts";import { threadPullRequestKeysEqual } from "@t3tools/shared/threadPullRequests";import { isImportedAgentSessionMessageId } from "@t3tools/contracts";import { compareDateTimeStrings } from "@t3tools/shared/dateTime"; export type ThreadDetailReducerResult = | { readonly kind: "updated"; readonly thread: OrchestrationThread } | { readonly kind: "deleted" } | { readonly kind: "unchanged" }; /** Keep only a legacy route supplied by the server; detail events cannot resolve project hosts. */function withPullRequests( thread: OrchestrationThread, pullRequests: ReadonlyArray<ThreadPullRequestLink>, updatedAt: string,): ThreadDetailReducerResult { return { kind: "updated", thread: { ...thread, pullRequests, linkedPullRequest: thread.linkedPullRequest && pullRequests.some( (link) => link.source !== "stack-dismissed" && link.url === thread.linkedPullRequest?.url, ) ? thread.linkedPullRequest : null, updatedAt, }, };} const proposedPlanOrder = O.combine<OrchestrationThread["proposedPlans"][number]>( O.mapInput(O.String, (p) => p.createdAt), O.mapInput(O.String, (p) => p.id),); const checkpointOrder = O.mapInput( O.Number, (cp: OrchestrationThread["checkpoints"][number]) => cp.checkpointTurnCount ?? Number.MAX_SAFE_INTEGER,); const activityOrder = O.combineAll<OrchestrationThreadActivity>([ O.mapInput(O.Number, (a) => a.sequence ?? Number.MAX_SAFE_INTEGER), O.mapInput(O.String, (a) => a.createdAt), O.mapInput(O.String, (a) => a.id),]); // Per-array id index so the streaming append path can reject a re-delivered// id without rescanning the history. Only arrays this reducer produced are// indexed: presence also proves the array is activityOrder-sorted, which// snapshot-loaded arrays (DB order, null sequences first) are not.const activityIdIndex = new WeakMap< ReadonlyArray<OrchestrationThreadActivity>, Set<OrchestrationThreadActivity["id"]>>(); /** * Matches the validity rule in `deriveLatestContextWindowSnapshot` (and the * server's snapshot-side `dropStaleContextWindowActivities`): rows without a * finite, non-negative `usedTokens` are skipped during the consumer's backward * walk, so they must not replace an earlier resolvable row here. */function isResolvableContextWindowActivity(activity: OrchestrationThreadActivity): boolean { if (activity.kind !== "context-window.updated") { return false; } const payload = activity.payload && typeof activity.payload === "object" ? (activity.payload as Record<string, unknown>) : null; const usedTokens = payload?.usedTokens; return typeof usedTokens === "number" && Number.isFinite(usedTokens) && usedTokens >= 0;} /** * Apply a single orchestration event to an `OrchestrationThread`, returning * the updated thread, a deletion signal, or an "unchanged" marker when the * event doesn't affect this thread. * * This is a pure reducer operating on contract types. UI-specific mapping * (e.g. resolving attachment preview URLs, normalising model slugs, adding * scoped fields like `environmentId`) is the caller's responsibility. */export function applyThreadDetailEvent( thread: OrchestrationThread, event: OrchestrationEvent,): ThreadDetailReducerResult { switch (event.type) { // ── Project events (irrelevant to thread detail) ──────────────── case "project.created": case "project.meta-updated": case "project.deleted": return { kind: "unchanged" }; // ── Thread lifecycle ──────────────────────────────────────────── case "thread.created": return { kind: "updated", thread: { id: event.payload.threadId, projectId: event.payload.projectId, title: event.payload.title, modelSelection: event.payload.modelSelection, runtimeMode: event.payload.runtimeMode, interactionMode: event.payload.interactionMode, branch: event.payload.branch, worktreePath: event.payload.worktreePath, branchPullRequest: null, latestTurn: null, createdAt: event.payload.createdAt, updatedAt: event.payload.updatedAt, archivedAt: null, settledOverride: null, settledAt: null, unsettledAt: null, activeOrderKey: null, snoozedUntil: null, snoozedAt: null, deletedAt: null, pullRequests: [], messages: [], proposedPlans: [], activities: [], checkpoints: [], session: null, }, }; case "thread.deleted": return { kind: "deleted" }; case "thread.archived": return { kind: "updated", thread: { ...thread, archivedAt: event.payload.archivedAt, titleRegeneration: null, updatedAt: event.payload.updatedAt, }, }; case "thread.unarchived": return { kind: "updated", thread: { ...thread, archivedAt: null, updatedAt: event.payload.updatedAt }, }; case "thread.settled": return { kind: "updated", thread: { ...thread, settledOverride: "settled", settledAt: event.payload.settledAt, unsettledAt: null, activeOrderKey: null, updatedAt: event.payload.updatedAt, }, }; case "thread.unsettled": return { kind: "updated", thread: { ...thread, settledOverride: event.payload.reason === "user" ? "active" : null, settledAt: null, // A thread already pinned active keeps its re-entry stamp: the // activity reset that clears the pin must not reorder the list. unsettledAt: thread.settledOverride === "active" ? (thread.unsettledAt ?? null) : event.payload.updatedAt, updatedAt: event.payload.updatedAt, }, }; case "thread.snoozed": return { kind: "updated", thread: { ...thread, snoozedUntil: event.payload.snoozedUntil, snoozedAt: event.payload.snoozedAt, updatedAt: event.payload.updatedAt, }, }; case "thread.unsnoozed": return { kind: "updated", thread: { ...thread, snoozedUntil: null, snoozedAt: null, updatedAt: event.payload.updatedAt, }, }; case "thread.pinned": return { kind: "updated", thread: { ...thread, pinnedAt: event.payload.pinnedAt, ...(event.payload.pinOrderKey !== undefined ? { pinOrderKey: event.payload.pinOrderKey } : {}), updatedAt: event.payload.updatedAt, }, }; case "thread.unpinned": return { kind: "updated", thread: { ...thread, pinnedAt: null, pinOrderKey: null, updatedAt: event.payload.updatedAt, }, }; case "thread.pin-reordered": return { kind: "updated", thread: { ...thread, pinOrderKey: event.payload.orderKey, updatedAt: event.payload.updatedAt, }, }; // ── Thread metadata ───────────────────────────────────────────── case "thread.meta-updated": return { kind: "updated", thread: { ...thread, ...(event.payload.title !== undefined ? { title: event.payload.title } : {}), ...(event.payload.titleRegeneration !== undefined ? { titleRegeneration: event.payload.titleRegeneration } : {}), ...(event.payload.modelSelection !== undefined ? { modelSelection: event.payload.modelSelection } : {}), ...(event.payload.branch !== undefined ? { branch: event.payload.branch } : {}), ...(event.payload.worktreePath !== undefined ? { worktreePath: event.payload.worktreePath } : {}), ...(event.payload.linkedPullRequest !== undefined ? { linkedPullRequest: event.payload.linkedPullRequest } : {}), ...(event.payload.branchPullRequest !== undefined ? { branchPullRequest: event.payload.branchPullRequest } : {}), ...(event.payload.activeOrderKey !== undefined ? { activeOrderKey: event.payload.activeOrderKey } : {}), updatedAt: event.payload.updatedAt, }, }; case "thread.pull-request-linked": { const link = event.payload.link; const others = thread.pullRequests.filter( (existing) => !threadPullRequestKeysEqual(existing, link), ); return withPullRequests(thread, [...others, link], event.payload.updatedAt); } case "thread.pull-request-unlinked": return withPullRequests( thread, thread.pullRequests.filter( (existing) => !threadPullRequestKeysEqual(existing, event.payload), ), event.payload.updatedAt, ); case "thread.pull-request-synced": { if ( !thread.pullRequests.some((existing) => threadPullRequestKeysEqual(existing, event.payload)) ) { return { kind: "unchanged" }; } return withPullRequests( thread, thread.pullRequests.map((existing) => threadPullRequestKeysEqual(existing, event.payload) ? { ...existing, snapshot: event.payload.snapshot, stack: event.payload.stack } : existing, ), event.payload.updatedAt, ); } case "thread.runtime-mode-set": return { kind: "updated", thread: { ...thread, runtimeMode: event.payload.runtimeMode, updatedAt: event.payload.updatedAt, }, }; case "thread.interaction-mode-set": return { kind: "updated", thread: { ...thread, interactionMode: event.payload.interactionMode, updatedAt: event.payload.updatedAt, }, }; // ── Turn lifecycle ────────────────────────────────────────────── case "thread.turn-start-requested": return { kind: "updated", thread: { ...thread, ...(event.payload.modelSelection !== undefined ? { modelSelection: event.payload.modelSelection } : {}), runtimeMode: event.payload.runtimeMode, interactionMode: event.payload.interactionMode, updatedAt: event.occurredAt, }, }; case "thread.turn-interrupt-requested": { if (event.payload.turnId === undefined) { return { kind: "unchanged" }; } const latestTurn = thread.latestTurn; if (latestTurn === null || latestTurn.turnId !== event.payload.turnId) { return { kind: "unchanged" }; } return { kind: "updated", thread: { ...thread, latestTurn: { ...latestTurn, state: "interrupted", startedAt: latestTurn.startedAt ?? event.payload.createdAt, completedAt: latestTurn.completedAt ?? event.payload.createdAt, }, updatedAt: event.occurredAt, }, }; } // ── Messages ──────────────────────────────────────────────────── case "thread.message-sent": { const message: OrchestrationMessage = { id: event.payload.messageId, role: event.payload.role, text: event.payload.text, ...(event.payload.attachments !== undefined ? { attachments: event.payload.attachments } : {}), turnId: event.payload.turnId, streaming: event.payload.streaming, createdAt: event.payload.createdAt, updatedAt: event.payload.updatedAt, }; const existingMessage = thread.messages.find((entry) => entry.id === message.id); const messages = existingMessage ? Arr.map(thread.messages, (entry) => entry.id !== message.id ? entry : { ...entry, text: message.streaming ? `${entry.text}${message.text}` : message.text.length > 0 ? message.text : entry.text, streaming: message.streaming, ...(message.turnId !== undefined ? { turnId: message.turnId } : {}), ...(message.streaming ? {} : { updatedAt: message.updatedAt }), ...(message.attachments !== undefined ? { attachments: message.attachments } : {}), }, ) : Arr.append(thread.messages, message); // Update latestTurn for assistant messages bound to a turn. A completed // assistant message only settles the turn once the session is no longer // running it — providers may emit several assistant messages per turn // (commentary between tool calls), and the turn must stay unsettled // until the provider reports turn end. Streaming deltas recompute the // same record, so the previous reference is kept when nothing changed. const turnStillRunning = event.payload.turnId !== null && thread.session?.status === "running" && thread.session.activeTurnId === event.payload.turnId; const settlesTurn = !event.payload.streaming && !turnStillRunning; const latestTurn = reuseLatestTurn( thread.latestTurn, event.payload.role === "assistant" && event.payload.turnId !== null && (thread.latestTurn === null || thread.latestTurn.turnId === event.payload.turnId) ? { turnId: event.payload.turnId, state: settlesTurn ? thread.latestTurn?.state === "interrupted" ? "interrupted" : thread.latestTurn?.state === "error" ? "error" : "completed" : "running", requestedAt: thread.latestTurn?.turnId === event.payload.turnId ? thread.latestTurn.requestedAt : event.payload.createdAt, startedAt: thread.latestTurn?.turnId === event.payload.turnId ? (thread.latestTurn.startedAt ?? event.payload.createdAt) : event.payload.createdAt, completedAt: settlesTurn ? event.payload.updatedAt : thread.latestTurn?.turnId === event.payload.turnId ? (thread.latestTurn.completedAt ?? null) : null, assistantMessageId: event.payload.messageId, } : thread.latestTurn, ); // Rebind checkpoint assistant message IDs for assistant messages. The // helper hands back the same array when the entry is already bound. const checkpoints = event.payload.role === "assistant" && event.payload.turnId !== null ? rebindCheckpointAssistantMessage( thread.checkpoints, event.payload.turnId, event.payload.messageId, ) : thread.checkpoints; return { kind: "updated", thread: { ...thread, messages, checkpoints, latestTurn, updatedAt: event.occurredAt, }, }; } // ── Session ───────────────────────────────────────────────────── case "thread.session-set": { // Leaving the "running" session status is the turn-end signal: settle a // still-running latest turn so its duration reflects the whole turn. const settledTurnState = settledTurnStateForSessionStatus(event.payload.session.status); const latestTurn = reuseLatestTurn( thread.latestTurn, event.payload.session.status === "running" && event.payload.session.activeTurnId !== null ? { turnId: event.payload.session.activeTurnId, state: "running", requestedAt: thread.latestTurn?.turnId === event.payload.session.activeTurnId ? thread.latestTurn.requestedAt : event.payload.session.updatedAt, startedAt: thread.latestTurn?.turnId === event.payload.session.activeTurnId ? (thread.latestTurn.startedAt ?? event.payload.session.updatedAt) : event.payload.session.updatedAt, completedAt: null, assistantMessageId: thread.latestTurn?.turnId === event.payload.session.activeTurnId ? thread.latestTurn.assistantMessageId : null, }Lines 1–500 of 860. Load more
import { pipe } from "effect/Function"; import * as Arr from "effect/Array"; import * as O from "effect/Order"; import type { MessageId, OrchestrationCheckpointSummary, OrchestrationEvent, OrchestrationLatestTurn, OrchestrationMessage, OrchestrationSession, OrchestrationThread, OrchestrationThreadActivity, ThreadPullRequestLink, TurnId, } from "@t3tools/contracts"; import { threadPullRequestKeysEqual } from "@t3tools/shared/threadPullRequests"; import { isImportedAgentSessionMessageId } from "@t3tools/contracts"; import { compareDateTimeStrings } from "@t3tools/shared/dateTime"; export type ThreadDetailReducerResult = | { readonly kind: "updated"; readonly thread: OrchestrationThread } | { readonly kind: "deleted" } | { readonly kind: "unchanged" }; /** Keep only a legacy route supplied by the server; detail events cannot resolve project hosts. */ function withPullRequests( thread: OrchestrationThread, pullRequests: ReadonlyArray<ThreadPullRequestLink>, updatedAt: string, ): ThreadDetailReducerResult { return { kind: "updated", thread: { ...thread, pullRequests, linkedPullRequest: thread.linkedPullRequest && pullRequests.some( (link) => link.source !== "stack-dismissed" && link.url === thread.linkedPullRequest?.url, ) ? thread.linkedPullRequest : null, updatedAt, }, }; } const proposedPlanOrder = O.combine<OrchestrationThread["proposedPlans"][number]>( O.mapInput(O.String, (p) => p.createdAt), O.mapInput(O.String, (p) => p.id), ); const checkpointOrder = O.mapInput( O.Number, (cp: OrchestrationThread["checkpoints"][number]) => cp.checkpointTurnCount ?? Number.MAX_SAFE_INTEGER, ); const activityOrder = O.combineAll<OrchestrationThreadActivity>([ O.mapInput(O.Number, (a) => a.sequence ?? Number.MAX_SAFE_INTEGER), O.mapInput(O.String, (a) => a.createdAt), O.mapInput(O.String, (a) => a.id), ]); // Per-array id index so the streaming append path can reject a re-delivered // id without rescanning the history. Only arrays this reducer produced are // indexed: presence also proves the array is activityOrder-sorted, which // snapshot-loaded arrays (DB order, null sequences first) are not. const activityIdIndex = new WeakMap< ReadonlyArray<OrchestrationThreadActivity>, Set<OrchestrationThreadActivity["id"]> >(); /** * Matches the validity rule in `deriveLatestContextWindowSnapshot` (and the * server's snapshot-side `dropStaleContextWindowActivities`): rows without a * finite, non-negative `usedTokens` are skipped during the consumer's backward * walk, so they must not replace an earlier resolvable row here. */ function isResolvableContextWindowActivity(activity: OrchestrationThreadActivity): boolean { if (activity.kind !== "context-window.updated") { return false; } const payload = activity.payload && typeof activity.payload === "object" ? (activity.payload as Record<string, unknown>) : null; const usedTokens = payload?.usedTokens; return typeof usedTokens === "number" && Number.isFinite(usedTokens) && usedTokens >= 0; } /** * Apply a single orchestration event to an `OrchestrationThread`, returning * the updated thread, a deletion signal, or an "unchanged" marker when the * event doesn't affect this thread. * * This is a pure reducer operating on contract types. UI-specific mapping * (e.g. resolving attachment preview URLs, normalising model slugs, adding * scoped fields like `environmentId`) is the caller's responsibility. */ export function applyThreadDetailEvent( thread: OrchestrationThread, event: OrchestrationEvent, ): ThreadDetailReducerResult { switch (event.type) { // ── Project events (irrelevant to thread detail) ──────────────── case "project.created": case "project.meta-updated": case "project.deleted": return { kind: "unchanged" }; // ── Thread lifecycle ──────────────────────────────────────────── case "thread.created": return { kind: "updated", thread: { id: event.payload.threadId, projectId: event.payload.projectId, title: event.payload.title, modelSelection: event.payload.modelSelection, runtimeMode: event.payload.runtimeMode, interactionMode: event.payload.interactionMode, branch: event.payload.branch, worktreePath: event.payload.worktreePath, branchPullRequest: null, latestTurn: null, createdAt: event.payload.createdAt, updatedAt: event.payload.updatedAt, archivedAt: null, settledOverride: null, settledAt: null, unsettledAt: null, activeOrderKey: null, snoozedUntil: null, snoozedAt: null, deletedAt: null, pullRequests: [], messages: [], proposedPlans: [], activities: [], checkpoints: [], session: null, }, }; case "thread.deleted": return { kind: "deleted" }; case "thread.archived": return { kind: "updated", thread: { ...thread, archivedAt: event.payload.archivedAt, titleRegeneration: null, updatedAt: event.payload.updatedAt, }, }; case "thread.unarchived": return { kind: "updated", thread: { ...thread, archivedAt: null, updatedAt: event.payload.updatedAt }, }; case "thread.settled": return { kind: "updated", thread: { ...thread, settledOverride: "settled", settledAt: event.payload.settledAt, unsettledAt: null, activeOrderKey: null, updatedAt: event.payload.updatedAt, }, }; case "thread.unsettled": return { kind: "updated", thread: { ...thread, settledOverride: event.payload.reason === "user" ? "active" : null, settledAt: null, // A thread already pinned active keeps its re-entry stamp: the // activity reset that clears the pin must not reorder the list. unsettledAt: thread.settledOverride === "active" ? (thread.unsettledAt ?? null) : event.payload.updatedAt, updatedAt: event.payload.updatedAt, }, }; case "thread.snoozed": return { kind: "updated", thread: { ...thread, snoozedUntil: event.payload.snoozedUntil, snoozedAt: event.payload.snoozedAt, updatedAt: event.payload.updatedAt, }, }; case "thread.unsnoozed": return { kind: "updated", thread: { ...thread, snoozedUntil: null, snoozedAt: null, updatedAt: event.payload.updatedAt, }, }; case "thread.pinned": return { kind: "updated", thread: { ...thread, pinnedAt: event.payload.pinnedAt, ...(event.payload.pinOrderKey !== undefined ? { pinOrderKey: event.payload.pinOrderKey } : {}), updatedAt: event.payload.updatedAt, }, }; case "thread.unpinned": return { kind: "updated", thread: { ...thread, pinnedAt: null, pinOrderKey: null, updatedAt: event.payload.updatedAt, }, }; case "thread.pin-reordered": return { kind: "updated", thread: { ...thread, pinOrderKey: event.payload.orderKey, updatedAt: event.payload.updatedAt, }, }; // ── Thread metadata ───────────────────────────────────────────── case "thread.meta-updated": return { kind: "updated", thread: { ...thread, ...(event.payload.title !== undefined ? { title: event.payload.title } : {}), ...(event.payload.titleRegeneration !== undefined ? { titleRegeneration: event.payload.titleRegeneration } : {}), ...(event.payload.modelSelection !== undefined ? { modelSelection: event.payload.modelSelection } : {}), ...(event.payload.branch !== undefined ? { branch: event.payload.branch } : {}), ...(event.payload.worktreePath !== undefined ? { worktreePath: event.payload.worktreePath } : {}), ...(event.payload.linkedPullRequest !== undefined ? { linkedPullRequest: event.payload.linkedPullRequest } : {}), ...(event.payload.branchPullRequest !== undefined ? { branchPullRequest: event.payload.branchPullRequest } : {}), ...(event.payload.activeOrderKey !== undefined ? { activeOrderKey: event.payload.activeOrderKey } : {}), updatedAt: event.payload.updatedAt, }, }; case "thread.pull-request-linked": { const link = event.payload.link; const others = thread.pullRequests.filter( (existing) => !threadPullRequestKeysEqual(existing, link), ); return withPullRequests(thread, [...others, link], event.payload.updatedAt); } case "thread.pull-request-unlinked": return withPullRequests( thread, thread.pullRequests.filter( (existing) => !threadPullRequestKeysEqual(existing, event.payload), ), event.payload.updatedAt, ); case "thread.pull-request-synced": { if ( !thread.pullRequests.some((existing) => threadPullRequestKeysEqual(existing, event.payload)) ) { return { kind: "unchanged" }; } return withPullRequests( thread, thread.pullRequests.map((existing) => threadPullRequestKeysEqual(existing, event.payload) ? { ...existing, snapshot: event.payload.snapshot, stack: event.payload.stack } : existing, ), event.payload.updatedAt, ); } case "thread.runtime-mode-set": return { kind: "updated", thread: { ...thread, runtimeMode: event.payload.runtimeMode, updatedAt: event.payload.updatedAt, }, }; case "thread.interaction-mode-set": return { kind: "updated", thread: { ...thread, interactionMode: event.payload.interactionMode, updatedAt: event.payload.updatedAt, }, }; // ── Turn lifecycle ────────────────────────────────────────────── case "thread.turn-start-requested": return { kind: "updated", thread: { ...thread, ...(event.payload.modelSelection !== undefined ? { modelSelection: event.payload.modelSelection } : {}), runtimeMode: event.payload.runtimeMode, interactionMode: event.payload.interactionMode, updatedAt: event.occurredAt, }, }; case "thread.turn-interrupt-requested": { if (event.payload.turnId === undefined) { return { kind: "unchanged" }; } const latestTurn = thread.latestTurn; if (latestTurn === null || latestTurn.turnId !== event.payload.turnId) { return { kind: "unchanged" }; } return { kind: "updated", thread: { ...thread, latestTurn: { ...latestTurn, state: "interrupted", startedAt: latestTurn.startedAt ?? event.payload.createdAt, completedAt: latestTurn.completedAt ?? event.payload.createdAt, }, updatedAt: event.occurredAt, }, }; } // ── Messages ──────────────────────────────────────────────────── case "thread.message-sent": { const message: OrchestrationMessage = { id: event.payload.messageId, role: event.payload.role, text: event.payload.text, ...(event.payload.attachments !== undefined ? { attachments: event.payload.attachments } : {}), turnId: event.payload.turnId, streaming: event.payload.streaming, createdAt: event.payload.createdAt, updatedAt: event.payload.updatedAt, }; const existingMessage = thread.messages.find((entry) => entry.id === message.id); const messages = existingMessage ? Arr.map(thread.messages, (entry) => entry.id !== message.id ? entry : { ...entry, text: message.streaming ? `${entry.text}${message.text}` : message.text.length > 0 ? message.text : entry.text, streaming: message.streaming, ...(message.turnId !== undefined ? { turnId: message.turnId } : {}), ...(message.streaming ? {} : { updatedAt: message.updatedAt }), ...(message.attachments !== undefined ? { attachments: message.attachments } : {}), }, ) : Arr.append(thread.messages, message); // Update latestTurn for assistant messages bound to a turn. A completed // assistant message only settles the turn once the session is no longer // running it — providers may emit several assistant messages per turn // (commentary between tool calls), and the turn must stay unsettled // until the provider reports turn end. Streaming deltas recompute the // same record, so the previous reference is kept when nothing changed. const turnStillRunning = event.payload.turnId !== null && thread.session?.status === "running" && thread.session.activeTurnId === event.payload.turnId; const settlesTurn = !event.payload.streaming && !turnStillRunning; const latestTurn = reuseLatestTurn( thread.latestTurn, event.payload.role === "assistant" && event.payload.turnId !== null && (thread.latestTurn === null || thread.latestTurn.turnId === event.payload.turnId) ? { turnId: event.payload.turnId, state: settlesTurn ? thread.latestTurn?.state === "interrupted" ? "interrupted" : thread.latestTurn?.state === "error" ? "error" : "completed" : "running", requestedAt: thread.latestTurn?.turnId === event.payload.turnId ? thread.latestTurn.requestedAt : event.payload.createdAt, startedAt: thread.latestTurn?.turnId === event.payload.turnId ? (thread.latestTurn.startedAt ?? event.payload.createdAt) : event.payload.createdAt, completedAt: settlesTurn ? event.payload.updatedAt : thread.latestTurn?.turnId === event.payload.turnId ? (thread.latestTurn.completedAt ?? null) : null, assistantMessageId: event.payload.messageId, } : thread.latestTurn, ); // Rebind checkpoint assistant message IDs for assistant messages. The // helper hands back the same array when the entry is already bound. const checkpoints = event.payload.role === "assistant" && event.payload.turnId !== null ? rebindCheckpointAssistantMessage( thread.checkpoints, event.payload.turnId, event.payload.messageId, ) : thread.checkpoints; return { kind: "updated", thread: { ...thread, messages, checkpoints, latestTurn, updatedAt: event.occurredAt, }, }; } // ── Session ───────────────────────────────────────────────────── case "thread.session-set": { // Leaving the "running" session status is the turn-end signal: settle a // still-running latest turn so its duration reflects the whole turn. const settledTurnState = settledTurnStateForSessionStatus(event.payload.session.status); const latestTurn = reuseLatestTurn( thread.latestTurn, event.payload.session.status === "running" && event.payload.session.activeTurnId !== null ? { turnId: event.payload.session.activeTurnId, state: "running", requestedAt: thread.latestTurn?.turnId === event.payload.session.activeTurnId ? thread.latestTurn.requestedAt : event.payload.session.updatedAt, startedAt: thread.latestTurn?.turnId === event.payload.session.activeTurnId ? (thread.latestTurn.startedAt ?? event.payload.session.updatedAt) : event.payload.session.updatedAt, completedAt: null, assistantMessageId: thread.latestTurn?.turnId === event.payload.session.activeTurnId ? thread.latestTurn.assistantMessageId : null, }