102 lines · 3.1 KB · TypeScript
RawDownload
import { scopeProjectRef } from "@t3tools/client-runtime/environment";import type {  EnvironmentId,  ModelSelection,  ProjectId,  ScopedProjectRef,} from "@t3tools/contracts";import type { ComposerThreadDraftState, DraftThreadEnvMode } from "../composerDraftStore"; type ComposerModelSelectionState = Pick<  ComposerThreadDraftState,  "activeProvider" | "modelSelectionByProvider" | "modelSelectionExplicit">; interface ThreadContextLike {  environmentId: EnvironmentId;  projectId: ProjectId;} interface NewThreadHandler {  (    projectRef: ScopedProjectRef,    options?: {      branch?: string | null;      worktreePath?: string | null;      envMode?: DraftThreadEnvMode;      startFromOrigin?: boolean;    },    // The opened draft's identity, which most callers have no use for.  ): Promise<unknown>;} export interface ChatThreadActionContext {  readonly activeDraftThread: ThreadContextLike | null;  readonly activeThread: ThreadContextLike | undefined;  readonly defaultProjectRef: ScopedProjectRef | null;  readonly handleNewThread: NewThreadHandler;} export function resolveNewDraftStartFromOrigin(input: {  envMode: DraftThreadEnvMode;  newWorktreesStartFromOrigin: boolean;}): boolean {  return input.envMode === "worktree" && input.newWorktreesStartFromOrigin;} export function resolveNewThreadModelSelectionOverride(input: {  readonly projectDefaultSelection: ModelSelection | null;  readonly carrySelection: ModelSelection | null;  readonly carrySourceDraftId: string | null;  readonly destinationDraftId: string;}): ModelSelection | null {  return (    input.projectDefaultSelection ??    (input.carrySourceDraftId === input.destinationDraftId ? null : input.carrySelection)  );} export function hasExplicitComposerModelSelection(  draft: ComposerModelSelectionState | null | undefined,): boolean {  const activeProvider = draft?.activeProvider;  return (    draft?.modelSelectionExplicit === true &&    activeProvider !== null &&    activeProvider !== undefined &&    draft.modelSelectionByProvider[activeProvider] !== undefined  );} export function resolveThreadActionProjectRef(  context: ChatThreadActionContext,): ScopedProjectRef | null {  if (context.activeThread) {    return scopeProjectRef(context.activeThread.environmentId, context.activeThread.projectId);  }  if (context.activeDraftThread) {    return scopeProjectRef(      context.activeDraftThread.environmentId,      context.activeDraftThread.projectId,    );  }  return context.defaultProjectRef;} // New threads inherit only the *project* from the current context. Branch,// worktree, and env mode always come from the user's configured defaults —// carrying them over from the viewed thread meant "new thread" silently// reused checkouts and branches. Explicit affordances (branch toolbar's// "new thread in this worktree") pass those options to handleNewThread// directly instead.export async function startNewThreadFromContext(  context: ChatThreadActionContext,): Promise<boolean> {  const projectRef = resolveThreadActionProjectRef(context);  if (!projectRef) {    return false;  }   await context.handleNewThread(projectRef);  return true;}