Julius Marmingesimplify layersfb458968 months agoHistory
1228 lines · 44.1 KB · TypeScript
RawDownload
import Mime from "@effect/platform-node/Mime";import {  ApprovalRequestId,  type ChatAttachment,  type OrchestrationEvent,} from "@t3tools/contracts";import * as NodeServices from "@effect/platform-node/NodeServices";import { Effect, FileSystem, Layer, Option, Path, Stream } from "effect";import * as SqlClient from "effect/unstable/sql/SqlClient"; import { toPersistenceSqlError, type ProjectionRepositoryError } from "../../persistence/Errors.ts";import { OrchestrationEventStore } from "../../persistence/Services/OrchestrationEventStore.ts";import { ProjectionPendingApprovalRepository } from "../../persistence/Services/ProjectionPendingApprovals.ts";import { ProjectionProjectRepository } from "../../persistence/Services/ProjectionProjects.ts";import { ProjectionStateRepository } from "../../persistence/Services/ProjectionState.ts";import { ProjectionThreadActivityRepository } from "../../persistence/Services/ProjectionThreadActivities.ts";import { type ProjectionThreadActivity } from "../../persistence/Services/ProjectionThreadActivities.ts";import {  type ProjectionThreadMessage,  ProjectionThreadMessageRepository,} from "../../persistence/Services/ProjectionThreadMessages.ts";import { ProjectionThreadSessionRepository } from "../../persistence/Services/ProjectionThreadSessions.ts";import {  type ProjectionTurn,  ProjectionTurnRepository,} from "../../persistence/Services/ProjectionTurns.ts";import { ProjectionThreadRepository } from "../../persistence/Services/ProjectionThreads.ts";import { ProjectionPendingApprovalRepositoryLive } from "../../persistence/Layers/ProjectionPendingApprovals.ts";import { ProjectionProjectRepositoryLive } from "../../persistence/Layers/ProjectionProjects.ts";import { ProjectionStateRepositoryLive } from "../../persistence/Layers/ProjectionState.ts";import { ProjectionThreadActivityRepositoryLive } from "../../persistence/Layers/ProjectionThreadActivities.ts";import { ProjectionThreadMessageRepositoryLive } from "../../persistence/Layers/ProjectionThreadMessages.ts";import { ProjectionThreadSessionRepositoryLive } from "../../persistence/Layers/ProjectionThreadSessions.ts";import { ProjectionTurnRepositoryLive } from "../../persistence/Layers/ProjectionTurns.ts";import { ProjectionThreadRepositoryLive } from "../../persistence/Layers/ProjectionThreads.ts";import { ServerConfig } from "../../config.ts";import {  OrchestrationProjectionPipeline,  type OrchestrationProjectionPipelineShape,} from "../Services/ProjectionPipeline.ts";import {  ATTACHMENTS_ROUTE_PREFIX,  IMAGE_EXTENSION_BY_MIME_TYPE,  SAFE_IMAGE_FILE_EXTENSIONS,} from "../../projectFaviconRoute.ts"; export const ORCHESTRATION_PROJECTOR_NAMES = {  projects: "projection.projects",  threads: "projection.threads",  threadMessages: "projection.thread-messages",  threadActivities: "projection.thread-activities",  threadSessions: "projection.thread-sessions",  threadTurns: "projection.thread-turns",  checkpoints: "projection.checkpoints",  pendingApprovals: "projection.pending-approvals",} as const; type ProjectorName =  (typeof ORCHESTRATION_PROJECTOR_NAMES)[keyof typeof ORCHESTRATION_PROJECTOR_NAMES]; interface ProjectorDefinition {  readonly name: ProjectorName;  readonly apply: (    event: OrchestrationEvent,    attachmentSideEffects: AttachmentSideEffects,  ) => Effect.Effect<void, ProjectionRepositoryError>;} interface PendingAttachmentWrite {  readonly absolutePath: string;  readonly bytes: Uint8Array;} interface AttachmentSideEffects {  readonly writes: Array<PendingAttachmentWrite>;  readonly deletedThreadIds: Set<string>;  readonly prunedThreadRelativePaths: Map<string, Set<string>>;} function toSafeThreadAttachmentSegment(threadId: string): string | null {  const segment = encodeURIComponent(threadId);  if (    segment.length === 0 ||    segment === "." ||    segment === ".." ||    segment.includes("/") ||    segment.includes("\\") ||    segment.includes("\0")  ) {    return null;  }  return segment;} function parseBase64DataUrl(  dataUrl: string,): { readonly mimeType: string; readonly base64: string } | null {  const match = /^data:([^;,]+);base64,([a-z0-9+/=\r\n]+)$/i.exec(dataUrl.trim());  if (!match) return null;   const mimeType = match[1]?.trim().toLowerCase();  const base64 = match[2]?.replace(/\s+/g, "");  if (!mimeType || !base64) return null;   return { mimeType, base64 };} function inferImageExtension(attachment: Extract<ChatAttachment, { type: "image" }>): string {  const normalizedMimeType = attachment.mimeType.toLowerCase();  if (normalizedMimeType.startsWith("image/")) {    const fromMime = IMAGE_EXTENSION_BY_MIME_TYPE[normalizedMimeType];    if (fromMime) return fromMime;  }  const ext = Mime.getExtension(attachment.mimeType);  if (ext && SAFE_IMAGE_FILE_EXTENSIONS.has(ext)) return ext;  return ".bin";} const materializeAttachmentsForProjection = Effect.fn(function* (input: {  readonly threadId: string;  readonly messageId: string;  readonly attachments: ReadonlyArray<ChatAttachment>;  readonly stageFileWrite: (pendingWrite: PendingAttachmentWrite) => void;}) {  if (input.attachments.length === 0) return [];   const serverConfig = yield* Effect.serviceOption(ServerConfig);  if (Option.isNone(serverConfig)) return input.attachments;   const path = yield* Path.Path;  const attachmentsRootDir = path.join(serverConfig.value.stateDir, "attachments");  const threadSegment = toSafeThreadAttachmentSegment(input.threadId);  if (!threadSegment) {    yield* Effect.logWarning("skipping attachment materialization for unsafe thread id", {      threadId: input.threadId,      messageId: input.messageId,    });    return input.attachments;  }  const messageSegment = encodeURIComponent(input.messageId);   return yield* Effect.forEach(    input.attachments,    (attachment, index) =>      Effect.sync(() => {        if (attachment.type !== "image" || !attachment.dataUrl.startsWith("data:")) {          return attachment;        }         const parsed = parseBase64DataUrl(attachment.dataUrl);        if (!parsed) {          return attachment;        }        if (!parsed.mimeType.startsWith("image/")) {          return attachment;        }         const bytes = Buffer.from(parsed.base64, "base64");        if (bytes.byteLength === 0) return attachment;         const fileName = `${index}${inferImageExtension({          ...attachment,          mimeType: parsed.mimeType,        })}`;        const uniqueFileName = `${messageSegment}-${fileName}`;        const relativePath = `${threadSegment}/${uniqueFileName}`;        const absolutePath = path.join(attachmentsRootDir, threadSegment, uniqueFileName);        input.stageFileWrite({ absolutePath, bytes });         return {          ...attachment,          dataUrl: `${ATTACHMENTS_ROUTE_PREFIX}/${relativePath}`,        } satisfies ChatAttachment;      }),    { concurrency: 1 },  );}); function extractActivityRequestId(payload: unknown): ApprovalRequestId | null {  if (typeof payload !== "object" || payload === null) {    return null;  }  const requestId = (payload as Record<string, unknown>).requestId;  return typeof requestId === "string" ? ApprovalRequestId.makeUnsafe(requestId) : null;} function retainProjectionMessagesAfterRevert(  messages: ReadonlyArray<ProjectionThreadMessage>,  turns: ReadonlyArray<ProjectionTurn>,  turnCount: number,): ReadonlyArray<ProjectionThreadMessage> {  const retainedMessageIds = new Set<string>();  const retainedTurnIds = new Set<string>();  const keptTurns = turns.filter(    (turn) =>      turn.turnId !== null &&      turn.checkpointTurnCount !== null &&      turn.checkpointTurnCount <= turnCount,  );  for (const turn of keptTurns) {    if (turn.turnId !== null) {      retainedTurnIds.add(turn.turnId);    }    if (turn.pendingMessageId !== null) {      retainedMessageIds.add(turn.pendingMessageId);    }    if (turn.assistantMessageId !== null) {      retainedMessageIds.add(turn.assistantMessageId);    }  }   for (const message of messages) {    if (message.role === "system") {      retainedMessageIds.add(message.messageId);      continue;    }    if (message.turnId !== null && retainedTurnIds.has(message.turnId)) {      retainedMessageIds.add(message.messageId);    }  }   const retainedUserCount = messages.filter(    (message) => message.role === "user" && retainedMessageIds.has(message.messageId),  ).length;  const missingUserCount = Math.max(0, turnCount - retainedUserCount);  if (missingUserCount > 0) {    const fallbackUserMessages = messages      .filter(        (message) =>          message.role === "user" &&          !retainedMessageIds.has(message.messageId) &&          (message.turnId === null || retainedTurnIds.has(message.turnId)),      )      .toSorted(        (left, right) =>          left.createdAt.localeCompare(right.createdAt) ||          left.messageId.localeCompare(right.messageId),      )      .slice(0, missingUserCount);    for (const message of fallbackUserMessages) {      retainedMessageIds.add(message.messageId);    }  }   const retainedAssistantCount = messages.filter(    (message) => message.role === "assistant" && retainedMessageIds.has(message.messageId),  ).length;  const missingAssistantCount = Math.max(0, turnCount - retainedAssistantCount);  if (missingAssistantCount > 0) {    const fallbackAssistantMessages = messages      .filter(        (message) =>          message.role === "assistant" &&          !retainedMessageIds.has(message.messageId) &&          (message.turnId === null || retainedTurnIds.has(message.turnId)),      )      .toSorted(        (left, right) =>          left.createdAt.localeCompare(right.createdAt) ||          left.messageId.localeCompare(right.messageId),      )      .slice(0, missingAssistantCount);    for (const message of fallbackAssistantMessages) {      retainedMessageIds.add(message.messageId);    }  }   return messages.filter((message) => retainedMessageIds.has(message.messageId));} function retainProjectionActivitiesAfterRevert(  activities: ReadonlyArray<ProjectionThreadActivity>,  turns: ReadonlyArray<ProjectionTurn>,  turnCount: number,): ReadonlyArray<ProjectionThreadActivity> {  const retainedTurnIds = new Set<string>(    turns      .filter(        (turn) =>          turn.turnId !== null &&          turn.checkpointTurnCount !== null &&          turn.checkpointTurnCount <= turnCount,      )      .flatMap((turn) => (turn.turnId === null ? [] : [turn.turnId])),  );  return activities.filter(    (activity) => activity.turnId === null || retainedTurnIds.has(activity.turnId),  );} function toAttachmentRelativePath(dataUrl: string): string | null {  const prefix = `${ATTACHMENTS_ROUTE_PREFIX}/`;  if (!dataUrl.startsWith(prefix)) {    return null;  }  const relativePath = dataUrl.slice(prefix.length).replace(/^[/\\]+/, "");  if (relativePath.length === 0 || relativePath.startsWith("..")) {    return null;  }  return relativePath.replace(/\\/g, "/");} function collectThreadAttachmentRelativePaths(  threadId: string,  messages: ReadonlyArray<ProjectionThreadMessage>,): Set<string> {  const threadSegment = toSafeThreadAttachmentSegment(threadId);  if (!threadSegment) {    return new Set();  }  const threadPrefix = `${threadSegment}/`;  const relativePaths = new Set<string>();  for (const message of messages) {    for (const attachment of message.attachments ?? []) {      if (attachment.type !== "image") {        continue;      }      const relativePath = toAttachmentRelativePath(attachment.dataUrl);      if (!relativePath || !relativePath.startsWith(threadPrefix)) {        continue;      }      const threadRelativePath = relativePath.slice(threadPrefix.length).replace(/^[/\\]+/, "");      if (threadRelativePath.length === 0 || threadRelativePath.startsWith("..")) {        continue;      }      relativePaths.add(threadRelativePath.replace(/\\/g, "/"));    }  }  return relativePaths;} const runAttachmentSideEffects = Effect.fn(function* (input: {  readonly sideEffects: AttachmentSideEffects;  readonly fileSystem: FileSystem.FileSystem;  readonly path: Path.Path;}) {  const serverConfig = yield* Effect.serviceOption(ServerConfig);  if (Option.isNone(serverConfig)) {    return;  }   const attachmentsRootDir = input.path.join(serverConfig.value.stateDir, "attachments");   yield* Effect.forEach(    input.sideEffects.deletedThreadIds,    (threadId) =>      Effect.gen(function* () {        const threadSegment = toSafeThreadAttachmentSegment(threadId);        if (!threadSegment) {          yield* Effect.logWarning("skipping attachment cleanup for unsafe thread id", {            threadId,          });          return;        }        const threadDir = input.path.join(attachmentsRootDir, threadSegment);        yield* input.fileSystem.remove(threadDir, { recursive: true, force: true });      }),    { concurrency: 1 },  );   yield* Effect.forEach(    input.sideEffects.prunedThreadRelativePaths.entries(),    ([threadId, keptThreadRelativePaths]) => {      if (input.sideEffects.deletedThreadIds.has(threadId)) {        return Effect.void;      }      return Effect.gen(function* () {        const threadSegment = toSafeThreadAttachmentSegment(threadId);        if (!threadSegment) {          yield* Effect.logWarning("skipping attachment prune for unsafe thread id", { threadId });          return;        }        const threadDir = input.path.join(attachmentsRootDir, threadSegment);        const entries = yield* input.fileSystem          .readDirectory(threadDir, { recursive: true })          .pipe(Effect.catch(() => Effect.succeed([] as Array<string>)));        yield* Effect.forEach(          entries,          (entry) =>            Effect.gen(function* () {              const threadRelativePath = entry.replace(/^[/\\]+/, "").replace(/\\/g, "/");              if (threadRelativePath.length === 0 || threadRelativePath.startsWith("..")) {                return;              }               const absolutePath = input.path.join(threadDir, threadRelativePath);              const fileInfo = yield* input.fileSystem                .stat(absolutePath)                .pipe(Effect.catch(() => Effect.succeed(null)));              if (!fileInfo || fileInfo.type !== "File") {                return;              }               if (!keptThreadRelativePaths.has(threadRelativePath)) {                yield* input.fileSystem.remove(absolutePath, { force: true });              }            }),          { concurrency: 1 },        );      });    },    { concurrency: 1 },  );   const writesByPath = new Map<string, Uint8Array>();  for (const pendingWrite of input.sideEffects.writes) {    // Last write wins for a path so updated attachment contents replace stale bytes.    writesByPath.set(pendingWrite.absolutePath, pendingWrite.bytes);  }  yield* Effect.forEach(    writesByPath.entries(),    ([absolutePath, bytes]) =>      Effect.gen(function* () {        yield* input.fileSystem.makeDirectory(input.path.dirname(absolutePath), {          recursive: true,        });        yield* input.fileSystem.writeFile(absolutePath, bytes);      }),    { concurrency: 1 },  );}); const makeOrchestrationProjectionPipeline = Effect.gen(function* () {  const sql = yield* SqlClient.SqlClient;  const eventStore = yield* OrchestrationEventStore;  const projectionStateRepository = yield* ProjectionStateRepository;  const projectionProjectRepository = yield* ProjectionProjectRepository;  const projectionThreadRepository = yield* ProjectionThreadRepository;  const projectionThreadMessageRepository = yield* ProjectionThreadMessageRepository;  const projectionThreadActivityRepository = yield* ProjectionThreadActivityRepository;  const projectionThreadSessionRepository = yield* ProjectionThreadSessionRepository;  const projectionTurnRepository = yield* ProjectionTurnRepository;  const projectionPendingApprovalRepository = yield* ProjectionPendingApprovalRepository;   const fileSystem = yield* FileSystem.FileSystem;  const path = yield* Path.Path;   const applyProjectsProjection: ProjectorDefinition["apply"] = (event, _attachmentSideEffects) =>    Effect.gen(function* () {      switch (event.type) {        case "project.created":          yield* projectionProjectRepository.upsert({            projectId: event.payload.projectId,            title: event.payload.title,            workspaceRoot: event.payload.workspaceRoot,            defaultModel: event.payload.defaultModel,            scripts: event.payload.scripts,            createdAt: event.payload.createdAt,            updatedAt: event.payload.updatedAt,            deletedAt: null,          });          return;         case "project.meta-updated": {          const existingRow = yield* projectionProjectRepository.getById({            projectId: event.payload.projectId,          });          if (Option.isNone(existingRow)) {            return;          }          yield* projectionProjectRepository.upsert({            ...existingRow.value,            ...(event.payload.title !== undefined ? { title: event.payload.title } : {}),            ...(event.payload.workspaceRoot !== undefined              ? { workspaceRoot: event.payload.workspaceRoot }              : {}),            ...(event.payload.defaultModel !== undefined              ? { defaultModel: event.payload.defaultModel }              : {}),            ...(event.payload.scripts !== undefined ? { scripts: event.payload.scripts } : {}),            updatedAt: event.payload.updatedAt,          });          return;        }         case "project.deleted": {          const existingRow = yield* projectionProjectRepository.getById({            projectId: event.payload.projectId,          });          if (Option.isNone(existingRow)) {            return;          }          yield* projectionProjectRepository.upsert({            ...existingRow.value,            deletedAt: event.payload.deletedAt,            updatedAt: event.payload.deletedAt,          });          return;        }         default:          return;      }    });   const applyThreadsProjection: ProjectorDefinition["apply"] = (event, attachmentSideEffects) =>    Effect.gen(function* () {      switch (event.type) {        case "thread.created":          yield* projectionThreadRepository.upsert({

Lines 1–500 of 1228.