@morphllm/morphsdk 0.2.57 → 0.2.58

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.
Files changed (158) hide show
  1. package/dist/anthropic-CaFUHxBW.d.ts +89 -0
  2. package/dist/{chunk-6X5UOY7B.js → chunk-2CASO3ZO.js} +46 -79
  3. package/dist/chunk-2CASO3ZO.js.map +1 -0
  4. package/dist/chunk-374N3GIA.js +118 -0
  5. package/dist/chunk-374N3GIA.js.map +1 -0
  6. package/dist/chunk-3IQIT6MC.js +65 -0
  7. package/dist/chunk-3IQIT6MC.js.map +1 -0
  8. package/dist/chunk-4VGOBA2J.js +57 -0
  9. package/dist/chunk-4VGOBA2J.js.map +1 -0
  10. package/dist/chunk-527P5X2E.js +98 -0
  11. package/dist/chunk-527P5X2E.js.map +1 -0
  12. package/dist/chunk-6N6ZYZYD.js +74 -0
  13. package/dist/chunk-6N6ZYZYD.js.map +1 -0
  14. package/dist/chunk-6Y5JB4JC.js +195 -0
  15. package/dist/chunk-6Y5JB4JC.js.map +1 -0
  16. package/dist/{chunk-QFIHUCTF.js → chunk-7EIHYJSG.js} +18 -18
  17. package/dist/chunk-7EIHYJSG.js.map +1 -0
  18. package/dist/{chunk-TICMYDII.js → chunk-APP75CBN.js} +33 -16
  19. package/dist/chunk-APP75CBN.js.map +1 -0
  20. package/dist/chunk-ILJ3J5IA.js +72 -0
  21. package/dist/chunk-ILJ3J5IA.js.map +1 -0
  22. package/dist/chunk-ISWL67SF.js +1 -0
  23. package/dist/chunk-KW7OEGZK.js +9 -0
  24. package/dist/chunk-KW7OEGZK.js.map +1 -0
  25. package/dist/chunk-Q5AHGIQO.js +205 -0
  26. package/dist/chunk-Q5AHGIQO.js.map +1 -0
  27. package/dist/{chunk-TJIUA27P.js → chunk-XT5ZO6ES.js} +9 -5
  28. package/dist/chunk-XT5ZO6ES.js.map +1 -0
  29. package/dist/{chunk-LVPVVLTI.js → chunk-YV75OQTE.js} +105 -17
  30. package/dist/chunk-YV75OQTE.js.map +1 -0
  31. package/dist/{chunk-ZJIIICRA.js → chunk-ZO4PPFCZ.js} +60 -29
  32. package/dist/chunk-ZO4PPFCZ.js.map +1 -0
  33. package/dist/{client-CFoR--IU.d.ts → client-CextMMm9.d.ts} +10 -15
  34. package/dist/client.cjs +687 -341
  35. package/dist/client.cjs.map +1 -1
  36. package/dist/client.d.ts +3 -2
  37. package/dist/client.js +14 -14
  38. package/dist/finish-kXAcUJyB.d.ts +33 -0
  39. package/dist/gemini-CE80Pbdy.d.ts +117 -0
  40. package/dist/index.cjs +700 -341
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.ts +4 -3
  43. package/dist/index.js +16 -15
  44. package/dist/openai-Fvpqln7F.d.ts +89 -0
  45. package/dist/tools/warp_grep/agent/config.cjs +8 -4
  46. package/dist/tools/warp_grep/agent/config.cjs.map +1 -1
  47. package/dist/tools/warp_grep/agent/config.d.ts +7 -2
  48. package/dist/tools/warp_grep/agent/config.js +1 -1
  49. package/dist/tools/warp_grep/agent/formatter.cjs +32 -15
  50. package/dist/tools/warp_grep/agent/formatter.cjs.map +1 -1
  51. package/dist/tools/warp_grep/agent/formatter.d.ts +1 -1
  52. package/dist/tools/warp_grep/agent/formatter.js +1 -1
  53. package/dist/tools/warp_grep/agent/parser.cjs +104 -17
  54. package/dist/tools/warp_grep/agent/parser.cjs.map +1 -1
  55. package/dist/tools/warp_grep/agent/parser.d.ts +3 -5
  56. package/dist/tools/warp_grep/agent/parser.js +1 -3
  57. package/dist/tools/warp_grep/agent/prompt.cjs +132 -56
  58. package/dist/tools/warp_grep/agent/prompt.cjs.map +1 -1
  59. package/dist/tools/warp_grep/agent/prompt.d.ts +1 -1
  60. package/dist/tools/warp_grep/agent/prompt.js +1 -1
  61. package/dist/tools/warp_grep/agent/runner.cjs +459 -192
  62. package/dist/tools/warp_grep/agent/runner.cjs.map +1 -1
  63. package/dist/tools/warp_grep/agent/runner.d.ts +1 -0
  64. package/dist/tools/warp_grep/agent/runner.js +6 -8
  65. package/dist/tools/warp_grep/agent/types.cjs.map +1 -1
  66. package/dist/tools/warp_grep/agent/types.d.ts +9 -2
  67. package/dist/tools/warp_grep/anthropic.cjs +650 -260
  68. package/dist/tools/warp_grep/anthropic.cjs.map +1 -1
  69. package/dist/tools/warp_grep/anthropic.d.ts +4 -74
  70. package/dist/tools/warp_grep/anthropic.js +13 -15
  71. package/dist/tools/warp_grep/client.cjs +1593 -0
  72. package/dist/tools/warp_grep/client.cjs.map +1 -0
  73. package/dist/tools/warp_grep/client.d.ts +87 -0
  74. package/dist/tools/warp_grep/client.js +26 -0
  75. package/dist/tools/warp_grep/gemini.cjs +1587 -0
  76. package/dist/tools/warp_grep/gemini.cjs.map +1 -0
  77. package/dist/tools/warp_grep/gemini.d.ts +7 -0
  78. package/dist/tools/warp_grep/gemini.js +34 -0
  79. package/dist/tools/warp_grep/harness.cjs +556 -220
  80. package/dist/tools/warp_grep/harness.cjs.map +1 -1
  81. package/dist/tools/warp_grep/harness.d.ts +50 -119
  82. package/dist/tools/warp_grep/harness.js +33 -41
  83. package/dist/tools/warp_grep/harness.js.map +1 -1
  84. package/dist/tools/warp_grep/index.cjs +812 -346
  85. package/dist/tools/warp_grep/index.cjs.map +1 -1
  86. package/dist/tools/warp_grep/index.d.ts +11 -6
  87. package/dist/tools/warp_grep/index.js +43 -22
  88. package/dist/tools/warp_grep/openai.cjs +650 -258
  89. package/dist/tools/warp_grep/openai.cjs.map +1 -1
  90. package/dist/tools/warp_grep/openai.d.ts +4 -74
  91. package/dist/tools/warp_grep/openai.js +13 -13
  92. package/dist/tools/warp_grep/providers/local.cjs +66 -27
  93. package/dist/tools/warp_grep/providers/local.cjs.map +1 -1
  94. package/dist/tools/warp_grep/providers/local.d.ts +4 -9
  95. package/dist/tools/warp_grep/providers/local.js +2 -2
  96. package/dist/tools/warp_grep/providers/remote.cjs +211 -0
  97. package/dist/tools/warp_grep/providers/remote.cjs.map +1 -0
  98. package/dist/tools/warp_grep/providers/remote.d.ts +67 -0
  99. package/dist/tools/warp_grep/providers/remote.js +9 -0
  100. package/dist/tools/warp_grep/providers/types.cjs.map +1 -1
  101. package/dist/tools/warp_grep/providers/types.d.ts +7 -15
  102. package/dist/tools/warp_grep/vercel.cjs +662 -277
  103. package/dist/tools/warp_grep/vercel.cjs.map +1 -1
  104. package/dist/tools/warp_grep/vercel.d.ts +4 -51
  105. package/dist/tools/warp_grep/vercel.js +16 -14
  106. package/dist/types-a_hxdPI6.d.ts +144 -0
  107. package/dist/vercel-3yjvfmVB.d.ts +66 -0
  108. package/package.json +12 -2
  109. package/dist/chunk-6X5UOY7B.js.map +0 -1
  110. package/dist/chunk-73RQWOQC.js +0 -16
  111. package/dist/chunk-73RQWOQC.js.map +0 -1
  112. package/dist/chunk-7OQOOB3R.js +0 -1
  113. package/dist/chunk-CFF636UC.js +0 -70
  114. package/dist/chunk-CFF636UC.js.map +0 -1
  115. package/dist/chunk-EK7OQPWD.js +0 -44
  116. package/dist/chunk-EK7OQPWD.js.map +0 -1
  117. package/dist/chunk-GJ5TYNRD.js +0 -107
  118. package/dist/chunk-GJ5TYNRD.js.map +0 -1
  119. package/dist/chunk-HQO45BAJ.js +0 -14
  120. package/dist/chunk-HQO45BAJ.js.map +0 -1
  121. package/dist/chunk-IMYQOKFO.js +0 -83
  122. package/dist/chunk-IMYQOKFO.js.map +0 -1
  123. package/dist/chunk-KBQWGT5L.js +0 -77
  124. package/dist/chunk-KBQWGT5L.js.map +0 -1
  125. package/dist/chunk-LVPVVLTI.js.map +0 -1
  126. package/dist/chunk-QFIHUCTF.js.map +0 -1
  127. package/dist/chunk-TICMYDII.js.map +0 -1
  128. package/dist/chunk-TJIUA27P.js.map +0 -1
  129. package/dist/chunk-WETRQJGU.js +0 -129
  130. package/dist/chunk-WETRQJGU.js.map +0 -1
  131. package/dist/chunk-ZJIIICRA.js.map +0 -1
  132. package/dist/core-CpkYEi_T.d.ts +0 -158
  133. package/dist/tools/warp_grep/tools/analyse.cjs +0 -40
  134. package/dist/tools/warp_grep/tools/analyse.cjs.map +0 -1
  135. package/dist/tools/warp_grep/tools/analyse.d.ts +0 -10
  136. package/dist/tools/warp_grep/tools/analyse.js +0 -8
  137. package/dist/tools/warp_grep/tools/finish.cjs +0 -69
  138. package/dist/tools/warp_grep/tools/finish.cjs.map +0 -1
  139. package/dist/tools/warp_grep/tools/finish.d.ts +0 -10
  140. package/dist/tools/warp_grep/tools/finish.js +0 -10
  141. package/dist/tools/warp_grep/tools/grep.cjs +0 -38
  142. package/dist/tools/warp_grep/tools/grep.cjs.map +0 -1
  143. package/dist/tools/warp_grep/tools/grep.d.ts +0 -8
  144. package/dist/tools/warp_grep/tools/grep.js +0 -15
  145. package/dist/tools/warp_grep/tools/grep.js.map +0 -1
  146. package/dist/tools/warp_grep/tools/read.cjs +0 -38
  147. package/dist/tools/warp_grep/tools/read.cjs.map +0 -1
  148. package/dist/tools/warp_grep/tools/read.d.ts +0 -9
  149. package/dist/tools/warp_grep/tools/read.js +0 -8
  150. package/dist/tools/warp_grep/utils/format.cjs +0 -42
  151. package/dist/tools/warp_grep/utils/format.cjs.map +0 -1
  152. package/dist/tools/warp_grep/utils/format.d.ts +0 -4
  153. package/dist/tools/warp_grep/utils/format.js +0 -18
  154. package/dist/tools/warp_grep/utils/format.js.map +0 -1
  155. /package/dist/{chunk-7OQOOB3R.js.map → chunk-ISWL67SF.js.map} +0 -0
  156. /package/dist/tools/warp_grep/{tools/analyse.js.map → client.js.map} +0 -0
  157. /package/dist/tools/warp_grep/{tools/finish.js.map → gemini.js.map} +0 -0
  158. /package/dist/tools/warp_grep/{tools/read.js.map → providers/remote.js.map} +0 -0
