deepagents 1.12.0 → 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.
@@ -366,11 +366,32 @@ function validatePath$1(path) {
366
366
  return normalized;
367
367
  }
368
368
  /**
369
+ * Resolve the files under `path` for grep/glob search.
370
+ *
371
+ * If `path` exactly names a file that exists in `files`, only that file is
372
+ * returned (exact match) — this lets grep/glob target a specific file
373
+ * directly instead of only matching directories. Otherwise `path` is treated
374
+ * as a directory and files are filtered by the normalized directory prefix.
375
+ *
376
+ * @returns Filtered files map, or null if `path` is invalid (e.g. whitespace-only).
377
+ */
378
+ function filterFilesByPath(files, path) {
379
+ const exactPath = path ? path.startsWith("/") ? path : "/" + path : "/";
380
+ if (Object.prototype.hasOwnProperty.call(files, exactPath)) return { [exactPath]: files[exactPath] };
381
+ try {
382
+ const normalizedPath = validatePath$1(path);
383
+ return Object.fromEntries(Object.entries(files).filter(([fp]) => fp.startsWith(normalizedPath)));
384
+ } catch {
385
+ return null;
386
+ }
387
+ }
388
+ /**
369
389
  * Search files dict for paths matching glob pattern.
370
390
  *
371
391
  * @param files - Dictionary of file paths to FileData
372
392
  * @param pattern - Glob pattern (e.g., `*.py`, `**\/*.ts`)
373
- * @param path - Base path to search from
393
+ * @param path - Base path to search from. If `path` names an exact file, only
394
+ * that file is considered.
374
395
  * @returns Newline-separated file paths, sorted by modification time (most recent first).
375
396
  * Returns "No files found" if no matches.
376
397
  *
@@ -382,13 +403,9 @@ function validatePath$1(path) {
382
403
  * ```
383
404
  */
