import { CommandId, MessageId, ThreadId, type ModelSelection, type ProjectId, type ProviderInteractionMode, type RuntimeMode,} from "@t3tools/contracts";import { assistantCitationsToPlainText } from "@t3tools/shared/assistantCitations"; import type { UploadedMobileAttachment } from "./attachmentUpload"; export function deriveThreadTitleFromPrompt(value: string): string { const trimmed = assistantCitationsToPlainText(value).trim(); if (trimmed.length === 0) { return "New thread"; } const compact = trimmed.replace(/\s+/g, " "); return compact.length <= 72 ? compact : `${compact.slice(0, 69).trimEnd()}...`;} export interface ProjectThreadStartTurnSpec { readonly projectId: ProjectId; readonly projectCwd: string; readonly threadId: string; readonly commandId: string; readonly messageId: string; readonly createdAt: string; readonly text: string; /** Wire attachments from `prepareTurnAttachments`, in composer order. */ readonly uploadedAttachments: ReadonlyArray<UploadedMobileAttachment>; readonly modelSelection: ModelSelection; readonly runtimeMode: RuntimeMode; readonly interactionMode: ProviderInteractionMode; readonly workspaceMode: "local" | "worktree"; readonly branch: string | null; readonly worktreePath: string | null; readonly startFromOrigin: boolean; /** Generated temp branch for worktree mode; unused for local mode. */ readonly worktreeBranchName: string;} /** * Single source of the `thread.turn.start` bootstrap payload used to create a * thread from a project draft — shared by the immediate send path and the * offline outbox drain so both deliver identical commands. */export function buildProjectThreadStartTurnInput(spec: ProjectThreadStartTurnSpec) { const title = deriveThreadTitleFromPrompt(spec.text); const isWorktree = spec.workspaceMode === "worktree"; return { commandId: CommandId.make(spec.commandId), threadId: ThreadId.make(spec.threadId), message: { messageId: MessageId.make(spec.messageId), role: "user" as const, text: spec.text, attachments: spec.uploadedAttachments, }, modelSelection: spec.modelSelection, titleSeed: title, runtimeMode: spec.runtimeMode, interactionMode: spec.interactionMode, bootstrap: { createThread: { projectId: spec.projectId, title, modelSelection: spec.modelSelection, runtimeMode: spec.runtimeMode, interactionMode: spec.interactionMode, branch: spec.branch, worktreePath: isWorktree ? null : spec.worktreePath, createdAt: spec.createdAt, }, ...(isWorktree ? { prepareWorktree: { projectCwd: spec.projectCwd, baseBranch: spec.branch!, branch: spec.worktreeBranchName, ...(spec.startFromOrigin ? { startFromOrigin: true } : {}), }, runSetupScript: true, } : {}), }, createdAt: spec.createdAt, };}
import {
CommandId,
MessageId,
ThreadId,
type ModelSelection,
type ProjectId,
type ProviderInteractionMode,
type RuntimeMode,
} from "@t3tools/contracts";
import { assistantCitationsToPlainText } from "@t3tools/shared/assistantCitations";
import type { UploadedMobileAttachment } from "./attachmentUpload";
export function deriveThreadTitleFromPrompt(value: string): string {
const trimmed = assistantCitationsToPlainText(value).trim();
if (trimmed.length === 0) {
return "New thread";
}
const compact = trimmed.replace(/\s+/g, " ");
return compact.length <= 72 ? compact : `${compact.slice(0, 69).trimEnd()}...`;
}
export interface ProjectThreadStartTurnSpec {
readonly projectId: ProjectId;
readonly projectCwd: string;
readonly threadId: string;
readonly commandId: string;
readonly messageId: string;
readonly createdAt: string;
readonly text: string;
/** Wire attachments from `prepareTurnAttachments`, in composer order. */
readonly uploadedAttachments: ReadonlyArray<UploadedMobileAttachment>;
readonly modelSelection: ModelSelection;
readonly runtimeMode: RuntimeMode;
readonly interactionMode: ProviderInteractionMode;
readonly workspaceMode: "local" | "worktree";
readonly branch: string | null;
readonly worktreePath: string | null;
readonly startFromOrigin: boolean;
/** Generated temp branch for worktree mode; unused for local mode. */
readonly worktreeBranchName: string;
}
/**
* Single source of the `thread.turn.start` bootstrap payload used to create a
* thread from a project draft — shared by the immediate send path and the
* offline outbox drain so both deliver identical commands.
*/
export function buildProjectThreadStartTurnInput(spec: ProjectThreadStartTurnSpec) {
const title = deriveThreadTitleFromPrompt(spec.text);
const isWorktree = spec.workspaceMode === "worktree";
return {
commandId: CommandId.make(spec.commandId),
threadId: ThreadId.make(spec.threadId),
message: {
messageId: MessageId.make(spec.messageId),
role: "user" as const,
text: spec.text,
attachments: spec.uploadedAttachments,
},
modelSelection: spec.modelSelection,
titleSeed: title,
runtimeMode: spec.runtimeMode,
interactionMode: spec.interactionMode,
bootstrap: {
createThread: {
projectId: spec.projectId,
title,
modelSelection: spec.modelSelection,
runtimeMode: spec.runtimeMode,
interactionMode: spec.interactionMode,
branch: spec.branch,
worktreePath: isWorktree ? null : spec.worktreePath,
createdAt: spec.createdAt,
},
...(isWorktree
? {
prepareWorktree: {
projectCwd: spec.projectCwd,
baseBranch: spec.branch!,
branch: spec.worktreeBranchName,
...(spec.startFromOrigin ? { startFromOrigin: true } : {}),
},
runSetupScript: true,
}
: {}),
},
createdAt: spec.createdAt,
};
}