deepagents 1.10.8 → 1.11.1

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.
@@ -494,7 +494,7 @@ function migrateToFileDataV2(data, filePath) {
494
494
  * @returns BackendProtocolV2-compatible backend
495
495
  */
496
496
  function adaptBackendProtocol(backend) {
497
- return {
497
+ const adapted = {
498
498
  async ls(path) {
499
499
  const result = await ("ls" in backend ? backend.ls(path) : backend.lsInfo(path));
500
500
  if (Array.isArray(result)) return { files: result };
@@ -512,6 +512,7 @@ function adaptBackendProtocol(backend) {
512
512
  },
513
513
  write: (filePath, content) => backend.write(filePath, content),
514
514
  edit: (filePath, oldString, newString, replaceAll) => backend.edit(filePath, oldString, newString, replaceAll),
515
+ delete: backend.delete?.bind(backend),
515
516
  uploadFiles: backend.uploadFiles ? (files) => backend.uploadFiles(files) : void 0,
516
517
  downloadFiles: backend.downloadFiles ? (paths) => backend.downloadFiles(paths) : void 0,
517
518
  async read(filePath, offset, limit) {
@@ -526,6 +527,13 @@ function adaptBackendProtocol(backend) {
526
527
  return result;
527
528
  }
528
529
  };
530
+ const routePrefixes = backend.routePrefixes;
531
+ if (Array.isArray(routePrefixes)) Object.defineProperty(adapted, "routePrefixes", {
532
+ value: routePrefixes,
533
+ enumerable: true,
534
+ configurable: true
535
+ });
536
+ return adapted;
529
537
  }
530
538
  /**
531
539
  * Adapt a sandbox backend from v1 to v2 interface.
@@ -693,7 +701,8 @@ var StateBackend = class {
693
701
  * In legacy mode, this is a no-op — the caller uses `filesUpdate`
694
702
  * from the return value instead.
695
703
  *
696
- * @param update - Map of file paths to their updated {@link FileData}
704
+ * @param update - Map of file paths to their updated {@link FileData},
705
+ * or null deletion markers.
697
706
  */
698
707
  sendFilesUpdate(update) {
699
708
  if (this.isLegacy) return;
@@ -817,6 +826,15 @@ var StateBackend = class {
817
826
  };
818
827
  }
819
828
  /**
829
+ * Delete a file from state by sending a null deletion marker through Pregel.
830
+ */
831
+ delete(filePath) {
832
+ if (!(filePath in this.files)) return { error: `Error: File '${filePath}' not found` };
833
+ if (this.isLegacy) return { error: "StateBackend.delete requires a zero-argument StateBackend in a LangGraph execution context." };
834
+ this.sendFilesUpdate({ [filePath]: null });
835
+ return { path: filePath };
836
+ }
837
+ /**
820
838
  * Search file contents for a literal text pattern.
821
839
  * Binary files are skipped.
822
840
  */
@@ -1183,6 +1201,19 @@ var CompositeBackend = class {
1183
1201
  return await backend.edit(strippedKey, oldString, newString, replaceAll);
1184
1202
  }
1185
1203
  /**
1204
+ * Delete a file, routing to the appropriate backend.
1205
+ */
1206
+ async delete(filePath) {
1207
+ const [backend, strippedKey] = this.getBackendAndKey(filePath);
1208
+ if (!backend.delete) return { error: "Backend does not support delete" };
1209
+ const result = await backend.delete(strippedKey);
1210
+ if (result.path !== void 0) return {
1211
+ ...result,
1212
+ path: filePath
1213
+ };
1214
+ return result;
1215
+ }
1216
+ /**
1186
1217
  * Execute a command via the default backend.
1187
1218
  * Execution is not path-specific, so it always delegates to the default backend.
1188
1219
  *
@@ -1324,14 +1355,10 @@ const FILESYSTEM_TOOL_NAMES = [
1324
1355
  "grep",
1325
1356
  "execute"
1326
1357
  ];
1327
- const TOOLS_EXCLUDED_FROM_EVICTION = [
1328
- "ls",
1329
- "glob",
1330
- "grep",
1331
- "read_file",
1332
- "edit_file",
1333
- "write_file"
1334
- ];
1358
+ function isFilesystemToolName(name) {
1359
+ return typeof name === "string" && FILESYSTEM_TOOL_NAMES.includes(name);
1360
+ }
1361
+ const TOOLS_EXCLUDED_FROM_EVICTION = FILESYSTEM_TOOL_NAMES.filter((name) => name !== "execute");
1335
1362
  /**
1336
1363
  * Maximum size for binary (non-text) files read via read_file, in bytes.
1337
1364
  * Base64-encoded content is ~33% larger, so 10MB raw ≈ 13.3MB in context.
@@ -1499,19 +1526,46 @@ const FilesystemStateSchema = new _langchain_langgraph.StateSchema({ files: new
1499
1526
  inputSchema: zod_v4.z.record(zod_v4.z.string(), FileDataSchema.nullable()).optional(),
1500
1527
  reducer: fileDataReducer
1501
1528
  }) });
1529
+ /** Extract a message string from an unknown thrown value without `instanceof`. */
1530
+ function getErrorMessage$1(error) {
1531
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") return error.message;
1532
+ return String(error);
1533
+ }
1502
1534
  /**
1503
- * Throw a permission-denied error if `path` is denied under `rules`.
1535
+ * Check whether `path` is permitted under `rules` for `operation`, returning an
1536
+ * error string to surface to the model (or `undefined` when allowed).
1504
1537
  *
1505
- * No-op when `rules` is empty (permissive default). Paths that fail
1506
- * `validatePath` are silently skipped the tool's own input validation
1507
- * will surface a better error.
1538
+ * Never throws: an invalid path (non-absolute, or containing `..` or `~`) or a
1539
+ * denied path is a recoverable tool error, not a fatal run-ending one. Such
1540
+ * paths are rejected, never normalized, so they cannot bypass a deny rule or
1541
+ * reach the backend.
1508
1542
  *
1509
1543
  * @internal
1510
1544
  */
1511
- function enforcePermission(rules, operation, path) {
1545
+ function checkPermission(rules, operation, path) {
1512
1546
  if (rules.length === 0) return;
1513
- const canonical = validatePath(path);
1514
- if (decidePathAccess(rules, operation, canonical) === "deny") throw new Error(`Error: permission denied for ${operation} on ${canonical}`);
1547
+ let canonical;
1548
+ try {
1549
+ canonical = validatePath(path);
1550
+ } catch (error) {
1551
+ return `Error: ${getErrorMessage$1(error)}`;
1552
+ }
1553
+ if (decidePathAccess(rules, operation, canonical) === "deny") return `Error: permission denied for ${operation} on ${canonical}`;
1554
+ }
1555
+ /**
1556
+ * Build an error {@link ToolMessage} for a rejected or denied path. Returning a
1557
+ * bare string would be wrapped as a `status: "success"` message whose content
1558
+ * merely starts with "Error:"; marking `status: "error"` reports the failure
1559
+ * accurately so callers and the model can distinguish a real failure from a
1560
+ * successful result.
1561
+ */
1562
+ function toolError(runtime, toolName, message) {
1563
+ return new langchain.ToolMessage({
1564
+ content: message,
1565
+ name: toolName,
1566
+ tool_call_id: runtime.toolCall?.id,
1567
+ status: "error"
1568
+ });
1515
1569
  }
1516
1570
  /**
1517
1571
  * Filter a list of filesystem entries to those the rules permit.
@@ -1532,24 +1586,33 @@ function filterByPermissions(entries, rules, operation, getPath) {
1532
1586
  }
1533
1587
  });
1534
1588
  }
1535
- const FILESYSTEM_SYSTEM_PROMPT = langchain.context`
1536
- ## Following Conventions
1589
+ const FILESYSTEM_TOOL_DESCRIPTION_LINES = {
1590
+ ls: "ls: list files in a directory (requires absolute path)",
1591
+ read_file: "read_file: read a file from the filesystem",
1592
+ write_file: "write_file: write to a file in the filesystem",
1593
+ edit_file: "edit_file: edit a file in the filesystem",
1594
+ glob: "glob: find files matching a pattern (e.g., \"**/*.py\")",
1595
+ grep: "grep: search for text within files"
1596
+ };
1597
+ function hasFilesystemToolDescription(name) {
1598
+ return name in FILESYSTEM_TOOL_DESCRIPTION_LINES;
1599
+ }
1600
+ function buildFilesystemSystemPrompt(visibleTools) {
1601
+ const promptToolNames = FILESYSTEM_TOOL_NAMES.filter((name) => visibleTools.has(name));
1602
+ return langchain.context`
1603
+ ## Following Conventions
1537
1604
 
1538
- - Read files before editing — understand existing content before making changes
1539
- - Mimic existing style, naming conventions, and patterns
1605
+ - Read files before editing — understand existing content before making changes
1606
+ - Mimic existing style, naming conventions, and patterns
1540
1607
 
1541
- ## Filesystem Tools \`ls\`, \`read_file\`, \`write_file\`, \`edit_file\`, \`glob\`, \`grep\`
1608
+ ## Filesystem Tools ${promptToolNames.map((name) => `\`${name}\``).join(", ")}
1542
1609
 
1543
- You have access to a filesystem which you can interact with using these tools.
1544
- All file paths must start with a /.
1610
+ You have access to a filesystem which you can interact with using these tools.
1611
+ All file paths must start with a /.
1545
1612
 
1546
- - ls: list files in a directory (requires absolute path)
1547
- - read_file: read a file from the filesystem
1548
- - write_file: write to a file in the filesystem
1549
- - edit_file: edit a file in the filesystem
1550
- - glob: find files matching a pattern (e.g., "**/*.py")
1551
- - grep: search for text within files
1552
- `;
1613
+ ${promptToolNames.filter(hasFilesystemToolDescription).map((name) => `- ${FILESYSTEM_TOOL_DESCRIPTION_LINES[name]}`).join("\n")}
1614
+ `;
1615
+ }
1553
1616
  const LS_TOOL_DESCRIPTION = langchain.context`
1554
1617
  Lists all files in a directory.
1555
1618
 
@@ -1673,7 +1736,8 @@ const EXECUTION_SYSTEM_PROMPT = langchain.context`
1673
1736
  function createLsTool(backend, options) {
1674
1737
  const { customDescription, permissions } = options;
1675
1738
  return (0, langchain.tool)(async (input, runtime) => {
1676
- enforcePermission(permissions, "read", input.path ?? "/");
1739
+ const permissionError = checkPermission(permissions, "read", input.path ?? "/");
1740
+ if (permissionError !== void 0) return toolError(runtime, "ls", permissionError);
1677
1741
  const resolvedBackend = await resolveBackend(backend, runtime);
1678
1742
  const path = input.path || "/";
1679
1743
  const lsResult = await resolvedBackend.ls(path);
@@ -1701,7 +1765,8 @@ function createLsTool(backend, options) {
1701
1765
  function createReadFileTool(backend, options) {
1702
1766
  const { customDescription, toolTokenLimitBeforeEvict, permissions } = options;
1703
1767
  return (0, langchain.tool)(async (input, runtime) => {
1704
- enforcePermission(permissions, "read", input.file_path);
1768
+ const permissionError = checkPermission(permissions, "read", input.file_path);
1769
+ if (permissionError !== void 0) return toolError(runtime, "read_file", permissionError);
1705
1770
  const resolvedBackend = await resolveBackend(backend, runtime);
1706
1771
  const { file_path, offset = 0, limit = 100 } = input;
1707
1772
  const readResult = await resolvedBackend.read(file_path, offset, limit);
@@ -1778,7 +1843,8 @@ function createReadFileTool(backend, options) {
1778
1843
  function createWriteFileTool(backend, options) {
1779
1844
  const { customDescription, permissions } = options;
1780
1845
  return (0, langchain.tool)(async (input, runtime) => {
1781
- enforcePermission(permissions, "write", input.file_path);
1846
+ const permissionError = checkPermission(permissions, "write", input.file_path);
1847
+ if (permissionError !== void 0) return toolError(runtime, "write_file", permissionError);
1782
1848
  const resolvedBackend = await resolveBackend(backend, runtime);
1783
1849
  const { file_path, content } = input;
1784
1850
  const result = await resolvedBackend.write(file_path, content);
@@ -1809,7 +1875,8 @@ function createWriteFileTool(backend, options) {
1809
1875
  function createEditFileTool(backend, options) {
1810
1876
  const { customDescription, permissions } = options;
1811
1877
  return (0, langchain.tool)(async (input, runtime) => {
1812
- enforcePermission(permissions, "write", input.file_path);
1878
+ const permissionError = checkPermission(permissions, "write", input.file_path);
1879
+ if (permissionError !== void 0) return toolError(runtime, "edit_file", permissionError);
1813
1880
  const resolvedBackend = await resolveBackend(backend, runtime);
1814
1881
  const { file_path, old_string, new_string, replace_all = false } = input;
1815
1882
  const result = await resolvedBackend.edit(file_path, old_string, new_string, replace_all);
@@ -1842,7 +1909,8 @@ function createEditFileTool(backend, options) {
1842
1909
  function createGlobTool(backend, options) {
1843
1910
  const { customDescription, permissions } = options;
1844
1911
  return (0, langchain.tool)(async (input, runtime) => {
1845
- enforcePermission(permissions, "read", input.path ?? "/");
1912
+ const permissionError = checkPermission(permissions, "read", input.path ?? "/");
1913
+ if (permissionError !== void 0) return toolError(runtime, "glob", permissionError);
1846
1914
  const resolvedBackend = await resolveBackend(backend, runtime);
1847
1915
  const { pattern, path = "/" } = input;
1848
1916
  const globResult = await resolvedBackend.glob(pattern, path);
@@ -1867,7 +1935,8 @@ function createGlobTool(backend, options) {
1867
1935
  function createGrepTool(backend, options) {
1868
1936
  const { customDescription, permissions } = options;
1869
1937
  return (0, langchain.tool)(async (input, runtime) => {
1870
- enforcePermission(permissions, "read", input.path ?? "/");
1938
+ const permissionError = checkPermission(permissions, "read", input.path ?? "/");
1939
+ if (permissionError !== void 0) return toolError(runtime, "grep", permissionError);
1871
1940
  const resolvedBackend = await resolveBackend(backend, runtime);
1872
1941
  const { pattern, path = "/", glob = null } = input;
1873
1942
  const result = await resolvedBackend.grep(pattern, path, glob);
@@ -1923,6 +1992,12 @@ function createExecuteTool(backend, options) {
1923
1992
  * Returns true only when backend exposes route prefixes (CompositeBackend) and
1924
1993
  * every permission path is scoped under one of them.
1925
1994
  */
1995
+ function normalizeFilesystemTools(tools) {
1996
+ if (tools == null || tools === "all") return null;
1997
+ const enabledTools = new Set(tools);
1998
+ if (!enabledTools.has("read_file")) throw new Error("read_file must be included in tools; it is required by FilesystemMiddleware");
1999
+ return enabledTools;
2000
+ }
1926
2001
  function allPathsScopedToRoutes(permissions, backend) {
1927
2002
  if (!CompositeBackend.isInstance(backend)) return false;
1928
2003
  const prefixes = backend.routePrefixes;
@@ -1930,13 +2005,43 @@ function allPathsScopedToRoutes(permissions, backend) {
1930
2005
  return permissions.every((rule) => rule.paths.every((path) => prefixes.some((prefix) => path.startsWith(prefix.endsWith("/") ? prefix : `${prefix}/`))));
1931
2006
  }
1932
2007
  /**
1933
- * Create filesystem middleware with all tools and features.
2008
+ * Create middleware that provides built-in filesystem tools and filesystem-aware
2009
+ * prompt guidance.
2010
+ *
2011
+ * By default, the middleware registers every built-in filesystem tool listed in
2012
+ * {@link FILESYSTEM_TOOL_NAMES}. Use {@link FilesystemMiddlewareOptions.tools}
2013
+ * to narrow that set for read-only, search-only, or otherwise restricted
2014
+ * agents. The allowlist only controls built-in filesystem tools; custom tools
2015
+ * from the agent or other middleware are left untouched.
2016
+ *
2017
+ * The middleware also filters tools whose backend capabilities are unavailable
2018
+ * at request time. In particular, `execute` is only visible when the resolved
2019
+ * backend supports command execution. The filesystem prompt is generated from
2020
+ * the final visible filesystem tools so the model is not instructed to call
2021
+ * tools it cannot see.
2022
+ *
2023
+ * @param options Filesystem middleware configuration.
2024
+ * @returns Agent middleware that contributes filesystem state, tools, prompt
2025
+ * guidance, permission checks, and large-result eviction.
2026
+ *
2027
+ * @example Read-only filesystem middleware
2028
+ * ```ts
2029
+ * const middleware = createFilesystemMiddleware({
2030
+ * tools: ["read_file", "ls", "glob", "grep"],
2031
+ * });
2032
+ * ```
1934
2033
  */
1935
2034
  function createFilesystemMiddleware(options = {}) {
1936
- const { backend = (runtime) => new StateBackend(runtime), systemPrompt: customSystemPrompt = null, customToolDescriptions = null, toolTokenLimitBeforeEvict = 2e4, humanMessageTokenLimitBeforeEvict = 5e4, permissions = [] } = options;
2035
+ const { backend = (runtime) => new StateBackend(runtime), systemPrompt: customSystemPrompt = null, customToolDescriptions = null, toolTokenLimitBeforeEvict = 2e4, humanMessageTokenLimitBeforeEvict = 5e4, permissions = [], tools: filesystemTools = null } = options;
2036
+ const enabledFilesystemTools = normalizeFilesystemTools(filesystemTools);
2037
+ const executeToolEnabled = enabledFilesystemTools == null || enabledFilesystemTools.has("execute");
1937
2038
  if (permissions.length > 0) validatePermissionPaths(permissions);
1938
- if (permissions.length > 0 && typeof backend !== "function" && isSandboxBackend(backend) && !allPathsScopedToRoutes(permissions, backend)) throw new Error("Filesystem permissions cannot be used with a backend that supports command execution. Shell commands can access any path, making path-based rules ineffective. Either remove permissions, use a backend without execution support, or use a CompositeBackend with all permission paths scoped to a route prefix.");
1939
- const baseSystemPrompt = customSystemPrompt || FILESYSTEM_SYSTEM_PROMPT;
2039
+ if (permissions.length > 0 && executeToolEnabled && typeof backend !== "function" && isSandboxBackend(backend) && !allPathsScopedToRoutes(permissions, backend)) throw new Error("Filesystem permissions cannot be used with a backend that supports command execution. Shell commands can access any path, making path-based rules ineffective. Either remove permissions, use a backend without execution support, or use a CompositeBackend with all permission paths scoped to a route prefix.");
2040
+ const baseSystemPrompt = customSystemPrompt ?? null;
2041
+ /**
2042
+ * All tools including execute
2043
+ * (execute will be filtered at runtime if backend doesn't support it)
2044
+ */
1940
2045
  const allToolsByName = {
1941
2046
  ls: createLsTool(backend, {
1942
2047
  customDescription: customToolDescriptions?.ls,
@@ -1968,7 +2073,7 @@ function createFilesystemMiddleware(options = {}) {
1968
2073
  permissions
1969
2074
  })
1970
2075
  };
1971
- const allTools = Object.values(allToolsByName);
2076
+ const allTools = FILESYSTEM_TOOL_NAMES.filter((name) => enabledFilesystemTools == null || enabledFilesystemTools.has(name)).map((name) => allToolsByName[name]);
1972
2077
  async function processToolMessage(msg, runtime, state, fallbackToolCallId) {
1973
2078
  if (!toolTokenLimitBeforeEvict) return {
1974
2079
  message: msg,
@@ -2042,8 +2147,14 @@ function createFilesystemMiddleware(options = {}) {
2042
2147
  }));
2043
2148
  let tools = request.tools;
2044
2149
  if (!supportsExecution) tools = tools.filter((t) => t.name !== "execute");
2045
- let filesystemPrompt = baseSystemPrompt;
2046
- if (supportsExecution) filesystemPrompt = `${filesystemPrompt}\n\n${EXECUTION_SYSTEM_PROMPT}`;
2150
+ const visibleFilesystemTools = /* @__PURE__ */ new Set();
2151
+ for (const currentTool of tools) {
2152
+ const toolName = typeof currentTool.name === "string" ? currentTool.name : void 0;
2153
+ if (isFilesystemToolName(toolName)) visibleFilesystemTools.add(toolName);
2154
+ }
2155
+ const executionActive = supportsExecution && visibleFilesystemTools.has("execute");
2156
+ let filesystemPrompt = baseSystemPrompt ?? buildFilesystemSystemPrompt(visibleFilesystemTools);
2157
+ if (executionActive) filesystemPrompt = `${filesystemPrompt}\n\n${EXECUTION_SYSTEM_PROMPT}`;
2047
2158
  const newSystemMessage = request.systemMessage.concat(filesystemPrompt);
2048
2159
  let messages = request.messages;
2049
2160
  if (humanMessageTokenLimitBeforeEvict && messages) {
@@ -3487,6 +3598,41 @@ function createSkillsMiddleware(options) {
3487
3598
  *
3488
3599
  * This module provides shared helpers used across middleware implementations.
3489
3600
  */
3601
+ /**
3602
+ * Merge custom middleware into an assembled stack by `.name`.
3603
+ *
3604
+ * Matching custom middleware replaces the existing entry in place. New
3605
+ * middleware is appended after the base stack in caller-provided order.
3606
+ */
3607
+ function mergeMiddleware$1(base, custom) {
3608
+ const merged = new Map(base.map((middleware) => [middleware.name, middleware]));
3609
+ for (const middleware of custom) merged.set(middleware.name, middleware);
3610
+ return [...merged.values()];
3611
+ }
3612
+ function middlewareNames(middleware) {
3613
+ return new Set(middleware.map((entry) => entry.name));
3614
+ }
3615
+ function matchingMiddleware(middleware, names) {
3616
+ return middleware.filter((entry) => names.has(entry.name));
3617
+ }
3618
+ /**
3619
+ * Merge custom middleware into default and tail middleware segments.
3620
+ *
3621
+ * Same-name custom entries replace matching defaults in either segment. Novel
3622
+ * custom entries are inserted between the default and tail segments unless
3623
+ * `appendNew` is false.
3624
+ */
3625
+ function mergeMiddlewareStack(defaultMiddleware, customMiddleware, tailMiddleware = [], options = {}) {
3626
+ const defaultMiddlewareNames = middlewareNames(defaultMiddleware);
3627
+ const tailMiddlewareNames = middlewareNames(tailMiddleware);
3628
+ const knownMiddlewareNames = /* @__PURE__ */ new Set([...defaultMiddlewareNames, ...tailMiddlewareNames]);
3629
+ const novelMiddleware = options.appendNew === false ? [] : customMiddleware.filter((entry) => !knownMiddlewareNames.has(entry.name));
3630
+ return [
3631
+ ...mergeMiddleware$1(defaultMiddleware, matchingMiddleware(customMiddleware, defaultMiddlewareNames)),
3632
+ ...novelMiddleware,
3633
+ ...mergeMiddleware$1(tailMiddleware, matchingMiddleware(customMiddleware, tailMiddlewareNames))
3634
+ ];
3635
+ }
3490
3636
  //#endregion
3491
3637
  //#region src/middleware/completion_callback.ts
3492
3638
  /**
@@ -5079,6 +5225,28 @@ function createCacheBreakpointMiddleware() {
5079
5225
  });
5080
5226
  }
5081
5227
  //#endregion
5228
+ //#region src/middleware/tool_exclusion.ts
5229
+ function hasToolName(tool) {
5230
+ return tool !== null && typeof tool === "object" && "name" in tool && typeof tool.name === "string";
5231
+ }
5232
+ /**
5233
+ * Create middleware that removes excluded tools after all tool-injecting
5234
+ * middleware has had a chance to add tools to the request.
5235
+ *
5236
+ * @internal
5237
+ */
5238
+ function createToolExclusionMiddleware(excludedTools) {
5239
+ return (0, langchain.createMiddleware)({
5240
+ name: "_ToolExclusionMiddleware",
5241
+ wrapModelCall(request, handler) {
5242
+ return handler({
5243
+ ...request,
5244
+ tools: request.tools?.filter((tool) => !hasToolName(tool) || !excludedTools.has(tool.name))
5245
+ });
5246
+ }
5247
+ });
5248
+ }
5249
+ //#endregion
5082
5250
  //#region src/profiles/keys.ts
5083
5251
  /**
5084
5252
  * Normalize and validate a profile registry key.
@@ -5782,6 +5950,31 @@ const BASE_AGENT_PROMPT = langchain.context`
5782
5950
 
5783
5951
  For longer tasks, provide brief progress updates at reasonable intervals — a concise sentence recapping what you've done and what's next.
5784
5952
  `;
5953
+ const PROMPT_SEPARATOR = "\n\n";
5954
+ /** Normalize legacy system prompt values to the structured representation. */
5955
+ function normalizeSystemPrompt(systemPrompt) {
5956
+ if (systemPrompt === void 0) return {};
5957
+ if (typeof systemPrompt === "string" || langchain.SystemMessage.isInstance(systemPrompt)) return { prefix: systemPrompt };
5958
+ return systemPrompt;
5959
+ }
5960
+ /** Assemble prompt parts while preserving structured message content blocks. */
5961
+ function assemblePromptParts(parts) {
5962
+ if (parts.length === 0) return "";
5963
+ if (parts.every((part) => typeof part === "string")) return parts.join(PROMPT_SEPARATOR);
5964
+ const contentBlocks = [];
5965
+ for (const [index, part] of parts.entries()) {
5966
+ if (index > 0) contentBlocks.push({
5967
+ type: "text",
5968
+ text: PROMPT_SEPARATOR
5969
+ });
5970
+ if (langchain.SystemMessage.isInstance(part)) contentBlocks.push(...part.contentBlocks);
5971
+ else contentBlocks.push({
5972
+ type: "text",
5973
+ text: part
5974
+ });
5975
+ }
5976
+ return new langchain.SystemMessage({ contentBlocks });
5977
+ }
5785
5978
  const BUILTIN_TOOL_NAMES = /* @__PURE__ */ new Set([
5786
5979
  ...FILESYSTEM_TOOL_NAMES,
5787
5980
  ...ASYNC_TASK_TOOL_NAMES,
@@ -5827,6 +6020,8 @@ function createDeepAgent(params = {}) {
5827
6020
  providerHint: getModelProvider(model),
5828
6021
  identifierHint: getModelIdentifier(model)
5829
6022
  });
6023
+ const filesystemTools = FILESYSTEM_TOOL_NAMES.filter((toolName) => !harnessProfile.excludedTools.has(toolName));
6024
+ const profileFilesystemTools = filesystemTools.length === FILESYSTEM_TOOL_NAMES.length || !filesystemTools.includes("read_file") ? void 0 : filesystemTools;
5830
6025
  const toolOverrides = harnessProfile.toolDescriptionOverrides;
5831
6026
  const effectiveTools = Object.keys(toolOverrides).length > 0 ? tools.map((t) => t.name in toolOverrides ? Object.assign(Object.create(Object.getPrototypeOf(t)), t, { description: toolOverrides[t.name] }) : t) : tools;
5832
6027
  const anthropicModel = isAnthropicModel(model);
@@ -5848,23 +6043,26 @@ function createDeepAgent(params = {}) {
5848
6043
  * Only the general-purpose subagent inherits the main agent's skills.
5849
6044
  * If a custom subagent needs skills, it must specify its own `skills` array.
5850
6045
  */
5851
- const normalizeSubagentSpec = (input) => {
6046
+ const createSubagentDefaultMiddleware = (input) => {
5852
6047
  const effectivePermissions = input.permissions ?? permissions;
5853
- const subagentMiddleware = [
6048
+ return [
5854
6049
  (0, langchain.todoListMiddleware)(),
5855
6050
  createFilesystemMiddleware({
5856
6051
  backend,
5857
- permissions: effectivePermissions
6052
+ permissions: effectivePermissions,
6053
+ tools: profileFilesystemTools
5858
6054
  }),
5859
6055
  createSummarizationMiddleware({ backend }),
5860
6056
  createPatchToolCallsMiddleware(),
5861
6057
  ...input.skills != null && input.skills.length > 0 ? [createSkillsMiddleware({
5862
6058
  backend,
5863
6059
  sources: input.skills
5864
- })] : [],
5865
- ...input.middleware ?? [],
5866
- ...cacheMiddleware
6060
+ })] : []
5867
6061
  ];
6062
+ };
6063
+ const normalizeSubagentSpec = (input) => {
6064
+ let subagentMiddleware = mergeMiddlewareStack(createSubagentDefaultMiddleware(input), input.middleware ?? [], cacheMiddleware);
6065
+ if (harnessProfile.excludedMiddleware.size > 0) subagentMiddleware = subagentMiddleware.filter((middleware) => !harnessProfile.excludedMiddleware.has(middleware.name));
5868
6066
  return {
5869
6067
  ...input,
5870
6068
  tools: input.tools ?? [],
@@ -5885,6 +6083,7 @@ function createDeepAgent(params = {}) {
5885
6083
  skills,
5886
6084
  tools: effectiveTools
5887
6085
  });
6086
+ generalPurposeSpec.middleware = mergeMiddlewareStack(generalPurposeSpec.middleware ?? [], customMiddleware, [], { appendNew: false });
5888
6087
  inlineSubagents.unshift(generalPurposeSpec);
5889
6088
  }
5890
6089
  const skillsMiddleware = skills != null && skills.length > 0 ? [createSkillsMiddleware({
@@ -5895,7 +6094,8 @@ function createDeepAgent(params = {}) {
5895
6094
  (0, langchain.todoListMiddleware)(),
5896
6095
  createFilesystemMiddleware({
5897
6096
  backend,
5898
- permissions
6097
+ permissions,
6098
+ tools: profileFilesystemTools
5899
6099
  }),
5900
6100
  createSubAgentMiddleware({
5901
6101
  defaultModel: model,
@@ -5907,15 +6107,16 @@ function createDeepAgent(params = {}) {
5907
6107
  createSummarizationMiddleware({ backend }),
5908
6108
  createPatchToolCallsMiddleware()
5909
6109
  ];
5910
- const middleware = [
6110
+ let middleware = mergeMiddlewareStack([
5911
6111
  todoMiddleware,
5912
6112
  ...skillsMiddleware,
5913
6113
  fsMiddleware,
5914
6114
  subagentMiddleware,
5915
6115
  summarizationMiddleware,
5916
6116
  patchToolCallsMiddleware,
5917
- ...asyncSubAgents.length > 0 ? [createAsyncSubAgentMiddleware({ asyncSubAgents })] : [],
5918
- ...customMiddleware,
6117
+ ...asyncSubAgents.length > 0 ? [createAsyncSubAgentMiddleware({ asyncSubAgents })] : []
6118
+ ], customMiddleware, [
6119
+ ...resolveMiddleware(harnessProfile.extraMiddleware),
5919
6120
  ...cacheMiddleware,
5920
6121
  ...memory && memory.length > 0 ? [createMemoryMiddleware({
5921
6122
  backend,
@@ -5923,32 +6124,19 @@ function createDeepAgent(params = {}) {
5923
6124
  addCacheControl: anthropicModel
5924
6125
  })] : [],
5925
6126
  ...interruptOn ? [(0, langchain.humanInTheLoopMiddleware)({ interruptOn })] : []
5926
- ];
5927
- const profileMiddleware = resolveMiddleware(harnessProfile.extraMiddleware);
5928
- if (profileMiddleware.length > 0) {
5929
- const cacheIdx = middleware.findIndex((m) => m.name === "AnthropicPromptCachingMiddleware");
5930
- if (cacheIdx !== -1) middleware.splice(cacheIdx, 0, ...profileMiddleware);
5931
- else middleware.push(...profileMiddleware);
5932
- }
6127
+ ]);
5933
6128
  if (harnessProfile.excludedMiddleware.size > 0) {
5934
6129
  const excluded = harnessProfile.excludedMiddleware;
5935
- const filtered = middleware.filter((m) => !excluded.has(m.name));
5936
- middleware.length = 0;
5937
- middleware.push(...filtered);
5938
- }
5939
- if (harnessProfile.excludedTools.size > 0) {
5940
- const excludedTools = harnessProfile.excludedTools;
5941
- middleware.push((0, langchain.createMiddleware)({
5942
- name: "_ToolExclusionMiddleware",
5943
- wrapModelCall: async (request, handler) => {
5944
- return handler({
5945
- ...request,
5946
- tools: request.tools?.filter((t) => !excludedTools.has(t.name))
5947
- });
5948
- }
5949
- }));
5950
- }
5951
- const effectiveBasePrompt = applyProfilePrompt(harnessProfile, BASE_AGENT_PROMPT);
6130
+ middleware = middleware.filter((entry) => !excluded.has(entry.name));
6131
+ }
6132
+ if (harnessProfile.excludedTools.size > 0) middleware.push(createToolExclusionMiddleware(harnessProfile.excludedTools));
6133
+ const promptConfig = normalizeSystemPrompt(systemPrompt);
6134
+ const promptParts = [];
6135
+ if (promptConfig.prefix !== void 0 && promptConfig.prefix !== null) promptParts.push(promptConfig.prefix);
6136
+ const activeBasePrompt = promptConfig.base !== void 0 ? promptConfig.base : harnessProfile.baseSystemPrompt ?? BASE_AGENT_PROMPT;
6137
+ if (activeBasePrompt !== null) promptParts.push(activeBasePrompt);
6138
+ if (promptConfig.suffix) promptParts.push(promptConfig.suffix);
6139
+ if (harnessProfile.systemPromptSuffix) promptParts.push(harnessProfile.systemPromptSuffix);
5952
6140
  /**
5953
6141
  * Return as DeepAgent with proper DeepAgentTypeConfig
5954
6142
  * - Response: InferStructuredResponse<TResponse> (unwraps ToolStrategy<T>/ProviderStrategy<T> → T)
@@ -5961,19 +6149,7 @@ function createDeepAgent(params = {}) {
5961
6149
  */
5962
6150
  return (0, langchain.createAgent)({
5963
6151
  model,
5964
- systemPrompt: typeof systemPrompt === "string" ? new langchain.SystemMessage({ contentBlocks: [{
5965
- type: "text",
5966
- text: systemPrompt
5967
- }, {
5968
- type: "text",
5969
- text: effectiveBasePrompt
5970
- }] }) : langchain.SystemMessage.isInstance(systemPrompt) ? new langchain.SystemMessage({ contentBlocks: [...systemPrompt.contentBlocks, {
5971
- type: "text",
5972
- text: effectiveBasePrompt
5973
- }] }) : new langchain.SystemMessage({ contentBlocks: [{
5974
- type: "text",
5975
- text: effectiveBasePrompt
5976
- }] }),
6152
+ systemPrompt: assemblePromptParts(promptParts),
5977
6153
  stateSchema,
5978
6154
  tools: effectiveTools,
5979
6155
  middleware,
@@ -6325,6 +6501,19 @@ var StoreBackend = class {
6325
6501
  }
6326
6502
  }
6327
6503
  /**
6504
+ * Delete a file from the store.
6505
+ *
6506
+ * The file path is used as an exact store key. Wildcards are treated
6507
+ * literally and do not expand to multiple entries.
6508
+ */
6509
+ async delete(filePath) {
6510
+ const store = this.getStore();
6511
+ const namespace = this.getNamespace();
6512
+ if (!await store.get(namespace, filePath)) return { error: `Error: File '${filePath}' not found` };
6513
+ await store.delete(namespace, filePath);
6514
+ return { path: filePath };
6515
+ }
6516
+ /**
6328
6517
  * Search file contents for a literal text pattern.
6329
6518
  * Binary files are skipped.
6330
6519
  */
@@ -6537,10 +6726,10 @@ var ContextHubBackend = class ContextHubBackend {
6537
6726
  if (this.cache === null) throw new Error("Context Hub cache failed to initialize");
6538
6727
  return this.cache;
6539
6728
  }
6540
- async commit(files) {
6541
- if (Object.keys(files).length === 0) return;
6729
+ async commit(changes) {
6730
+ if (Object.keys(changes).length === 0) return;
6542
6731
  const payload = {};
6543
- for (const [path, content] of Object.entries(files)) payload[path] = {
6732
+ for (const [path, content] of Object.entries(changes)) payload[path] = content === null ? null : {
6544
6733
  type: "file",
6545
6734
  content
6546
6735
  };
@@ -6550,7 +6739,14 @@ var ContextHubBackend = class ContextHubBackend {
6550
6739
  });
6551
6740
  const match = URL_COMMIT_SUFFIX_RE.exec(url);
6552
6741
  if (match) this.commitHash = match[1];
6553
- if (this.cache !== null) for (const [path, content] of Object.entries(files)) this.cache[path] = content;
6742
+ if (this.cache !== null) {
6743
+ const deletions = new Set(Object.entries(changes).filter(([, content]) => content === null).map(([path]) => path));
6744
+ const updates = Object.fromEntries(Object.entries(changes).filter((entry) => entry[1] !== null));
6745
+ this.cache = {
6746
+ ...Object.fromEntries(Object.entries(this.cache).filter(([path]) => !deletions.has(path))),
6747
+ ...updates
6748
+ };
6749
+ }
6554
6750
  }
6555
6751
  /**
6556
6752
  * Return linked-entry paths mapped to their repo handles.
@@ -6709,6 +6905,20 @@ var ContextHubBackend = class ContextHubBackend {
6709
6905
  throw error;
6710
6906
  }
6711
6907
  }
6908
+ async delete(filePath) {
6909
+ const hubPath = ContextHubBackend.stripPrefix(filePath);
6910
+ try {
6911
+ if (!(hubPath in await this.ensureCache())) return { error: `Error: File '${filePath}' not found` };
6912
+ await this.commit({ [hubPath]: null });
6913
+ return { path: filePath };
6914
+ } catch (error) {
6915
+ if (isLangSmithError(error)) {
6916
+ this.cache = null;
6917
+ return { error: ContextHubBackend.toHubUnavailableError(error) };
6918
+ }
6919
+ throw error;
6920
+ }
6921
+ }
6712
6922
  async uploadFiles(files) {
6713
6923
  const decoder = new TextDecoder("utf-8", { fatal: true });
6714
6924
  const decoded = [];
@@ -7201,6 +7411,16 @@ var BaseSandbox = class {
7201
7411
  occurrences: count
7202
7412
  };
7203
7413
  }
7414
+ /**
7415
+ * Delete a file from the sandbox via a server-side rm.
7416
+ *
7417
+ * Uses rm -f, so deleting a path that does not exist succeeds silently.
7418
+ */
7419
+ async delete(filePath) {
7420
+ const result = await this.execute(`rm -f ${shellQuote(filePath)}`);
7421
+ if (result.exitCode === 0) return { path: filePath };
7422
+ return { error: `Error deleting file '${filePath}': ${result.output.trim() || "unknown error"}` };
7423
+ }
7204
7424
  };
7205
7425
  //#endregion
7206
7426
  //#region src/backends/langsmith.ts
@@ -7691,4 +7911,4 @@ Object.defineProperty(exports, "serializeProfile", {
7691
7911
  }
7692
7912
  });
7693
7913
 
7694
- //# sourceMappingURL=langsmith-CiAeUke2.cjs.map
7914
+ //# sourceMappingURL=langsmith-DhbsxI45.cjs.map