deepagents 1.12.1 → 1.12.2

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.
@@ -554,9 +554,12 @@ function adaptBackendProtocol(backend) {
554
554
  if (typeof result === "string") return { content: result };
555
555
  return result;
556
556
  },
557
- async grep(pattern, path, glob) {
558
- const result = await ("grep" in backend ? backend.grep(pattern, path, glob) : backend.grepRaw(pattern, path, glob));
559
- if (Array.isArray(result)) return { matches: result };
557
+ async grep(pattern, path, glob, maxCount) {
558
+ const result = await ("grep" in backend ? backend.grep(pattern, path, glob, maxCount) : backend.grepRaw(pattern, path, glob));
559
+ if (Array.isArray(result)) return applyGrepMaxCount({
560
+ result: { matches: result },
561
+ maxCount
562
+ });
560
563
  if (typeof result === "string") return { error: result };
561
564
  return result;
562
565
  }
@@ -591,6 +594,21 @@ function adaptSandboxProtocol(sandbox) {
591
594
  //#endregion
592
595
  //#region src/backends/protocol.ts
593
596
  /**
597
+ * Enforce a match cap after a backend grep has completed.
598
+ *
599
+ * When `maxCount` is set and the result exceeds it, the matches are sliced
600
+ * to the cap and the result is flagged `truncated: true`.
601
+ */
602
+ function applyGrepMaxCount(params) {
603
+ const { result, maxCount } = params;
604
+ if (maxCount == null || result.matches == null || result.matches.length <= maxCount) return result;
605
+ return {
606
+ error: result.error,
607
+ matches: result.matches.slice(0, maxCount),
608
+ truncated: true
609
+ };
610
+ }
611
+ /**
594
612
  * Type guard to check if a backend supports execution.
595
613
  *
596
614
  * @param backend - Backend instance to check
@@ -871,9 +889,12 @@ var StateBackend = class {
871
889
  * Search file contents for a literal text pattern.
872
890
  * Binary files are skipped.
873
891
  */
874
- grep(pattern, path = "/", glob = null) {
892
+ grep(pattern, path = "/", glob = null, maxCount = null) {
875
893
  const files = this.files;
876
- return { matches: grepMatchesFromFiles(files, pattern, path, glob) };
894
+ return applyGrepMaxCount({
895
+ result: { matches: grepMatchesFromFiles(files, pattern, path, glob) },
896
+ maxCount
897
+ });
877
898
  }
878
899
  /**
879
900
  * Structured glob matching returning FileInfo objects.
@@ -1115,7 +1136,7 @@ var CompositeBackend = class {
1115
1136
  const results = [];
1116
1137
  const defaultResult = await this.default.ls(path);
1117
1138
  if (defaultResult.error) return defaultResult;
1118
- results.push(...defaultResult.files || []);
1139
+ for (const fi of defaultResult.files || []) results.push(fi);
1119
1140
  for (const [routePrefix] of this.sortedRoutes) results.push({
1120
1141
  path: routePrefix,
1121
1142
  is_dir: true,
@@ -1151,33 +1172,56 @@ var CompositeBackend = class {
1151
1172
  }
1152
1173
  /**
1153
1174
  * Structured search results or error string for invalid input.
1175
+ *
1176
+ * @param maxCount - Optional total cap on returned matches across all routed
1177
+ * backends. When the cap is reached, remaining routes are
1178
+ * short-circuited and the result is flagged `truncated: true`.
1154
1179
  */
1155
- async grep(pattern, path = "/", glob = null) {
1180
+ async grep(pattern, path = "/", glob = null, maxCount = null) {
1156
1181
  const searchPath = path || "/";
1157
1182
  for (const [routePrefix, backend] of this.sortedRoutes) if (this.isPathWithinRoute(searchPath, routePrefix)) {
1158
1183
  const routeSearchPath = searchPath.substring(routePrefix.length - 1);
1159
- const raw = await backend.grep(pattern, routeSearchPath || "/", glob);
1184
+ const raw = await backend.grep(pattern, routeSearchPath || "/", glob, maxCount);
1160
1185
  if (raw.error) return raw;
1161
- return { matches: (raw.matches || []).map((m) => ({
1162
- ...m,
1163
- path: routePrefix.slice(0, -1) + m.path
1164
- })) };
1186
+ return applyGrepMaxCount({
1187
+ result: {
1188
+ matches: (raw.matches || []).map((m) => ({
1189
+ ...m,
1190
+ path: routePrefix.slice(0, -1) + m.path
1191
+ })),
1192
+ truncated: raw.truncated
1193
+ },
1194
+ maxCount
1195
+ });
1165
1196
  }
1166
1197
  const allMatches = [];
1167
- const rawDefault = await this.default.grep(pattern, searchPath, glob);
1198
+ let truncated = false;
1199
+ const rawDefault = await this.default.grep(pattern, searchPath, glob, maxCount);
1168
1200
  if (rawDefault.error) return rawDefault;
1169
- allMatches.push(...rawDefault.matches || []);
1201
+ for (const m of rawDefault.matches || []) allMatches.push(m);
1202
+ truncated = truncated || rawDefault.truncated === true;
1170
1203
  for (const [routePrefix, backend] of Object.entries(this.routes)) {
1171
1204
  if (!this.isRouteUnderPath(routePrefix, searchPath)) continue;
1172
- const raw = await backend.grep(pattern, "/", glob);
1205
+ const remaining = maxCount == null ? null : Math.max(maxCount - allMatches.length, 0);
1206
+ if (remaining === 0) {
1207
+ truncated = true;
1208
+ break;
1209
+ }
1210
+ const raw = await backend.grep(pattern, "/", glob, remaining);
1173
1211
  if (raw.error) return raw;
1174
- const matches = (raw.matches || []).map((m) => ({
1212
+ for (const m of raw.matches || []) allMatches.push({
1175
1213
  ...m,
1176
1214
  path: routePrefix.slice(0, -1) + m.path
1177
- }));
1178
- allMatches.push(...matches);
1215
+ });
1216
+ truncated = truncated || raw.truncated === true;
1179
1217
  }
1180
- return { matches: allMatches };
1218
+ return applyGrepMaxCount({
1219
+ result: {
1220
+ matches: allMatches,
1221
+ truncated
1222
+ },
1223
+ maxCount
1224
+ });
1181
1225
  }
1182
1226
  /**
1183
1227
  * Structured glob matching returning FileInfo objects.
@@ -1188,26 +1232,33 @@ var CompositeBackend = class {
1188
1232
  const searchPath = path.substring(routePrefix.length - 1);
1189
1233
  const result = await backend.glob(pattern, searchPath || "/");
1190
1234
  if (result.error) return result;
1191
- return { files: (result.files || []).map((fi) => ({
1192
- ...fi,
1193
- path: routePrefix.slice(0, -1) + fi.path
1194
- })) };
1235
+ return {
1236
+ files: (result.files || []).map((fi) => ({
1237
+ ...fi,
1238
+ path: routePrefix.slice(0, -1) + fi.path
1239
+ })),
1240
+ truncated: result.truncated
1241
+ };
1195
1242
  }
1196
1243
  const defaultResult = await this.default.glob(pattern, path);
1197
1244
  if (defaultResult.error) return defaultResult;
1198
- results.push(...defaultResult.files || []);
1245
+ for (const fi of defaultResult.files || []) results.push(fi);
1246
+ let truncated = defaultResult.truncated === true;
1199
1247
  for (const [routePrefix, backend] of Object.entries(this.routes)) {
1200
1248
  if (!this.isRouteUnderPath(routePrefix, path)) continue;
1201
1249
  const result = await backend.glob(pattern, "/");
1202
1250
  if (result.error) continue;
1203
- const files = (result.files || []).map((fi) => ({
1251
+ for (const fi of result.files || []) results.push({
1204
1252
  ...fi,
1205
1253
  path: routePrefix.slice(0, -1) + fi.path
1206
- }));
1207
- results.push(...files);
1254
+ });
1255
+ truncated = truncated || result.truncated === true;
1208
1256
  }
1209
1257
  results.sort((a, b) => a.path.localeCompare(b.path));
1210
- return { files: results };
1258
+ return {
1259
+ files: results,
1260
+ truncated
1261
+ };
1211
1262
  }
1212
1263
  /**
1213
1264
  * Write content to a file, routing to appropriate backend.
@@ -1403,6 +1454,15 @@ const READ_FILE_TRUNCATION_MSG = `
1403
1454
 
1404
1455
  [Output was truncated due to size limits. The file content is very large. Consider reformatting the file to make it easier to navigate. For example, if this is JSON, use execute(command='jq . {file_path}') to pretty-print it with line breaks. For other formats, you can use appropriate formatting tools to split long lines.]`;
1405
1456
  /**
1457
+ * Note appended to grep results that were cut short by the match-count cap.
1458
+ */
1459
+ const GREP_TRUNCATION_NOTE = "Note: the search stopped early because it hit the maximum match count. The matches above are valid but incomplete. Narrow the search (a more specific pattern or a narrower path), or raise max_count, to see the rest.";
1460
+ /**
1461
+ * Default cap on the number of matches the grep tool returns.
1462
+ * Set to null to disable the cap.
1463
+ */
1464
+ const DEFAULT_GREP_MAX_COUNT = 1e3;
1465
+ /**
1406
1466
  * Message template for evicted tool results.
1407
1467
  */
1408
1468
  const TOO_LARGE_TOOL_MSG = langchain.context`
@@ -1891,13 +1951,14 @@ function createGlobTool(backend, options) {
1891
1951
  * Create grep tool using backend.
1892
1952
  */
1893
1953
  function createGrepTool(backend, options) {
1894
- const { customDescription, permissions, includeExecution } = options;
1954
+ const { customDescription, permissions, includeExecution, grepMaxCount } = options;
1895
1955
  return (0, langchain.tool)(async (input, runtime) => {
1896
1956
  const permissionError = checkPermission(permissions, "read", input.path ?? "/");
1897
1957
  if (permissionError !== void 0) return toolError(runtime, "grep", permissionError);
1898
1958
  const resolvedBackend = await resolveBackend(backend, runtime);
1899
1959
  const { pattern, path = "/", glob = null } = input;
1900
- const result = await resolvedBackend.grep(pattern, path, glob);
1960
+ const maxCount = input.max_count ?? grepMaxCount;
1961
+ const result = await resolvedBackend.grep(pattern, path, glob, maxCount);
1901
1962
  if (result.error) return result.error;
1902
1963
  const matches = filterByPermissions(result.matches ?? [], permissions, "read", (m) => m.path);
1903
1964
  if (matches.length === 0) return `No matches found for pattern '${pattern}'`;
@@ -1911,15 +1972,17 @@ function createGrepTool(backend, options) {
1911
1972
  lines.push(` ${match.line}: ${match.text}`);
1912
1973
  }
1913
1974
  const truncated = truncateIfTooLong(lines);
1914
- if (Array.isArray(truncated)) return truncated.join("\n");
1915
- return truncated;
1975
+ let content = Array.isArray(truncated) ? truncated.join("\n") : truncated;
1976
+ if (result.truncated) content += `\n\n${GREP_TRUNCATION_NOTE}`;
1977
+ return content;
1916
1978
  }, {
1917
1979
  name: "grep",
1918
1980
  description: customDescription || getGrepToolDescription(includeExecution),
1919
1981
  schema: zod_v4.z.object({
1920
1982
  pattern: zod_v4.z.string().describe("Literal text pattern to search for (not regex)"),
1921
1983
  path: zod_v4.z.string().optional().default("/").describe("Base path to search from (default: /)"),
1922
- glob: zod_v4.z.string().optional().nullable().default(null).describe("Optional glob pattern to filter files (e.g., '*.py')")
1984
+ glob: zod_v4.z.string().optional().nullable().default(null).describe("Optional glob pattern to filter files (e.g., '*.py')"),
1985
+ max_count: zod_v4.z.number().int().positive().optional().nullable().default(null).describe("Optional cap on the total number of matches returned across all files. Leave unset to use the configured default. When the cap is hit, results are truncated and a note says so; narrow the pattern or path to see the rest.")
1923
1986
  })
1924
1987
  });
1925
1988
  }
@@ -1988,7 +2051,7 @@ function allPathsScopedToRoutes(permissions, backend) {
1988
2051
  * ```
1989
2052
  */
1990
2053
  function createFilesystemMiddleware(options = {}) {
1991
- const { backend = (runtime) => new StateBackend(runtime), systemPrompt: customSystemPrompt = null, customToolDescriptions = null, toolTokenLimitBeforeEvict = 2e4, humanMessageTokenLimitBeforeEvict = 5e4, permissions = [], tools: filesystemTools = null } = options;
2054
+ const { backend = (runtime) => new StateBackend(runtime), systemPrompt: customSystemPrompt = null, customToolDescriptions = null, toolTokenLimitBeforeEvict = 2e4, humanMessageTokenLimitBeforeEvict = 5e4, permissions = [], tools: filesystemTools = null, grepMaxCount = DEFAULT_GREP_MAX_COUNT } = options;
1992
2055
  const enabledFilesystemTools = normalizeFilesystemTools(filesystemTools);
1993
2056
  const executeToolEnabled = enabledFilesystemTools == null || enabledFilesystemTools.has("execute");
1994
2057
  if (permissions.length > 0) validatePermissionPaths(permissions);
@@ -2024,7 +2087,8 @@ function createFilesystemMiddleware(options = {}) {
2024
2087
  grep: createGrepTool(backend, {
2025
2088
  customDescription: customToolDescriptions?.grep,
2026
2089
  permissions,
2027
- includeExecution: configuredToolNames.has("execute") && typeof backend !== "function" && isSandboxBackend(backend)
2090
+ includeExecution: configuredToolNames.has("execute") && typeof backend !== "function" && isSandboxBackend(backend),
2091
+ grepMaxCount
2028
2092
  }),
2029
2093
  execute: createExecuteTool(backend, {
2030
2094
  customDescription: customToolDescriptions?.execute,
@@ -6386,7 +6450,7 @@ var StoreBackend = class {
6386
6450
  * Search file contents for a literal text pattern.
6387
6451
  * Binary files are skipped.
6388
6452
  */
6389
- async grep(pattern, path = "/", glob = null) {
6453
+ async grep(pattern, path = "/", glob = null, maxCount = null) {
6390
6454
  const store = this.getStore();
6391
6455
  const namespace = this.getNamespace();
6392
6456
  const items = await this.searchStorePaginated(store, namespace);
@@ -6396,7 +6460,10 @@ var StoreBackend = class {
6396
6460
  } catch {
6397
6461
  continue;
6398
6462
  }
6399
- return { matches: grepMatchesFromFiles(files, pattern, path, glob) };
6463
+ return applyGrepMaxCount({
6464
+ result: { matches: grepMatchesFromFiles(files, pattern, path, glob) },
6465
+ maxCount
6466
+ });
6400
6467
  }
6401
6468
  /**
6402
6469
  * Structured glob matching returning FileInfo objects.
@@ -6695,7 +6762,7 @@ var ContextHubBackend = class ContextHubBackend {
6695
6762
  modified_at: now
6696
6763
  } };
6697
6764
  }
6698
- async grep(pattern, path = null, glob = null) {
6765
+ async grep(pattern, path = null, glob = null, maxCount = null) {
6699
6766
  let cache;
6700
6767
  try {
6701
6768
  cache = await this.ensureCache();
@@ -6718,7 +6785,10 @@ var ContextHubBackend = class ContextHubBackend {
6718
6785
  });
6719
6786
  }
6720
6787
  }
6721
- return { matches };
6788
+ return applyGrepMaxCount({
6789
+ result: { matches },
6790
+ maxCount
6791
+ });
6722
6792
  }
6723
6793
  async glob(pattern, _path = "/") {
6724
6794
  let cache;
@@ -7114,7 +7184,7 @@ var BaseSandbox = class {
7114
7184
  * @param glob - Optional glob pattern to filter which files to search.
7115
7185
  * @returns List of GrepMatch dicts containing path, line number, and matched text.
7116
7186
  */
7117
- async grep(pattern, path = "/", glob = null) {
7187
+ async grep(pattern, path = "/", glob = null, maxCount = null) {
7118
7188
  const command = buildGrepCommand(pattern, path, glob);
7119
7189
  const output = (await this.execute(command)).output.trim();
7120
7190
  if (!output) return { matches: [] };
@@ -7132,7 +7202,10 @@ var BaseSandbox = class {
7132
7202
  });
7133
7203
  }
7134
7204
  }
7135
- return { matches };
7205
+ return applyGrepMaxCount({
7206
+ result: { matches },
7207
+ maxCount
7208
+ });
7136
7209
  }
7137
7210
  /**
7138
7211
  * Structured glob matching returning FileInfo objects.
@@ -7631,6 +7704,12 @@ Object.defineProperty(exports, "adaptSandboxProtocol", {
7631
7704
  return adaptSandboxProtocol;
7632
7705
  }
7633
7706
  });
7707
+ Object.defineProperty(exports, "applyGrepMaxCount", {
7708
+ enumerable: true,
7709
+ get: function() {
7710
+ return applyGrepMaxCount;
7711
+ }
7712
+ });
7634
7713
  Object.defineProperty(exports, "checkEmptyContent", {
7635
7714
  enumerable: true,
7636
7715
  get: function() {
@@ -7794,4 +7873,4 @@ Object.defineProperty(exports, "serializeProfile", {
7794
7873
  }
7795
7874
  });
7796
7875
 
7797
- //# sourceMappingURL=langsmith-DdOXam6Z.cjs.map
7876
+ //# sourceMappingURL=langsmith-D2d3Dwcc.cjs.map