1import type { ThreadId } from "@t3tools/contracts";
2import {
3 formatComposerContextReference,
4 toKindScopedComposerContextId,
5} from "@t3tools/shared/composerContextReferences";
6
7export interface TerminalContextSelection {
8 terminalId: string;
9 terminalLabel: string;
10 lineStart: number;
11 lineEnd: number;
12 text: string;
13}
14
15export interface TerminalContextDraft extends TerminalContextSelection {
16 id: string;
17 threadId: ThreadId;
18 createdAt: string;
19}
20
21export const INLINE_TERMINAL_CONTEXT_PLACEHOLDER = "\uFFFC";
22
23export interface TerminalContextReferenceSource {
24 id: string;
25 terminalLabel: string;
26 lineStart: number;
27 lineEnd: number;
28}
29
30export function formatTerminalContextReference(context: TerminalContextReferenceSource): string {
31 return formatComposerContextReference({
32 kind: "terminal",
33 contextId: toKindScopedComposerContextId("terminal", context.id),
34 label: formatTerminalContextLabel(context),
35 });
36}
37
38export function normalizeTerminalContextText(text: string): string {
39 return text.replace(/\r\n/g, "\n").replace(/^\n+|\n+$/g, "");
40}
41
42function hasTerminalContextText(context: { text: string }): boolean {
43 return normalizeTerminalContextText(context.text).length > 0;
44}
45
46export function isTerminalContextExpired(context: { text: string }): boolean {
47 return !hasTerminalContextText(context);
48}
49
50export function filterTerminalContextsWithText<T extends { text: string }>(
51 contexts: ReadonlyArray<T>,
52): T[] {
53 return contexts.filter((context) => hasTerminalContextText(context));
54}
55
56function formatTerminalContextRange(selection: { lineStart: number; lineEnd: number }): string {
57 return selection.lineStart === selection.lineEnd
58 ? `line ${selection.lineStart}`
59 : `lines ${selection.lineStart}-${selection.lineEnd}`;
60}
61
62export function formatTerminalContextLabel(selection: {
63 terminalLabel: string;
64 lineStart: number;
65 lineEnd: number;
66}): string {
67 return `${selection.terminalLabel} ${formatTerminalContextRange(selection)}`;
68}
69
70export function migrateLegacyTerminalContextPlaceholders(
71 prompt: string,
72 contexts: ReadonlyArray<TerminalContextReferenceSource>,
73): string {
74 if (!prompt.includes(INLINE_TERMINAL_CONTEXT_PLACEHOLDER)) return prompt;
75 let index = 0;
76 return prompt.replaceAll(INLINE_TERMINAL_CONTEXT_PLACEHOLDER, () => {
77 const context = contexts[index];
78 index += 1;
79 return context ? formatTerminalContextReference(context) : "";
80 });
81}