@@ -3,6 +3,7 @@ import { stripHistoricalRuntimeContextCustomMessages } from "../../internal-runt
33import type { AgentMessage } from "../../runtime/index.js" ;
44import { stripToolResultDetails } from "../../session-transcript-repair.js" ;
55import { normalizeAssistantReplayContent } from "../replay-history.js" ;
6+ import { markTranscriptPromptText } from "../tool-result-context-guard.js" ;
67import type { RuntimeContextCustomMessage } from "./runtime-context-prompt.js" ;
78
89export function normalizeMessagesForLlmBoundary ( messages : AgentMessage [ ] ) : AgentMessage [ ] {
@@ -103,6 +104,141 @@ export function insertRuntimeContextMessageForPrompt(params: {
103104 ] ;
104105}
105106
107+ function replaceLastUserTextPrompt ( params : {
108+ messages : AgentMessage [ ] ;
109+ shouldCapture ?: ( message : AgentMessage ) => boolean ;
110+ transcriptText ?: string ;
111+ replace : ( text : string ) => string | undefined ;
112+ } ) : AgentMessage [ ] {
113+ const userIndex = params . messages . findLastIndex ( ( message ) => message . role === "user" ) ;
114+ if ( userIndex === - 1 ) {
115+ return params . messages ;
116+ }
117+ const message = params . messages [ userIndex ] ;
118+ if ( ! message || message . role !== "user" ) {
119+ return params . messages ;
120+ }
121+ if ( params . shouldCapture && ! params . shouldCapture ( message ) ) {
122+ return params . messages ;
123+ }
124+ const content = ( message as { content ?: unknown } ) . content ;
125+ if ( typeof content === "string" ) {
126+ const replacement = params . replace ( content ) ;
127+ if ( replacement === undefined ) {
128+ return params . messages ;
129+ }
130+ const next = params . messages . slice ( ) ;
131+ next [ userIndex ] = { ...message , content : replacement } as AgentMessage ;
132+ if ( params . transcriptText !== undefined ) {
133+ markTranscriptPromptText ( next [ userIndex ] , params . transcriptText ) ;
134+ }
135+ return next ;
136+ }
137+ if ( ! Array . isArray ( content ) ) {
138+ return params . messages ;
139+ }
140+ let replaced = false ;
141+ const nextContent = content . map ( ( block ) => {
142+ if ( replaced || ! block || typeof block !== "object" ) {
143+ return block ;
144+ }
145+ const textBlock = block as { type ?: unknown ; text ?: unknown } ;
146+ if ( textBlock . type !== "text" || typeof textBlock . text !== "string" ) {
147+ return block ;
148+ }
149+ const replacement = params . replace ( textBlock . text ) ;
150+ if ( replacement === undefined ) {
151+ return block ;
152+ }
153+ replaced = true ;
154+ return Object . assign ( { } , block , { text : replacement } ) ;
155+ } ) ;
156+ if ( ! replaced ) {
157+ return params . messages ;
158+ }
159+ const next = params . messages . slice ( ) ;
160+ next [ userIndex ] = { ...message , content : nextContent } as AgentMessage ;
161+ if ( params . transcriptText !== undefined ) {
162+ markTranscriptPromptText ( next [ userIndex ] , params . transcriptText ) ;
163+ }
164+ return next ;
165+ }
166+
167+ function composeModelPromptContext ( params : {
168+ prompt : string ;
169+ prependContext ?: string ;
170+ appendContext ?: string ;
171+ } ) : string {
172+ return [ params . prependContext , params . prompt , params . appendContext ]
173+ . filter ( ( value ) : value is string => Boolean ( value ?. trim ( ) ) )
174+ . join ( "\n\n" ) ;
175+ }
176+
177+ export function installModelPromptTransform ( params : {
178+ session : {
179+ agent : {
180+ transformContext ?: (
181+ messages : AgentMessage [ ] ,
182+ signal ?: AbortSignal ,
183+ ) => Promise < AgentMessage [ ] > ;
184+ } ;
185+ } ;
186+ transcriptPrompt : string ;
187+ modelPrompt ?: string ;
188+ prependContext ?: string ;
189+ appendContext ?: string ;
190+ shouldCapturePrompt : ( ) => boolean ;
191+ } ) : ( ) => void {
192+ const modelPrompt = params . modelPrompt ;
193+ const hasPromptContext =
194+ Boolean ( params . prependContext ?. trim ( ) ) || Boolean ( params . appendContext ?. trim ( ) ) ;
195+ if ( ( ! modelPrompt ?. trim ( ) || modelPrompt === params . transcriptPrompt ) && ! hasPromptContext ) {
196+ return ( ) => undefined ;
197+ }
198+ const agent = params . session . agent ;
199+ const originalTransformContext = agent . transformContext ;
200+ let targetPromptTimestamp : number | undefined ;
201+ agent . transformContext = async ( messages , signal ) => {
202+ const promptMessages = replaceLastUserTextPrompt ( {
203+ messages,
204+ transcriptText : params . transcriptPrompt ,
205+ shouldCapture : ( message ) => {
206+ const timestamp = ( message as { timestamp ?: unknown } ) . timestamp ;
207+ if ( targetPromptTimestamp !== undefined ) {
208+ return timestamp === targetPromptTimestamp ;
209+ }
210+ if ( ! params . shouldCapturePrompt ( ) ) {
211+ return false ;
212+ }
213+ if ( typeof timestamp === "number" ) {
214+ targetPromptTimestamp = timestamp ;
215+ }
216+ return true ;
217+ } ,
218+ replace : ( text ) => {
219+ if ( modelPrompt ?. trim ( ) && text === params . transcriptPrompt ) {
220+ return modelPrompt ;
221+ }
222+ if ( ! hasPromptContext ) {
223+ return undefined ;
224+ }
225+ const replacement = composeModelPromptContext ( {
226+ prompt : text ,
227+ prependContext : params . prependContext ,
228+ appendContext : params . appendContext ,
229+ } ) ;
230+ return replacement === text ? undefined : replacement ;
231+ } ,
232+ } ) ;
233+ return originalTransformContext
234+ ? await originalTransformContext . call ( agent , promptMessages , signal )
235+ : promptMessages ;
236+ } ;
237+ return ( ) => {
238+ agent . transformContext = originalTransformContext ;
239+ } ;
240+ }
241+
106242function stripHistoricalInboundMetadataFromUserMessages ( messages : AgentMessage [ ] ) : AgentMessage [ ] {
107243 const activeUserMessageIndex = findActiveUserMessageIndex ( messages ) ;
108244 let changed = false ;
0 commit comments