Repository: cardstack/boxel
Pull request #2558: Send placeholder in place of SEARCH/REPLACE blocks when sending context to LLM
Author: login: lukemelia
Created: 2025-05-07T02:05:07Z | Merged: 2025-05-08T11:53:03Z



Diff:
diff --git a/packages/ai-bot/helpers.ts b/packages/ai-bot/helpers.ts
index 8a8e97cd6de..6617eb6f0d6 100644
--- a/packages/ai-bot/helpers.ts
+++ b/packages/ai-bot/helpers.ts
@@ -2,6 +2,9 @@ import {
   LooseCardResource,
   type LooseSingleCardDocument,
   type CardResource,
+  SEARCH_MARKER,
+  SEPARATOR_MARKER,
+  REPLACE_MARKER,
 } from '@cardstack/runtime-common';
 import { ToolChoice } from '@cardstack/runtime-common/helpers/ai';
 import type {
@@ -628,7 +631,7 @@ export async function getModifyPrompt(
       let toolCalls = toToolCalls(event as CardMessageEvent);
       let historicalMessage: OpenAIPromptMessage = {
         role: 'assistant',
-        content: body,
+        content: elideCodeBlocks(body),
       };
       if (toolCalls.length) {
         historicalMessage.tool_calls = toolCalls;
@@ -768,3 +771,30 @@ export function isCommandResultEvent(
       APP_BOXEL_COMMAND_RESULT_REL_TYPE
   );
 }
+
+function elideCodeBlocks(content: string) {
+  const PLACEHOLDER: string = '[Proposed code change]';
+
+  while (
+    content.includes(SEARCH_MARKER) &&
+    content.includes(SEPARATOR_MARKER) &&
+    content.includes(REPLACE_MARKER)
+  ) {
+    const searchStartIndex: number = content.indexOf(SEARCH_MARKER);
+    const separatorIndex: number = content.indexOf(
+      SEPARATOR_MARKER,
+      searchStartIndex,
+    );
+    const replaceEndIndex: number = content.indexOf(
+      REPLACE_MARKER,
+      separatorIndex,
+    );
+
+    // replace the content between the markers with a placeholder
+    content =
+      content.substring(0, searchStartIndex) +
+      PLACEHOLDER +
+      content.substring(replaceEndIndex + REPLACE_MARKER.length);
+  }
+  return content;
+}
diff --git a/packages/ai-bot/tests/prompt-construction-test.ts b/packages/ai-bot/tests/prompt-construction-test.ts
index 0c6de91ad80..189ccdb9291 100644
--- a/packages/ai-bot/tests/prompt-construction-test.ts
+++ b/packages/ai-bot/tests/prompt-construction-test.ts
@@ -2711,6 +2711,37 @@ Attached files:
       'Should not include old command description',
     );
   });
