1import { imageMimeType } from "@t3tools/shared/image";
2import type {
3 ChatFileAttachment as ContractChatFileAttachment,
4 ChatImageAttachment as ContractChatImageAttachment,
5 ChatUnknownAttachment as ContractChatUnknownAttachment,
6 MessageId,
7 OrchestrationV2Actor,
8 OrchestrationV2CreationSource,
9 OrchestrationV2PlanArtifact,
10 OrchestrationV2UserMessageInputIntent,
11 PlanId,
12 ProjectScript as ContractProjectScript,
13 ProviderInteractionMode,
14 RunId,
15 RuntimeMode,
16 ScheduledTaskId,
17 ThreadId,
18} from "@t3tools/contracts";
19import type {
20 EnvironmentProject,
21 EnvironmentThreadShell,
22 ThreadRunSummary,
23 ThreadRuntimeSummary,
24} from "@t3tools/client-runtime/state/shell";
25import type { ThreadCheckpointSummary } from "@t3tools/client-runtime/state/thread-checkpoints";
26import { videoMimeType } from "@t3tools/shared/video";
27
28export { videoMimeType } from "@t3tools/shared/video";
29
30export type SessionPhase = "disconnected" | "connecting" | "ready" | "running";
31export const DEFAULT_RUNTIME_MODE: RuntimeMode = "full-access";
32
33export const DEFAULT_INTERACTION_MODE: ProviderInteractionMode = "default";
34export const DEFAULT_THREAD_TERMINAL_HEIGHT = 280;
35export const DEFAULT_THREAD_TERMINAL_ID = "term-1";
36export const MAX_TERMINALS_PER_GROUP = 4;
37export type ProjectScript = ContractProjectScript;
38
39export interface ThreadTerminalGroup {
40 id: string;
41 terminalIds: string[];
42 splitDirection?: "horizontal" | "vertical";
43}
44
45export interface ChatImageAttachment extends ContractChatImageAttachment {
46 readonly previewUrl?: string;
47}
48
49export interface ChatFileAttachment extends ContractChatFileAttachment {
50 readonly previewUrl?: string;
51 readonly downloadable?: boolean;
52 /** A page an agent published with `html_render`; its viewer hands it the app theme. */
53 readonly htmlRender?: boolean;
54}
55
56// Attachment types this build does not know pass through with the contract
57// shape. The UI renders them as inert rows so a newer server cannot crash an
58// older client.
59export type ChatUnknownAttachment = ContractChatUnknownAttachment;
60
61export type ChatAttachment = ChatImageAttachment | ChatFileAttachment | ChatUnknownAttachment;
62
63// The union has an open member (`type: string`), so a literal comparison does
64// not narrow. Use these guards wherever type-specific fields are read.
65export function isImageAttachment(attachment: ChatAttachment): attachment is ChatImageAttachment {
66 // Messages sent before pictures were typed by content carry `file`; they are still
67 // pictures, and reading them as such is what lets them render instead of listing. Only
68 // `file` is reclassified: an attachment type this client does not know yet is not a
69 // picture by default, whatever its name says.
70 if (attachment.type === "image") return true;
71 return attachment.type === "file" && imageMimeType(attachment) !== null;
72}
73
74export function isFileAttachment(attachment: ChatAttachment): attachment is ChatFileAttachment {
75 // Disjoint from `isImageAttachment` on purpose: a legacy `file` carrying an image reads as a
76 // picture, and callers filter both sets independently, so overlap renders it twice.
77 return attachment.type === "file" && !isImageAttachment(attachment);
78}
79
80export function isVideoAttachment(attachment: ChatFileAttachment): boolean {
81 return videoMimeType(attachment) !== null;
82}
83
84export function isBrowserPreviewAttachment(attachment: ChatFileAttachment): boolean {
85 const mimeType = attachment.mimeType.split(";", 1)[0]?.trim().toLowerCase();
86 return (
87 /\.(?:html?|pdf)$/i.test(attachment.name) ||
88 mimeType === "application/pdf" ||
89 mimeType === "text/html"
90 );
91}
92
93export interface ChatMessage {
94 readonly context?: import("@t3tools/contracts").OrchestrationMessageContext | undefined;
95 readonly id: MessageId;
96 readonly role: "user" | "assistant" | "system";
97 readonly text: string;
98 readonly attachments?: ReadonlyArray<ChatAttachment> | undefined;
99 readonly runId: RunId | null;
100 readonly streaming: boolean;
101 readonly createdBy?: OrchestrationV2Actor;
102 readonly creationSource?: OrchestrationV2CreationSource;
103 readonly scheduledTaskId?: ScheduledTaskId;
104 readonly senderThreadId?: ThreadId;
105 readonly createdAt: string;
106 readonly updatedAt: string;
107 readonly inputIntent?: OrchestrationV2UserMessageInputIntent | undefined;
108}
109
110export interface ProposedPlan {
111 readonly id: PlanId;
112 readonly runId: RunId | null;
113 readonly planMarkdown: string;
114 readonly status: OrchestrationV2PlanArtifact["status"];
115 readonly createdAt: string;
116 readonly updatedAt: string;
117}
118export type TurnDiffFileChange = ThreadCheckpointSummary["files"][number];
119export type TurnDiffSummary = ThreadCheckpointSummary;
120
121export type Project = EnvironmentProject;
122export type Thread = EnvironmentThreadShell;
123export type ThreadShell = EnvironmentThreadShell;
124
125export type SidebarThreadSummary = EnvironmentThreadShell;
126export type ThreadSession = ThreadRuntimeSummary;