package/dist/client.cjs CHANGED
@@ -947,9 +947,13 @@ async function checkHealth(config = {}) {
947
947
 
948
948
  // tools/warp_grep/agent/config.ts
949
949
  var AGENT_CONFIG = {
950
- // Give the model freedom; failsafe cap to prevent infinite loops
951
- MAX_ROUNDS: 10,
952
- TIMEOUT_MS: 3e4
950
+ MAX_TURNS: 4,
951
+ TIMEOUT_MS: 3e4,
952
+ MAX_CONTEXT_CHARS: 54e4,
953
+ MAX_OUTPUT_LINES: 200,
954
+ MAX_READ_LINES: 800,
955
+ MAX_LIST_DEPTH: 3,
956
+ LIST_TIMEOUT_MS: 2e3
953
957
  };
954
958
  var BUILTIN_EXCLUDES = [
955
959
  // Version control
@@ -1031,113 +1035,191 @@ var BUILTIN_EXCLUDES = [
1031
1035
  ".*"
1032
1036
  ];
1033
1037
  var DEFAULT_EXCLUDES = (process.env.MORPH_WARP_GREP_EXCLUDE || "").split(",").map((s) => s.trim()).filter(Boolean).concat(BUILTIN_EXCLUDES);
1034
- var DEFAULT_MODEL = "morph-warp-grep";
1038
+ var DEFAULT_MODEL = "morph-warp-grep-v1";
1035
1039
 
1036
1040
  // tools/warp_grep/agent/prompt.ts
1037
- var SYSTEM_PROMPT = `You are a code search agent. Your task is to find all relevant code for a given query.
1041
+ var SYSTEM_PROMPT = `You are a code search agent. Your task is to find all relevant code for a given search_string.
1038
1042
 
1039
- <workflow>
1043
+ ### workflow
1040
1044
  You have exactly 4 turns. The 4th turn MUST be a \`finish\` call. Each turn allows up to 8 parallel tool calls.
1041
1045
 
1042
- - Turn 1: Map the territory OR dive deep (based on query specificity)
1046
+ - Turn 1: Map the territory OR dive deep (based on search_string specificity)
1043
1047
  - Turn 2-3: Refine based on findings
1044
1048
  - Turn 4: MUST call \`finish\` with all relevant code locations
1045
1049
  - You MAY call \`finish\` early if confident\u2014but never before at least 1 search turn.
1050
+ - The user strongly prefers if you can call the finish tool early, but you must be correct
1046
1051
 
1047
- Remember, if the task feels easy to you, it is strongly desirable to call \`finish\` early using fewer turns, but quality over speed.
1048
- </workflow>
1052
+ Remember, if the task feels easy to you, it is strongly desirable to call 'finish' early using fewer turns, but quality over speed
1049
1053
 
1050
- <tools>
1051
- ### \`analyse <path> [pattern]\`
1052
- Directory tree or file search. Shows structure of a path, optionally filtered by regex pattern.
1053
- - \`path\`: Required. Directory or file path (use \`.\` for repo root)
1054
- - \`pattern\`: Optional regex to filter results
1054
+ ### tools
1055
+ Tool calls use nested XML elements:
1056
+ \`\`\`xml
1057
+ <tool_name>
1058
+ <parameter>value</parameter>
1059
+ </tool_name>
1060
+ \`\`\`
1061
+
1062
+ ### \`list_directory\`
1063
+ Directory tree view. Shows structure of a path, optionally filtered by regex pattern.
1064
+
1065
+ Elements:
1066
+ - \`<path>\` (required): Directory path to list (use \`.\` for repo root)
1067
+ - \`<pattern>\` (optional): Regex to filter results
1055
1068
 
1056
1069
  Examples:
1057
1070
  \`\`\`
1058
- analyse .
1059
- analyse src/api
1060
- analyse . ".*\\.ts$"
1061
- analyse src "test.*"
1071
+ <list_directory>
1072
+ <path>src/services</path>
1073
+ </list_directory>
1074
+
1075
+ <list_directory>
1076
+ <path>lib/utils</path>
1077
+ <pattern>.*\\.(ts|js)$</pattern>
1078
+ </list_directory>
1062
1079
  \`\`\`
1063
1080
 
1064
- ### \`read <path>[:start-end]\`
1065
- Read file contents. Line range is 1-based, inclusive.
1081
+ ### \`read\`
1082
+ Read file contents. Supports multiple line ranges.
1066
1083
  - Returns numbered lines for easy reference
1067
- - Omit range to read entire file
1084
+ - ALWAYS include import statements (usually lines 1-20). Better to over-include than miss context.
1085
+
1086
+ Elements:
1087
+ - \`<path>\` (required): File path to read
1088
+ - \`<lines>\` (optional): Line ranges like "1-50,75-80,100-120" (omit to read entire file)
1068
1089
 
1069
1090
  Examples:
1070
1091
  \`\`\`
1071
- read src/main.py
1072
- read src/db/conn.py:10-50
1073
- read package.json:1-20
1092
+ <read>
1093
+ <path>src/main.py</path>
1094
+ </read>
1095
+
1096
+ <read>
1097
+ <path>src/auth.py</path>
1098
+ <lines>1-20,45-80,150-200</lines>
1099
+ </read>
1074
1100
  \`\`\`
1075
1101
 
1076
- ### \`grep '<pattern>' <path>\`
1077
- Ripgrep search. Finds pattern matches across files.
1078
- - \`'<pattern>'\`: Required. Regex pattern wrapped in single quotes
1079
- - \`<path>\`: Required. Directory or file to search (use \`.\` for repo root)
1102
+ ### \`grep\`
1103
+ Search for pattern matches across files. Returns matches with 1 line of context above and below.
1104
+ - Match lines use \`:\` separator \u2192 \`filepath:linenum:content\`
1105
+ - Context lines use \`-\` separator \u2192 \`filepath-linenum-content\`
1106
+
1107
+ Elements:
1108
+ - \`<pattern>\` (required): Search pattern (regex). Use \`(a|b)\` for OR patterns.
1109
+ - \`<sub_dir>\` (optional): Subdirectory to search in (defaults to \`.\`)
1110
+ - \`<glob>\` (optional): File pattern filter like \`*.py\` or \`*.{ts,tsx}\`
1080
1111
 
1081
1112
  Examples:
1082
1113
  \`\`\`
1083
- grep 'class.*Service' src/
1084
- grep 'def authenticate' .
1085
- grep 'import.*from' src/components/
1086
- grep 'TODO' .
1114
+ <grep>
1115
+ <pattern>(authenticate|authorize|login)</pattern>
1116
+ <sub_dir>src/auth/</sub_dir>
1117
+ </grep>
1118
+
1119
+ <grep>
1120
+ <pattern>class.*(Service|Controller)</pattern>
1121
+ <glob>*.{ts,js}</glob>
1122
+ </grep>
1123
+
1124
+ <grep>
1125
+ <pattern>(DB_HOST|DATABASE_URL|connection)</pattern>
1126
+ <glob>*.{py,yaml,env}</glob>
1127
+ <sub_dir>lib/</sub_dir>
1128
+ </grep>
1087
1129
  \`\`\`
1088
1130
 
1089
- ### \`finish <file1:ranges> [file2:ranges ...]\`
1090
- Submit final answer with all relevant code locations.
1091
- - Include generous line ranges\u2014don't be stingy with context
1092
- - Ranges are comma-separated: \`file.py:10-30,50-60\`
1093
- - ALWAYS include import statements at the top of files (usually lines 1-20)
1094
- - If code spans multiple files, include ALL of them
1095
- - Small files can be returned in full
1131
+ ### \`finish\`
1132
+ Submit final answer with all relevant code locations. Uses nested \`<file>\` elements.
1133
+
1134
+ File elements:
1135
+ - \`<path>\` (required): File path
1136
+ - \`<lines>\` (optional): Line ranges like "1-50,75-80" (\`*\` for entire file)
1137
+
1138
+ ALWAYS include import statements (usually lines 1-20). Better to over-include than miss context.
1096
1139
 
1097
1140
  Examples:
1098
1141
  \`\`\`
1099
- finish src/auth.py:1-15,25-50,75-80 src/models/user.py:1-10,20-45
1100
- finish src/index.ts:1-100
1142
+ <finish>
1143
+ <file>
1144
+ <path>src/auth.py</path>
1145
+ <lines>1-15,25-50,75-80</lines>
1146
+ </file>
1147
+ <file>
1148
+ <path>src/models/user.py</path>
1149
+ <lines>*</lines>
1150
+ </file>
1151
+ </finish>
1101
1152
  \`\`\`
1102
1153
  </tools>
1103
1154
 
1104
1155
  <strategy>
1105
- **Before your first tool call, classify the query:**
1156
+ **Before your first tool call, classify the search_string:**
1106
1157
 
1107
- | Query Type | Turn 1 Strategy | Early Finish? |
1108
- |------------|-----------------|---------------|
1109
- | **Specific** (function name, error string, unique identifier) | 8 parallel greps on likely paths | Often by turn 2 |
1110
- | **Conceptual** (how does X work, where is Y handled) | analyse + 2-3 broad greps | Rarely early |
1111
- | **Exploratory** (find all tests, list API endpoints) | analyse at multiple depths | Usually needs 3 turns |
1158
+ | Search_string Type | Round 1 Strategy | Early Finish? |
1159
+ |------------|------------------|---------------|
1160
+ | **Specific** (function name, error string, unique identifier) | 8 parallel greps on likely paths | Often by round 2 |
1161
+ | **Conceptual** (how does X work, where is Y handled) | list_directory + 2-3 broad greps | Rarely early |
1162
+ | **Exploratory** (find all tests, list API endpoints) | list_directory at multiple depths | Usually needs 3 rounds |
1112
1163
 
1113
1164
  **Parallel call patterns:**
1114
1165
  - **Shotgun grep**: Same pattern, 8 different directories\u2014fast coverage
1115
1166
  - **Variant grep**: 8 pattern variations (synonyms, naming conventions)\u2014catches inconsistent codebases
1116
- - **Funnel**: 1 analyse + 7 greps\u2014orient and search simultaneously
1167
+ - **Funnel**: 1 list_directory + 7 greps\u2014orient and search simultaneously
1117
1168
  - **Deep read**: 8 reads on files you already identified\u2014gather full context fast
1169
+
1170
+ **Tool call expectations:**
1171
+ - Low quality tool calls are ones that give back sparse information. This either means they are not well thought out and are not educated guesses OR, they are too broad and give back too many results.
1172
+ - High quality tool calls strike a balance between complexity in the tool call to exclude results we know we don't want, and how wide the search space is so that we don't miss anything. It is ok to start off with wider search spaces, but is imperative that you use your intuition from there on out and seek high quality tool calls only.
1173
+ - You are not starting blind, you have some information about root level repo structure going in, so use that to prevent making trivial repo wide queries.
1174
+ - The grep tool shows you which file path and line numbers the pattern was found in, use this information smartly when trying to read the file.
1118
1175
  </strategy>
1119
1176
 
1120
1177
  <output_format>
1121
1178
  EVERY response MUST follow this exact format:
1122
1179
 
1123
1180
  1. First, wrap your reasoning in \`<think>...</think>\` tags containing:
1124
- - Query classification (specific/conceptual/exploratory)
1125
- - Confidence estimate (can I finish in 1-2 turns?)
1126
- - This turn's parallel strategy
1181
+ - Search_string classification (specific/conceptual/exploratory)
1182
+ - Confidence estimate (can I finish in 1-2 rounds?)
1183
+ - This round's parallel strategy
1127
1184
  - What signals would let me finish early?
1128
1185
 
1129
- 2. Then, output tool calls wrapped in \`<tool_call>...</tool_call>\` tags, one per line.
1186
+ 2. Then, output up to 8 tool calls using nested XML elements.
1130
1187
 
1131
1188
  Example:
1132
1189
  \`\`\`
1133
1190
  <think>
1134
- This is a specific query about authentication. I'll grep for auth-related patterns.
1135
- High confidence I can finish in 2 turns if I find the auth module.
1191
+ This is a specific search_string about authentication. I'll grep for auth-related patterns.
1192
+ High confidence I can finish in 2 rounds if I find the auth module. I have already been shown the repo's structure at root
1136
1193
  Strategy: Shotgun grep across likely directories.
1137
1194
  </think>
1138
- <tool_call>grep 'authenticate' src/</tool_call>
1139
- <tool_call>grep 'login' src/</tool_call>
1140
- <tool_call>analyse src/auth</tool_call>
1195
+ <grep>
1196
+ <pattern>(authenticate|login|session)</pattern>
1197
+ <sub_dir>src/auth/</sub_dir>
1198
+ </grep>
1199
+ <grep>
1200
+ <pattern>(middleware|interceptor)</pattern>
1201
+ <glob>*.{ts,js}</glob>
1202
+ </grep>
1203
+ <list_directory>
1204
+ <path>src/auth</path>
1205
+ </list_directory>
1206
+ \`\`\`
1207
+
1208
+ Finishing example:
1209
+ \`\`\`
1210
+ <think>
1211
+ I think I have a rough idea, but this is my last turn so I must call the finish tool regardless.
1212
+ </think>
1213
+ <finish>
1214
+ <file>
1215
+ <path>src/auth/login.py</path>
1216
+ <lines>1-50</lines>
1217
+ </file>
1218
+ <file>
1219
+ <path>src/middleware/session.py</path>
1220
+ <lines>10-80</lines>
1221
+ </file>
1222
+ </finish>
1141
1223
  \`\`\`
1142
1224
 
1143
1225
  No commentary outside \`<think>\`. No explanations after tool calls.
@@ -1150,17 +1232,111 @@ When calling \`finish\`:
1150
1232
  - Include any type definitions, interfaces, or constants used
1151
1233
  - Better to over-include than leave the user missing context
1152
1234
  - If unsure about boundaries, include more rather than less
1153
- </finishing_requirements>
1154
-
1155
- Begin your exploration now to find code relevant to the query.`;
1235
+ </finishing_requirements>`;
1156
1236
  function getSystemPrompt() {
1157
1237
  return SYSTEM_PROMPT;
1158
1238
  }
1159
1239
 
1160
1240
  // tools/warp_grep/agent/parser.ts
1161
- var VALID_COMMANDS = ["analyse", "grep", "read", "finish"];
1241
+ var VALID_COMMANDS = ["list_directory", "grep", "read", "finish"];
1242
+ function isValidCommand(name) {
1243
+ return VALID_COMMANDS.includes(name);
1244
+ }
1245
+ function getXmlElementText(xml, tagName) {
1246
+ const regex = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`, "i");
1247
+ const match = xml.match(regex);
1248
+ return match ? match[1].trim() : null;
1249
+ }
1250
+ function parseNestedXmlTools(text) {
1251
+ const tools = [];
1252
+ const toolRegex = /<([a-z_][a-z0-9_]*)>([\s\S]*?)<\/\1>/gi;
1253
+ let match;
1254
+ while ((match = toolRegex.exec(text)) !== null) {
1255
+ const rawToolName = match[1].toLowerCase();
1256
+ const content = match[2];
1257
+ if (!isValidCommand(rawToolName)) continue;
1258
+ const toolName = rawToolName;
1259
+ if (toolName === "list_directory") {
1260
+ const path5 = getXmlElementText(content, "path");
1261
+ const pattern = getXmlElementText(content, "pattern");
1262
+ if (path5) {
1263
+ tools.push({ name: "list_directory", arguments: { path: path5, pattern } });
1264
+ }
1265
+ } else if (toolName === "grep") {
1266
+ const pattern = getXmlElementText(content, "pattern");
1267
+ const subDir = getXmlElementText(content, "sub_dir");
1268
+ const glob = getXmlElementText(content, "glob");
1269
+ if (pattern) {
1270
+ tools.push({
1271
+ name: "grep",
1272
+ arguments: {
1273
+ pattern,
1274
+ path: subDir || ".",
1275
+ ...glob && { glob }
1276
+ }
1277
+ });
1278
+ }
1279
+ } else if (toolName === "read") {
1280
+ const path5 = getXmlElementText(content, "path");
1281
+ const linesStr = getXmlElementText(content, "lines");
1282
+ if (path5) {
1283
+ const args = { path: path5 };
1284
+ if (linesStr) {
1285
+ const ranges = [];
1286
+ for (const rangeStr of linesStr.split(",")) {
1287
+ const trimmed = rangeStr.trim();
1288
+ if (!trimmed) continue;
1289
+ const [s, e] = trimmed.split("-").map((v) => parseInt(v.trim(), 10));
1290
+ if (Number.isFinite(s) && Number.isFinite(e)) {
1291
+ ranges.push([s, e]);
1292
+ } else if (Number.isFinite(s)) {
1293
+ ranges.push([s, s]);
1294
+ }
1295
+ }
1296
+ if (ranges.length === 1) {
1297
+ args.start = ranges[0][0];
1298
+ args.end = ranges[0][1];
1299
+ } else if (ranges.length > 1) {
1300
+ args.lines = ranges;
1301
+ }
1302
+ }
1303
+ tools.push({ name: "read", arguments: args });
1304
+ }
1305
+ } else if (toolName === "finish") {
1306
+ const fileRegex = /<file>([\s\S]*?)<\/file>/gi;
1307
+ const files = [];
1308
+ let fileMatch;
1309
+ while ((fileMatch = fileRegex.exec(content)) !== null) {
1310
+ const fileContent = fileMatch[1];
1311
+ const filePath = getXmlElementText(fileContent, "path");
1312
+ const linesStr = getXmlElementText(fileContent, "lines");
1313
+ if (filePath && linesStr) {
1314
+ const ranges = [];
1315
+ for (const rangeStr of linesStr.split(",")) {
1316
+ if (rangeStr.trim() === "*") {
1317
+ ranges.push([1, 999999]);
1318
+ } else {
1319
+ const [s, e] = rangeStr.split("-").map((v) => parseInt(v.trim(), 10));
1320
+ if (Number.isFinite(s) && Number.isFinite(e)) {
1321
+ ranges.push([s, e]);
1322
+ }
1323
+ }
1324
+ }
1325
+ if (ranges.length > 0) {
1326
+ files.push({ path: filePath, lines: ranges });
1327
+ }
1328
+ }
1329
+ }
1330
+ if (files.length > 0) {
1331
+ tools.push({ name: "finish", arguments: { files } });
1332
+ }
1333
+ }
1334
+ }
1335
+ return tools;
1336
+ }
1162
1337
  function preprocessText(text) {
1163
1338
  let processed = text.replace(/<think>[\s\S]*?<\/think>/gi, "");
1339
+ const nestedTools = parseNestedXmlTools(processed);
1164
1340
  const openingTagRegex = /<tool_call>|<tool>/gi;
1165
1341
  const closingTagRegex = /<\/tool_call>|<\/tool>/gi;
1166
1342
  const openingMatches = processed.match(openingTagRegex) || [];
@@ -1197,7 +1373,7 @@ function preprocessText(text) {
1197
1373
  }
1198
1374
  }
