deepagents 1.6.1 → 1.6.3

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.
package/dist/index.cjs CHANGED
@@ -193,8 +193,14 @@ function formatReadResponse(fileData, offset, limit) {
193
193
  * @param newString - Replacement string
194
194
  * @param replaceAll - Whether to replace all occurrences
195
195
  * @returns Tuple of [new_content, occurrences] on success, or error message string
196
+ *
197
+ * Special case: When both content and oldString are empty, this sets the initial
198
+ * content to newString. This allows editing empty files by treating empty oldString
199
+ * as "set initial content" rather than "replace nothing".
196
200
  */
197
201
  function performStringReplacement(content, oldString, newString, replaceAll) {
202
+ if (content === "" && oldString === "") return [newString, 0];
203
+ if (oldString === "") return "Error: oldString cannot be empty when file has content";
198
204
  const occurrences = content.split(oldString).length - 1;
199
205
  if (occurrences === 0) return `Error: String not found in file: '${oldString}'`;
200
206
  if (occurrences > 1 && !replaceAll) return `Error: String '${oldString}' appears ${occurrences} times in file. Use replace_all=True to replace all instances, or provide a more specific string with surrounding context.`;
@@ -557,6 +563,18 @@ const TOOLS_EXCLUDED_FROM_EVICTION = [
557
563
  */
558
564
  const NUM_CHARS_PER_TOKEN = 4;
559
565
  /**
566
+ * Default values for read_file tool pagination (in lines).
567
+ */
568
+ const DEFAULT_READ_LINE_OFFSET = 0;
569
+ const DEFAULT_READ_LINE_LIMIT = 100;
570
+ /**
571
+ * Template for truncation message in read_file.
572
+ * {file_path} will be filled in at runtime.
573
+ */
574
+ const READ_FILE_TRUNCATION_MSG = `
575
+
576
+ [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.]`;
577
+ /**
560
578
  * Message template for evicted tool results.
561
579
  */
562
580
  const TOO_LARGE_TOOL_MSG = `Tool result too large, the result of this tool call {tool_call_id} was saved in the filesystem at this path: {file_path}
@@ -661,7 +679,7 @@ const READ_FILE_TOOL_DESCRIPTION = `Reads a file from the filesystem.
661
679
  Assume this tool is able to read all files. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.
662
680
 
663
681
  Usage:
664
- - By default, it reads up to 500 lines starting from the beginning of the file
682
+ - By default, it reads up to 100 lines starting from the beginning of the file
665
683
  - **IMPORTANT for large files and codebase exploration**: Use pagination with offset and limit parameters to avoid context overflow
666
684
  - First scan: read_file(path, limit=100) to see file structure
667
685
  - Read more sections: read_file(path, offset=100, limit=200) for next 200 lines
@@ -781,21 +799,29 @@ function createLsTool(backend, options) {
781
799
  * Create read_file tool using backend.
782
800
  */
783
801
  function createReadFileTool(backend, options) {
784
- const { customDescription } = options;
802
+ const { customDescription, toolTokenLimitBeforeEvict } = options;
785
803
  return (0, langchain.tool)(async (input, config) => {
786
804
  const resolvedBackend = getBackend(backend, {
787
805
  state: (0, _langchain_langgraph.getCurrentTaskInput)(config),
788
806
  store: config.store
789
807
  });
790
- const { file_path, offset = 0, limit = 500 } = input;
791
- return await resolvedBackend.read(file_path, offset, limit);
808
+ const { file_path, offset = DEFAULT_READ_LINE_OFFSET, limit = DEFAULT_READ_LINE_LIMIT } = input;
809
+ let result = await resolvedBackend.read(file_path, offset, limit);
810
+ const lines = result.split("\n");
811
+ if (lines.length > limit) result = lines.slice(0, limit).join("\n");
812
+ if (toolTokenLimitBeforeEvict && result.length >= NUM_CHARS_PER_TOKEN * toolTokenLimitBeforeEvict) {
813
+ const truncationMsg = READ_FILE_TRUNCATION_MSG.replace("{file_path}", file_path);
814
+ const maxContentLength = NUM_CHARS_PER_TOKEN * toolTokenLimitBeforeEvict - truncationMsg.length;
815
+ result = result.substring(0, maxContentLength) + truncationMsg;
816
+ }
817
+ return result;
792
818
  }, {
793
819
  name: "read_file",
794
820
  description: customDescription || READ_FILE_TOOL_DESCRIPTION,
795
821
  schema: zod_v4.z.object({
796
822
  file_path: zod_v4.z.string().describe("Absolute path to the file to read"),
797
- offset: zod_v4.z.coerce.number().optional().default(0).describe("Line offset to start reading from (0-indexed)"),
798
- limit: zod_v4.z.coerce.number().optional().default(500).describe("Maximum number of lines to read")
823
+ offset: zod_v4.z.coerce.number().optional().default(DEFAULT_READ_LINE_OFFSET).describe("Line offset to start reading from (0-indexed)"),
824
+ limit: zod_v4.z.coerce.number().optional().default(DEFAULT_READ_LINE_LIMIT).describe("Maximum number of lines to read")
799
825
  })
800
826
  });
801
827
  }
@@ -960,7 +986,10 @@ function createFilesystemMiddleware(options = {}) {
960
986
  stateSchema: FilesystemStateSchema,
961
987
  tools: [
962
988
  createLsTool(backend, { customDescription: customToolDescriptions?.ls }),
963
- createReadFileTool(backend, { customDescription: customToolDescriptions?.read_file }),
989
+ createReadFileTool(backend, {
990
+ customDescription: customToolDescriptions?.read_file,
991
+ toolTokenLimitBeforeEvict
992
+ }),
964
993
  createWriteFileTool(backend, { customDescription: customToolDescriptions?.write_file }),
965
994
  createEditFileTool(backend, { customDescription: customToolDescriptions?.edit_file }),
966
995
  createGlobTool(backend, { customDescription: customToolDescriptions?.glob }),
@@ -1324,12 +1353,52 @@ function createSubAgentMiddleware(options) {
1324
1353
  //#endregion
1325
1354
  //#region src/middleware/patch_tool_calls.ts
1326
1355
  /**
1356
+ * Patch dangling tool calls in a messages array.
1357
+ * Returns the patched messages array and a flag indicating if patching was needed.
1358
+ *
1359
+ * @param messages - The messages array to patch
1360
+ * @returns Object with patched messages and needsPatch flag
1361
+ */
1362
+ function patchDanglingToolCalls(messages) {
1363
+ if (!messages || messages.length === 0) return {
1364
+ patchedMessages: [],
1365
+ needsPatch: false
1366
+ };
1367
+ const patchedMessages = [];
1368
+ let needsPatch = false;
1369
+ for (let i = 0; i < messages.length; i++) {
1370
+ const msg = messages[i];
1371
+ patchedMessages.push(msg);
1372
+ if (langchain.AIMessage.isInstance(msg) && msg.tool_calls != null) {
1373
+ for (const toolCall of msg.tool_calls) if (!messages.slice(i).find((m) => langchain.ToolMessage.isInstance(m) && m.tool_call_id === toolCall.id)) {
1374
+ needsPatch = true;
1375
+ const toolMsg = `Tool call ${toolCall.name} with id ${toolCall.id} was cancelled - another message came in before it could be completed.`;
1376
+ patchedMessages.push(new langchain.ToolMessage({
1377
+ content: toolMsg,
1378
+ name: toolCall.name,
1379
+ tool_call_id: toolCall.id
1380
+ }));
1381
+ }
1382
+ }
1383
+ }
1384
+ return {
1385
+ patchedMessages,
1386
+ needsPatch
1387
+ };
1388
+ }
1389
+ /**
1327
1390
  * Create middleware that patches dangling tool calls in the messages history.
1328
1391
  *
1329
1392
  * When an AI message contains tool_calls but subsequent messages don't include
1330
1393
  * the corresponding ToolMessage responses, this middleware adds synthetic
1331
1394
  * ToolMessages saying the tool call was cancelled.
1332
1395
  *
1396
+ * This middleware patches in two places:
1397
+ * 1. `beforeAgent`: Patches state at the start of the agent loop (handles most cases)
1398
+ * 2. `wrapModelCall`: Patches the request right before model invocation (handles
1399
+ * edge cases like HITL rejection during graph resume where state updates from
1400
+ * beforeAgent may not be applied in time)
1401
+ *
1333
1402
  * @returns AgentMiddleware that patches dangling tool calls
1334
1403
  *
1335
1404
  * @example
@@ -1349,26 +1418,61 @@ function createPatchToolCallsMiddleware() {
1349
1418
  beforeAgent: async (state) => {
1350
1419
  const messages = state.messages;
1351
1420
  if (!messages || messages.length === 0) return;
1352
- const patchedMessages = [];
1353
- for (let i = 0; i < messages.length; i++) {
1354
- const msg = messages[i];
1355
- patchedMessages.push(msg);
1356
- if (langchain.AIMessage.isInstance(msg) && msg.tool_calls != null) {
1357
- for (const toolCall of msg.tool_calls) if (!messages.slice(i).find((m) => langchain.ToolMessage.isInstance(m) && m.tool_call_id === toolCall.id)) {
1358
- const toolMsg = `Tool call ${toolCall.name} with id ${toolCall.id} was cancelled - another message came in before it could be completed.`;
1359
- patchedMessages.push(new langchain.ToolMessage({
1360
- content: toolMsg,
1361
- name: toolCall.name,
1362
- tool_call_id: toolCall.id
1363
- }));
1364
- }
1365
- }
1366
- }
1421
+ const { patchedMessages, needsPatch } = patchDanglingToolCalls(messages);
1422
+ /**
1423
+ * Only trigger REMOVE_ALL_MESSAGES if patching is actually needed
1424
+ */
1425
+ if (!needsPatch) return;
1367
1426
  return { messages: [new _langchain_core_messages.RemoveMessage({ id: _langchain_langgraph.REMOVE_ALL_MESSAGES }), ...patchedMessages] };
1427
+ },
1428
+ wrapModelCall: async (request, handler) => {
1429
+ const messages = request.messages;
1430
+ if (!messages || messages.length === 0) return handler(request);
1431
+ const { patchedMessages, needsPatch } = patchDanglingToolCalls(messages);
1432
+ if (!needsPatch) return handler(request);
1433
+ return handler({
1434
+ ...request,
1435
+ messages: patchedMessages
1436
+ });
1368
1437
  }
1369
1438
  });
1370
1439
  }
1371
1440
 
1441
+ //#endregion
1442
+ //#region src/values.ts
1443
+ /**
1444
+ * Shared state values for use in StateSchema definitions.
1445
+ *
1446
+ * This module provides pre-configured ReducedValue instances that can be
1447
+ * reused across different state schemas, similar to LangGraph's messagesValue.
1448
+ */
1449
+ /**
1450
+ * Shared ReducedValue for file data state management.
1451
+ *
1452
+ * This provides a reusable pattern for managing file state with automatic
1453
+ * merging of concurrent updates from parallel subagents. Files can be updated
1454
+ * or deleted (using null values) and the reducer handles the merge logic.
1455
+ *
1456
+ * Similar to LangGraph's messagesValue, this encapsulates the common pattern
1457
+ * of managing files in agent state so you don't have to manually configure
1458
+ * the ReducedValue each time.
1459
+ *
1460
+ * @example
1461
+ * ```typescript
1462
+ * import { filesValue } from "@anthropic/deepagents";
1463
+ * import { StateSchema } from "@langchain/langgraph";
1464
+ *
1465
+ * const MyStateSchema = new StateSchema({
1466
+ * files: filesValue,
1467
+ * // ... other state fields
1468
+ * });
1469
+ * ```
1470
+ */
1471
+ const filesValue = new _langchain_langgraph.ReducedValue(zod.z.record(zod.z.string(), FileDataSchema).default(() => ({})), {
1472
+ inputSchema: zod.z.record(zod.z.string(), FileDataSchema.nullable()).optional(),
1473
+ reducer: fileDataReducer
1474
+ });
1475
+
1372
1476
  //#endregion
1373
1477
  //#region src/middleware/memory.ts
1374
1478
  /**
@@ -1423,7 +1527,10 @@ function createPatchToolCallsMiddleware() {
1423
1527
  /**
1424
1528
  * State schema for memory middleware.
1425
1529
  */
1426
- const MemoryStateSchema = zod.z.object({ memoryContents: zod.z.record(zod.z.string(), zod.z.string()).optional() });
1530
+ const MemoryStateSchema = new _langchain_langgraph.StateSchema({
1531
+ memoryContents: zod.z.record(zod.z.string(), zod.z.string()).optional(),
1532
+ files: filesValue
1533
+ });
1427
1534
  /**
1428
1535
  * Default system prompt template for memory.
1429
1536
  * Ported from Python's comprehensive memory guidelines.
@@ -1567,12 +1674,10 @@ function createMemoryMiddleware(options) {
1567
1674
  },
1568
1675
  wrapModelCall(request, handler) {
1569
1676
  const formattedContents = formatMemoryContents(request.state?.memoryContents || {}, sources);
1570
- const memorySection = MEMORY_SYSTEM_PROMPT.replace("{memory_contents}", formattedContents);
1571
- const currentSystemPrompt = request.systemPrompt || "";
1572
- const newSystemPrompt = currentSystemPrompt ? `${memorySection}\n\n${currentSystemPrompt}` : memorySection;
1677
+ const newSystemMessage = new langchain.SystemMessage(MEMORY_SYSTEM_PROMPT.replace("{memory_contents}", formattedContents)).concat(request.systemMessage);
1573
1678
  return handler({
1574
1679
  ...request,
1575
- systemPrompt: newSystemPrompt
1680
+ systemMessage: newSystemMessage
1576
1681
  });
1577
1682
  }
1578
1683
  });
@@ -1655,10 +1760,13 @@ function skillsMetadataReducer(current, update) {
1655
1760
  * State schema for skills middleware.
1656
1761
  * Uses ReducedValue for skillsMetadata to allow concurrent updates from parallel subagents.
1657
1762
  */
1658
- const SkillsStateSchema = new _langchain_langgraph.StateSchema({ skillsMetadata: new _langchain_langgraph.ReducedValue(zod.z.array(SkillMetadataEntrySchema).default(() => []), {
1659
- inputSchema: zod.z.array(SkillMetadataEntrySchema).optional(),
1660
- reducer: skillsMetadataReducer
1661
- }) });
1763
+ const SkillsStateSchema = new _langchain_langgraph.StateSchema({
1764
+ skillsMetadata: new _langchain_langgraph.ReducedValue(zod.z.array(SkillMetadataEntrySchema).default(() => []), {
1765
+ inputSchema: zod.z.array(SkillMetadataEntrySchema).optional(),
1766
+ reducer: skillsMetadataReducer
1767
+ }),
1768
+ files: filesValue
1769
+ });
1662
1770
  /**
1663
1771
  * Skills System Documentation prompt template.
1664
1772
  */
@@ -1879,7 +1987,7 @@ function createSkillsMiddleware(options) {
1879
1987
  stateSchema: SkillsStateSchema,
1880
1988
  async beforeAgent(state) {
1881
1989
  if (loadedSkills.length > 0) return;
1882
- if ("skillsMetadata" in state && state.skillsMetadata != null) {
1990
+ if ("skillsMetadata" in state && Array.isArray(state.skillsMetadata) && state.skillsMetadata.length > 0) {
1883
1991
  loadedSkills = state.skillsMetadata;
1884
1992
  return;
1885
1993
  }
@@ -3457,6 +3565,9 @@ const BASE_PROMPT = `In order to complete the objective that the user asks of yo
3457
3565
  */
3458
3566
  function createDeepAgent(params = {}) {
3459
3567
  const { model = "claude-sonnet-4-5-20250929", tools = [], systemPrompt, middleware: customMiddleware = [], subagents = [], responseFormat, contextSchema, checkpointer, store, backend, interruptOn, name, memory, skills } = params;
3568
+ /**
3569
+ * Combine system prompt with base prompt like Python implementation
3570
+ */
3460
3571
  const finalSystemPrompt = systemPrompt ? typeof systemPrompt === "string" ? `${systemPrompt}\n\n${BASE_PROMPT}` : new langchain.SystemMessage({ content: [{
3461
3572
  type: "text",
3462
3573
  text: BASE_PROMPT
@@ -3464,22 +3575,61 @@ function createDeepAgent(params = {}) {
3464
3575
  type: "text",
3465
3576
  text: systemPrompt.content
3466
3577
  }] : systemPrompt.content] }) : BASE_PROMPT;
3578
+ /**
3579
+ * Create backend configuration for filesystem middleware
3580
+ * If no backend is provided, use a factory that creates a StateBackend
3581
+ */
3467
3582
  const filesystemBackend = backend ? backend : (config) => new StateBackend(config);
3468
- const skillsMiddleware = skills != null && skills.length > 0 ? [createSkillsMiddleware({
3583
+ /**
3584
+ * Skills middleware (created conditionally for runtime use)
3585
+ */
3586
+ const skillsMiddlewareArray = skills != null && skills.length > 0 ? [createSkillsMiddleware({
3469
3587
  backend: filesystemBackend,
3470
3588
  sources: skills
3471
3589
  })] : [];
3472
- const builtInMiddleware = [
3473
- (0, langchain.todoListMiddleware)(),
3474
- ...skillsMiddleware,
3475
- createFilesystemMiddleware({ backend: filesystemBackend }),
3476
- createSubAgentMiddleware({
3477
- defaultModel: model,
3478
- defaultTools: tools,
3479
- defaultMiddleware: [
3590
+ /**
3591
+ * Memory middleware (created conditionally for runtime use)
3592
+ */
3593
+ const memoryMiddlewareArray = memory != null && memory.length > 0 ? [createMemoryMiddleware({
3594
+ backend: filesystemBackend,
3595
+ sources: memory
3596
+ })] : [];
3597
+ /**
3598
+ * Return as DeepAgent with proper DeepAgentTypeConfig
3599
+ * - Response: TResponse (from responseFormat parameter)
3600
+ * - State: undefined (state comes from middleware)
3601
+ * - Context: ContextSchema
3602
+ * - Middleware: AllMiddleware (built-in + custom + subagent middleware for state inference)
3603
+ * - Tools: TTools
3604
+ * - Subagents: TSubagents (for type-safe streaming)
3605
+ */
3606
+ return (0, langchain.createAgent)({
3607
+ model,
3608
+ systemPrompt: finalSystemPrompt,
3609
+ tools,
3610
+ middleware: [
3611
+ ...[
3480
3612
  (0, langchain.todoListMiddleware)(),
3481
- ...skillsMiddleware,
3482
3613
  createFilesystemMiddleware({ backend: filesystemBackend }),
3614
+ createSubAgentMiddleware({
3615
+ defaultModel: model,
3616
+ defaultTools: tools,
3617
+ defaultMiddleware: [
3618
+ (0, langchain.todoListMiddleware)(),
3619
+ ...skillsMiddlewareArray,
3620
+ createFilesystemMiddleware({ backend: filesystemBackend }),
3621
+ (0, langchain.summarizationMiddleware)({
3622
+ model,
3623
+ trigger: { tokens: 17e4 },
3624
+ keep: { messages: 6 }
3625
+ }),
3626
+ (0, langchain.anthropicPromptCachingMiddleware)({ unsupportedModelBehavior: "ignore" }),
3627
+ createPatchToolCallsMiddleware()
3628
+ ],
3629
+ defaultInterruptOn: interruptOn,
3630
+ subagents,
3631
+ generalPurposeAgent: true
3632
+ }),
3483
3633
  (0, langchain.summarizationMiddleware)({
3484
3634
  model,
3485
3635
  trigger: { tokens: 17e4 },
@@ -3488,34 +3638,17 @@ function createDeepAgent(params = {}) {
3488
3638
  (0, langchain.anthropicPromptCachingMiddleware)({ unsupportedModelBehavior: "ignore" }),
3489
3639
  createPatchToolCallsMiddleware()
3490
3640
  ],
3491
- defaultInterruptOn: interruptOn,
3492
- subagents,
3493
- generalPurposeAgent: true
3494
- }),
3495
- (0, langchain.summarizationMiddleware)({
3496
- model,
3497
- trigger: { tokens: 17e4 },
3498
- keep: { messages: 6 }
3499
- }),
3500
- (0, langchain.anthropicPromptCachingMiddleware)({ unsupportedModelBehavior: "ignore" }),
3501
- createPatchToolCallsMiddleware(),
3502
- ...memory != null && memory.length > 0 ? [createMemoryMiddleware({
3503
- backend: filesystemBackend,
3504
- sources: memory
3505
- })] : []
3506
- ];
3507
- if (interruptOn) builtInMiddleware.push((0, langchain.humanInTheLoopMiddleware)({ interruptOn }));
3508
- return (0, langchain.createAgent)({
3509
- model,
3510
- systemPrompt: finalSystemPrompt,
3511
- tools,
3512
- middleware: [...builtInMiddleware, ...customMiddleware],
3641
+ ...skillsMiddlewareArray,
3642
+ ...memoryMiddlewareArray,
3643
+ ...interruptOn ? [(0, langchain.humanInTheLoopMiddleware)({ interruptOn })] : [],
3644
+ ...customMiddleware
3645
+ ],
3513
3646
  responseFormat,
3514
3647
  contextSchema,
3515
3648
  checkpointer,
3516
3649
  store,
3517
3650
  name
3518
- });
3651
+ }).withConfig({ recursionLimit: 1e4 });
3519
3652
  }
3520
3653
 
3521
3654
  //#endregion
@@ -4079,6 +4212,7 @@ exports.createPatchToolCallsMiddleware = createPatchToolCallsMiddleware;
4079
4212
  exports.createSettings = createSettings;
4080
4213
  exports.createSkillsMiddleware = createSkillsMiddleware;
4081
4214
  exports.createSubAgentMiddleware = createSubAgentMiddleware;
4215
+ exports.filesValue = filesValue;
4082
4216
  exports.findProjectRoot = findProjectRoot;
4083
4217
  exports.isSandboxBackend = isSandboxBackend;
4084
4218
  exports.listSkills = listSkills;