返回 DeepSeek-Reasonix
terminalOutput.ts
根目录 / desktop / frontend / src / lib / terminalOutput.ts
1 const MAX_TERMINAL_CHAT_CHARS = 120_000;
2
3 function consumeCSI(value: string, start: number): number {
4 let index = start;
5 while (index < value.length) {
6 const code = value.charCodeAt(index);
7 index += 1;
8 if (code >= 0x40 && code <= 0x7e) return index;
9 }
10 return value.length;
11 }
12
13 function consumeStringSequence(value: string, start: number): number {
14 let index = start;
15 while (index < value.length) {
16 const code = value.charCodeAt(index);
17 if (code === 0x07 || code === 0x9c) return index + 1;
18 if (code === 0x1b && value.charCodeAt(index + 1) === 0x5c) return index + 2;
19 index += 1;
20 }
21 return value.length;
22 }
23
24 function consumeEscapeSequence(value: string, start: number): number {
25 const first = value.charCodeAt(start + 1);
26 if (first === 0x5b) return consumeCSI(value, start + 2);
27 if (first === 0x5d || first === 0x50 || first === 0x5e || first === 0x5f || first === 0x58) {
28 return consumeStringSequence(value, start + 2);
29 }
30 let index = start + 1;
31 while (index < value.length) {
32 const code = value.charCodeAt(index);
33 index += 1;
34 if (code >= 0x30 && code <= 0x7e) return index;
35 if (code < 0x20 || code > 0x2f) return index;
36 }
37 return value.length;
38 }
39
40 function consumeC1Sequence(value: string, start: number): number {
41 const code = value.charCodeAt(start);
42 if (code === 0x9b) {
43 return consumeCSI(value, start + 1);
44 }
45 if (code === 0x90 || code === 0x98 || code === 0x9d || code === 0x9e || code === 0x9f) {
46 return consumeStringSequence(value, start + 1);
47 }
48 return start + 1;
49 }
50
51 export function stripTerminalControlSequences(value: string): string {
52 let result = "";
53 for (let index = 0; index < value.length;) {
54 const code = value.charCodeAt(index);
55 if (code === 0x1b) {
56 index = consumeEscapeSequence(value, index);
57 continue;
58 }
59 if (code >= 0x80 && code <= 0x9f) {
60 index = consumeC1Sequence(value, index);
61 continue;
62 }
63 if (code < 0x20 && code !== 0x09 && code !== 0x0a && code !== 0x0d) {
64 index += 1;
65 continue;
66 }
67 if (code === 0x7f) {
68 index += 1;
69 continue;
70 }
71 result += value[index];
72 index += 1;
73 }
74 return result.replace(/\r\n?/g, "\n");
75 }
76
77 function markdownFenceFor(value: string): string {
78 let longestRun = 0;
79 let currentRun = 0;
80 for (const character of value) {
81 if (character === "`") {
82 currentRun += 1;
83 longestRun = Math.max(longestRun, currentRun);
84 } else {
85 currentRun = 0;
86 }
87 }
88 return "`".repeat(Math.max(3, longestRun + 1));
89 }
90
91 export function formatTerminalOutputForComposer(value: string, maxChars = MAX_TERMINAL_CHAT_CHARS): string {
92 const normalized = stripTerminalControlSequences(value);
93 const clipped = normalized.length > maxChars ? normalized.slice(-maxChars) : normalized;
94 if (!clipped.trim()) return "";
95 const fence = markdownFenceFor(clipped);
96 return `${fence}console\n${clipped}\n${fence}`;
97 }
98
98 lines TYPESCRIPT