+
+  test('Elides code blocks in prompt', async () => {
+    // sending older codeblocks back to the model just confuses it and wastes tokens
+    // so we need to remove them from the prompt
+    const eventList: DiscreteMatrixEvent[] = JSON.parse(
+      readFileSync(
+        path.join(__dirname, 'resources/chats/code-blocks.json'),
+        'utf-8',
+      ),
+    );
+
+    const { messages } = await getPromptParts(
+      eventList,
+      '@aibot:localhost',
+      fakeMatrixClient,
+    );
+    assert.equal(messages!.length, 3);
+    assert.equal(messages![2].role, 'assistant');
+    assert.equal(
+      messages![2].content,
+      'Right, let us make a tic tac toe game.\n' +
+        '\n' +
+        '// File url: https://test.com/tic-tac.gts\n' +
+        '[Proposed code change]\n' +
+        '\n' +
+        '// File url: https://test.com/tac-toe.gts\n' +
+        '[Proposed code change]\n' +
+        '\n' +
+        'I can add some more whiz bang if you want. Let me know!',
+    );
+  });
 });
 
 module('set model in prompt', (hooks) => {
diff --git a/packages/ai-bot/tests/resources/chats/code-blocks.json b/packages/ai-bot/tests/resources/chats/code-blocks.json
new file mode 100644
index 00000000000..210e5731282
--- /dev/null
+++ b/packages/ai-bot/tests/resources/chats/code-blocks.json
@@ -0,0 +1,358 @@
+[
+  {
+    "type": "m.room.create",
+    "room_id": "!EtlFgGnAaFCLoozHPV:localhost",
+    "sender": "@admin:localhost",
+    "content": {
+      "room_version": "10",
+      "creator": "@admin:localhost"
+    },
+    "state_key": "",
+    "origin_server_ts": 1736864362728,
+    "unsigned": {
+      "age": 71431
+    },
+    "event_id": "$BbFv3bQk_6IXS845CttWkNqprc00PaW7egknDPzAb3U",
+    "user_id": "@admin:localhost",
+    "age": 71431
+  },
+  {
+    "type": "m.room.member",
+    "room_id": "!EtlFgGnAaFCLoozHPV:localhost",
+    "sender": "@admin:localhost",
+    "content": {
+      "membership": "join",
+      "displayname": "admin"
+    },
+    "state_key": "@admin:localhost",
+    "origin_server_ts": 1736864362765,
+    "unsigned": {
+      "age": 71394
+    },
+    "event_id": "$auoMF6x0EHAGAIVbeHjsLdrepN5NWaoXVkWPa_QNZ1o",
+    "user_id": "@admin:localhost",
+    "age": 71394
+  },
+  {
+    "type": "m.room.power_levels",
+    "room_id": "!EtlFgGnAaFCLoozHPV:localhost",
+    "sender": "@admin:localhost",
+    "content": {
+      "users": {
+        "@admin:localhost": 100
+      },
+      "users_default": 0,
+      "events": {
+        "m.room.name": 50,
+        "m.room.power_levels": 100,
+        "m.room.history_visibility": 100,
+        "m.room.canonical_alias": 50,
+        "m.room.avatar": 50,
+        "m.room.tombstone": 100,
+        "m.room.server_acl": 100,
+        "m.room.encryption": 100
+      },
+      "events_default": 0,
+      "state_default": 50,
+      "ban": 50,
+      "kick": 50,
+      "redact": 50,
+      "invite": 0,
+      "historical": 100
+    },
+    "state_key": "",
+    "origin_server_ts": 1736864362793,
+    "unsigned": {
+      "age": 71366
+    },
+    "event_id": "$XPa4Nfoi-LpJRvCCHMPd088dnrzt-Uc8R0lb-56MCUQ",
+    "user_id": "@admin:localhost",
+    "age": 71366
+  },
+  {
+    "type": "m.room.canonical_alias",
+    "room_id": "!EtlFgGnAaFCLoozHPV:localhost",
+    "sender": "@admin:localhost",
+    "content": {
+      "alias": "#Weather%20Travel%20Advice%20-%202025-01-14T14%3A19%3A22.706%2B00%3A00%20-%20%40admin%3Alocalhost:localhost"
+    },
+    "state_key": "",
+    "origin_server_ts": 1736864362807,
+    "unsigned": {
+      "age": 71352
+    },
+    "event_id": "$C7j2gqIzpWOjWp5yabZ2JLVGc5sh_KjuOdrtv7TavNE",
+    "user_id": "@admin:localhost",
+    "age": 71352
+  },
+  {
+    "type": "m.room.join_rules",
+    "room_id": "!EtlFgGnAaFCLoozHPV:localhost",
+    "sender": "@admin:localhost",
+    "content": {
+      "join_rule": "invite"
+    },
+    "state_key": "",
+    "origin_server_ts": 1736864362808,
+    "unsigned": {
+      "age": 71351
+    },
+    "event_id": "$8FT4iaFSox71dX5-Z0xgbiY2isoPL6ybLM37wXSKJOs",
+    "user_id": "@admin:localhost",
+    "age": 71351
+  },
+  {
+    "type": "m.room.history_visibility",
+    "room_id": "!EtlFgGnAaFCLoozHPV:localhost",
+    "sender": "@admin:localhost",
+    "content": {
+      "history_visibility": "shared"
+    },
+    "state_key": "",
+    "origin_server_ts": 1736864362809,
+    "unsigned": {
+      "age": 71350
+    },
+    "event_id": "$cxBIW2re3D1puIfhAyg067GLdW24FXctOqQ8Juma-Hs",
+    "user_id": "@admin:localhost",
+    "age": 71350
+  },
+  {
+    "type": "m.room.guest_access",
+    "room_id": "!EtlFgGnAaFCLoozHPV:localhost",
+    "sender": "@admin:localhost",
+    "content": {
+      "guest_access": "can_join"
+    },
+    "state_key": "",
+    "origin_server_ts": 1736864362809,
+    "unsigned": {
+      "age": 71350
+    },
+    "event_id": "$hSlWgIKf_y_uycz1Ae4USMOa4K17dq7f6VHSrN2nhcU",
+    "user_id": "@admin:localhost",
+    "age": 71350
+  },
+  {
+    "type": "m.room.name",
+    "room_id": "!EtlFgGnAaFCLoozHPV:localhost",
+    "sender": "@admin:localhost",
+    "content": {
+      "name": "Writing Some Code"
+    },
+    "state_key": "",
+    "origin_server_ts": 1736864362810,
+    "unsigned": {
+      "age": 71349
+    },
+    "event_id": "$isUAMN-pIaBk9gFFTn6UyyTk9QcG59QwOuWhXbfCEkw",
+    "user_id": "@admin:localhost",
+    "age": 71349
+  },
+  {
+    "type": "m.room.member",
+    "room_id": "!EtlFgGnAaFCLoozHPV:localhost",
+    "sender": "@admin:localhost",
+    "content": {
+      "membership": "invite",
+      "displayname": "aibot"
+    },
+    "state_key": "@aibot:localhost",
+    "origin_server_ts": 1736864362916,
+    "unsigned": {
+      "age": 71243
+    },
+    "event_id": "$Odud1s75LuD0WjceV_Y_Ek-OVoH5unWa_-g_NI_D1rc",
+    "user_id": "@admin:localhost",
+    "age": 71243
+  },
+  {
+    "type": "m.room.power_levels",
+    "room_id": "!EtlFgGnAaFCLoozHPV:localhost",
+    "sender": "@admin:localhost",
+    "content": {
+      "users": {
+        "@admin:localhost": 100,
+        "@aibot:localhost": 50
+      },
+      "users_default": 0,
+      "events": {
+        "m.room.name": 50,
+        "m.room.power_levels": 100,
+        "m.room.history_visibility": 100,
+        "m.room.canonical_alias": 50,
+        "m.room.avatar": 50,
+        "m.room.tombstone": 100,
+        "m.room.server_acl": 100,
+        "m.room.encryption": 100
+      },
+      "events_default": 0,
+      "state_default": 50,
+      "ban": 50,
+      "kick": 50,
+      "redact": 50,
+      "invite": 0,
+      "historical": 100
+    },
+    "state_key": "",
+    "origin_server_ts": 1736864363018,
+    "unsigned": {
+      "replaces_state": "$XPa4Nfoi-LpJRvCCHMPd088dnrzt-Uc8R0lb-56MCUQ",
+      "prev_content": {
+        "users": {
+          "@admin:localhost": 100
+        },
+        "users_default": 0,
+        "events": {
+          "m.room.name": 50,
+          "m.room.power_levels": 100,
+          "m.room.history_visibility": 100,
+          "m.room.canonical_alias": 50,
+          "m.room.avatar": 50,
+          "m.room.tombstone": 100,
+          "m.room.server_acl": 100,
+          "m.room.encryption": 100
+        },
+        "events_default": 0,
+        "state_default": 50,
+        "ban": 50,
+        "kick": 50,
+        "redact": 50,
+        "invite": 0,
+        "historical": 100
+      },
+      "prev_sender": "@admin:localhost",
+      "age": 71141
+    },
+    "event_id": "$TM9oHBXj1Iaa56XU4WF-yxjREXQNQJE1cWaqOqaWqrQ",
+    "user_id": "@admin:localhost",
+    "age": 71141,
+    "replaces_state": "$XPa4Nfoi-LpJRvCCHMPd088dnrzt-Uc8R0lb-56MCUQ",
+    "prev_content": {
+      "users": {
+        "@admin:localhost": 100
+      },
+      "users_default": 0,
+      "events": {
+        "m.room.name": 50,
+        "m.room.power_levels": 100,
+        "m.room.history_visibility": 100,
+        "m.room.canonical_alias": 50,
+        "m.room.avatar": 50,
+        "m.room.tombstone": 100,
+        "m.room.server_acl": 100,
+        "m.room.encryption": 100
+      },
+      "events_default": 0,
+      "state_default": 50,
+      "ban": 50,
+      "kick": 50,
+      "redact": 50,
+      "invite": 0,
+      "historical": 100
+    }
+  },
+  {
+    "type": "m.room.member",
+    "room_id": "!EtlFgGnAaFCLoozHPV:localhost",
+    "sender": "@aibot:localhost",
+    "content": {
+      "membership": "join",
+      "displayname": "aibot"
+    },
+    "state_key": "@aibot:localhost",
+    "origin_server_ts": 1736864363054,
+    "unsigned": {
+      "replaces_state": "$Odud1s75LuD0WjceV_Y_Ek-OVoH5unWa_-g_NI_D1rc",
+      "prev_content": {
+        "membership": "invite",
+        "displayname": "aibot"
+      },
+      "prev_sender": "@admin:localhost",
+      "age": 71105
+    },
+    "event_id": "$Mp2uUXS9lIbgx8qKF43zBiqU-B0EXsdWv-IsAs8kz1I",
+    "user_id": "@aibot:localhost",
+    "age": 71105,
+    "replaces_state": "$Odud1s75LuD0WjceV_Y_Ek-OVoH5unWa_-g_NI_D1rc",
+    "prev_content": {
+      "membership": "invite",
+      "displayname": "aibot"
+    }
+  },
+  {
+    "type": "app.boxel.active-llm",
+    "room_id": "!EtlFgGnAaFCLoozHPV:localhost",
+    "sender": "@aibot:localhost",
+    "content": {
+      "model": "openai/gpt-4o"
+    },
+    "state_key": "",
+    "origin_server_ts": 1736864363147,
+    "unsigned": {
+      "age": 71012
+    },
+    "event_id": "$fsEdqZdOBurry8ujwLTo38DlybaqGuOtas59BiZedeg",
+    "user_id": "@aibot:localhost",
+    "age": 71012
+  },
+  {
+    "type": "m.room.message",
+    "room_id": "!EtlFgGnAaFCLoozHPV:localhost",
+    "sender": "@admin:localhost",
+    "content": {
+      "msgtype": "app.boxel.message",
+      "body": "Write me some code to play tic tac toe",
+      "format": "org.matrix.custom.html",
+      "clientGeneratedId": "4a3d6e7f-f492-48de-b3e5-7ef8c5bda696",
+      "data": "{\"attachedCards\":[{\"sourceUrl\":\"http://test-realm-server/my-realm/tic-tac.gts\",\"url\":\"http://test.com/tic-tac.gts\",\"name\":\"tic-tac.gts\",\"contentType\":\"text/plain\"}],\"context\":{\"tools\":[],\"requireToolCall\":false}}"
+    },
+    "origin_server_ts": 1736864363324,
+    "unsigned": {
+      "age": 70835
+    },
+    "event_id": "$FKof2RxEh_MEfR5iSCwXPOyfcC-t961Nnmvn2Hid6_I",
+    "user_id": "@admin:localhost",
+    "age": 70835
+  },
+  {
+    "type": "m.room.message",
+    "room_id": "!EtlFgGnAaFCLoozHPV:localhost",
+    "sender": "@aibot:localhost",
+    "content": {
+      "body": "Thinking...",
+      "msgtype": "m.text",
+      "format": "org.matrix.custom.html"
+    },
+    "origin_server_ts": 1736864365020,
+    "unsigned": {
+      "age": 69139
+    },
+    "event_id": "$VyCYG0DAuudwMGN8hStuSVfXrjMVZu2ZcJgIs9sIwLE",
+    "user_id": "@aibot:localhost",
+    "age": 69139
+  },
+  {
+    "type": "m.room.message",
+    "room_id": "!EtlFgGnAaFCLoozHPV:localhost",
+    "sender": "@aibot:localhost",
+    "content": {
+      "body": "Right, let us make a tic tac toe game.\n\n// File url: https://test.com/tic-tac.gts\n<<<<<<< SEARCH\nlet a = 1;\n=======\nlet a = 2;\n>>>>>>> REPLACE\n\n// File url: https://test.com/tac-toe.gts\n<<<<<<< SEARCH\nlet b = 2;\n=======\nlet b = 3;\n>>>>>>> REPLACE\n\nI can add some more whiz bang if you want. Let me know!",
+      "msgtype": "app.boxel.message",
+      "format": "org.matrix.custom.html",
+      "data": "{}",
+      "app.boxel.commandRequests": [],
+      "m.relates_to": {
+        "rel_type": "m.replace",
+        "event_id": "$VyCYG0DAuudwMGN8hStuSVfXrjMVZu2ZcJgIs9sIwLE"
+      }
+    },
+    "origin_server_ts": 1736864370642,
+    "unsigned": {
+      "age": 63517
+    },
+    "event_id": "$V4k751dVMZphpB6P7OJ3Q-vRbTihn5KekUpTNL1LVpE",
+    "user_id": "@aibot:localhost",
+    "age": 63517
+  }
+]
diff --git a/packages/host/app/lib/search-replace-block-parsing.ts b/packages/host/app/lib/search-replace-block-parsing.ts
index 6ce9e2093ba..fbfa77164c0 100644
--- a/packages/host/app/lib/search-replace-block-parsing.ts
+++ b/packages/host/app/lib/search-replace-block-parsing.ts
@@ -1,3 +1,9 @@
+import {
+  SEARCH_MARKER,
+  SEPARATOR_MARKER,
+  REPLACE_MARKER,
+} from '@cardstack/runtime-common';
+
 interface SearchReplaceResult {
   searchContent: string;
   replaceContent: string | null;
@@ -19,11 +25,6 @@ interface SearchReplaceResult {
  */
 
 export function parseSearchReplace(input: string): SearchReplaceResult {
-  // Define constants for marker texts
-  const SEARCH_MARKER: string = '<<<<<<< SEARCH';
-  const SEPARATOR_MARKER: string = '=======';
-  const REPLACE_MARKER: string = '>>>>>>> REPLACE';
-
   // Initialize result object
   const result: SearchReplaceResult = {
     searchContent: '',
diff --git a/packages/host/package.json b/packages/host/package.json
index 075a8f4c750..6e24f4cb589 100644
--- a/packages/host/package.json
+++ b/packages/host/package.json
@@ -78,7 +78,7 @@
     "@typescript-eslint/eslint-plugin": "catalog:",
     "@typescript-eslint/parser": "catalog:",
     "broccoli-asset-rev": "catalog:",
-    "broccoli-funnel": "^3.0.8",
+    "broccoli-funnel": "catalog:",
     "broccoli-merge-trees": "catalog:",
     "broccoli-plugin": "catalog:",
     "broccoli-source": "catalog:",
diff --git a/packages/runtime-common/constants.ts b/packages/runtime-common/constants.ts
index 4f8aee2dab1..9a429c7374f 100644
--- a/packages/runtime-common/constants.ts
+++ b/packages/runtime-common/constants.ts
@@ -48,3 +48,7 @@ export interface Permissions {
   readonly canRead: boolean;
   readonly canWrite: boolean;
 }
+
+export const SEARCH_MARKER: string = '<<<<<<< SEARCH';
+export const SEPARATOR_MARKER: string = '=======';
+export const REPLACE_MARKER: string = '>>>>>>> REPLACE';
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 724e875917d..f25b59cdc00 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -39,6 +39,9 @@ catalogs:
     broccoli-asset-rev:
       specifier: ^3.0.0
       version: 3.0.0
+    broccoli-funnel:
+      specifier: ^3.0.8
+      version: 3.0.8
     broccoli-merge-trees:
       specifier: ^4.2.0
       version: 4.2.0
@@ -1656,7 +1659,7 @@ importers:
         specifier: 'catalog:'
         version: 3.0.0
       broccoli-funnel:
-        specifier: ^3.0.8
+        specifier: 'catalog:'
         version: 3.0.8
       broccoli-merge-trees:
         specifier: 'catalog:'
@@ -2675,10 +2678,6 @@ packages:
     resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/code-frame@7.25.7':
-    resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==}
-    engines: {node: '>=6.9.0'}
-
   '@babel/code-frame@7.26.2':
     resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
     engines: {node: '>=6.9.0'}
@@ -2713,10 +2712,6 @@ packages:
     resolution: {integrity: sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/generator@7.24.10':
-    resolution: {integrity: sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==}
-    engines: {node: '>=6.9.0'}
-
   '@babel/generator@7.27.0':
     resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==}
     engines: {node: '>=6.9.0'}
@@ -2819,12 +2814,6 @@ packages:
     resolution: {integrity: sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-module-transforms@7.23.3':
-    resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-
   '@babel/helper-module-transforms@7.26.0':
     resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==}
     engines: {node: '>=6.9.0'}
@@ -2964,11 +2953,6 @@ packages:
     engines: {node: '>=6.0.0'}
     hasBin: true
 
-  '@babel/parser@7.25.7':
-    resolution: {integrity: sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==}
-    engines: {node: '>=6.0.0'}
-    hasBin: true
-
   '@babel/parser@7.27.0':
     resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==}
     engines: {node: '>=6.0.0'}
@@ -10650,10 +10634,6 @@ packages:
     resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
     engines: {node: '>=8'}
 
-  lru-cache@10.2.2:
-    resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==}
-    engines: {node: 14 || >=16.14}
-
   lru-cache@10.4.3:
     resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
 
@@ -14153,11 +14133,6 @@ snapshots:
       '@babel/highlight': 7.24.7
       picocolors: 1.0.0
 
-  '@babel/code-frame@7.25.7':
-    dependencies:
-      '@babel/highlight': 7.25.7
-      picocolors: 1.1.1
-
   '@babel/code-frame@7.26.2':
     dependencies:
       '@babel/helper-validator-identifier': 7.25.9
@@ -14210,13 +14185,6 @@ snapshots:
       jsesc: 3.0.2
       source-map: 0.5.7
 
-  '@babel/generator@7.24.10':
-    dependencies:
-      '@babel/types': 7.27.0
-      '@jridgewell/gen-mapping': 0.3.8
-      '@jridgewell/trace-mapping': 0.3.25
-      jsesc: 3.1.0
-
   '@babel/generator@7.27.0':
     dependencies:
       '@babel/parser': 7.27.0
@@ -14257,9 +14225,9 @@ snapshots:
     dependencies:
       '@babel/core': 7.26.10(supports-color@8.1.1)
       '@babel/helper-annotate-as-pure': 7.25.9
-      '@babel/helper-member-expression-to-functions': 7.25.7(supports-color@8.1.1)
+      '@babel/helper-member-expression-to-functions': 7.25.7
       '@babel/helper-optimise-call-expression': 7.25.7
-      '@babel/helper-replace-supers': 7.25.7(@babel/core@7.26.10)(supports-color@8.1.1)
+      '@babel/helper-replace-supers': 7.25.7(@babel/core@7.26.10)
       '@babel/helper-skip-transparent-expression-wrappers': 7.25.9(supports-color@8.1.1)
       '@babel/traverse': 7.27.0(supports-color@8.1.1)
       semver: 6.3.1
@@ -14339,7 +14307,7 @@ snapshots:
     dependencies:
       '@babel/types': 7.27.0
 
-  '@babel/helper-member-expression-to-functions@7.25.7(supports-color@8.1.1)':
+  '@babel/helper-member-expression-to-functions@7.25.7':
     dependencies:
       '@babel/traverse': 7.27.0(supports-color@8.1.1)
       '@babel/types': 7.27.0
@@ -14377,17 +14345,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@babel/helper-module-transforms@7.23.3(@babel/core@7.26.10)':
-    dependencies:
-      '@babel/core': 7.26.10(supports-color@8.1.1)
-      '@babel/helper-environment-visitor': 7.24.7
-      '@babel/helper-module-imports': 7.25.9(supports-color@8.1.1)
-      '@babel/helper-simple-access': 7.22.5
-      '@babel/helper-split-export-declaration': 7.24.7
-      '@babel/helper-validator-identifier': 7.25.9
-    transitivePeerDependencies:
-      - supports-color
-
   '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)(supports-color@8.1.1)':
     dependencies:
       '@babel/core': 7.26.10(supports-color@8.1.1)
@@ -14429,11 +14386,11 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@babel/helper-replace-supers@7.25.7(@babel/core@7.26.10)(supports-color@8.1.1)':
+  '@babel/helper-replace-supers@7.25.7(@babel/core@7.26.10)':
     dependencies:
       '@babel/core': 7.26.10(supports-color@8.1.1)
-      '@babel/helper-member-expression-to-functions': 7.25.7(supports-color@8.1.1)
-      '@babel/helper-optimise-call-expression': 7.25.7
+      '@babel/helper-member-expression-to-functions': 7.25.9(supports-color@8.1.1)
+      '@babel/helper-optimise-call-expression': 7.25.9
       '@babel/traverse': 7.27.0(supports-color@8.1.1)
     transitivePeerDependencies:
       - supports-color
@@ -14467,7 +14424,7 @@ snapshots:
 
   '@babel/helper-split-export-declaration@7.22.6':
     dependencies:
-      '@babel/types': 7.25.7
+      '@babel/types': 7.27.0
 
   '@babel/helper-split-export-declaration@7.24.7':
     dependencies:
@@ -14529,10 +14486,6 @@ snapshots:
       '@babel/types': 7.23.0
 
   '@babel/parser@7.24.8':
-    dependencies:
-      '@babel/types': 7.25.7
-
-  '@babel/parser@7.25.7':
     dependencies:
       '@babel/types': 7.27.0
 
@@ -14638,7 +14591,7 @@ snapshots:
   '@babel/plugin-proposal-private-property-in-object@7.21.11(@babel/core@7.26.10)':
     dependencies:
       '@babel/core': 7.26.10(supports-color@8.1.1)
-      '@babel/helper-annotate-as-pure': 7.25.7
+      '@babel/helper-annotate-as-pure': 7.25.9
       '@babel/helper-create-class-features-plugin': 7.27.0(@babel/core@7.26.10)(supports-color@8.1.1)
       '@babel/helper-plugin-utils': 7.26.5
       '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.10)
@@ -14864,9 +14817,9 @@ snapshots:
       '@babel/helper-compilation-targets': 7.27.0
       '@babel/helper-environment-visitor': 7.24.7
       '@babel/helper-function-name': 7.24.7
-      '@babel/helper-optimise-call-expression': 7.25.7
+      '@babel/helper-optimise-call-expression': 7.25.9
       '@babel/helper-plugin-utils': 7.26.5
-      '@babel/helper-replace-supers': 7.25.7(@babel/core@7.26.10)(supports-color@8.1.1)
+      '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10)(supports-color@8.1.1)
       '@babel/helper-split-export-declaration': 7.24.7
       globals: 11.12.0
     transitivePeerDependencies:
@@ -15050,7 +15003,7 @@ snapshots:
   '@babel/plugin-transform-modules-amd@7.23.0(@babel/core@7.26.10)':
     dependencies:
       '@babel/core': 7.26.10(supports-color@8.1.1)
-      '@babel/helper-module-transforms': 7.23.3(@babel/core@7.26.10)
+      '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10)(supports-color@8.1.1)
       '@babel/helper-plugin-utils': 7.26.5
     transitivePeerDependencies:
       - supports-color
@@ -15189,7 +15142,7 @@ snapshots:
     dependencies:
       '@babel/core': 7.26.10(supports-color@8.1.1)
       '@babel/helper-plugin-utils': 7.26.5
-      '@babel/helper-replace-supers': 7.25.7(@babel/core@7.26.10)(supports-color@8.1.1)
+      '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10)(supports-color@8.1.1)
     transitivePeerDependencies:
       - supports-color
 
@@ -15700,14 +15653,14 @@ snapshots:
 
   '@babel/traverse@7.24.8':
     dependencies:
-      '@babel/code-frame': 7.25.7
-      '@babel/generator': 7.24.10
+      '@babel/code-frame': 7.26.2
+      '@babel/generator': 7.27.0
       '@babel/helper-environment-visitor': 7.24.7
       '@babel/helper-function-name': 7.24.7
       '@babel/helper-hoist-variables': 7.24.7
       '@babel/helper-split-export-declaration': 7.24.7
-      '@babel/parser': 7.25.7
-      '@babel/types': 7.25.7
+      '@babel/parser': 7.27.0
+      '@babel/types': 7.27.0
       debug: 4.4.0(supports-color@8.1.1)
       globals: 11.12.0
     transitivePeerDependencies:
@@ -24994,8 +24947,6 @@ snapshots:
 
   lowercase-keys@2.0.0: {}
 
-  lru-cache@10.2.2: {}
-
   lru-cache@10.4.3: {}
 
   lru-cache@11.0.1: {}
@@ -25947,7 +25898,7 @@ snapshots:
 
   path-scurry@1.11.1:
     dependencies:
-      lru-cache: 10.2.2
+      lru-cache: 10.4.3
       minipass: 7.1.2
 
   path-scurry@2.0.0:
@@ -26560,7 +26511,7 @@ snapshots:
   remove-types@1.0.0:
     dependencies:
       '@babel/core': 7.26.10(supports-color@8.1.1)
-      '@babel/plugin-syntax-decorators': 7.24.1(@babel/core@7.26.10)
+      '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.26.10)
       '@babel/plugin-transform-typescript': 7.25.7(@babel/core@7.26.10)
       prettier: 2.8.8
     transitivePeerDependencies:
@@ -28252,7 +28203,7 @@ snapshots:
   validate-peer-dependencies@1.1.0:
     dependencies:
       resolve-package-path: 3.1.0
-      semver: 7.5.4
+      semver: 7.7.1
 
   validate-peer-dependencies@2.2.0:
     dependencies:
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 88972732337..0019f753630 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -15,6 +15,7 @@ catalog:
   "@typescript-eslint/parser": ^7.18.0
   "@types/qunit": 2.11.3
   broccoli-asset-rev: ^3.0.0
+  broccoli-funnel: ^3.0.8
   broccoli-merge-trees: ^4.2.0
   broccoli-plugin: ^4.0.7
   broccoli-source: ^3.0.1


Reviews:
user: login: jurgenwerk
body: Does this work when there are multiple SEARCH/REPLACE blocks in a message? 
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T07:29:16Z
user: login: jurgenwerk
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T07:24:32Z

Review comments:
diff_hunk: @@ -768,3 +768,34 @@ export function isCommandResultEvent(
       APP_BOXEL_COMMAND_RESULT_REL_TYPE
   );
 }
+
+function elideCodeBlocks(content: string) {
+  // Define constants for marker texts
+  const SEARCH_MARKER: string = '<<<<<<< SEARCH';
path: packages/ai-bot/helpers.ts
user: login: jurgenwerk
body: We already have these constants in `host/app/lib/search-replace-block-parsing.ts`, maybe we can extract this lib to runtime-common
created_at: 2025-05-07T07:27:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2711,6 +2711,34 @@ Attached files:
       'Should not include old command description',
     );
   });
