@estebanforge/pi-antigravity-bridge 1.8.7 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,180 @@
1
+ // Stream Delta Sanitizer:
2
+ // Google Antigravity injects asynchronous background task notifications into
3
+ // the active LLM context formatted as:
4
+ // The following is a <SYSTEM_MESSAGE> not actually sent by the user...
5
+ // <SYSTEM_MESSAGE>
6
+ // [Message] timestamp=... sender=... content=...
7
+ // </SYSTEM_MESSAGE>
8
+ //
9
+ // Models (notably Gemini 3.8 Flash) frequently regurgitate or echo this preamble
10
+ // verbatim as an assistant text response before proceeding with tools or answer.
11
+ //
12
+ // StreamSanitizer buffers and strips these blocks statefully across streaming
13
+ // deltas so they never leak into the user's transcript.
14
+
15
+ export const START_MARKER = "The following is a <SYSTEM_MESSAGE>";
16
+ export const END_MARKER = "</SYSTEM_MESSAGE>";
17
+
18
+ export const SYSTEM_MESSAGE_RE =
19
+ /(?:\r?\n)?The following is a <SYSTEM_MESSAGE>[\s\S]*?<\/SYSTEM_MESSAGE>(?:\r?\n)?/g;
20
+
21
+ /** Non-streaming sanitization for full text strings (result bodies, parked turns, transcripts). */
22
+ export function sanitizeSystemMessage(text: string): string {
23
+ if (!text) return "";
24
+ return text.replace(SYSTEM_MESSAGE_RE, "");
25
+ }
26
+
27
+ /** Stateful streaming sanitizer that inspects and suppresses <SYSTEM_MESSAGE> blocks across chunks. */
28
+ export class StreamSanitizer {
29
+ #buffer = "";
30
+ #suppressing = false;
31
+ #suppressedAcc = "";
32
+ #emittedAny = false;
33
+
34
+ /**
35
+ * Feed an incoming chunk of streamed text.
36
+ * Returns the sanitized delta to emit downstream (empty string if suppressed or buffering).
37
+ */
38
+ push(chunk: string): string {
39
+ if (!chunk) return "";
40
+ this.#buffer += chunk;
41
+ let out = "";
42
+
43
+ while (this.#buffer.length > 0) {
44
+ if (!this.#suppressing) {
45
+ const startIdx = this.#buffer.indexOf(START_MARKER);
46
+ if (startIdx !== -1) {
47
+ // There is a start marker in the buffer.
48
+ if (startIdx > 0) {
49
+ const before = this.#buffer.slice(0, startIdx);
50
+ if (!this.#emittedAny && before.trim() === "") {
51
+ // Discard leading padding before initial system message.
52
+ } else {
53
+ // Strip at most one trailing newline right before the block.
54
+ const trimmedBefore = before.replace(/\r?\n$/, "");
55
+ if (trimmedBefore.length > 0) {
56
+ out += trimmedBefore;
57
+ this.#emittedAny = true;
58
+ }
59
+ }
60
+ }
61
+ this.#buffer = this.#buffer.slice(startIdx + START_MARKER.length);
62
+ this.#suppressing = true;
63
+ this.#suppressedAcc = START_MARKER;
64
+ continue;
65
+ }
66
+
67
+ // START_MARKER is not yet in the buffer.
68
+ // Check if the tail of buffer matches a prefix of START_MARKER.
69
+ const maxOverlap = Math.min(this.#buffer.length, START_MARKER.length - 1);
70
+ let overlapLen = 0;
71
+ for (let len = maxOverlap; len > 0; len--) {
72
+ if (START_MARKER.startsWith(this.#buffer.slice(-len))) {
73
+ overlapLen = len;
74
+ break;
75
+ }
76
+ }
77
+
78
+ if (overlapLen > 0) {
79
+ const safeLen = this.#buffer.length - overlapLen;
80
+ if (safeLen > 0) {
81
+ out += this.#buffer.slice(0, safeLen);
82
+ this.#emittedAny = true;
83
+ this.#buffer = this.#buffer.slice(safeLen);
84
+ }
85
+ // Keep overlapLen in buffer to see if next chunk completes the marker.
86
+ break;
87
+ } else {
88
+ out += this.#buffer;
89
+ this.#emittedAny = true;
90
+ this.#buffer = "";
91
+ break;
92
+ }
93
+ } else {
94
+ // Suppressing inside <SYSTEM_MESSAGE>...</SYSTEM_MESSAGE>
95
+ const endIdx = this.#buffer.indexOf(END_MARKER);
96
+ if (endIdx !== -1) {
97
+ let afterEnd = endIdx + END_MARKER.length;
98
+ if (!this.#emittedAny) {
99
+ // Discard all leading newlines before the first real content.
100
+ while (
101
+ afterEnd < this.#buffer.length &&
102
+ (this.#buffer[afterEnd] === "\r" || this.#buffer[afterEnd] === "\n")
103
+ ) {
104
+ afterEnd++;
105
+ }
106
+ } else {
107
+ // Consume trailing newlines after </SYSTEM_MESSAGE> to preserve paragraph spacing.
108
+ if (this.#buffer.startsWith("\r\n\r\n", afterEnd)) {
109
+ afterEnd += 4;
110
+ } else if (this.#buffer.startsWith("\n\n", afterEnd)) {
111
+ afterEnd += 2;
112
+ } else if (this.#buffer.startsWith("\r\n", afterEnd)) {
113
+ afterEnd += 2;
114
+ } else if (this.#buffer.startsWith("\n", afterEnd)) {
115
+ afterEnd += 1;
116
+ }
117
+ }
118
+ this.#buffer = this.#buffer.slice(afterEnd);
119
+ this.#suppressing = false;
120
+ this.#suppressedAcc = "";
121
+ continue;
122
+ }
123
+
124
+ // END_MARKER not yet encountered. Check if tail matches prefix of END_MARKER.
125
+ const maxOverlap = Math.min(this.#buffer.length, END_MARKER.length - 1);
126
+ let overlapLen = 0;
127
+ for (let len = maxOverlap; len > 0; len--) {
128
+ if (END_MARKER.startsWith(this.#buffer.slice(-len))) {
129
+ overlapLen = len;
130
+ break;
131
+ }
132
+ }
133
+
134
+ if (overlapLen > 0) {
135
+ this.#suppressedAcc += this.#buffer.slice(0, -overlapLen);
136
+ this.#buffer = this.#buffer.slice(-overlapLen);
137
+ } else {
138
+ this.#suppressedAcc += this.#buffer;
139
+ this.#buffer = "";
140
+ }
141
+ break;
142
+ }
143
+ }
144
+
145
+ return out;
146
+ }
147
+
148
+ /** Flush remaining buffered text when stream ends. */
149
+ flush(): string {
150
+ if (this.#suppressing) {
151
+ const unclosed = this.#suppressedAcc + this.#buffer;
152
+ this.#buffer = "";
153
+ this.#suppressedAcc = "";
154
+ this.#suppressing = false;
155
+ if (unclosed.length > 0) {
156
+ this.#emittedAny = true;
157
+ }
158
+ return unclosed;
159
+ }
160
+ const remaining = this.#buffer;
161
+ this.#buffer = "";
162
+ if (remaining.length > 0) {
163
+ this.#emittedAny = true;
164
+ }
165
+ return remaining;
166
+ }
167
+
168
+ /** Peek at non-suppressed buffer contents without consuming them. */
169
+ peek(): string {
170
+ return this.#suppressing ? "" : this.#buffer;
171
+ }
172
+
173
+ /** Reset state. */
174
+ reset(): void {
175
+ this.#buffer = "";
176
+ this.#suppressedAcc = "";
177
+ this.#suppressing = false;
178
+ this.#emittedAny = false;
179
+ }
180
+ }