2776 lines · 97.2 KB · TypeScript
RawDownload
import {  scopedProjectKey,  scopedThreadKey,  scopeProjectRef,  scopeThreadRef,} from "@t3tools/client-runtime/environment";import * as Schema from "effect/Schema";import {  defaultInstanceIdForDriver,  EnvironmentId,  MessageId,  ProjectId,  ProviderDriverKind,  ProviderInstanceId,  PROVIDER_SEND_TURN_MAX_ATTACHMENTS,  ThreadId,  type ModelSelection,  type ProviderOptionSelection,} from "@t3tools/contracts";import { createModelSelection } from "@t3tools/shared/model";import {  collectAssistantCitations,  serializeAssistantCitation,} from "@t3tools/shared/assistantCitations"; // The composer draft's `modelSelectionByProvider` and// `stickyModelSelectionByProvider` maps are keyed by `ProviderInstanceId`// in production; these aliases keep the legacy-key migration tests concise.const CODEX_INSTANCE = ProviderInstanceId.make("codex");const CODEX_SECONDARY_INSTANCE = ProviderInstanceId.make("codex_secondary");const CLAUDE_AGENT_INSTANCE = ProviderInstanceId.make("claudeAgent");const CURSOR_INSTANCE = ProviderInstanceId.make("cursor");const CODEX_DRIVER = ProviderDriverKind.make("codex");const CLAUDE_AGENT_DRIVER = ProviderDriverKind.make("claudeAgent");const CURSOR_DRIVER = ProviderDriverKind.make("cursor"); type ProviderOptionSelectionBag = ReadonlyArray<ProviderOptionSelection>;type ProviderOptionSelectionsByProvider = Partial<Record<string, ProviderOptionSelectionBag>>; function toSelections(  options: Record<string, string | boolean | undefined> | undefined,): ReadonlyArray<ProviderOptionSelection> {  const result: Array<ProviderOptionSelection> = [];  if (!options) return result;  for (const [id, value] of Object.entries(options)) {    if (typeof value === "string" || typeof value === "boolean") {      result.push({ id, value });    }  }  return result;} function selectionsByProvider(  options: Partial<Record<ProviderDriverKind, Record<string, string | boolean | undefined>>>,): ProviderOptionSelectionsByProvider {  const result: ProviderOptionSelectionsByProvider = {};  for (const [provider, bag] of Object.entries(options) as Array<    [ProviderDriverKind, Record<string, string | boolean | undefined>]  >) {    result[provider] = toSelections(bag);  }  return result;}import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; import {  COMPOSER_DRAFT_STORAGE_KEY,  clearComposerDraftsEnvironment,  composerDraftHasUserContent,  finalizePromotedDraftThreadByRef,  markPromotedDraftThreadByRef,  type ComposerFileAttachment,  type ComposerImageAttachment,  composerFileNeedsReattach,  partializeComposerDraftStoreState,  useComposerDraftStore,  DraftId,} from "./composerDraftStore";import { removeLocalStorageItem, setLocalStorageItem } from "./hooks/useLocalStorage";import {  INLINE_TERMINAL_CONTEXT_PLACEHOLDER,  insertInlineTerminalContextPlaceholder,  type TerminalContextDraft,} from "./lib/terminalContext";import { createDeferredStorage } from "./lib/storage"; function makeImage(input: {  id: string;  previewUrl: string;  name?: string;  mimeType?: string;  sizeBytes?: number;  lastModified?: number;}): ComposerImageAttachment {  const name = input.name ?? "image.png";  const mimeType = input.mimeType ?? "image/png";  const sizeBytes = input.sizeBytes ?? 4;  const lastModified = input.lastModified ?? 1_700_000_000_000;  const file = new File([new Uint8Array(sizeBytes).fill(1)], name, {    type: mimeType,    lastModified,  });  return {    type: "image",    id: input.id,    name,    mimeType,    sizeBytes: file.size,    previewUrl: input.previewUrl,    file,  };} function makeFile(id: string): ComposerFileAttachment {  const file = new File(["report"], "report.pdf", { type: "application/pdf" });  return {    type: "file",    id,    name: file.name,    mimeType: file.type,    sizeBytes: file.size,    file,  };} function makeTerminalContext(input: {  id: string;  text?: string;  terminalId?: string;  terminalLabel?: string;  lineStart?: number;  lineEnd?: number;}): TerminalContextDraft {  return {    id: input.id,    threadId: ThreadId.make("thread-dedupe"),    terminalId: input.terminalId ?? "default",    terminalLabel: input.terminalLabel ?? "Terminal 1",    lineStart: input.lineStart ?? 4,    lineEnd: input.lineEnd ?? 5,    text: input.text ?? "git status\nOn branch main",    createdAt: "2026-03-13T12:00:00.000Z",  };} function resetComposerDraftStore() {  useComposerDraftStore.setState({    draftsByThreadKey: {},    draftThreadsByThreadKey: {},    logicalProjectDraftThreadKeyByLogicalProjectKey: {},    stickyModelSelectionByProvider: {},    stickyActiveProvider: null,  });} function modelSelection(  provider: ProviderDriverKind,  model: string,  options?: Record<string, string | boolean | undefined>,): ModelSelection {  return createModelSelection(defaultInstanceIdForDriver(provider), model, toSelections(options));} function providerModelOptions(  options: Partial<Record<string, Record<string, string | boolean | undefined>>>,): ProviderOptionSelectionsByProvider {  return selectionsByProvider(options);} const TEST_ENVIRONMENT_ID = EnvironmentId.make("environment-local");const OTHER_TEST_ENVIRONMENT_ID = EnvironmentId.make("environment-remote");const LEGACY_TEST_ENVIRONMENT_ID = EnvironmentId.make("__legacy__"); function threadKeyFor(  threadId: ThreadId,  environmentId: EnvironmentId = LEGACY_TEST_ENVIRONMENT_ID,): string {  if (environmentId === LEGACY_TEST_ENVIRONMENT_ID) {    return threadId;  }  return scopedThreadKey(scopeThreadRef(environmentId, threadId));} function draftFor(threadId: ThreadId, environmentId: EnvironmentId = LEGACY_TEST_ENVIRONMENT_ID) {  const store = useComposerDraftStore.getState().draftsByThreadKey;  return store[threadKeyFor(threadId, environmentId)] ?? store[threadId] ?? undefined;} function draftByKey(key: string) {  return useComposerDraftStore.getState().draftsByThreadKey[key] ?? undefined;} describe("composerDraftStore assistant citations", () => {  beforeEach(resetComposerDraftStore);  afterEach(resetComposerDraftStore);   it("keeps quotes, comments, and remote source IDs through persistence and removes them on clear", async () => {    await useComposerDraftStore.persist.clearStorage();    vi.useFakeTimers();    try {      const threadId = ThreadId.make("citation-draft");      const threadRef = scopeThreadRef(TEST_ENVIRONMENT_ID, threadId);      const citation = {        version: 1 as const,        environmentId: EnvironmentId.make("remote-source"),        threadId: ThreadId.make("source-thread"),        messageId: MessageId.make("source-message"),        text: "Preserve the selected text after reload.",        comment: "Why is this important?\nPlease show an example.",        start: 12,        end: 52,        prefix: "Before. ",        suffix: " After.",      };      const prompt = `Explain ${serializeAssistantCitation(citation)} further.`;      useComposerDraftStore.getState().setPrompt(threadRef, prompt);      await vi.advanceTimersByTimeAsync(300);      resetComposerDraftStore();      await useComposerDraftStore.persist.rehydrate();      const restored = draftFor(threadId, TEST_ENVIRONMENT_ID)?.prompt ?? "";      expect(restored).toBe(prompt);      expect(collectAssistantCitations(restored).map((entry) => entry.citation)).toEqual([        citation,      ]);      useComposerDraftStore.getState().clearComposerContent(threadRef);      expect(        collectAssistantCitations(draftFor(threadId, TEST_ENVIRONMENT_ID)?.prompt ?? ""),      ).toEqual([]);    } finally {      await useComposerDraftStore.persist.clearStorage();      vi.useRealTimers();    }  });}); describe("composerDraftStore addImages", () => {  const threadId = ThreadId.make("thread-dedupe");  const threadRef = scopeThreadRef(TEST_ENVIRONMENT_ID, threadId);  let originalRevokeObjectUrl: typeof URL.revokeObjectURL;  let revokeSpy: ReturnType<typeof vi.fn<(url: string) => void>>;   beforeEach(() => {    resetComposerDraftStore();    originalRevokeObjectUrl = URL.revokeObjectURL;    revokeSpy = vi.fn();    URL.revokeObjectURL = revokeSpy;  });   afterEach(() => {    URL.revokeObjectURL = originalRevokeObjectUrl;  });   it("deduplicates identical images in one batch by file signature", () => {    const first = makeImage({      id: "img-1",      previewUrl: "blob:first",      name: "same.png",      mimeType: "image/png",      sizeBytes: 12,      lastModified: 12345,    });    const duplicate = makeImage({      id: "img-2",      previewUrl: "blob:duplicate",      name: "same.png",      mimeType: "image/png",      sizeBytes: 12,      lastModified: 12345,    });     useComposerDraftStore.getState().addImages(threadRef, [first, duplicate]);     const draft = draftFor(threadId, TEST_ENVIRONMENT_ID);    expect(draft?.images.map((image) => image.id)).toEqual(["img-1"]);    expect(revokeSpy).toHaveBeenCalledWith("blob:duplicate");  });   it("deduplicates against existing images across calls by file signature", () => {    const first = makeImage({      id: "img-a",      previewUrl: "blob:a",      name: "same.png",      mimeType: "image/png",      sizeBytes: 9,      lastModified: 777,    });    const duplicateLater = makeImage({      id: "img-b",      previewUrl: "blob:b",      name: "same.png",      mimeType: "image/png",      sizeBytes: 9,      lastModified: 999,    });     expect(useComposerDraftStore.getState().addImage(threadRef, first)).toBe(true);    expect(useComposerDraftStore.getState().addImage(threadRef, duplicateLater)).toBe(false);     const draft = draftFor(threadId, TEST_ENVIRONMENT_ID);    expect(draft?.images.map((image) => image.id)).toEqual(["img-a"]);    expect(revokeSpy).toHaveBeenCalledWith("blob:b");  });   it("returns false when addImage receives an existing image id", () => {    const first = makeImage({ id: "img-same", previewUrl: "blob:first" });    const duplicate = makeImage({      id: "img-same",      previewUrl: "blob:duplicate",      name: "different.png",    });     expect(useComposerDraftStore.getState().addImage(threadRef, first)).toBe(true);    expect(useComposerDraftStore.getState().addImage(threadRef, duplicate)).toBe(false);    expect(draftFor(threadId, TEST_ENVIRONMENT_ID)?.images).toEqual([first]);    expect(revokeSpy).toHaveBeenCalledWith("blob:duplicate");  });   it("does not revoke blob URLs that are still used by an accepted duplicate image", () => {    const first = makeImage({      id: "img-shared",      previewUrl: "blob:shared",    });    const duplicateSameUrl = makeImage({      id: "img-shared",      previewUrl: "blob:shared",    });     useComposerDraftStore.getState().addImages(threadRef, [first, duplicateSameUrl]);     const draft = draftFor(threadId, TEST_ENVIRONMENT_ID);    expect(draft?.images.map((image) => image.id)).toEqual(["img-shared"]);    expect(revokeSpy).not.toHaveBeenCalledWith("blob:shared");  });}); describe("composerDraftStore clearComposerContent", () => {  const threadId = ThreadId.make("thread-clear");  const threadRef = scopeThreadRef(TEST_ENVIRONMENT_ID, threadId);  let originalRevokeObjectUrl: typeof URL.revokeObjectURL;  let revokeSpy: ReturnType<typeof vi.fn<(url: string) => void>>;   beforeEach(() => {    resetComposerDraftStore();    originalRevokeObjectUrl = URL.revokeObjectURL;    revokeSpy = vi.fn();    URL.revokeObjectURL = revokeSpy;  });   afterEach(() => {    URL.revokeObjectURL = originalRevokeObjectUrl;  });   it("does not revoke blob preview URLs when clearing composer content", () => {    const first = makeImage({      id: "img-optimistic",      previewUrl: "blob:optimistic",    });    useComposerDraftStore.getState().addImage(threadRef, first);     useComposerDraftStore.getState().clearComposerContent(threadRef);     const draft = draftFor(threadId, TEST_ENVIRONMENT_ID);    expect(draft).toBeUndefined();    expect(revokeSpy).not.toHaveBeenCalledWith("blob:optimistic");  });}); describe("composerDraftStore unsent draft marker", () => {  const threadId = ThreadId.make("thread-unsent-marker");  const threadRef = scopeThreadRef(TEST_ENVIRONMENT_ID, threadId);   beforeEach(() => {    resetComposerDraftStore();  });   it("reports content for typed text and clears when the composer is emptied", () => {    const hasDraft = () =>      composerDraftHasUserContent(useComposerDraftStore.getState().getComposerDraft(threadRef));     expect(hasDraft()).toBe(false);     useComposerDraftStore.getState().setPrompt(threadRef, "   ");    expect(hasDraft()).toBe(false);     useComposerDraftStore.getState().setPrompt(threadRef, "follow up on the relay case");    expect(hasDraft()).toBe(true);     useComposerDraftStore.getState().clearComposerContent(threadRef);    expect(hasDraft()).toBe(false);  });}); describe("composerDraftStore file attachments", () => {  const threadId = ThreadId.make("thread-files");  const threadRef = scopeThreadRef(TEST_ENVIRONMENT_ID, threadId);   beforeEach(() => {    resetComposerDraftStore();  });   it("persists uploaded file references without including file contents", () => {    const store = useComposerDraftStore.getState();    store.addFiles(threadRef, [makeFile("file-1")]);    store.setFileUpload(threadRef, "file-1", TEST_ENVIRONMENT_ID, "pending-report-pdf");     const persistApi = useComposerDraftStore.persist as unknown as {      getOptions: () => {        merge: (          persistedState: unknown,          currentState: ReturnType<typeof useComposerDraftStore.getState>,        ) => ReturnType<typeof useComposerDraftStore.getState>;      };    };    const options = persistApi.getOptions();    const persisted = partializeComposerDraftStoreState(useComposerDraftStore.getState());     expect(persisted.draftsByThreadKey[threadKeyFor(threadId, TEST_ENVIRONMENT_ID)]?.files).toEqual(      [        {          id: "file-1",          name: "report.pdf",          mimeType: "application/pdf",          sizeBytes: 6,          attachmentId: "pending-report-pdf",          environmentId: TEST_ENVIRONMENT_ID,        },      ],    );     const hydrated = options.merge(persisted, useComposerDraftStore.getState());    expect(hydrated.draftsByThreadKey[threadKeyFor(threadId, TEST_ENVIRONMENT_ID)]?.files).toEqual([      {        type: "file",        id: "file-1",        name: "report.pdf",        mimeType: "application/pdf",        sizeBytes: 6,        file: null,        uploadedAttachmentId: "pending-report-pdf",        uploadEnvironmentId: TEST_ENVIRONMENT_ID,      },    ]);  });   it("persists a pending file as a needs-reattach marker instead of dropping it", () => {    const store = useComposerDraftStore.getState();    // No setFileUpload: the upload never finished, so there is no attachment    // id and the File handle cannot serialize.    store.addFiles(threadRef, [makeFile("file-pending")]);     const persistApi = useComposerDraftStore.persist as unknown as {      getOptions: () => {        merge: (          persistedState: unknown,          currentState: ReturnType<typeof useComposerDraftStore.getState>,        ) => ReturnType<typeof useComposerDraftStore.getState>;      };    };    const options = persistApi.getOptions();    const persisted = partializeComposerDraftStoreState(useComposerDraftStore.getState());     expect(persisted.draftsByThreadKey[threadKeyFor(threadId, TEST_ENVIRONMENT_ID)]?.files).toEqual(      [        {          id: "file-pending",          name: "report.pdf",          mimeType: "application/pdf",          sizeBytes: 6,        },      ],    );     const hydrated = options.merge(persisted, useComposerDraftStore.getState());    const hydratedFiles =      hydrated.draftsByThreadKey[threadKeyFor(threadId, TEST_ENVIRONMENT_ID)]?.files;    expect(hydratedFiles).toEqual([      {        type: "file",        id: "file-pending",        name: "report.pdf",        mimeType: "application/pdf",        sizeBytes: 6,        file: null,      },    ]);    expect(hydratedFiles?.every(composerFileNeedsReattach)).toBe(true);  });   it("marks only the matching byte-less upload as missing", () => {    const store = useComposerDraftStore.getState();    const hydrated: ComposerFileAttachment = {      ...makeFile("file-hydrated"),      file: null,      uploadedAttachmentId: "pending-old",      uploadEnvironmentId: TEST_ENVIRONMENT_ID,    };    const local: ComposerFileAttachment = {      ...makeFile("file-local"),      name: "local.txt",      mimeType: "text/plain",

Lines 1–500 of 2776.