@@ -6,6 +6,10 @@ import {
66 GATEWAY_CLIENT_MODES ,
77} from "../../../packages/gateway-protocol/src/client-info.js" ;
88import type { SourceReplyDeliveryMode } from "../../auto-reply/get-reply-options.types.js" ;
9+ import {
10+ hasInboundMetadataSentinel ,
11+ stripInboundMetadata ,
12+ } from "../../auto-reply/reply/strip-inbound-meta.js" ;
913import type { InboundEventKind } from "../../channels/inbound-event/kind.js" ;
1014import {
1115 getChannelPlugin ,
@@ -96,75 +100,90 @@ function normalizeEscapedLineBreaksForVisibleText(text: string): string {
96100 return text . replace ( / \\ r \\ n | \\ n | \\ r / g, "\n" ) ;
97101}
98102
103+ type VisibleTextSuppressionReason = "internal_runtime_context_echo" | "inbound_metadata_echo" ;
104+
99105function sanitizeUserVisibleToolTextResult (
100106 text : string ,
101107 bootPrompt : string | undefined ,
102- ) : { text : string ; suppressed : boolean } {
108+ ) : {
109+ text : string ;
110+ suppressionReason ?: VisibleTextSuppressionReason ;
111+ } {
103112 const normalized = normalizeEscapedLineBreaksForVisibleText ( text ) ;
104113 const strippedReasoning = stripFormattedReasoningMessage ( normalized ) ;
105114 const strippedInternal = stripInternalRuntimeContext ( strippedReasoning ) ;
106115 const strippedBoot = stripBootEchoFromOutboundText ( strippedInternal , bootPrompt ) ;
116+ const strippedInbound = hasInboundMetadataSentinel ( strippedBoot )
117+ ? stripInboundMetadata ( strippedBoot )
118+ : strippedBoot ;
119+ const suppressionReason =
120+ strippedBoot . trim ( ) . length === 0 &&
121+ strippedReasoning . trim ( ) . length > 0 &&
122+ ( strippedInternal !== strippedReasoning || strippedBoot !== strippedInternal )
123+ ? "internal_runtime_context_echo"
124+ : strippedInbound . trim ( ) . length === 0 &&
125+ strippedBoot . trim ( ) . length > 0 &&
126+ strippedInbound !== strippedBoot
127+ ? "inbound_metadata_echo"
128+ : undefined ;
107129 return {
108- text : strippedBoot ,
109- suppressed :
110- strippedBoot . trim ( ) . length === 0 &&
111- strippedReasoning . trim ( ) . length > 0 &&
112- ( strippedInternal !== strippedReasoning || strippedBoot !== strippedInternal ) ,
130+ text : strippedInbound ,
131+ ...( suppressionReason ? { suppressionReason } : { } ) ,
113132 } ;
114133}
115134
116135function sanitizeStringParam (
117136 params : Record < string , unknown > ,
118137 field : string ,
119138 bootPrompt : string | undefined ,
120- ) : boolean {
139+ ) : VisibleTextSuppressionReason | undefined {
121140 if ( typeof params [ field ] !== "string" ) {
122- return false ;
141+ return undefined ;
123142 }
124143 const sanitized = sanitizeUserVisibleToolTextResult ( params [ field ] , bootPrompt ) ;
125144 params [ field ] = sanitized . text ;
126- return sanitized . suppressed ;
145+ return sanitized . suppressionReason ;
127146}
128147
129148function sanitizeStringArrayParam (
130149 params : Record < string , unknown > ,
131150 field : string ,
132151 bootPrompt : string | undefined ,
133- ) : boolean {
152+ ) : VisibleTextSuppressionReason | undefined {
134153 const value = params [ field ] ;
135154 if ( typeof value === "string" ) {
136155 const sanitized = sanitizeUserVisibleToolTextResult ( value , bootPrompt ) ;
137156 params [ field ] = sanitized . text ;
138- return sanitized . suppressed ;
157+ return sanitized . suppressionReason ;
139158 }
140159 if ( ! Array . isArray ( value ) ) {
141- return false ;
160+ return undefined ;
142161 }
143- let suppressed = false ;
162+ let suppressionReason : VisibleTextSuppressionReason | undefined ;
144163 params [ field ] = value . map ( ( entry ) => {
145164 if ( typeof entry !== "string" ) {
146165 return entry ;
147166 }
148167 const sanitized = sanitizeUserVisibleToolTextResult ( entry , bootPrompt ) ;
149- suppressed || = sanitized . suppressed ;
168+ suppressionReason ?? = sanitized . suppressionReason ;
150169 return sanitized . text ;
151170 } ) ;
152- return suppressed ;
171+ return suppressionReason ;
153172}
154173
155174function sanitizePresentationTextFieldsResult (
156175 value : unknown ,
157176 bootPrompt : string | undefined ,
158- ) : { value : unknown ; suppressed : boolean } {
177+ ) : { value : unknown ; suppressionReason ?: VisibleTextSuppressionReason } {
159178 if ( ! value || typeof value !== "object" || Array . isArray ( value ) ) {
160- return { value, suppressed : false } ;
179+ return { value } ;
161180 }
162- let suppressed = false ;
181+ let suppressionReason : VisibleTextSuppressionReason | undefined ;
163182 const presentation = { ...( value as Record < string , unknown > ) } ;
164183 if ( typeof presentation . title === "string" ) {
165184 const sanitized = sanitizeUserVisibleToolTextResult ( presentation . title , bootPrompt ) ;
166185 presentation . title = sanitized . text ;
167- suppressed || = sanitized . suppressed ;
186+ suppressionReason ?? = sanitized . suppressionReason ;
168187 }
169188 if ( Array . isArray ( presentation . blocks ) ) {
170189 presentation . blocks = presentation . blocks . map ( ( block ) => {
@@ -176,7 +195,7 @@ function sanitizePresentationTextFieldsResult(
176195 if ( typeof sanitizedBlock [ field ] === "string" ) {
177196 const sanitized = sanitizeUserVisibleToolTextResult ( sanitizedBlock [ field ] , bootPrompt ) ;
178197 sanitizedBlock [ field ] = sanitized . text ;
179- suppressed || = sanitized . suppressed ;
198+ suppressionReason ?? = sanitized . suppressionReason ;
180199 }
181200 }
182201 if ( Array . isArray ( sanitizedBlock . buttons ) ) {
@@ -188,7 +207,7 @@ function sanitizePresentationTextFieldsResult(
188207 if ( typeof sanitizedButton . label === "string" ) {
189208 const sanitized = sanitizeUserVisibleToolTextResult ( sanitizedButton . label , bootPrompt ) ;
190209 sanitizedButton . label = sanitized . text ;
191- suppressed || = sanitized . suppressed ;
210+ suppressionReason ?? = sanitized . suppressionReason ;
192211 }
193212 if ( typeof sanitizedButton . url === "string" ) {
194213 const sanitized = sanitizeUserVisibleToolTextResult ( sanitizedButton . url , bootPrompt ) ;
@@ -197,7 +216,7 @@ function sanitizePresentationTextFieldsResult(
197216 } else {
198217 delete sanitizedButton . url ;
199218 }
200- suppressed || = sanitized . suppressed ;
219+ suppressionReason ?? = sanitized . suppressionReason ;
201220 }
202221 for ( const webAppField of [ "webApp" , "web_app" ] ) {
203222 const webApp = sanitizedButton [ webAppField ] ;
@@ -215,7 +234,7 @@ function sanitizePresentationTextFieldsResult(
215234 } else {
216235 delete sanitizedButton [ webAppField ] ;
217236 }
218- suppressed || = sanitized . suppressed ;
237+ suppressionReason ?? = sanitized . suppressionReason ;
219238 }
220239 return sanitizedButton ;
221240 } ) ;
@@ -229,15 +248,15 @@ function sanitizePresentationTextFieldsResult(
229248 if ( typeof sanitizedOption . label === "string" ) {
230249 const sanitized = sanitizeUserVisibleToolTextResult ( sanitizedOption . label , bootPrompt ) ;
231250 sanitizedOption . label = sanitized . text ;
232- suppressed || = sanitized . suppressed ;
251+ suppressionReason ?? = sanitized . suppressionReason ;
233252 }
234253 return sanitizedOption ;
235254 } ) ;
236255 }
237256 return sanitizedBlock ;
238257 } ) ;
239258 }
240- return { value : presentation , suppressed } ;
259+ return { value : presentation , ... ( suppressionReason ? { suppressionReason } : { } ) } ;
241260}
242261
243262function readFirstStringParam ( params : Record < string , unknown > , keys : readonly string [ ] ) : string {
@@ -1150,7 +1169,7 @@ export function createMessageTool(options?: MessageToolOptions): AnyAgentTool {
11501169 // that paraphrase out the wrapper markers but reproduce a
11511170 // substantial chunk of the boot prompt content. Refs #53732.
11521171 const bootPromptForSession = getBootEchoContextForSession ( options ?. agentSessionKey ) ;
1153- let suppressedVisiblePayload = false ;
1172+ let suppressedVisiblePayloadReason : VisibleTextSuppressionReason | undefined ;
11541173 parseJsonMessageParam ( params , "presentation" ) ;
11551174 parseInteractiveParam ( params ) ;
11561175 for ( const field of [
@@ -1162,42 +1181,45 @@ export function createMessageTool(options?: MessageToolOptions): AnyAgentTool {
11621181 "quoteText" ,
11631182 "quote_text" ,
11641183 ] ) {
1165- suppressedVisiblePayload =
1166- sanitizeStringParam ( params , field , bootPromptForSession ) || suppressedVisiblePayload ;
1184+ const suppressionReason = sanitizeStringParam ( params , field , bootPromptForSession ) ;
1185+ suppressedVisiblePayloadReason ??= suppressionReason ;
11671186 }
11681187 for ( const field of [ "pollQuestion" , "poll_question" ] ) {
1169- suppressedVisiblePayload =
1170- sanitizeStringParam ( params , field , bootPromptForSession ) || suppressedVisiblePayload ;
1188+ const suppressionReason = sanitizeStringParam ( params , field , bootPromptForSession ) ;
1189+ suppressedVisiblePayloadReason ??= suppressionReason ;
11711190 }
11721191 for ( const field of [ "pollOption" , "poll_option" ] ) {
1173- suppressedVisiblePayload =
1174- sanitizeStringArrayParam ( params , field , bootPromptForSession ) || suppressedVisiblePayload ;
1192+ const suppressionReason = sanitizeStringArrayParam ( params , field , bootPromptForSession ) ;
1193+ suppressedVisiblePayloadReason ??= suppressionReason ;
11751194 }
11761195 const sanitizedPresentation = sanitizePresentationTextFieldsResult (
11771196 params . presentation ,
11781197 bootPromptForSession ,
11791198 ) ;
11801199 params . presentation = sanitizedPresentation . value ;
1181- suppressedVisiblePayload || = sanitizedPresentation . suppressed ;
1200+ suppressedVisiblePayloadReason ?? = sanitizedPresentation . suppressionReason ;
11821201 const sanitizedInteractive = sanitizePresentationTextFieldsResult (
11831202 params . interactive ,
11841203 bootPromptForSession ,
11851204 ) ;
11861205 params . interactive = sanitizedInteractive . value ;
1187- suppressedVisiblePayload || = sanitizedInteractive . suppressed ;
1206+ suppressedVisiblePayloadReason ?? = sanitizedInteractive . suppressionReason ;
11881207
11891208 const action = readStringParam ( params , "action" , {
11901209 required : true ,
11911210 } ) as ChannelMessageActionName ;
11921211 if (
1193- suppressedVisiblePayload &&
1212+ suppressedVisiblePayloadReason &&
11941213 action === "send" &&
11951214 ! hasSanitizedSendPayloadContent ( params )
11961215 ) {
11971216 return jsonResult ( {
11981217 status : "suppressed" ,
1199- reason : "internal_runtime_context_echo" ,
1200- message : "Suppressed outbound message text because it matched internal runtime context." ,
1218+ reason : suppressedVisiblePayloadReason ,
1219+ message :
1220+ suppressedVisiblePayloadReason === "inbound_metadata_echo"
1221+ ? "Suppressed outbound message text because it matched inbound runtime metadata."
1222+ : "Suppressed outbound message text because it matched internal runtime context." ,
12011223 } ) ;
12021224 }
12031225 const requireExplicitTarget = options ?. requireExplicitTarget === true ;
0 commit comments