257 lines · 8.2 KB · TypeScript
RawDownload
/** * CheckpointDiffQuery - Query interface for computed checkpoint diffs. * * Provides read-only diff operations across checkpoint snapshots used by * orchestration APIs. * * @module CheckpointDiffQuery */import {  OrchestrationGetTurnDiffResult,  type OrchestrationGetFullThreadDiffInput,  type OrchestrationGetFullThreadDiffResult,  type OrchestrationGetTurnDiffInput,  type OrchestrationGetTurnDiffResult as OrchestrationGetTurnDiffResultType,  type ThreadId,} from "@t3tools/contracts";import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Schema from "effect/Schema"; import { checkpointRefForScopeOrdinal } from "../orchestration-v2/CheckpointService.ts";import * as ThreadManagement from "../orchestration-v2/ThreadManagementService.ts";import {  CheckpointDiffResultInvalidError,  CheckpointRefUnavailableError,  CheckpointThreadNotFoundError,  CheckpointTurnRangeUnavailableError,  CheckpointWorkspacePathMissingError,  type CheckpointServiceError,} from "./Errors.ts";import * as CheckpointStore from "./CheckpointStore.ts"; /** Service tag for checkpoint diff queries. */export class CheckpointDiffQuery extends Context.Service<  CheckpointDiffQuery,  {    /**     * Read the patch diff for a single turn checkpoint transition.     *     * Verifies checkpoint availability in both projection state and filesystem.     */    readonly getTurnDiff: (      input: OrchestrationGetTurnDiffInput,    ) => Effect.Effect<OrchestrationGetTurnDiffResultType, CheckpointServiceError>;     /**     * Read the full patch diff across a thread range of checkpoints.     *     * Uses turn-diff semantics with `fromTurnCount = 0`.     */    readonly getFullThreadDiff: (      input: OrchestrationGetFullThreadDiffInput,    ) => Effect.Effect<OrchestrationGetFullThreadDiffResult, CheckpointServiceError>;  }>()("t3/checkpointing/CheckpointDiffQuery") {} const isTurnDiffResult = Schema.is(OrchestrationGetTurnDiffResult); function buildTurnDiffResult(  input: {    readonly threadId: ThreadId;    readonly fromTurnCount: number;    readonly toTurnCount: number;  },  diff: string,): OrchestrationGetTurnDiffResultType {  return {    threadId: input.threadId,    fromTurnCount: input.fromTurnCount,    toTurnCount: input.toTurnCount,    diff,  };} export const make = Effect.gen(function* () {  const threads = yield* ThreadManagement.ThreadManagementService;  const checkpointStore = yield* CheckpointStore.CheckpointStore;   const getTurnDiff: CheckpointDiffQuery["Service"]["getTurnDiff"] = Effect.fn("getTurnDiff")(    function* (input) {      const operation = "CheckpointDiffQuery.getTurnDiff";      const ignoreWhitespace = input.ignoreWhitespace ?? true;      yield* Effect.annotateCurrentSpan({        "checkpoint.thread_id": input.threadId,        "checkpoint.from_turn_count": input.fromTurnCount,        "checkpoint.to_turn_count": input.toTurnCount,        "checkpoint.ignore_whitespace": ignoreWhitespace,      });       if (input.fromTurnCount === input.toTurnCount) {        const emptyDiff = buildTurnDiffResult(input, "");        if (!isTurnDiffResult(emptyDiff)) {          return yield* new CheckpointDiffResultInvalidError({            operation,            threadId: input.threadId,          });        }        return emptyDiff;      }       const projection = yield* threads.getCheckpointContext(input.threadId).pipe(        Effect.mapError(          () =>            new CheckpointThreadNotFoundError({              operation,              threadId: input.threadId,            }),        ),        Effect.withSpan("checkpoint.turnDiff.lookupContext"),      );      const completedRunIds = new Set(        projection.runs.filter((run) => run.status === "completed").map((run) => run.id),      );      const readyCheckpoints = projection.checkpoints.filter(        (checkpoint) =>          checkpoint.status === "ready" &&          checkpoint.appRunOrdinal !== null &&          checkpoint.runId !== null &&          completedRunIds.has(checkpoint.runId),      );      const maxTurnCount = readyCheckpoints.reduce(        (max, checkpoint) => Math.max(max, checkpoint.appRunOrdinal ?? 0),        0,      );      if (input.toTurnCount > maxTurnCount) {        return yield* new CheckpointTurnRangeUnavailableError({          operation,          threadId: input.threadId,          requestedTurnCount: input.toTurnCount,          availableTurnCount: maxTurnCount,        });      }       const toCheckpoint = readyCheckpoints.find(        (checkpoint) => checkpoint.appRunOrdinal === input.toTurnCount,      );      if (toCheckpoint === undefined) {        return yield* new CheckpointRefUnavailableError({          operation,          threadId: input.threadId,          turnCount: input.toTurnCount,          checkpoint: "to",        });      }       const toScope = projection.checkpointScopes.find(        (scope) => scope.id === toCheckpoint.scopeId,      );      if (toScope === undefined) {        return yield* new CheckpointWorkspacePathMissingError({          operation,          threadId: input.threadId,        });      }       const fromCheckpointRef =        input.fromTurnCount === 0          ? (() => {              const firstRun = projection.runs.find((run) => run.ordinal === 1);              const firstScope = projection.checkpointScopes.find(                (scope) => scope.runId === firstRun?.id && scope.kind === "root_run",              );              return firstScope === undefined                ? undefined                : checkpointRefForScopeOrdinal({                    scopeId: firstScope.id,                    ordinalWithinScope: 0,                  });            })()          : readyCheckpoints.find((checkpoint) => checkpoint.appRunOrdinal === input.fromTurnCount)              ?.ref;      if (fromCheckpointRef === undefined) {        return yield* new CheckpointRefUnavailableError({          operation,          threadId: input.threadId,          turnCount: input.fromTurnCount,          checkpoint: "from",        });      }       const diff = yield* checkpointStore        .diffCheckpoints({          cwd: toScope.cwd,          fromCheckpointRef,          toCheckpointRef: toCheckpoint.ref,          fallbackFromToHead: false,          ignoreWhitespace,        })        .pipe(Effect.withSpan("checkpoint.turnDiff.diffCheckpoints"));       const turnDiff = buildTurnDiffResult(input, diff);      if (!isTurnDiffResult(turnDiff)) {        return yield* new CheckpointDiffResultInvalidError({          operation,          threadId: input.threadId,        });      }       return turnDiff;    },  );   const getFullThreadDiff: CheckpointDiffQuery["Service"]["getFullThreadDiff"] = Effect.fn(    "CheckpointDiffQuery.getFullThreadDiff",  )(function* (input) {    const operation = "CheckpointDiffQuery.getFullThreadDiff";    const ignoreWhitespace = input.ignoreWhitespace ?? true;    yield* Effect.annotateCurrentSpan({      "checkpoint.thread_id": input.threadId,      "checkpoint.from_turn_count": 0,      "checkpoint.to_turn_count": input.toTurnCount,      "checkpoint.ignore_whitespace": ignoreWhitespace,      "checkpoint.diff_kind": "full-thread",    });     if (input.toTurnCount === 0) {      const emptyDiff = buildTurnDiffResult(        {          threadId: input.threadId,          fromTurnCount: 0,          toTurnCount: 0,        },        "",      );      if (!isTurnDiffResult(emptyDiff)) {        return yield* new CheckpointDiffResultInvalidError({          operation,          threadId: input.threadId,        });      }      return emptyDiff satisfies OrchestrationGetFullThreadDiffResult;    }     const turnDiff = yield* getTurnDiff({      threadId: input.threadId,      fromTurnCount: 0,      toTurnCount: input.toTurnCount,      ignoreWhitespace,    });    if (!isTurnDiffResult(turnDiff)) {      return yield* new CheckpointDiffResultInvalidError({        operation,        threadId: input.threadId,      });    }     return turnDiff satisfies OrchestrationGetFullThreadDiffResult;  });   return CheckpointDiffQuery.of({    getTurnDiff,    getFullThreadDiff,  });}); export const layer = Layer.effect(CheckpointDiffQuery, make);