@@ -79,6 +79,11 @@ import { MultiSearchReplaceDiffStrategy } from "../diff/strategies/multi-search-
7979import { MultiFileSearchReplaceDiffStrategy } from "../diff/strategies/multi-file-search-replace"
8080import { readApiMessages , saveApiMessages , readTaskMessages , saveTaskMessages , taskMetadata } from "../task-persistence"
8181import { getEnvironmentDetails } from "../environment/getEnvironmentDetails"
82+ import {
83+ shouldUseSyntheticMessages ,
84+ handleFirstMessageWithFileMentions ,
85+ createSyntheticReadFileMessage ,
86+ } from "./synthetic-messages"
8287import {
8388 type CheckpointDiffOptions ,
8489 type CheckpointRestoreOptions ,
@@ -88,7 +93,6 @@ import {
8893 checkpointDiff ,
8994} from "../checkpoints"
9095import { processUserContentMentions } from "../mentions/processUserContentMentions"
91- import { extractFileMentions , hasFileMentions } from "../mentions/extractFileMentions"
9296import { ApiMessage } from "../task-persistence/apiMessages"
9397import { getMessagesSinceLastSummary , summarizeConversation } from "../condense"
9498import { maybeRemoveImageBlocks } from "../../api/transform/image-cleaning"
@@ -330,7 +334,7 @@ export class Task extends EventEmitter<ClineEvents> {
330334 return readApiMessages ( { taskId : this . taskId , globalStoragePath : this . globalStoragePath } )
331335 }
332336
333- private async addToApiConversationHistory ( message : Anthropic . MessageParam ) {
337+ public async addToApiConversationHistory ( message : Anthropic . MessageParam ) {
334338 const messageWithTs = { ...message , ts : Date . now ( ) }
335339 this . apiConversationHistory . push ( messageWithTs )
336340 await this . saveApiConversationHistory ( )
@@ -1120,102 +1124,6 @@ export class Task extends EventEmitter<ClineEvents> {
11201124
11211125 // Helper methods for synthetic message generation
11221126
1123- /**
1124- * Checks if we should use synthetic messages for the first user message.
1125- * This is true when the message contains @filename mentions.
1126- */
1127- private shouldUseSyntheticMessages ( userContent : Anthropic . Messages . ContentBlockParam [ ] ) : boolean {
1128- // Only check text blocks for file mentions
1129- return hasFileMentions ( userContent )
1130- }
1131-
1132- /**
1133- * Handles the first message when it contains file mentions by creating synthetic messages.
1134- * This implements the 3-message pattern:
1135- * 1. Original user message (without embedded file content)
1136- * 2. Synthetic assistant message with read_file tool calls
1137- * 3. User message with file contents
1138- */
1139- private async handleFirstMessageWithFileMentions (
1140- userContent : Anthropic . Messages . ContentBlockParam [ ] ,
1141- ) : Promise < void > {
1142- // Extract file mentions from the user content
1143- const fileMentions : string [ ] = [ ]
1144- for ( const block of userContent ) {
1145- if ( block . type === "text" && block . text ) {
1146- const mentions = extractFileMentions ( block . text )
1147- fileMentions . push ( ...mentions . map ( ( m ) => m . path ) )
1148- }
1149- }
1150-
1151- if ( fileMentions . length === 0 ) {
1152- // No file mentions found, proceed normally
1153- return
1154- }
1155-
1156- // Step 1: Add the original user message to conversation history (without processing mentions)
1157- await this . addToApiConversationHistory ( { role : "user" , content : userContent } )
1158-
1159- // Step 2: Create synthetic assistant message with read_file tool calls
1160- const syntheticAssistantContent = this . createSyntheticReadFileMessage ( fileMentions )
1161- await this . addToApiConversationHistory ( {
1162- role : "assistant" ,
1163- content : [ { type : "text" , text : syntheticAssistantContent } ] ,
1164- } )
1165-
1166- // Step 3: Process the mentions to get file contents and create user response
1167- const {
1168- showRooIgnoredFiles = true ,
1169- includeDiagnosticMessages = true ,
1170- maxDiagnosticMessages = 50 ,
1171- } = ( await this . providerRef . deref ( ) ?. getState ( ) ) ?? { }
1172-
1173- const processedContent = await processUserContentMentions ( {
1174- userContent,
1175- cwd : this . cwd ,
1176- urlContentFetcher : this . urlContentFetcher ,
1177- fileContextTracker : this . fileContextTracker ,
1178- rooIgnoreController : this . rooIgnoreController ,
1179- showRooIgnoredFiles,
1180- includeDiagnosticMessages,
1181- maxDiagnosticMessages,
1182- } )
1183-
1184- // Add environment details
1185- const environmentDetails = await getEnvironmentDetails ( this , true )
1186- const fileContentResponse = [ ...processedContent , { type : "text" as const , text : environmentDetails } ]
1187-
1188- // Add the file content as a user message
1189- await this . addToApiConversationHistory ( { role : "user" , content : fileContentResponse } )
1190-
1191- // Now continue with the normal flow - the model will see all 3 messages
1192- // but the task history only stored the original message without embedded content
1193- }
1194-
1195- /**
1196- * Creates a synthetic assistant message with read_file tool calls
1197- */
1198- private createSyntheticReadFileMessage ( filePaths : string [ ] ) : string {
1199- if ( filePaths . length === 0 ) {
1200- return ""
1201- }
1202-
1203- // Create read_file tool calls for each file
1204- const toolCalls = filePaths
1205- . map ( ( path ) => {
1206- return `<read_file>
1207- <args>
1208- <file>
1209- <path>${ path } </path>
1210- </file>
1211- </args>
1212- </read_file>`
1213- } )
1214- . join ( "\n\n" )
1215-
1216- return `I'll help you with that. Let me first read the mentioned file${ filePaths . length > 1 ? "s" : "" } to understand the context.\n\n${ toolCalls } `
1217- }
1218-
12191127 // Task Loop
12201128
12211129 private async initiateTaskLoop ( userContent : Anthropic . Messages . ContentBlockParam [ ] ) : Promise < void > {
@@ -1230,9 +1138,9 @@ export class Task extends EventEmitter<ClineEvents> {
12301138
12311139 while ( ! this . abort ) {
12321140 // Check if this is the first message and contains file mentions
1233- if ( isFirstMessage && this . shouldUseSyntheticMessages ( nextUserContent ) ) {
1141+ if ( isFirstMessage && shouldUseSyntheticMessages ( nextUserContent ) ) {
12341142 // Handle first message with file mentions using synthetic messages
1235- await this . handleFirstMessageWithFileMentions ( nextUserContent )
1143+ await handleFirstMessageWithFileMentions ( this , nextUserContent )
12361144 isFirstMessage = false
12371145 // The synthetic messages have been processed, continue with normal flow
12381146 // but skip the first iteration since we've already handled it
0 commit comments