3574 lines · 135.8 KB · TypeScript
RawDownload
import {  DEFAULT_MODEL,  DEFAULT_MODEL_BY_PROVIDER,  defaultInstanceIdForDriver,  type EnvironmentId,  ModelSelection,  ProjectId,  ProviderInstanceId,  ProviderInteractionMode,  ProviderDriverKind,  ProviderOptionSelection,  PreviewAnnotationPayloadSchema,  type PreviewAnnotationPayload,  RuntimeMode,  type ServerProvider,  type ScopedProjectRef,  type ScopedThreadRef,  ThreadId,} from "@t3tools/contracts";import {  parseScopedProjectKey,  parseScopedThreadKey,  scopedProjectKey,  scopeProjectRef,  scopedThreadKey,  scopeThreadRef,} from "@t3tools/client-runtime/environment";import * as Schema from "effect/Schema";import * as Equal from "effect/Equal";import * as Effect from "effect/Effect";import { DeepMutable } from "effect/Types";import { createModelSelection, normalizeModelSlug } from "@t3tools/shared/model";import { useMemo } from "react";import { getLocalStorageItem } from "./hooks/useLocalStorage";import { resolveAppModelSelection, resolveAppModelSelectionForInstance } from "./modelSelection";import { DEFAULT_INTERACTION_MODE, DEFAULT_RUNTIME_MODE, type ChatImageAttachment } from "./types";import {  type TerminalContextDraft,  ensureInlineTerminalContextPlaceholders,  normalizeTerminalContextText,} from "./lib/terminalContext";import {  type ElementContextDraft,  type ElementContextSelection,  elementContextDedupKey,  newElementContextId,} from "./lib/elementContext";import { create } from "zustand";import { createJSONStorage, persist } from "zustand/middleware";import { useShallow } from "zustand/react/shallow";import { createDebouncedStorage, createMemoryStorage } from "./lib/storage";import { getDefaultServerModel } from "./providerModels";import { UnifiedSettings } from "@t3tools/contracts/settings";import { ReviewCommentContextSchema, type ReviewCommentContext } from "./reviewCommentContext";const isRuntimeMode = Schema.is(RuntimeMode);const isProviderDriverKind = Schema.is(ProviderDriverKind);const isReviewCommentContext = Schema.is(ReviewCommentContextSchema); export const COMPOSER_DRAFT_STORAGE_KEY = "t3code:composer-drafts:v1";const COMPOSER_DRAFT_STORAGE_VERSION = 8;const DraftThreadEnvModeSchema = Schema.Literals(["local", "worktree"]);export type DraftThreadEnvMode = typeof DraftThreadEnvModeSchema.Type; export const DraftId = Schema.String.pipe(Schema.brand("DraftId"));export type DraftId = typeof DraftId.Type; const COMPOSER_PERSIST_DEBOUNCE_MS = 300; const composerDebouncedStorage = createDebouncedStorage(  typeof localStorage !== "undefined" ? localStorage : createMemoryStorage(),  COMPOSER_PERSIST_DEBOUNCE_MS,); // Flush pending composer draft writes before page unload to prevent data loss.if (typeof window !== "undefined" && typeof window.addEventListener === "function") {  window.addEventListener("beforeunload", () => {    composerDebouncedStorage.flush();  });} export const PersistedComposerImageAttachment = Schema.Struct({  id: Schema.String,  name: Schema.String,  mimeType: Schema.String,  sizeBytes: Schema.Number,  dataUrl: Schema.String,});export type PersistedComposerImageAttachment = typeof PersistedComposerImageAttachment.Type; export interface ComposerImageAttachment extends Omit<ChatImageAttachment, "previewUrl"> {  previewUrl: string;  file: File;} const PersistedTerminalContextDraft = Schema.Struct({  id: Schema.String,  threadId: ThreadId,  createdAt: Schema.String,  terminalId: Schema.String,  terminalLabel: Schema.String,  lineStart: Schema.Number,  lineEnd: Schema.Number,});type PersistedTerminalContextDraft = typeof PersistedTerminalContextDraft.Type; const PersistedElementContextStackFrame = Schema.Struct({  functionName: Schema.NullOr(Schema.String),  fileName: Schema.NullOr(Schema.String),  lineNumber: Schema.NullOr(Schema.Number),  columnNumber: Schema.NullOr(Schema.Number),}); const PersistedElementContextDraft = Schema.Struct({  id: Schema.String,  threadId: ThreadId,  pickedAt: Schema.String,  pageUrl: Schema.String,  pageTitle: Schema.NullOr(Schema.String),  tagName: Schema.String,  selector: Schema.NullOr(Schema.String),  htmlPreview: Schema.String,  componentName: Schema.NullOr(Schema.String),  source: Schema.NullOr(PersistedElementContextStackFrame),  styles: Schema.String,});type PersistedElementContextDraft = typeof PersistedElementContextDraft.Type; const PersistedComposerThreadDraftState = Schema.Struct({  prompt: Schema.String,  attachments: Schema.Array(PersistedComposerImageAttachment),  terminalContexts: Schema.optionalKey(Schema.Array(PersistedTerminalContextDraft)),  elementContexts: Schema.optionalKey(Schema.Array(PersistedElementContextDraft)),  previewAnnotations: Schema.optionalKey(Schema.Array(PreviewAnnotationPayloadSchema)),  reviewComments: Schema.optionalKey(Schema.Array(ReviewCommentContextSchema)),  // Keyed by `ProviderInstanceId` (open branded slug) so custom provider  // instances (e.g. `codex_personal`) round-trip alongside the built-in  // `codex` / `claudeAgent` / ... entries. Every prior `ProviderDriverKind`  // literal satisfies the `ProviderInstanceId` slug pattern, so existing  // persisted drafts decode unchanged.  //  // The record's value schema is NOT wrapped in `Schema.optionalKey`:  // that helper is only meaningful on property signatures with a known  // key set, and `Schema.Record(<branded string>, …)` produces an index  // signature at runtime (Schema rejects the combination). Absence of  // an entry already encodes "no selection for this instance".  modelSelectionByProvider: Schema.optionalKey(Schema.Record(ProviderInstanceId, ModelSelection)),  activeProvider: Schema.optionalKey(Schema.NullOr(ProviderInstanceId)),  runtimeMode: Schema.optionalKey(RuntimeMode),  interactionMode: Schema.optionalKey(ProviderInteractionMode),});type PersistedComposerThreadDraftState = typeof PersistedComposerThreadDraftState.Type; /** * Per-provider record of generic option selections. Used as a transient * representation when migrating legacy v2 storage payloads and when * deriving per-provider option bundles for downstream consumers. */type ProviderOptionSelectionsByProvider = Partial<  Record<string, ReadonlyArray<ProviderOptionSelection>>>; type LegacyCodexFields = {  effort?: unknown;  codexFastMode?: unknown;  serviceTier?: unknown;}; type LegacyThreadModelFields = {  provider?: unknown;  model?: unknown;  modelOptions?: unknown;}; type LegacyV2ThreadDraftFields = {  modelSelection?: ModelSelection | null;  modelOptions?: unknown;}; type LegacyPersistedComposerThreadDraftState = PersistedComposerThreadDraftState &  LegacyCodexFields &  LegacyThreadModelFields &  LegacyV2ThreadDraftFields; type LegacyStickyModelFields = {  stickyProvider?: unknown;  stickyModel?: unknown;  stickyModelOptions?: unknown;}; type LegacyV2StoreFields = {  stickyModelSelection?: ModelSelection | null;  stickyModelOptions?: unknown;  projectDraftThreadIdByProjectId?: Record<string, string> | null;  draftsByThreadId?: Record<string, PersistedComposerThreadDraftState> | null;  draftThreadsByThreadId?: Record<string, PersistedDraftThreadState> | null;  projectDraftThreadIdByProjectKey?: Record<string, string> | null;  draftsByThreadKey?: Record<string, PersistedComposerThreadDraftState> | null;  draftThreadsByThreadKey?: Record<string, PersistedDraftThreadState> | null;  projectDraftThreadKeyByProjectKey?: Record<string, string> | null;  logicalProjectDraftThreadKeyByLogicalProjectKey?: Record<string, string> | null;}; type LegacyPersistedComposerDraftStoreState = PersistedComposerDraftStoreState &  LegacyStickyModelFields &  LegacyV2StoreFields; const PersistedDraftThreadState = Schema.Struct({  threadId: ThreadId,  environmentId: Schema.String,  projectId: ProjectId,  logicalProjectKey: Schema.optionalKey(Schema.String),  createdAt: Schema.String,  runtimeMode: RuntimeMode,  interactionMode: ProviderInteractionMode,  branch: Schema.NullOr(Schema.String),  worktreePath: Schema.NullOr(Schema.String),  envMode: DraftThreadEnvModeSchema,  startFromOrigin: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),  promotedTo: Schema.optionalKey(    Schema.NullOr(      Schema.Struct({        environmentId: Schema.String,        threadId: Schema.String,      }),    ),  ),});type PersistedDraftThreadState = typeof PersistedDraftThreadState.Type; const PersistedComposerDraftStoreState = Schema.Struct({  draftsByThreadKey: Schema.Record(Schema.String, PersistedComposerThreadDraftState),  draftThreadsByThreadKey: Schema.Record(Schema.String, PersistedDraftThreadState),  logicalProjectDraftThreadKeyByLogicalProjectKey: Schema.Record(Schema.String, Schema.String),  stickyModelSelectionByProvider: Schema.optionalKey(    Schema.Record(ProviderInstanceId, ModelSelection),  ),  stickyActiveProvider: Schema.optionalKey(Schema.NullOr(ProviderInstanceId)),});type PersistedComposerDraftStoreState = typeof PersistedComposerDraftStoreState.Type; const PersistedComposerDraftStoreStorage = Schema.Struct({  version: Schema.Number,  state: PersistedComposerDraftStoreState,}); /** * Composer content keyed by either a draft session (`DraftId`) or a real server * thread (`ScopedThreadRef`). This is the editable payload shown in the composer. */export interface ComposerThreadDraftState {  prompt: string;  images: ComposerImageAttachment[];  nonPersistedImageIds: string[];  persistedAttachments: PersistedComposerImageAttachment[];  terminalContexts: TerminalContextDraft[];  /**   * Element-pick attachments captured from the in-app preview browser. The   * full payload (selector / html / styles / source frame) is persisted   * inline because — unlike terminal contexts — there's no live session to   * re-derive the snapshot from on reload.   */  elementContexts: ElementContextDraft[];  previewAnnotations: PreviewAnnotationPayload[];  reviewComments: ReviewCommentContext[];  /**   * Per-instance model selection. Keyed by `ProviderInstanceId` (open   * branded slug) so a default `codex` instance and a user-authored   * `codex_personal` instance each persist their own selected model. Every   * historical `ProviderDriverKind` literal (`codex` / `claudeAgent` / `cursor` /   * `opencode`) also satisfies the `ProviderInstanceId` slug pattern, so   * legacy kind-keyed drafts round-trip unchanged.   */  modelSelectionByProvider: Partial<Record<ProviderInstanceId, ModelSelection>>;  /** Routing key of the last picked instance (see `modelSelectionByProvider`). */  activeProvider: ProviderInstanceId | null;  runtimeMode: RuntimeMode | null;  interactionMode: ProviderInteractionMode | null;} /** * Mutable routing and execution context for a pre-thread draft session. * * Unlike a real server thread, a draft session can still change target * environment/worktree configuration before the first send. */export interface DraftSessionState {  threadId: ThreadId;  environmentId: EnvironmentId;  projectId: ProjectId;  logicalProjectKey: string;  createdAt: string;  runtimeMode: RuntimeMode;  interactionMode: ProviderInteractionMode;  branch: string | null;  worktreePath: string | null;  envMode: DraftThreadEnvMode;  startFromOrigin: boolean;  promotedTo?: ScopedThreadRef | null;} export type DraftThreadState = DraftSessionState; /** * Draft session metadata paired with its stable draft-session identity. */interface ProjectDraftSession extends DraftSessionState {  draftId: DraftId;} /** * App-facing composer identity: * - `DraftId` for pre-thread draft sessions * - `ScopedThreadRef` for server-backed threads * * Raw `ThreadId` is intentionally excluded so callers cannot drop environment * identity for real threads. */type ComposerThreadTarget = ScopedThreadRef | DraftId; /** * Persisted store for composer content plus draft-session metadata. * * The store intentionally models two domains: * - draft sessions keyed by `DraftId` * - server thread composer state keyed by `ScopedThreadRef` */interface ComposerDraftStoreState {  draftsByThreadKey: Record<string, ComposerThreadDraftState>;  draftThreadsByThreadKey: Record<string, DraftThreadState>;  logicalProjectDraftThreadKeyByLogicalProjectKey: Record<string, string>;  stickyModelSelectionByProvider: Partial<Record<ProviderInstanceId, ModelSelection>>;  stickyActiveProvider: ProviderInstanceId | null;  /** Returns the editable composer content for a draft session or server thread. */  getComposerDraft: (target: ComposerThreadTarget) => ComposerThreadDraftState | null;  /** Looks up the active draft session for a logical project identity. */  getDraftThreadByLogicalProjectKey: (logicalProjectKey: string) => ProjectDraftSession | null;  getDraftSessionByLogicalProjectKey: (logicalProjectKey: string) => ProjectDraftSession | null;  getDraftThreadByProjectRef: (projectRef: ScopedProjectRef) => ProjectDraftSession | null;  getDraftSessionByProjectRef: (projectRef: ScopedProjectRef) => ProjectDraftSession | null;  /** Reads mutable draft-session metadata by `DraftId`. */  getDraftSession: (draftId: DraftId) => DraftSessionState | null;  /** Resolves a server-thread ref back to a matching draft session when one exists. */  getDraftSessionByRef: (threadRef: ScopedThreadRef) => DraftSessionState | null;  getDraftThreadByRef: (threadRef: ScopedThreadRef) => DraftThreadState | null;  getDraftThread: (threadRef: ComposerThreadTarget) => DraftThreadState | null;  listDraftThreadKeys: () => string[];  hasDraftThreadsInEnvironment: (environmentId: EnvironmentId) => boolean;  /** Creates or updates the draft session tracked for a logical project. */  setLogicalProjectDraftThreadId: (    logicalProjectKey: string,    projectRef: ScopedProjectRef,    draftId: DraftId,    options?: {      threadId?: ThreadId;      branch?: string | null;      worktreePath?: string | null;      createdAt?: string;      envMode?: DraftThreadEnvMode;      startFromOrigin?: boolean;      runtimeMode?: RuntimeMode;      interactionMode?: ProviderInteractionMode;    },  ) => void;  /** Creates or updates the draft session tracked for a concrete project ref. */  setProjectDraftThreadId: (    projectRef: ScopedProjectRef,    draftId: DraftId,    options?: {      threadId?: ThreadId;      branch?: string | null;      worktreePath?: string | null;      createdAt?: string;      envMode?: DraftThreadEnvMode;      startFromOrigin?: boolean;      runtimeMode?: RuntimeMode;      interactionMode?: ProviderInteractionMode;    },  ) => void;  /** Updates mutable draft-session metadata without touching composer content. */  setDraftThreadContext: (    threadRef: ComposerThreadTarget,    options: {      branch?: string | null;      worktreePath?: string | null;      projectRef?: ScopedProjectRef;      createdAt?: string;      envMode?: DraftThreadEnvMode;      startFromOrigin?: boolean;      runtimeMode?: RuntimeMode;      interactionMode?: ProviderInteractionMode;    },  ) => void;  clearProjectDraftThreadId: (projectRef: ScopedProjectRef) => void;  clearProjectDraftThreadById: (    projectRef: ScopedProjectRef,    threadRef: ComposerThreadTarget,  ) => void;  /** Marks a draft session as being promoted to a real server thread. */  markDraftThreadPromoting: (threadRef: ComposerThreadTarget, promotedTo?: ScopedThreadRef) => void;  /** Removes draft-session metadata after promotion is complete. */  finalizePromotedDraftThread: (threadRef: ComposerThreadTarget) => void;  clearDraftThread: (threadRef: ComposerThreadTarget) => void;  setStickyModelSelection: (modelSelection: ModelSelection | null | undefined) => void;  setPrompt: (threadRef: ComposerThreadTarget, prompt: string) => void;  setTerminalContexts: (threadRef: ComposerThreadTarget, contexts: TerminalContextDraft[]) => void;  setModelSelection: (    threadRef: ComposerThreadTarget,    modelSelection: ModelSelection | null | undefined,    opts?: {      /**       * Replace the stored entry outright instead of preserving its       * existing options when the incoming selection has none. Used when       * the selection is a complete snapshot (e.g. carried from another       * thread) rather than a model-only change.       */      replaceOptions?: boolean;    },  ) => void;  /** Replace the model options for one or more providers in the draft. */  setModelOptions: (    threadRef: ComposerThreadTarget,    modelOptions:      | Partial<Record<string, ReadonlyArray<ProviderOptionSelection>>>      | null      | undefined,  ) => void;  applyStickyState: (threadRef: ComposerThreadTarget) => void;  setProviderModelOptions: (    threadRef: ComposerThreadTarget,    provider: ProviderDriverKind,    nextProviderOptions: ReadonlyArray<ProviderOptionSelection> | null | undefined,    options?: {      instanceId?: ProviderInstanceId | null | undefined;      model?: string | null | undefined;      persistSticky?: boolean;    },  ) => void;  setRuntimeMode: (    threadRef: ComposerThreadTarget,    runtimeMode: RuntimeMode | null | undefined,  ) => void;  setInteractionMode: (    threadRef: ComposerThreadTarget,    interactionMode: ProviderInteractionMode | null | undefined,  ) => void;  addImage: (threadRef: ComposerThreadTarget, image: ComposerImageAttachment) => void;  addImages: (threadRef: ComposerThreadTarget, images: ComposerImageAttachment[]) => void;  removeImage: (threadRef: ComposerThreadTarget, imageId: string) => void;  insertTerminalContext: (    threadRef: ComposerThreadTarget,    prompt: string,    context: TerminalContextDraft,    index: number,  ) => boolean;  addTerminalContext: (threadRef: ComposerThreadTarget, context: TerminalContextDraft) => void;  addTerminalContexts: (threadRef: ComposerThreadTarget, contexts: TerminalContextDraft[]) => void;  removeTerminalContext: (threadRef: ComposerThreadTarget, contextId: string) => void;  clearTerminalContexts: (threadRef: ComposerThreadTarget) => void;  /**   * Append a fresh element pick to the draft. Returns true when accepted,   * false when deduped against an existing pick of the same element.   */  addElementContext: (    threadRef: ComposerThreadTarget,    selection: ElementContextSelection,  ) => boolean;  /**   * Replace the entire element-contexts list (used by send-failure retry to   * restore the pre-send snapshot).   */  setElementContexts: (    threadRef: ComposerThreadTarget,    contexts: ReadonlyArray<ElementContextDraft>,  ) => void;  removeElementContext: (threadRef: ComposerThreadTarget, contextId: string) => void;  clearElementContexts: (threadRef: ComposerThreadTarget) => void;  addPreviewAnnotation: (    threadRef: ComposerThreadTarget,    annotation: PreviewAnnotationPayload,  ) => void;  setPreviewAnnotations: (    threadRef: ComposerThreadTarget,    annotations: ReadonlyArray<PreviewAnnotationPayload>,  ) => void;  removePreviewAnnotation: (threadRef: ComposerThreadTarget, annotationId: string) => void;  addReviewComment: (threadRef: ComposerThreadTarget, comment: ReviewCommentContext) => void;  setReviewComments: (    threadRef: ComposerThreadTarget,    comments: ReadonlyArray<ReviewCommentContext>,  ) => void;  removeReviewComment: (threadRef: ComposerThreadTarget, commentId: string) => void;  clearPersistedAttachments: (threadRef: ComposerThreadTarget) => void;  syncPersistedAttachments: (    threadRef: ComposerThreadTarget,    attachments: PersistedComposerImageAttachment[],  ) => void;  clearComposerContent: (threadRef: ComposerThreadTarget) => void;} export interface EffectiveComposerModelState {

Lines 1–500 of 3574.