384
405
  function globSearchFiles(files, pattern, path = "/") {
385
- let normalizedPath;
386
- try {
387
- normalizedPath = validatePath$1(path);
388
- } catch {
389
- return "No files found";
390
- }
391
- const filtered = Object.fromEntries(Object.entries(files).filter(([fp]) => fp.startsWith(normalizedPath)));
406
+ const filtered = filterFilesByPath(files, path);
407
+ if (filtered === null) return "No files found";
408
+ const normalizedPath = validatePath$1(path);
392
409
  const effectivePattern = pattern;
393
410
  const matches = [];
394
411
  for (const [filePath, fileData] of Object.entries(filtered)) {
@@ -411,16 +428,12 @@ function globSearchFiles(files, pattern, path = "/") {
411
428
  * Return structured grep matches from an in-memory files mapping.
412
429
  *
413
430
  * Performs literal text search (not regex). Binary files are skipped.
431
+ * If `path` names an exact file, only that file is considered.
414
432
  * Returns an empty array when no matches are found or on invalid input.
415
433
  */
416
434
  function grepMatchesFromFiles(files, pattern, path = null, glob = null) {
417
- let normalizedPath;
418
- try {
419
- normalizedPath = validatePath$1(path);
420
- } catch {
421
- return [];
422
- }
423
- let filtered = Object.fromEntries(Object.entries(files).filter(([fp]) => fp.startsWith(normalizedPath)));
435
+ let filtered = filterFilesByPath(files, path);
436
+ if (filtered === null) return [];
424
437
  if (glob) filtered = Object.fromEntries(Object.entries(filtered).filter(([fp]) => micromatch.default.isMatch(basename(fp), glob, {
425
438
  dot: true,
426
439
  nobrace: false
@@ -541,9 +554,12 @@ function adaptBackendProtocol(backend) {
541
554
  if (typeof result === "string") return { content: result };
542
555
  return result;
543
556
  },
544
- async grep(pattern, path, glob) {
545
- const result = await ("grep" in backend ? backend.grep(pattern, path, glob) : backend.grepRaw(pattern, path, glob));
546
- 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
+ });
547
563
  if (typeof result === "string") return { error: result };
548
564
  return result;
549
565
  }
@@ -578,6 +594,21 @@ function adaptSandboxProtocol(sandbox) {
578
594
  //#endregion
579
595
  //#region src/backends/protocol.ts
580
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
+ /**
581
612
  * Type guard to check if a backend supports execution.
582
613
  *
583
614
  * @param backend - Backend instance to check
@@ -858,9 +889,12 @@ var StateBackend = class {
858
889
  * Search file contents for a literal text pattern.
859
890
  * Binary files are skipped.
860
891
  */
861
- grep(pattern, path = "/", glob = null) {
892
+ grep(pattern, path = "/", glob = null, maxCount = null) {
862
893
  const files = this.files;
863
- return { matches: grepMatchesFromFiles(files, pattern, path, glob) };
894
+ return applyGrepMaxCount({
895
+ result: { matches: grepMatchesFromFiles(files, pattern, path, glob) },
896
+ maxCount
897
+ });
864
898
  }
865
899
  /**
866
900
  * Structured glob matching returning FileInfo objects.
@@ -1102,7 +1136,7 @@ var CompositeBackend = class {
1102
1136
  const results = [];
1103
1137
  const defaultResult = await this.default.ls(path);
1104
1138
  if (defaultResult.error) return defaultResult;
1105
- results.push(...defaultResult.files || []);
1139
+ for (const fi of defaultResult.files || []) results.push(fi);
1106
1140
  for (const [routePrefix] of this.sortedRoutes) results.push({
1107
1141
  path: routePrefix,
1108
1142
  is_dir: true,
@@ -1138,33 +1172,56 @@ var CompositeBackend = class {
1138
1172
  }
1139
1173
  /**
1140
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`.
1141
1179
  */
1142
- async grep(pattern, path = "/", glob = null) {
1180
+ async grep(pattern, path = "/", glob = null, maxCount = null) {
1143
1181
  const searchPath = path || "/";
1144
1182
  for (const [routePrefix, backend] of this.sortedRoutes) if (this.isPathWithinRoute(searchPath, routePrefix)) {
1145
1183
  const routeSearchPath = searchPath.substring(routePrefix.length - 1);
1146
- const raw = await backend.grep(pattern, routeSearchPath || "/", glob);
1184
+ const raw = await backend.grep(pattern, routeSearchPath || "/", glob, maxCount);
1147
1185
  if (raw.error) return raw;
1148
- return { matches: (raw.matches || []).map((m) => ({
1149
- ...m,
1150
- path: routePrefix.slice(0, -1) + m.path
1151
- })) };
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
+ });
1152
1196
  }
1153
1197
  const allMatches = [];
1154
- const rawDefault = await this.default.grep(pattern, searchPath, glob);
1198
+ let truncated = false;
1199
+ const rawDefault = await this.default.grep(pattern, searchPath, glob, maxCount);
1155
1200
  if (rawDefault.error) return rawDefault;
1156
- allMatches.push(...rawDefault.matches || []);
1201
+ for (const m of rawDefault.matches || []) allMatches.push(m);
1202
+ truncated = truncated || rawDefault.truncated === true;
1157
1203
  for (const [routePrefix, backend] of Object.entries(this.routes)) {
1158
1204
  if (!this.isRouteUnderPath(routePrefix, searchPath)) continue;
1159
- 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);
1160
1211
  if (raw.error) return raw;
1161
- const matches = (raw.matches || []).map((m) => ({
1212
+ for (const m of raw.matches || []) allMatches.push({
1162
1213
  ...m,
1163
1214
  path: routePrefix.slice(0, -1) + m.path
1164
- }));
1165
- allMatches.push(...matches);
1215
+ });
1216
+ truncated = truncated || raw.truncated === true;
1166
1217
  }
1167
- return { matches: allMatches };
1218
+ return applyGrepMaxCount({
1219
+ result: {
1220
+ matches: allMatches,
1221
+ truncated
1222
+ },
1223
+ maxCount
1224
+ });
1168
1225
  }
1169
1226
  /**
1170
1227
  * Structured glob matching returning FileInfo objects.
@@ -1175,26 +1232,33 @@ var CompositeBackend = class {
1175
1232
  const searchPath = path.substring(routePrefix.length - 1);
1176
1233
  const result = await backend.glob(pattern, searchPath || "/");
1177
1234
  if (result.error) return result;
1178
- return { files: (result.files || []).map((fi) => ({
1179
- ...fi,
1180
- path: routePrefix.slice(0, -1) + fi.path
1181
- })) };
1235
+ return {
1236
+ files: (result.files || []).map((fi) => ({
1237
+ ...fi,
1238
+ path: routePrefix.slice(0, -1) + fi.path
1239
+ })),
1240
+ truncated: result.truncated
1241
+ };
1182
1242
  }
1183
1243
  const defaultResult = await this.default.glob(pattern, path);
1184
1244
  if (defaultResult.error) return defaultResult;
1185
- results.push(...defaultResult.files || []);
1245
+ for (const fi of defaultResult.files || []) results.push(fi);
1246
+ let truncated = defaultResult.truncated === true;
1186
1247
  for (const [routePrefix, backend] of Object.entries(this.routes)) {
1187
1248
  if (!this.isRouteUnderPath(routePrefix, path)) continue;
1188
1249
  const result = await backend.glob(pattern, "/");
1189
1250
  if (result.error) continue;
1190
- const files = (result.files || []).map((fi) => ({
1251
+ for (const fi of result.files || []) results.push({
1191
1252
  ...fi,
1192
1253
  path: routePrefix.slice(0, -1) + fi.path
1193
- }));
1194
- results.push(...files);
1254
+ });
1255
+ truncated = truncated || result.truncated === true;
1195
1256
  }
1196
1257
  results.sort((a, b) => a.path.localeCompare(b.path));
1197
- return { files: results };
1258
+ return {
1259
+ files: results,
1260
+ truncated
1261
+ };
1198
1262
  }
1199
1263
  /**
1200
1264
  * Write content to a file, routing to appropriate backend.
@@ -1390,6 +1454,15 @@ const READ_FILE_TRUNCATION_MSG = `
1390
1454
 
1391
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.]`;
1392
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
+ /**
1393
1466
  * Message template for evicted tool results.
1394
1467
  */
1395
1468
  const TOO_LARGE_TOOL_MSG = langchain.context`
@@ -1878,13 +1951,14 @@ function createGlobTool(backend, options) {
1878
1951
  * Create grep tool using backend.
1879
1952
  */
1880
1953
  function createGrepTool(backend, options) {
1881
- const { customDescription, permissions, includeExecution } = options;
1954
+ const { customDescription, permissions, includeExecution, grepMaxCount } = options;
1882
1955
  return (0, langchain.tool)(async (input, runtime) => {
1883
1956
  const permissionError = checkPermission(permissions, "read", input.path ?? "/");
1884
1957
  if (permissionError !== void 0) return toolError(runtime, "grep", permissionError);
1885
1958
  const resolvedBackend = await resolveBackend(backend, runtime);
1886
1959
  const { pattern, path = "/", glob = null } = input;
1887
- 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);
1888
1962
  if (result.error) return result.error;
1889
1963
  const matches = filterByPermissions(result.matches ?? [], permissions, "read", (m) => m.path);
1890
1964
  if (matches.length === 0) return `No matches found for pattern '${pattern}'`;
@@ -1898,15 +1972,17 @@ function createGrepTool(backend, options) {
1898
1972
  lines.push(` ${match.line}: ${match.text}`);
1899
1973
  }
1900
1974
  const truncated = truncateIfTooLong(lines);
1901
- if (Array.isArray(truncated)) return truncated.join("\n");
1902
- 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;
1903
1978
  }, {
1904
1979
  name: "grep",
1905
1980
  description: customDescription || getGrepToolDescription(includeExecution),
1906
1981
  schema: zod_v4.z.object({
1907
1982
  pattern: zod_v4.z.string().describe("Literal text pattern to search for (not regex)"),
1908
1983
  path: zod_v4.z.string().optional().default("/").describe("Base path to search from (default: /)"),
1909
- 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.")
1910
1986
  })
1911
1987
  });
1912
1988
  }
@@ -1975,7 +2051,7 @@ function allPathsScopedToRoutes(permissions, backend) {
1975
2051
  * ```
1976
2052
  */
1977
2053
  function createFilesystemMiddleware(options = {}) {
1978
- 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;
1979
2055
  const enabledFilesystemTools = normalizeFilesystemTools(filesystemTools);
1980
2056
  const executeToolEnabled = enabledFilesystemTools == null || enabledFilesystemTools.has("execute");
1981
2057
  if (permissions.length > 0) validatePermissionPaths(permissions);
@@ -2011,7 +2087,8 @@ function createFilesystemMiddleware(options = {}) {
2011
2087
  grep: createGrepTool(backend, {
2012
2088
  customDescription: customToolDescriptions?.grep,
2013
2089
  permissions,
2014
- includeExecution: configuredToolNames.has("execute") && typeof backend !== "function" && isSandboxBackend(backend)
2090
+ includeExecution: configuredToolNames.has("execute") && typeof backend !== "function" && isSandboxBackend(backend),
2091
+ grepMaxCount
2015
2092
  }),
2016
2093
  execute: createExecuteTool(backend, {
2017
2094
  customDescription: customToolDescriptions?.execute,
@@ -6373,7 +6450,7 @@ var StoreBackend = class {
6373
6450
  * Search file contents for a literal text pattern.
6374
6451
  * Binary files are skipped.
6375
6452
  */
6376
- async grep(pattern, path = "/", glob = null) {
6453
+ async grep(pattern, path = "/", glob = null, maxCount = null) {
6377
6454
  const store = this.getStore();
6378
6455
  const namespace = this.getNamespace();
6379
6456
  const items = await this.searchStorePaginated(store, namespace);
@@ -6383,7 +6460,10 @@ var StoreBackend = class {
6383
6460
  } catch {
6384
6461
  continue;
6385
6462
  }
6386
- return { matches: grepMatchesFromFiles(files, pattern, path, glob) };
6463
+ return applyGrepMaxCount({
6464
+ result: { matches: grepMatchesFromFiles(files, pattern, path, glob) },
6465
+ maxCount
6466
+ });
6387
6467
  }
6388
6468
  /**
6389
6469
  * Structured glob matching returning FileInfo objects.
@@ -6682,7 +6762,7 @@ var ContextHubBackend = class ContextHubBackend {
6682
6762
  modified_at: now
6683
6763
  } };
6684
6764
  }
6685
- async grep(pattern, path = null, glob = null) {
6765
+ async grep(pattern, path = null, glob = null, maxCount = null) {
6686
6766
  let cache;
6687
6767
  try {
6688
6768
  cache = await this.ensureCache();
@@ -6705,7 +6785,10 @@ var ContextHubBackend = class ContextHubBackend {
6705
6785
  });
6706
6786
  }
6707
6787
  }
6708
- return { matches };
6788
+ return applyGrepMaxCount({
6789
+ result: { matches },
6790
+ maxCount
6791
+ });
6709
6792
  }
6710
6793
  async glob(pattern, _path = "/") {
6711
6794
  let cache;
@@ -7101,7 +7184,7 @@ var BaseSandbox = class {
7101
7184
  * @param glob - Optional glob pattern to filter which files to search.
7102
7185
  * @returns List of GrepMatch dicts containing path, line number, and matched text.
7103
7186
  */
7104
- async grep(pattern, path = "/", glob = null) {
7187
+ async grep(pattern, path = "/", glob = null, maxCount = null) {
7105
7188
  const command = buildGrepCommand(pattern, path, glob);
7106
7189
  const output = (await this.execute(command)).output.trim();
7107
7190
  if (!output) return { matches: [] };
@@ -7119,7 +7202,10 @@ var BaseSandbox = class {
7119
7202
  });
7120
7203
  }
7121
7204
  }
7122
- return { matches };
7205
+ return applyGrepMaxCount({
7206
+ result: { matches },
7207
+ maxCount
7208
+ });
7123
7209
  }
7124
7210
  /**
7125
7211
  * Structured glob matching returning FileInfo objects.
@@ -7618,6 +7704,12 @@ Object.defineProperty(exports, "adaptSandboxProtocol", {
7618
7704
  return adaptSandboxProtocol;
7619
7705
  }
7620
7706
  });
7707
+ Object.defineProperty(exports, "applyGrepMaxCount", {
7708
+ enumerable: true,
7709
+ get: function() {
7710
+ return applyGrepMaxCount;
7711
+ }
7712
+ });
7621
7713
  Object.defineProperty(exports, "checkEmptyContent", {
7622
7714
  enumerable: true,
7623
7715
  get: function() {
@@ -7781,4 +7873,4 @@ Object.defineProperty(exports, "serializeProfile", {
7781
7873
  }
7782
7874
  });
7783
7875
 
7784
- //# sourceMappingURL=langsmith-CiiwzXI3.cjs.map
7876
+ //# sourceMappingURL=langsmith-D2d3Dwcc.cjs.map