Julius MarmingeT3 Code Mobile [WIP] (#2013)b3e8c035 months agoHistory
523 lines · 18.4 KB · TypeScript
RawDownload
import { pipe } from "effect/Function";import * as Arr from "effect/Array";import * as O from "effect/Order";import type {  MessageId,  OrchestrationCheckpointSummary,  OrchestrationEvent,  OrchestrationLatestTurn,  OrchestrationMessage,  OrchestrationThread,  OrchestrationThreadActivity,  TurnId,} from "@t3tools/contracts"; /** * Retention limits for collections within a thread. * These prevent unbounded growth of in-memory thread state. */export interface ThreadDetailRetentionLimits {  readonly maxMessages: number;  readonly maxProposedPlans: number;  readonly maxCheckpoints: number;  readonly maxActivities: number;} export const DEFAULT_THREAD_DETAIL_LIMITS: ThreadDetailRetentionLimits = {  maxMessages: 512,  maxProposedPlans: 64,  maxCheckpoints: 256,  maxActivities: 128,}; export type ThreadDetailReducerResult =  | { readonly kind: "updated"; readonly thread: OrchestrationThread }  | { readonly kind: "deleted" }  | { readonly kind: "unchanged" }; 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),]); /** * 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,  limits: ThreadDetailRetentionLimits = DEFAULT_THREAD_DETAIL_LIMITS,): 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,          latestTurn: null,          createdAt: event.payload.createdAt,          updatedAt: event.payload.updatedAt,          archivedAt: null,          deletedAt: null,          messages: [],          proposedPlans: [],          activities: [],          checkpoints: [],          session: null,        },      };     case "thread.deleted":      return { kind: "deleted" };     case "thread.archived":      return {        kind: "updated",        thread: {          ...thread,          archivedAt: event.payload.archivedAt,          updatedAt: event.payload.updatedAt,        },      };     case "thread.unarchived":      return {        kind: "updated",        thread: { ...thread, archivedAt: null, 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.modelSelection !== undefined            ? { modelSelection: event.payload.modelSelection }            : {}),          ...(event.payload.branch !== undefined ? { branch: event.payload.branch } : {}),          ...(event.payload.worktreePath !== undefined            ? { worktreePath: event.payload.worktreePath }            : {}),          updatedAt: 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);      const cappedMessages = Arr.takeRight(messages, limits.maxMessages);       // Update latestTurn for assistant messages bound to a turn.      const latestTurn: OrchestrationThread["latestTurn"] =        event.payload.role === "assistant" &&        event.payload.turnId !== null &&        (thread.latestTurn === null || thread.latestTurn.turnId === event.payload.turnId)          ? {              turnId: event.payload.turnId,              state: event.payload.streaming                ? "running"                : thread.latestTurn?.state === "interrupted"                  ? "interrupted"                  : thread.latestTurn?.state === "error"                    ? "error"                    : "completed",              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: event.payload.streaming                ? thread.latestTurn?.turnId === event.payload.turnId                  ? (thread.latestTurn.completedAt ?? null)                  : null                : event.payload.updatedAt,              assistantMessageId: event.payload.messageId,            }          : thread.latestTurn;       // Rebind checkpoint assistant message IDs for assistant messages.      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: cappedMessages,          checkpoints,          latestTurn,          updatedAt: event.occurredAt,        },      };    }     // ── Session ─────────────────────────────────────────────────────    case "thread.session-set": {      const latestTurn: OrchestrationLatestTurn | null =        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,            }          : thread.latestTurn;       return {        kind: "updated",        thread: {          ...thread,          session: event.payload.session,          latestTurn,          updatedAt: event.occurredAt,        },      };    }     case "thread.session-stop-requested":      return thread.session === null        ? { kind: "unchanged" }        : {            kind: "updated",            thread: {              ...thread,              session: {                ...thread.session,                status: "stopped",                activeTurnId: null,                updatedAt: event.payload.createdAt,              },              updatedAt: event.occurredAt,            },          };     // ── Proposed plans ──────────────────────────────────────────────    case "thread.proposed-plan-upserted": {      const proposedPlan = event.payload.proposedPlan;       const proposedPlans = pipe(        thread.proposedPlans,        Arr.filter((entry) => entry.id !== proposedPlan.id),        Arr.append(proposedPlan),        Arr.sort(proposedPlanOrder),        Arr.takeRight(limits.maxProposedPlans),      );       return {        kind: "updated",        thread: { ...thread, proposedPlans, updatedAt: event.occurredAt },      };    }     // ── Checkpoints / turn diffs ────────────────────────────────────    case "thread.turn-diff-completed": {      const checkpoint: OrchestrationCheckpointSummary = {        turnId: event.payload.turnId,        checkpointTurnCount: event.payload.checkpointTurnCount,        checkpointRef: event.payload.checkpointRef,        status: event.payload.status,        files: event.payload.files,        assistantMessageId: event.payload.assistantMessageId,        completedAt: event.payload.completedAt,      };       const existing = thread.checkpoints.find((entry) => entry.turnId === checkpoint.turnId);      // Don't overwrite a non-missing checkpoint with a missing one.      if (existing && existing.status !== "missing" && checkpoint.status === "missing") {        return { kind: "unchanged" };      }       const checkpoints = pipe(        thread.checkpoints,        Arr.filter((entry) => entry.turnId !== checkpoint.turnId),        Arr.append(checkpoint),        Arr.sort(checkpointOrder),        Arr.takeRight(limits.maxCheckpoints),      );       const latestTurn =        thread.latestTurn === null || thread.latestTurn.turnId === event.payload.turnId          ? {              turnId: event.payload.turnId,              state: checkpointStatusToTurnState(event.payload.status),              requestedAt: thread.latestTurn?.requestedAt ?? event.payload.completedAt,              startedAt: thread.latestTurn?.startedAt ?? event.payload.completedAt,              completedAt: event.payload.completedAt,              assistantMessageId: event.payload.assistantMessageId,            }          : thread.latestTurn;       return {        kind: "updated",        thread: { ...thread, checkpoints, latestTurn, updatedAt: event.occurredAt },      };    }     // ── Revert ──────────────────────────────────────────────────────    case "thread.reverted": {      const checkpoints = pipe(        thread.checkpoints,        Arr.filter(          (entry) =>            entry.checkpointTurnCount !== undefined &&            entry.checkpointTurnCount <= event.payload.turnCount,        ),        Arr.sort(checkpointOrder),        Arr.takeRight(limits.maxCheckpoints),      );       const retainedTurnIds = new Set(Arr.map(checkpoints, (entry) => entry.turnId));      const messages = pipe(        retainMessagesAfterRevert(thread.messages, retainedTurnIds),        Arr.takeRight(limits.maxMessages),      );      const proposedPlans = pipe(        thread.proposedPlans,        Arr.filter((plan) => plan.turnId === null || retainedTurnIds.has(plan.turnId)),        Arr.takeRight(limits.maxProposedPlans),      );      const activities = pipe(        thread.activities,        Arr.filter((activity) => activity.turnId === null || retainedTurnIds.has(activity.turnId)),      );      const latestCheckpoint = checkpoints.at(-1) ?? null;       return {        kind: "updated",        thread: {          ...thread,          checkpoints,          messages,          proposedPlans,          activities,          latestTurn:            latestCheckpoint === null              ? null              : {                  turnId: latestCheckpoint.turnId,                  state: checkpointStatusToTurnState(                    latestCheckpoint.status as "ready" | "missing" | "error",                  ),                  requestedAt: latestCheckpoint.completedAt,                  startedAt: latestCheckpoint.completedAt,                  completedAt: latestCheckpoint.completedAt,                  assistantMessageId: latestCheckpoint.assistantMessageId ?? null,                },          updatedAt: event.occurredAt,        },      };    }     // ── Activities ──────────────────────────────────────────────────    case "thread.activity-appended": {      const activities = pipe(        thread.activities,        Arr.filter((activity) => activity.id !== event.payload.activity.id),        Arr.append(event.payload.activity),        Arr.sort(activityOrder),        Arr.takeRight(limits.maxActivities),      );       return {        kind: "updated",        thread: { ...thread, activities, updatedAt: event.occurredAt },      };    }     // ── Events that don't mutate thread state directly ──────────────    case "thread.approval-response-requested":    case "thread.user-input-response-requested":    case "thread.checkpoint-revert-requested":      return { kind: "unchanged" };  }   // Forward-compatible: ignore unrecognized event types.  return { kind: "unchanged" };} // ── Helpers ────────────────────────────────────────────────────────── function checkpointStatusToTurnState(  status: "ready" | "missing" | "error",): OrchestrationLatestTurn["state"] {  switch (status) {    case "ready":      return "completed";    case "error":      return "error";    case "missing":      return "completed";  }} function rebindCheckpointAssistantMessage(  checkpoints: ReadonlyArray<OrchestrationCheckpointSummary>,  turnId: TurnId,

Lines 1–500 of 523.