Skip to content
Source
Sign inSign up

composerContextReferences.ts

116 lines3.7 KB

1import type { ComposerContextId, ComposerContextKind } from "@t3tools/contracts";
2import {
3 collectComposerContextReferences,
4 formatComposerContextReference,
5 replaceComposerContextReferences,
6} from "@t3tools/shared/composerContextReferences";
7​
8export interface ComposerContextReference {
9 kind: ComposerContextKind;
10 contextId: string;
11 label: string;
13​
14export function producerIdFromComposerContextId(
15 kind: ComposerContextKind,
16 contextId: string,
17): string {
18 const prefix = `${kind}_`;
19 return contextId.startsWith(prefix) ? contextId.slice(prefix.length) : contextId;
21​
22export function formatInlineContextReference(reference: ComposerContextReference): string {
23 return formatComposerContextReference({
24 kind: reference.kind,
25 contextId: reference.contextId as ComposerContextId,
26 label: reference.label,
27 });
29​
30export function collectInlineContextIds(prompt: string): string[] {
31 return Array.from(
32 new Set(collectComposerContextReferences(prompt).map((occurrence) => occurrence.contextId)),
33 );
35​
36export function stripInlineContextReferences(prompt: string): string {
37 return replaceComposerContextReferences(prompt, () => "");
39​
40function isBoundaryWhitespace(char: string | undefined): boolean {
41 return char === undefined || char === " " || char === "\n" || char === "\t" || char === "\r";
43​
44export function inlineContextReferenceReplacement(
45 prompt: string,
46 selection: { start: number; end: number },
47 references: ReadonlyArray<ComposerContextReference>,
48): { start: number; end: number; text: string } {
49 const start = Math.max(0, Math.min(prompt.length, Math.floor(selection.start)));
50 const end = Math.max(start, Math.min(prompt.length, Math.floor(selection.end)));
51 const needsLeadingSpace = !isBoundaryWhitespace(prompt[start - 1]);
52 return {
53 start,
54 end: prompt[end] === " " ? end + 1 : end,
55 text: `${needsLeadingSpace ? " " : ""}${references.map(formatInlineContextReference).join(" ")} `,
56 };
58​
59export function insertInlineContextReference(
60 prompt: string,
61 cursorInput: number,
62 reference: ComposerContextReference,
63): { prompt: string; cursor: number } {
64 const edit = inlineContextReferenceReplacement(prompt, { start: cursorInput, end: cursorInput }, [
65 reference,
66 ]);
67 return {
68 prompt: `${prompt.slice(0, edit.start)}${edit.text}${prompt.slice(edit.end)}`,
69 cursor: edit.start + edit.text.length,
70 };
72​
73export function appendInlineContextReference(
74 prompt: string,
75 reference: ComposerContextReference,
76): string {
77 return insertInlineContextReference(prompt, prompt.length, reference).prompt;
79​
80export function removeInlineContextReference(
81 prompt: string,
82 contextId: string,
83): { prompt: string; cursor: number } {
84 const occurrences = collectComposerContextReferences(prompt).filter(
85 (candidate) => candidate.contextId === contextId,
86 );
87 if (occurrences.length === 0) return { prompt, cursor: prompt.length };
88 let result = prompt;
89 let cursor = prompt.length;
90 for (const occurrence of occurrences.reverse()) {
91 let { start, end } = occurrence;
92 if (result[end] === " ") end += 1;
93 else if (result[start - 1] === " ") start -= 1;
94 result = `${result.slice(0, start)}${result.slice(end)}`;
95 cursor = start;
96 }
97 if (cursor >= result.length) {
98 result = result.trimEnd();
99 cursor = result.length;
100 }
101 return { prompt: result, cursor };
103​
104export function ensureInlineContextReferences(
105 prompt: string,
106 references: ReadonlyArray<ComposerContextReference>,
107): string {
108 const referenced = new Set(collectInlineContextIds(prompt));
109 let result = prompt;
110 for (const reference of references) {
111 if (referenced.has(reference.contextId)) continue;
112 referenced.add(reference.contextId);
113 result = appendInlineContextReference(result, reference);
114 }
115 return result;