+
+  test('Elides code blocks in prompt', async () => {
+    // sending older codeblocks back to the model just confuses it
path: packages/ai-bot/tests/prompt-construction-test.ts
user: login: jurgenwerk
body: ```suggestion
    // sending older codeblocks back to the model just confuses it and wastes tokens
```
created_at: 2025-05-07T07:28:16Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-07T02:20:41Z
body: ## Host Test Results
  1 files  ±0    1 suites  ±0   32m 50s ⏱️ - 4m 6s
983 tests ±0  978 ✅ +1  5 💤 ±0  0 ❌ ±0 
990 runs  ±0  985 ✅ +2  5 💤 ±0  0 ❌  - 1 

Results for commit 94bd1c1e. ± Comparison against base commit b4795bb1.

[test-results]:data:application/gzip;base64,H4sIAF2aHGgC/02MSwrDIBQArxJcd6GmovYywc8TpEksflahd69KY7KcGZgDOb9CQq+JPCaUis8DbIkq+7A3lBxXU1tuVYr5pCUVY5ri4lJv/6mKDeGUX6sYiwViDPFvYtn7U54wloINcx0734ad7z8Tts3nCkg+tSWGANFKzRKDoEQ7awQ4zSjM3HLMFVCLvj+wm3s9BgEAAA==

:recycle: This comment has been updated with latest results.
author_association: CONTRIBUTOR
user: login: lukemelia
created_at: 2025-05-07T10:47:21Z
body: > Does this work when there are multiple SEARCH/REPLACE blocks in a message?

Yes, I updated the test to demonstrate/cover.
author_association: CONTRIBUTOR