1199
1375
  }
1200
- return toolCallLines;
1376
+ return { lines: toolCallLines, nestedTools };
1201
1377
  }
1202
1378
  var LLMResponseParser = class {
1203
1379
  finishSpecSplitRe = /,(?=[^,\s]+:)/;
@@ -1205,8 +1381,8 @@ var LLMResponseParser = class {
1205
1381
  if (typeof text !== "string") {
1206
1382
  throw new TypeError("Command text must be a string.");
1207
1383
  }
1208
- const lines = preprocessText(text);
1209
- const commands = [];
1384
+ const { lines, nestedTools } = preprocessText(text);
1385
+ const commands = [...nestedTools];
1210
1386
  let finishAccumulator = null;
1211
1387
  lines.forEach((line) => {
1212
1388
  if (!line || line.startsWith("#")) return;
@@ -1214,8 +1390,8 @@ var LLMResponseParser = class {
1214
1390
  if (parts.length === 0) return;
1215
1391
  const cmd = parts[0];
1216
1392
  switch (cmd) {
1217
- case "analyse":
1218
- this.handleAnalyse(parts, line, commands);
1393
+ case "list_directory":
1394
+ this.handleListDirectory(parts, line, commands);
1219
1395
  break;
1220
1396
  case "grep":
1221
1397
  this.handleGrep(parts, line, commands);
@@ -1233,8 +1409,8 @@ var LLMResponseParser = class {
1233
1409
  if (finishAccumulator) {
1234
1410
  const map = finishAccumulator;
1235
1411
  const entries = [...map.entries()];
1236
- const filesPayload = entries.map(([path4, ranges]) => ({
1237
- path: path4,
1412
+ const filesPayload = entries.map(([path5, ranges]) => ({
1413
+ path: path5,
1238
1414
  lines: [...ranges].sort((a, b) => a[0] - b[0])
1239
1415
  }));
1240
1416
  commands.push({ name: "finish", arguments: { files: filesPayload } });
@@ -1266,18 +1442,17 @@ var LLMResponseParser = class {
1266
1442
  skip(message) {
1267
1443
  return { name: "_skip", arguments: { message } };
1268
1444
  }
1269
- handleAnalyse(parts, rawLine, commands) {
1445
+ handleListDirectory(parts, rawLine, commands) {
1270
1446
  if (parts.length < 2) {
1271
1447
  commands.push(this.skip(
1272
- `[SKIPPED] Your command "${rawLine}" is missing a path. Correct format: analyse <path> [pattern]. Example: analyse src/`
1448
+ `[SKIPPED] Your command "${rawLine}" is missing a path. Correct format: list_directory <path> [pattern]. Example: list_directory src/`
1273
1449
  ));
1274
1450
  return;
1275
1451
  }
1276
- const path4 = parts[1];
1452
+ const path5 = parts[1];
1277
1453
  const pattern = parts[2]?.replace(/^"|"$/g, "") ?? null;
1278
- commands.push({ name: "analyse", arguments: { path: path4, pattern } });
1454
+ commands.push({ name: "list_directory", arguments: { path: path5, pattern } });
1279
1455
  }
1280
- // no glob tool in MCP
1281
1456
  handleGrep(parts, rawLine, commands) {
1282
1457
  if (parts.length < 3) {
1283
1458
  commands.push(this.skip(
@@ -1348,8 +1523,30 @@ var LLMResponseParser = class {
1348
1523
  }
1349
1524
  };
1350
1525
 
1351
- // tools/warp_grep/tools/read.ts
1526
+ // tools/warp_grep/agent/tools/grep.ts
1527
+ async function toolGrep(provider, args) {
1528
+ const res = await provider.grep(args);
1529
+ if (res.error) {
1530
+ return { output: res.error };
1531
+ }
1532
+ if (!res.lines.length) {
1533
+ return { output: "no matches" };
1534
+ }
1535
+ return { output: res.lines.join("\n") };
1536
+ }
1537
+
1538
+ // tools/warp_grep/agent/tools/read.ts
1352
1539
  async function toolRead(provider, args) {
1540
+ if (args.lines && args.lines.length > 0) {
1541
+ const chunks = [];
1542
+ for (const [start, end] of args.lines) {
1543
+ const res2 = await provider.read({ path: args.path, start, end });
1544
+ if (res2.error) return res2.error;
1545
+ chunks.push(res2.lines.join("\n"));
1546
+ }
1547
+ if (chunks.every((c) => c === "")) return "(empty file)";
1548
+ return chunks.join("\n...\n");
1549
+ }
1353
1550
  const res = await provider.read({ path: args.path, start: args.start, end: args.end });
1354
1551
  if (res.error) {
1355
1552
  return res.error;
@@ -1358,16 +1555,56 @@ async function toolRead(provider, args) {
1358
1555
  return res.lines.join("\n");
1359
1556
  }
1360
1557
 
1361
- // tools/warp_grep/tools/analyse.ts
1362
- async function toolAnalyse(provider, args) {
1363
- const list = await provider.analyse({
1558
+ // tools/warp_grep/agent/tools/list_directory.ts
1559
+ async function toolListDirectory(provider, args) {
1560
+ const list = await provider.listDirectory({
1364
1561
  path: args.path,
1365
1562
  pattern: args.pattern ?? null,
1366
- maxResults: args.maxResults ?? 100,
1367
- maxDepth: args.maxDepth ?? 2
1563
+ maxResults: args.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES,
1564
+ maxDepth: args.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH
1368
1565
  });
1369
1566
  if (!list.length) return "empty";
1370
- return list.map((e) => `${" ".repeat(e.depth)}- ${e.type === "dir" ? "[D]" : "[F]"} ${e.name}`).join("\n");
1567
+ if (list.length >= AGENT_CONFIG.MAX_OUTPUT_LINES) {
1568
+ return "query not specific enough, tool called tried to return too much context and failed";
1569
+ }
1570
+ return list.map((e) => {
1571
+ const indent = " ".repeat(e.depth);
1572
+ const name = e.type === "dir" ? `${e.name}/` : e.name;
1573
+ return `${indent}${name}`;
1574
+ }).join("\n");
1575
+ }
1576
+
1577
+ // tools/warp_grep/agent/tools/finish.ts
1578
+ async function readFinishFiles(repoRoot, files, reader) {
1579
+ const out = [];
1580
+ for (const f of files) {
1581
+ const ranges = mergeRanges(f.lines);
1582
+ const chunks = [];
1583
+ for (const [s, e] of ranges) {
1584
+ const lines = await reader(f.path, s, e);
1585
+ chunks.push(lines.join("\n"));
1586
+ }
1587
+ out.push({ path: f.path, ranges, content: chunks.join("\n") });
1588
+ }
1589
+ return out;
1590
+ }
1591
+ function mergeRanges(ranges) {
1592
+ if (!ranges.length) return [];
1593
+ const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
1594
+ const merged = [];
1595
+ let [cs, ce] = sorted[0];
1596
+ for (let i = 1; i < sorted.length; i++) {
1597
+ const [s, e] = sorted[i];
1598
+ if (s <= ce + 1) {
1599
+ ce = Math.max(ce, e);
1600
+ } else {
1601
+ merged.push([cs, ce]);
1602
+ cs = s;
1603
+ ce = e;
1604
+ }
1605
+ }
1606
+ merged.push([cs, ce]);
1607
+ return merged;
1371
1608
  }
1372
1609
 
1373
1610
  // tools/warp_grep/agent/formatter.ts
@@ -1386,8 +1623,8 @@ var ToolOutputFormatter = class {
1386
1623
  switch (name) {
1387
1624
  case "read":
1388
1625
  return this.formatRead(safeArgs, payload, isError);
1389
- case "analyse":
1390
- return this.formatAnalyse(safeArgs, payload, isError);
1626
+ case "list_directory":
1627
+ return this.formatListDirectory(safeArgs, payload, isError);
1391
1628
  case "grep":
1392
1629
  return this.formatGrep(safeArgs, payload, isError);
1393
1630
  default:
@@ -1400,39 +1637,56 @@ ${payload}
1400
1637
  if (isError) {
1401
1638
  return payload;
1402
1639
  }
1403
- const path4 = this.asString(args.path) || "...";
1404
- return `<file path="${path4}">
1640
+ const path5 = this.asString(args.path) || "...";
1641
+ const start = args.start;
1642
+ const end = args.end;
1643
+ const linesArray = args.lines;
1644
+ const attributes = [`path="${path5}"`];
1645
+ if (linesArray && linesArray.length > 0) {
1646
+ const rangeStr = linesArray.map(([s, e]) => `${s}-${e}`).join(",");
1647
+ attributes.push(`lines="${rangeStr}"`);
1648
+ } else if (start !== void 0 && end !== void 0) {
1649
+ attributes.push(`lines="${start}-${end}"`);
1650
+ }
1651
+ return `<read ${attributes.join(" ")}>
1405
1652
  ${payload}
1406
- </file>`;
1653
+ </read>`;
1407
1654
  }
1408
- formatAnalyse(args, payload, isError) {
1409
- const path4 = this.asString(args.path) || ".";
1655
+ formatListDirectory(args, payload, isError) {
1656
+ const path5 = this.asString(args.path) || ".";
1657
+ const pattern = this.asString(args.pattern);
1658
+ const attributes = [`path="${path5}"`];
1659
+ if (pattern) {
1660
+ attributes.push(`pattern="${pattern}"`);
1661
+ }
1410
1662
  if (isError) {
1411
- return `<analyse_results path="${path4}" status="error">
1412
- ${payload}
1413
- </analyse_results>`;
1663
+ attributes.push('status="error"');
1414
1664
  }
1415
- return `<analyse_results path="${path4}">
1665
+ return `<list_directory ${attributes.join(" ")}>
1416
1666
  ${payload}
1417
- </analyse_results>`;
1667
+ </list_directory>`;
1418
1668
  }
1419
1669
  formatGrep(args, payload, isError) {
1420
1670
  const pattern = this.asString(args.pattern);
1421
- const path4 = this.asString(args.path);
1671
+ const subDir = this.asString(args.path);
1672
+ const glob = this.asString(args.glob);
1422
1673
  const attributes = [];
1423
1674
  if (pattern !== void 0) {
1424
1675
  attributes.push(`pattern="${pattern}"`);
1425
1676
  }
1426
- if (path4 !== void 0) {
1427
- attributes.push(`path="${path4}"`);
1677
+ if (subDir !== void 0) {
1678
+ attributes.push(`sub_dir="${subDir}"`);
1679
+ }
1680
+ if (glob !== void 0) {
1681
+ attributes.push(`glob="${glob}"`);
1428
1682
  }
1429
1683
  if (isError) {
1430
1684
  attributes.push('status="error"');
1431
1685
  }
1432
1686
  const attrText = attributes.length ? ` ${attributes.join(" ")}` : "";
1433
- return `<grep_output${attrText}>
1687
+ return `<grep${attrText}>
1434
1688
  ${payload}
1435
- </grep_output>`;
1689
+ </grep>`;
1436
1690
  }
1437
1691
  asString(value) {
1438
1692
  if (value === null || value === void 0) {
@@ -1446,66 +1700,100 @@ function formatAgentToolOutput(toolName, args, output, options = {}) {
1446
1700
  return sharedFormatter.format(toolName, args, output, options);
1447
1701
  }
1448
1702
 
1449
- // tools/warp_grep/tools/finish.ts
1450
- async function readFinishFiles(repoRoot, files, reader) {
1451
- const out = [];
1452
- for (const f of files) {
1453
- const ranges = mergeRanges(f.lines);
1454
- const chunks = [];
1455
- for (const [s, e] of ranges) {
1456
- const lines = await reader(f.path, s, e);
1457
- chunks.push(lines.join("\n"));
1458
- }
1459
- out.push({ path: f.path, ranges, content: chunks.join("\n") });
1703
+ // tools/warp_grep/agent/helpers.ts
1704
+ var import_path2 = __toESM(require("path"), 1);
1705
+ var TRUNCATED_MARKER = "[truncated for context limit]";
1706
+ function formatTurnMessage(turnsUsed, maxTurns) {
1707
+ const turnsRemaining = maxTurns - turnsUsed;
1708
+ if (turnsRemaining === 1) {
1709
+ return `
1710
+ You have used ${turnsUsed} turns, you only have 1 turn remaining. You have run out of turns to explore the code base and MUST call the finish tool now`;
1711
+ }
1712
+ return `
1713
+ You have used ${turnsUsed} turn${turnsUsed === 1 ? "" : "s"} and have ${turnsRemaining} remaining`;
1714
+ }
1715
+ function calculateContextBudget(messages) {
1716
+ const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0);
1717
+ const maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS;
1718
+ const percent = Math.round(totalChars / maxChars * 100);
1719
+ const usedK = Math.round(totalChars / 1e3);
1720
+ const maxK = Math.round(maxChars / 1e3);
1721
+ return `<context_budget>${percent}% (${usedK}K/${maxK}K chars)</context_budget>`;
1722
+ }
1723
+ async function buildInitialState(repoRoot, query, provider) {
1724
+ try {
1725
+ const entries = await provider.listDirectory({
1726
+ path: ".",
1727
+ maxResults: AGENT_CONFIG.MAX_OUTPUT_LINES,
1728
+ maxDepth: 2
1729
+ });
1730
+ const treeLines = entries.map((e) => {
1731
+ const indent = " ".repeat(e.depth);
1732
+ const name = e.type === "dir" ? `${e.name}/` : e.name;
1733
+ return `${indent}${name}`;
1734
+ });
1735
+ const repoName = import_path2.default.basename(repoRoot);
1736
+ const treeOutput = treeLines.length > 0 ? `${repoName}/
1737
+ ${treeLines.join("\n")}` : `${repoName}/`;
1738
+ return `<repo_structure>
1739
+ ${treeOutput}
1740
+ </repo_structure>
1741
+
1742
+ <search_string>
1743
+ ${query}
1744
+ </search_string>`;
1745
+ } catch {
1746
+ const repoName = import_path2.default.basename(repoRoot);
1747
+ return `<repo_structure>
1748
+ ${repoName}/
1749
+ </repo_structure>
1750
+
1751
+ <search_string>
1752
+ ${query}
1753
+ </search_string>`;
1460
1754
  }
1461
- return out;
1462
1755
  }
1463
- function mergeRanges(ranges) {
1464
- if (!ranges.length) return [];
1465
- const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
1466
- const merged = [];
1467
- let [cs, ce] = sorted[0];
1468
- for (let i = 1; i < sorted.length; i++) {
1469
- const [s, e] = sorted[i];
1470
- if (s <= ce + 1) {
1471
- ce = Math.max(ce, e);
1472
- } else {
1473
- merged.push([cs, ce]);
1474
- cs = s;
1475
- ce = e;
1756
+ function enforceContextLimit(messages, maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS) {
1757
+ const getTotalChars = () => messages.reduce((sum, m) => sum + m.content.length, 0);
1758
+ if (getTotalChars() <= maxChars) {
1759
+ return messages;
1760
+ }
1761
+ const userIndices = [];
1762
+ let firstUserSkipped = false;
1763
+ for (let i = 0; i < messages.length; i++) {
1764
+ if (messages[i].role === "user") {
1765
+ if (!firstUserSkipped) {
1766
+ firstUserSkipped = true;
1767
+ continue;
1768
+ }
1769
+ userIndices.push(i);
1476
1770
  }
1477
1771
  }
1478
- merged.push([cs, ce]);
1479
- return merged;
1772
+ for (const idx of userIndices) {
1773
+ if (getTotalChars() <= maxChars) {
1774
+ break;
1775
+ }
1776
+ if (messages[idx].content !== TRUNCATED_MARKER) {
1777
+ messages[idx] = { role: "user", content: TRUNCATED_MARKER };
1778
+ }
1779
+ }
1780
+ return messages;
1480
1781
  }
1481
1782
 
1482
1783
  // tools/warp_grep/agent/runner.ts
1483
- var import_path2 = __toESM(require("path"), 1);
1784
+ var import_path3 = __toESM(require("path"), 1);
1484
1785
  var parser = new LLMResponseParser();
1485
- async function buildInitialState(repoRoot, query, provider) {
1486
- try {
1487
- const entries = await provider.analyse({ path: ".", maxResults: 100 });
1488
- const dirs = entries.filter((e) => e.type === "dir").map((d) => d.name).slice(0, 50);
1489
- const files = entries.filter((e) => e.type === "file").map((f) => f.name).slice(0, 50);
1490
- const parts = [
1491
- `<repo_root>${repoRoot}</repo_root>`,
1492
- `<top_dirs>${dirs.join(", ")}</top_dirs>`,
1493
- `<top_files>${files.join(", ")}</top_files>`
1494
- ];
1495
- return parts.join("\n");
1496
- } catch {
1497
- return `<repo_root>${repoRoot}</repo_root>`;
1498
- }
1499
- }
1500
- async function callModel(messages, model, apiKey) {
1501
- const api = "https://api.morphllm.com/v1/chat/completions";
1786
+ var DEFAULT_API_URL = "https://api.morphllm.com";
1787
+ async function callModel(messages, model, options = {}) {
1788
+ const baseUrl = DEFAULT_API_URL;
1789
+ const apiKey = options.morphApiKey || process.env.MORPH_API_KEY || "";
1502
1790
  const fetchPromise = fetchWithRetry(
1503
- api,
1791
+ `${baseUrl}/v1/chat/completions`,
1504
1792
  {
1505
1793
  method: "POST",
1506
1794
  headers: {
1507
1795
  "Content-Type": "application/json",
1508
- Authorization: `Bearer ${apiKey || process.env.MORPH_API_KEY || ""}`
1796
+ Authorization: `Bearer ${apiKey}`
1509
1797
  },
1510
1798
  body: JSON.stringify({
1511
1799
  model,
@@ -1514,10 +1802,15 @@ async function callModel(messages, model, apiKey) {
1514
1802
  messages
1515
1803
  })
1516
1804
  },
1517
- {}
1805
+ options.retryConfig
1518
1806
  );
1519
1807
  const resp = await withTimeout(fetchPromise, AGENT_CONFIG.TIMEOUT_MS, "morph-warp-grep request timed out");
1520
1808
  if (!resp.ok) {
1809
+ if (resp.status === 404) {
1810
+ throw new Error(
1811
+ "The endpoint you are trying to call is likely deprecated. Please update with: npm cache clean --force && npx -y @morphllm/morphmcp@latest or visit: https://morphllm.com/mcp"
1812
+ );
1813
+ }
1521
1814
  const t = await resp.text();
1522
1815
  throw new Error(`morph-warp-grep error ${resp.status}: ${t}`);
1523
1816
  }
@@ -1529,22 +1822,24 @@ async function callModel(messages, model, apiKey) {
1529
1822
  return content;
1530
1823
  }
1531
1824
  async function runWarpGrep(config) {
1532
- const repoRoot = import_path2.default.resolve(config.repoRoot || process.cwd());
1825
+ const repoRoot = import_path3.default.resolve(config.repoRoot || process.cwd());
1533
1826
  const messages = [];
1534
- const systemMessage = { role: "system", content: getSystemPrompt() };
1535
- messages.push(systemMessage);
1536
- const queryContent = `<query>${config.query}</query>`;
1537
- messages.push({ role: "user", content: queryContent });
1827
+ messages.push({ role: "system", content: getSystemPrompt() });
1538
1828
  const initialState = await buildInitialState(repoRoot, config.query, config.provider);
1539
1829
  messages.push({ role: "user", content: initialState });
1540
- const maxRounds = AGENT_CONFIG.MAX_ROUNDS;
1830
+ const maxTurns = AGENT_CONFIG.MAX_TURNS;
1541
1831
  const model = config.model || DEFAULT_MODEL;
1542
1832
  const provider = config.provider;
1543
1833
  const errors = [];
1544
1834
  let finishMeta;
1545
1835
  let terminationReason = "terminated";
1546
- for (let round = 1; round <= maxRounds; round += 1) {
1547
- const assistantContent = await callModel(messages, model, config.apiKey).catch((e) => {
1836
+ for (let turn = 1; turn <= maxTurns; turn += 1) {
1837
+ enforceContextLimit(messages);
1838
+ const assistantContent = await callModel(messages, model, {
1839
+ morphApiKey: config.morphApiKey,
1840
+ morphApiUrl: config.morphApiUrl,
1841
+ retryConfig: config.retryConfig
1842
+ }).catch((e) => {
1548
1843
  errors.push({ message: e instanceof Error ? e.message : String(e) });
1549
1844
  return "";
1550
1845
  });
@@ -1552,13 +1847,13 @@ async function runWarpGrep(config) {
1552
1847
  messages.push({ role: "assistant", content: assistantContent });
1553
1848
  const toolCalls = parser.parse(assistantContent);
1554
1849
  if (toolCalls.length === 0) {
1555
- errors.push({ message: "No tool calls produced by the model." });
1850
+ errors.push({ message: "No tool calls produced by the model. Your MCP is likely out of date! Update it by running: npm cache clean --force && npx -y @morphllm/morphmcp@latest" });
1556
1851
  terminationReason = "terminated";
1557
1852
  break;
1558
1853
  }
1559
1854
  const finishCalls = toolCalls.filter((c) => c.name === "finish");
1560
1855
  const grepCalls = toolCalls.filter((c) => c.name === "grep");
1561
- const analyseCalls = toolCalls.filter((c) => c.name === "analyse");
1856
+ const listDirCalls = toolCalls.filter((c) => c.name === "list_directory");
1562
1857
  const readCalls = toolCalls.filter((c) => c.name === "read");
1563
1858
  const skipCalls = toolCalls.filter((c) => c.name === "_skip");
1564
1859
  const formatted = [];
@@ -1570,24 +1865,18 @@ async function runWarpGrep(config) {
1570
1865
  for (const c of grepCalls) {
1571
1866
  const args = c.arguments ?? {};
1572
1867
  allPromises.push(
1573
- provider.grep({ pattern: args.pattern, path: args.path }).then(
1574
- (grepRes) => {
1575
- if (grepRes.error) {
1576
- return { terminate: true, error: grepRes.error };
1577
- }
1578
- const output = grepRes.lines.join("\n") || "no matches";
1579
- return formatAgentToolOutput("grep", args, output, { isError: false });
1580
- },
1868
+ toolGrep(provider, args).then(
1869
+ ({ output }) => formatAgentToolOutput("grep", args, output, { isError: false }),
1581
1870
  (err) => formatAgentToolOutput("grep", args, String(err), { isError: true })
1582
1871
  )
1583
1872
  );
1584
1873
  }
1585
- for (const c of analyseCalls) {
1874
+ for (const c of listDirCalls) {
1586
1875
  const args = c.arguments ?? {};
1587
1876
  allPromises.push(
1588
- toolAnalyse(provider, args).then(
1589
- (p) => formatAgentToolOutput("analyse", args, p, { isError: false }),
1590
- (err) => formatAgentToolOutput("analyse", args, String(err), { isError: true })
1877
+ toolListDirectory(provider, args).then(
1878
+ (p) => formatAgentToolOutput("list_directory", args, p, { isError: false }),
1879
+ (err) => formatAgentToolOutput("list_directory", args, String(err), { isError: true })
1591
1880
  )
1592
1881
  );
1593
1882
  }
@@ -1602,34 +1891,12 @@ async function runWarpGrep(config) {
1602
1891
  }
1603
1892
  const allResults = await Promise.all(allPromises);
1604
1893
  for (const result of allResults) {
1605
- if (typeof result === "object" && "terminate" in result) {
1606
- errors.push({ message: result.error });
1607
- return {
1608
- terminationReason: "terminated",
1609
- messages,
1610
- errors
1611
- };
1612
- }
1613
1894
  formatted.push(result);
1614
1895
  }
1615
1896
  if (formatted.length > 0) {
1616
- const turnsUsed = round;
1617
- const turnsRemaining = 4 - turnsUsed;
1618
- let turnMessage;
1619
- if (turnsRemaining === 0) {
1620
- turnMessage = `
1621
-
1622
- [Turn ${turnsUsed}/4] This is your LAST turn. You MUST call the finish tool now.`;
1623
- } else if (turnsRemaining === 1) {
1624
- turnMessage = `
1625
-
1626
- [Turn ${turnsUsed}/4] You have 1 turn remaining. Next turn you MUST call the finish tool.`;
1627
- } else {
1628
- turnMessage = `
1629
-
1630
- [Turn ${turnsUsed}/4] You have ${turnsRemaining} turns remaining.`;
1631
- }
1632
- messages.push({ role: "user", content: formatted.join("\n") + turnMessage });
1897
+ const turnMessage = formatTurnMessage(turn, maxTurns);
1898
+ const contextBudget = calculateContextBudget(messages);
1899
+ messages.push({ role: "user", content: formatted.join("\n") + turnMessage + "\n" + contextBudget });
1633
1900
  }
1634
1901
  if (finishCalls.length) {
1635
1902
  const fc = finishCalls[0];
@@ -1679,7 +1946,7 @@ async function runWarpGrep(config) {
1679
1946
 
1680
1947
  // tools/warp_grep/providers/local.ts
1681
1948
  var import_promises3 = __toESM(require("fs/promises"), 1);
1682
- var import_path4 = __toESM(require("path"), 1);
1949
+ var import_path5 = __toESM(require("path"), 1);
1683
1950
 
1684
1951
  // tools/warp_grep/utils/ripgrep.ts
1685
1952
  var import_child_process = require("child_process");
@@ -1744,21 +2011,21 @@ async function runRipgrep(args, opts) {
1744
2011
 
1745
2012
  // tools/warp_grep/utils/paths.ts
1746
2013
  var import_fs = __toESM(require("fs"), 1);
1747
- var import_path3 = __toESM(require("path"), 1);
2014
+ var import_path4 = __toESM(require("path"), 1);
1748
2015
  function resolveUnderRepo(repoRoot, targetPath) {
1749
- const absRoot = import_path3.default.resolve(repoRoot);
1750
- const resolved = import_path3.default.resolve(absRoot, targetPath);
2016
+ const absRoot = import_path4.default.resolve(repoRoot);
2017
+ const resolved = import_path4.default.resolve(absRoot, targetPath);
1751
2018
  ensureWithinRepo(absRoot, resolved);
1752
2019
  return resolved;
1753
2020
  }
1754
2021
  function ensureWithinRepo(repoRoot, absTarget) {
1755
- const rel = import_path3.default.relative(import_path3.default.resolve(repoRoot), import_path3.default.resolve(absTarget));
1756
- if (rel.startsWith("..") || import_path3.default.isAbsolute(rel)) {
2022
+ const rel = import_path4.default.relative(import_path4.default.resolve(repoRoot), import_path4.default.resolve(absTarget));
2023
+ if (rel.startsWith("..") || import_path4.default.isAbsolute(rel)) {
1757
2024
  throw new Error(`Path outside repository root: ${absTarget}`);
1758
2025
  }
1759
2026
  }
1760
2027
  function toRepoRelative(repoRoot, absPath) {
1761
- return import_path3.default.relative(import_path3.default.resolve(repoRoot), import_path3.default.resolve(absPath));
2028
+ return import_path4.default.relative(import_path4.default.resolve(repoRoot), import_path4.default.resolve(absPath));
1762
2029
  }
1763
2030
  function isSymlink(p) {
1764
2031
  try {
@@ -1801,10 +2068,18 @@ var LocalRipgrepProvider = class {
1801
2068
  this.excludes = excludes;
1802
2069
  }
1803
2070
  async grep(params) {
1804
- const abs = resolveUnderRepo(this.repoRoot, params.path);
2071
+ let abs;
2072
+ try {
2073
+ abs = resolveUnderRepo(this.repoRoot, params.path);
2074
+ } catch (err) {
2075
+ return {
2076
+ lines: [],
2077
+ error: `[PATH ERROR] ${err instanceof Error ? err.message : String(err)}`
2078
+ };
2079
+ }
1805
2080
  const stat = await import_promises3.default.stat(abs).catch(() => null);
1806
2081
  if (!stat) return { lines: [] };
1807
- const targetArg = abs === import_path4.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
2082
+ const targetArg = abs === import_path5.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
1808
2083
  const args = [
1809
2084
  "--no-config",
1810
2085
  "--no-heading",
@@ -1813,6 +2088,9 @@ var LocalRipgrepProvider = class {
1813
2088
  "--color=never",
1814
2089
  "--trim",
1815
2090
  "--max-columns=400",
2091
+ "-C",
2092
+ "1",
2093
+ ...params.glob ? ["--glob", params.glob] : [],
1816
2094
  ...this.excludes.flatMap((e) => ["-g", `!${e}`]),
1817
2095
  params.pattern,
1818
2096
  targetArg || "."
@@ -1837,29 +2115,24 @@ Details: ${res.stderr}` : ""}`
1837
2115
  };
1838
2116
  }
1839
2117
  const lines = (res.stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
1840
- return { lines };
1841
- }
1842
- async glob(params) {
1843
- const abs = resolveUnderRepo(this.repoRoot, params.path);
1844
- const targetArg = abs === import_path4.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
1845
- const args = [
1846
- "--no-config",
1847
- "--files",
1848
- "-g",
1849
- params.pattern,
1850
- ...this.excludes.flatMap((e) => ["-g", `!${e}`]),
1851
- targetArg || "."
1852
- ];
1853
- const res = await runRipgrep(args, { cwd: this.repoRoot });
1854
- if (res.exitCode === -1) {
1855
- console.warn(`[warp_grep] ripgrep not available for glob: ${res.stderr || "execution failed"}`);
1856
- return { files: [] };
2118
+ if (lines.length > AGENT_CONFIG.MAX_OUTPUT_LINES) {
2119
+ return {
2120
+ lines: [],
2121
+ error: "query not specific enough, tool tried to return too much context and failed"
2122
+ };
1857
2123
  }
1858
- const files = (res.stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
1859
- return { files };
2124
+ return { lines };
1860
2125
  }
1861
2126
  async read(params) {
1862
- const abs = resolveUnderRepo(this.repoRoot, params.path);
2127
+ let abs;
2128
+ try {
2129
+ abs = resolveUnderRepo(this.repoRoot, params.path);
2130
+ } catch (err) {
2131
+ return {
2132
+ lines: [],
2133
+ error: `[PATH ERROR] ${err instanceof Error ? err.message : String(err)}`
2134
+ };
2135
+ }
1863
2136
  const stat = await import_promises3.default.stat(abs).catch(() => null);
1864
2137
  if (!stat || !stat.isFile()) {
1865
2138
  return {
@@ -1879,7 +2152,15 @@ Details: ${res.stderr}` : ""}`
1879
2152
  error: `[UNREADABLE FILE] You tried to read "${params.path}" but this file is either too large or not a text file, so it cannot be read. Try a different file.`
1880
2153
  };
1881
2154
  }
1882
- const lines = await readAllLines(abs);
2155
+ let lines;
2156
+ try {
2157
+ lines = await readAllLines(abs);
2158
+ } catch (err) {
2159
+ return {
2160
+ lines: [],
2161
+ error: `[READ ERROR] Failed to read "${params.path}": ${err instanceof Error ? err.message : String(err)}`
2162
+ };
2163
+ }
1883
2164
  const total = lines.length;
1884
2165
  let s = params.start ?? 1;
1885
2166
  let e = Math.min(params.end ?? total, total);
@@ -1892,30 +2173,46 @@ Details: ${res.stderr}` : ""}`
1892
2173
  const content = lines[i - 1] ?? "";
1893
2174
  out.push(`${i}|${content}`);
1894
2175
  }
2176
+ if (out.length > AGENT_CONFIG.MAX_READ_LINES) {
2177
+ const truncated = out.slice(0, AGENT_CONFIG.MAX_READ_LINES);
2178
+ truncated.push(`... [truncated: showing ${AGENT_CONFIG.MAX_READ_LINES} of ${out.length} lines]`);
2179
+ return { lines: truncated };
2180
+ }
1895
2181
  return { lines: out };
1896
2182
  }
1897
- async analyse(params) {
1898
- const abs = resolveUnderRepo(this.repoRoot, params.path);
2183
+ async listDirectory(params) {
2184
+ let abs;
2185
+ try {
2186
+ abs = resolveUnderRepo(this.repoRoot, params.path);
2187
+ } catch {
2188
+ return [];
2189
+ }
1899
2190
  const stat = await import_promises3.default.stat(abs).catch(() => null);
1900
2191
  if (!stat || !stat.isDirectory()) {
1901
2192
  return [];
1902
2193
  }
1903
- const maxResults = params.maxResults ?? 100;
1904
- const maxDepth = params.maxDepth ?? 2;
2194
+ const maxResults = params.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES;
2195
+ const maxDepth = params.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH;
1905
2196
  const regex = params.pattern ? new RegExp(params.pattern) : null;
1906
2197
  const results = [];
2198
+ let timedOut = false;
2199
+ const startTime = Date.now();
1907
2200
  async function walk(dir, depth) {
2201
+ if (Date.now() - startTime > AGENT_CONFIG.LIST_TIMEOUT_MS) {
2202
+ timedOut = true;
2203
+ return;
2204
+ }
1908
2205
  if (depth > maxDepth || results.length >= maxResults) return;
1909
2206
  const entries = await import_promises3.default.readdir(dir, { withFileTypes: true });
1910
2207
  for (const entry of entries) {
1911
- const full = import_path4.default.join(dir, entry.name);
2208
+ if (timedOut || results.length >= maxResults) break;
2209
+ const full = import_path5.default.join(dir, entry.name);
1912
2210
  const rel = toRepoRelative(abs, full).replace(/^[.][/\\]?/, "");
1913
- if (DEFAULT_EXCLUDES.some((ex) => rel.split(import_path4.default.sep).includes(ex))) continue;
2211
+ if (DEFAULT_EXCLUDES.some((ex) => rel.split(import_path5.default.sep).includes(ex))) continue;
1914
2212
  if (regex && !regex.test(entry.name)) continue;
1915
- if (results.length >= maxResults) break;
1916
2213
  results.push({
1917
2214
  name: entry.name,
1918
- path: toRepoRelative(import_path4.default.resolve(""), full),
2215
+ path: toRepoRelative(import_path5.default.resolve(""), full),
1919
2216
  // relative display
1920
2217
  type: entry.isDirectory() ? "dir" : "file",
1921
2218
  depth
@@ -1930,12 +2227,103 @@ Details: ${res.stderr}` : ""}`
1930
2227
  }
1931
2228
  };
1932
2229
 
1933
- // tools/warp_grep/core.ts
2230
+ // tools/warp_grep/providers/remote.ts
2231
+ var RemoteCommandsProvider = class {
2232
+ constructor(repoRoot, commands) {
2233
+ this.repoRoot = repoRoot;
2234
+ this.commands = commands;
2235
+ }
2236
+ /**
2237
+ * Run grep command and parse ripgrep output
2238
+ */
2239
+ async grep(params) {
2240
+ try {
2241
+ const stdout = await this.commands.grep(params.pattern, params.path, params.glob);
2242
+ const lines = (stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
2243
+ if (lines.length > AGENT_CONFIG.MAX_OUTPUT_LINES) {
2244
+ return {
2245
+ lines: [],
2246
+ error: "Query not specific enough - too many results returned. Try a more specific pattern."
2247
+ };
2248
+ }
2249
+ return { lines };
2250
+ } catch (error) {
2251
+ return {
2252
+ lines: [],
2253
+ error: `[GREP ERROR] ${error instanceof Error ? error.message : String(error)}`
2254
+ };
2255
+ }
2256
+ }
2257
+ /**
2258
+ * Read file and add line numbers
2259
+ */
2260
+ async read(params) {
2261
+ const start = params.start ?? 1;
2262
+ const end = params.end ?? 1e6;
2263
+ try {
2264
+ const stdout = await this.commands.read(params.path, start, end);
2265
+ const contentLines = (stdout || "").split("\n");
2266
+ if (contentLines.length > 0 && contentLines[contentLines.length - 1] === "") {
2267
+ contentLines.pop();
2268
+ }
2269
+ const lines = contentLines.map((content, idx) => `${start + idx}|${content}`);
2270
+ if (lines.length > AGENT_CONFIG.MAX_READ_LINES) {
2271
+ const truncated = lines.slice(0, AGENT_CONFIG.MAX_READ_LINES);
2272
+ truncated.push(`... [truncated: showing ${AGENT_CONFIG.MAX_READ_LINES} of ${lines.length} lines]`);
2273
+ return { lines: truncated };
2274
+ }
2275
+ return { lines };
2276
+ } catch (error) {
2277
+ return {
2278
+ lines: [],
2279
+ error: `[READ ERROR] ${error instanceof Error ? error.message : String(error)}`
2280
+ };
2281
+ }
2282
+ }
2283
+ /**
2284
+ * List directory and parse find output
2285
+ */
2286
+ async listDirectory(params) {
2287
+ const maxDepth = params.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH;
2288
+ const maxResults = params.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES;
2289
+ try {
2290
+ const stdout = await this.commands.listDir(params.path, maxDepth);
2291
+ const paths = (stdout || "").trim().split(/\r?\n/).filter((p) => p.length > 0);
2292
+ const regex = params.pattern ? new RegExp(params.pattern) : null;
2293
+ const entries = [];
2294
+ for (const fullPath of paths) {
2295
+ if (fullPath === params.path || fullPath === this.repoRoot) continue;
2296
+ const name = fullPath.split("/").pop() || "";
2297
+ if (regex && !regex.test(name)) continue;
2298
+ let relativePath = fullPath;
2299
+ if (fullPath.startsWith(this.repoRoot)) {
2300
+ relativePath = fullPath.slice(this.repoRoot.length).replace(/^\//, "");
2301
+ }
2302
+ const depth = relativePath.split("/").filter(Boolean).length - 1;
2303
+ const hasExtension = name.includes(".") && !name.startsWith(".");
2304
+ const type = hasExtension ? "file" : "dir";
2305
+ entries.push({
2306
+ name,
2307
+ path: relativePath,
2308
+ type,
2309
+ depth: Math.max(0, depth)
2310
+ });
2311
+ if (entries.length >= maxResults) break;
2312
+ }
2313
+ return entries;
2314
+ } catch (error) {
2315
+ return [];
2316
+ }
2317
+ }
2318
+ };
2319
+
2320
+ // tools/warp_grep/client.ts
1934
2321
  var WarpGrepClient = class {
1935
2322
  config;
1936
2323
  constructor(config = {}) {
1937
2324
  this.config = {
1938
- apiKey: config.apiKey,
2325
+ morphApiKey: config.morphApiKey,
2326
+ morphApiUrl: config.morphApiUrl,
1939
2327
  debug: config.debug,
1940
2328
  timeout: config.timeout,
1941
2329
  retryConfig: config.retryConfig
@@ -1963,34 +2351,46 @@ var WarpGrepClient = class {
1963
2351
  * ```
1964
2352
  */
1965
2353
  async execute(input) {
1966
- const provider = input.provider ?? new LocalRipgrepProvider(input.repoRoot, input.excludes);
1967
- const result = await runWarpGrep({
1968
- query: input.query,
1969
- repoRoot: input.repoRoot,
1970
- provider,
1971
- excludes: input.excludes,
1972
- includes: input.includes,
1973
- debug: input.debug ?? this.config.debug ?? false,
1974
- apiKey: this.config.apiKey
1975
- });
1976
- const finish = result.finish;
1977
- if (result.terminationReason !== "completed" || !finish?.metadata) {
1978
- return {
1979
- success: false,
1980
- error: "Search did not complete"
1981
- };
1982
- }
1983
- const contexts = (finish.resolved ?? []).map((r) => ({
1984
- file: r.path,
1985
- content: r.content
1986
- }));
1987
- return {
1988
- success: true,
1989
- contexts,
1990
- summary: finish.payload
1991
- };
2354
+ return executeToolCall(
2355
+ { query: input.query },
2356
+ {
2357
+ repoRoot: input.repoRoot,
2358
+ remoteCommands: input.remoteCommands,
2359
+ provider: input.provider,
2360
+ excludes: input.excludes,
2361
+ includes: input.includes,
2362
+ debug: input.debug ?? this.config.debug,
2363
+ morphApiKey: this.config.morphApiKey,
2364
+ morphApiUrl: this.config.morphApiUrl,
2365
+ retryConfig: this.config.retryConfig
2366
+ }
2367
+ );
1992
2368
  }
1993
2369
  };
2370
+ async function executeToolCall(input, config) {
2371
+ const parsed = typeof input === "string" ? JSON.parse(input) : input;
2372
+ const provider = config.remoteCommands ? new RemoteCommandsProvider(config.repoRoot, config.remoteCommands) : config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
2373
+ const result = await runWarpGrep({
2374
+ query: parsed.query,
2375
+ repoRoot: config.repoRoot,
2376
+ provider,
2377
+ excludes: config.excludes,
2378
+ includes: config.includes,
2379
+ debug: config.debug ?? false,
2380
+ morphApiKey: config.morphApiKey,
2381
+ morphApiUrl: config.morphApiUrl,
2382
+ retryConfig: config.retryConfig
2383
+ });
2384
+ const finish = result.finish;
2385
+ if (result.terminationReason !== "completed" || !finish?.metadata) {
2386
+ return { success: false, error: "Search did not complete" };
2387
+ }
2388
+ const contexts = (finish.resolved ?? []).map((r) => ({
2389
+ file: r.path,
2390
+ content: r.content
2391
+ }));
2392
+ return { success: true, contexts, summary: finish.payload };
2393
+ }
1994
2394
  function formatResult(result) {
1995
2395
  if (!result.success) {
1996
2396
  return `Search failed: ${result.error}`;
@@ -2770,29 +3170,7 @@ var TOOL_PARAMETERS = {
2770
3170
  },
2771
3171
  required: ["query"]
2772
3172
  };
2773
- async function execute(input, config) {
2774
- const parsed = typeof input === "string" ? JSON.parse(input) : input;
2775
- const provider = config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
2776
- const result = await runWarpGrep({
2777
- query: parsed.query,
2778
- repoRoot: config.repoRoot,
2779
- provider,
2780
- excludes: config.excludes,
2781
- includes: config.includes,
2782
- debug: config.debug ?? false,
2783
- apiKey: config.apiKey
2784
- });
2785
- const finish = result.finish;
2786
- if (result.terminationReason !== "completed" || !finish?.metadata) {
2787
- return { success: false, error: "Search did not complete" };
2788
- }
2789
- const contexts = (finish.resolved ?? []).map((r) => ({
2790
- file: r.path,
2791
- content: r.content
2792
- }));
2793
- return { success: true, contexts, summary: finish.payload };
2794
- }
2795
- function createMorphWarpGrepTool(config) {
3173
+ function createWarpGrepTool(config) {
2796
3174
  const tool4 = {
2797
3175
  type: "function",
2798
3176
  function: {
@@ -2803,7 +3181,7 @@ function createMorphWarpGrepTool(config) {
2803
3181
  };
2804
3182
  return Object.assign(tool4, {
2805
3183
  execute: async (input) => {
2806
- return execute(input, config);
3184
+ return executeToolCall(input, config);
2807
3185
  },
2808
3186
  formatResult: (result) => {
2809
3187
  return formatResult(result);
@@ -2979,7 +3357,7 @@ var editFileTool = {
2979
3357
  }
2980
3358
  }
2981
3359
  };
2982
- async function execute2(input, config) {
3360
+ async function execute(input, config) {
2983
3361
  return executeEditFile(input, config);
2984
3362
  }
2985
3363
  function getSystemPrompt2() {
@@ -3017,7 +3395,7 @@ function createEditFileTool(config = {}) {
3017
3395
  return Object.assign({}, toolDef, {
3018
3396
  execute: async (input) => {
3019
3397
  const parsedInput = typeof input === "string" ? JSON.parse(input) : input;
3020
- return execute2(parsedInput, config);
3398
+ return execute(parsedInput, config);
3021
3399
  },
3022
3400
  formatResult: (result) => {
3023
3401
  return formatResult3(result);
@@ -3036,13 +3414,13 @@ var OpenAIToolFactory = class {
3036
3414
  /**
3037
3415
  * Create an OpenAI-compatible warp grep tool
3038
3416
  *
3039
- * @param toolConfig - Tool configuration (apiKey inherited from MorphClient)
3417
+ * @param toolConfig - Tool configuration (morphApiKey inherited from MorphClient)
3040
3418
  * @returns OpenAI ChatCompletionTool with execute and formatResult methods
3041
3419
  */
3042
3420
  createWarpGrepTool(toolConfig) {
3043
- return createMorphWarpGrepTool({
3421
+ return createWarpGrepTool({
3044
3422
  ...toolConfig,
3045
- apiKey: this.config.apiKey
3423
+ morphApiKey: this.config.apiKey
3046
3424
  });
3047
3425
  }
3048
3426
  /**
@@ -3079,29 +3457,7 @@ var INPUT_SCHEMA = {
3079
3457
  },
3080
3458
  required: ["query"]
3081
3459
  };
3082
- async function execute3(input, config) {
3083
- const parsed = typeof input === "string" ? JSON.parse(input) : input;
3084
- const provider = config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
3085
- const result = await runWarpGrep({
3086
- query: parsed.query,
3087
- repoRoot: config.repoRoot,
3088
- provider,
3089
- excludes: config.excludes,
3090
- includes: config.includes,
3091
- debug: config.debug ?? false,
3092
- apiKey: config.apiKey
3093
- });
3094
- const finish = result.finish;
3095
- if (result.terminationReason !== "completed" || !finish?.metadata) {
3096
- return { success: false, error: "Search did not complete" };
3097
- }
3098
- const contexts = (finish.resolved ?? []).map((r) => ({
3099
- file: r.path,
3100
- content: r.content
3101
- }));
3102
- return { success: true, contexts, summary: finish.payload };
3103
- }
3104
- function createMorphWarpGrepTool2(config) {
3460
+ function createWarpGrepTool2(config) {
3105
3461
  const tool4 = {
3106
3462
  name: config.name ?? WARP_GREP_TOOL_NAME,
3107
3463
  description: config.description ?? WARP_GREP_DESCRIPTION,
@@ -3109,7 +3465,7 @@ function createMorphWarpGrepTool2(config) {
3109
3465
  };
3110
3466
  return Object.assign(tool4, {
3111
3467
  execute: async (input) => {
3112
- return execute3(input, config);
3468
+ return executeToolCall(input, config);
3113
3469
  },
3114
3470
  formatResult: (result) => {
3115
3471
  return formatResult(result);
@@ -3259,13 +3615,13 @@ var AnthropicToolFactory = class {
3259
3615
  /**
3260
3616
  * Create an Anthropic-compatible warp grep tool
3261
3617
  *
3262
- * @param toolConfig - Tool configuration (apiKey inherited from MorphClient)
3618
+ * @param toolConfig - Tool configuration (morphApiKey inherited from MorphClient)
3263
3619
  * @returns Anthropic Tool with execute and formatResult methods
3264
3620
  */
3265
3621
  createWarpGrepTool(toolConfig) {
3266
- return createMorphWarpGrepTool2({
3622
+ return createWarpGrepTool2({
3267
3623
  ...toolConfig,
3268
- apiKey: this.config.apiKey
3624
+ morphApiKey: this.config.apiKey
3269
3625
  });
3270
3626
  }
3271
3627
  /**
@@ -3297,33 +3653,23 @@ var AnthropicToolFactory = class {
3297
3653
  // tools/warp_grep/vercel.ts
3298
3654
  var import_ai = require("ai");
3299
3655
  var import_zod = require("zod");
3300
- var warpGrepSchema = import_zod.z.object({
3301
- query: import_zod.z.string().describe("Free-form repository question")
3302
- });
3303
- function createMorphWarpGrepTool3(config) {
3656
+ function createWarpGrepTool3(config) {
3657
+ const schema = import_zod.z.object({
3658
+ query: import_zod.z.string().describe("Free-form repository question")
3659
+ });
3304
3660
  return (0, import_ai.tool)({
3305
3661
  description: config.description ?? WARP_GREP_DESCRIPTION,
3306
- inputSchema: warpGrepSchema,
3662
+ inputSchema: schema,
3307
3663
  execute: async (params) => {
3308
- const provider = config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
3309
- const result = await runWarpGrep({
3310
- query: params.query,
3311
- repoRoot: config.repoRoot,
3312
- provider,
3313
- excludes: config.excludes,
3314
- includes: config.includes,
3315
- debug: config.debug ?? false,
3316
- apiKey: config.apiKey
3317
- });
3318
- const finish = result.finish;
3319
- if (result.terminationReason !== "completed" || !finish?.metadata) {
3320
- return { success: false, error: "Search did not complete" };
3664
+ const result = await executeToolCall(params, config);
3665
+ if (!result.success) {
3666
+ throw new Error(`Failed to search codebase: ${result.error}`);
3321
3667
  }
3322
- const contexts = (finish.resolved ?? []).map((r) => ({
3323
- file: r.path,
3324
- content: r.content
3325
- }));
3326
- return { success: true, contexts, summary: finish.payload };
3668
+ return {
3669
+ success: true,
3670
+ contexts: result.contexts,
3671
+ summary: result.summary
3672
+ };
3327
3673
  }
3328
3674
  });
3329
3675
  }
@@ -3436,13 +3782,13 @@ var VercelToolFactory = class {
3436
3782
  /**
3437
3783
  * Create a Vercel AI SDK-compatible warp grep tool
3438
3784
  *
3439
- * @param toolConfig - Tool configuration (apiKey inherited from MorphClient)
3785
+ * @param toolConfig - Tool configuration (morphApiKey inherited from MorphClient)
3440
3786
  * @returns Vercel AI SDK tool
3441
3787
  */
3442
3788
  createWarpGrepTool(toolConfig) {
3443
- return createMorphWarpGrepTool3({
3789
+ return createWarpGrepTool3({
3444
3790
  ...toolConfig,
3445
- apiKey: this.config.apiKey
3791
+ morphApiKey: this.config.apiKey
3446
3792
  });
3447
3793
  }
3448
3794
  /**
@@ -3522,7 +3868,7 @@ var MorphClient = class {
3522
3868
  retryConfig: config.retryConfig
3523
3869
  });
3524
3870
  this.warpGrep = new WarpGrepClient({
3525
- apiKey: config.apiKey,
3871
+ morphApiKey: config.apiKey,
3526
3872
  debug: config.debug,
3527
3873
  timeout: config.timeout,
3528
3874
  retryConfig: config.retryConfig