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.js CHANGED
@@ -159,8 +159,14 @@ function formatReadResponse(fileData, offset, limit) {
159
159
  * @param newString - Replacement string
160
160
  * @param replaceAll - Whether to replace all occurrences
161
161
  * @returns Tuple of [new_content, occurrences] on success, or error message string
162
+ *
163
+ * Special case: When both content and oldString are empty, this sets the initial
164
+ * content to newString. This allows editing empty files by treating empty oldString
165
+ * as "set initial content" rather than "replace nothing".
162
166
  */
163
167
  function performStringReplacement(content, oldString, newString, replaceAll) {
168
+ if (content === "" && oldString === "") return [newString, 0];
169
+ if (oldString === "") return "Error: oldString cannot be empty when file has content";
164
170
  const occurrences = content.split(oldString).length - 1;
165
171
  if (occurrences === 0) return `Error: String not found in file: '${oldString}'`;
166
172
  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.`;
@@ -523,6 +529,18 @@ const TOOLS_EXCLUDED_FROM_EVICTION = [
523
529
  */
524
530
  const NUM_CHARS_PER_TOKEN = 4;
525
531
  /**
532
+ * Default values for read_file tool pagination (in lines).
533
+ */
534
+ const DEFAULT_READ_LINE_OFFSET = 0;
535
+ const DEFAULT_READ_LINE_LIMIT = 100;
536
+ /**
537
+ * Template for truncation message in read_file.
538
+ * {file_path} will be filled in at runtime.
539
+ */
540
+ const READ_FILE_TRUNCATION_MSG = `
541
+
542
+ [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.]`;
543
+ /**
526
544
  * Message template for evicted tool results.
527
545
  */
528
546
  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}
@@ -627,7 +645,7 @@ const READ_FILE_TOOL_DESCRIPTION = `Reads a file from the filesystem.
627
645
  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.
628
646
 
629
647
  Usage:
630
- - By default, it reads up to 500 lines starting from the beginning of the file
648
+ - By default, it reads up to 100 lines starting from the beginning of the file
631
649
  - **IMPORTANT for large files and codebase exploration**: Use pagination with offset and limit parameters to avoid context overflow
632
650
  - First scan: read_file(path, limit=100) to see file structure
633
651
  - Read more sections: read_file(path, offset=100, limit=200) for next 200 lines
@@ -747,21 +765,29 @@ function createLsTool(backend, options) {
747
765
  * Create read_file tool using backend.
748
766
  */
749
767
  function createReadFileTool(backend, options) {
750
- const { customDescription } = options;
768
+ const { customDescription, toolTokenLimitBeforeEvict } = options;
751
769
  return tool(async (input, config) => {
752
770
  const resolvedBackend = getBackend(backend, {
753
771
  state: getCurrentTaskInput(config),
754
772
  store: config.store
755
773
  });
756
- const { file_path, offset = 0, limit = 500 } = input;
757
- return await resolvedBackend.read(file_path, offset, limit);
774
+ const { file_path, offset = DEFAULT_READ_LINE_OFFSET, limit = DEFAULT_READ_LINE_LIMIT } = input;
775
+ let result = await resolvedBackend.read(file_path, offset, limit);
776
+ const lines = result.split("\n");
777
+ if (lines.length > limit) result = lines.slice(0, limit).join("\n");
778
+ if (toolTokenLimitBeforeEvict && result.length >= NUM_CHARS_PER_TOKEN * toolTokenLimitBeforeEvict) {
779
+ const truncationMsg = READ_FILE_TRUNCATION_MSG.replace("{file_path}", file_path);
780
+ const maxContentLength = NUM_CHARS_PER_TOKEN * toolTokenLimitBeforeEvict - truncationMsg.length;
781
+ result = result.substring(0, maxContentLength) + truncationMsg;
782
+ }
783
+ return result;
758
784
  }, {
759
785
  name: "read_file",
760
786
  description: customDescription || READ_FILE_TOOL_DESCRIPTION,
761
787
  schema: z.object({
762
788
  file_path: z.string().describe("Absolute path to the file to read"),
763
- offset: z.coerce.number().optional().default(0).describe("Line offset to start reading from (0-indexed)"),
764
- limit: z.coerce.number().optional().default(500).describe("Maximum number of lines to read")
789
+ offset: z.coerce.number().optional().default(DEFAULT_READ_LINE_OFFSET).describe("Line offset to start reading from (0-indexed)"),
790
+ limit: z.coerce.number().optional().default(DEFAULT_READ_LINE_LIMIT).describe("Maximum number of lines to read")
765
791
  })
766
792
  });
767
793
  }
@@ -926,7 +952,10 @@ function createFilesystemMiddleware(options = {}) {
926
952
  stateSchema: FilesystemStateSchema,
927
953
  tools: [
928
954
  createLsTool(backend, { customDescription: customToolDescriptions?.ls }),
929
- createReadFileTool(backend, { customDescription: customToolDescriptions?.read_file }),
955
+ createReadFileTool(backend, {
956
+ customDescription: customToolDescriptions?.read_file,
957
+ toolTokenLimitBeforeEvict
958
+ }),
930
959
  createWriteFileTool(backend, { customDescription: customToolDescriptions?.write_file }),
931
960
  createEditFileTool(backend, { customDescription: customToolDescriptions?.edit_file }),
932
961
  createGlobTool(backend, { customDescription: customToolDescriptions?.glob }),
@@ -1290,12 +1319,52 @@ function createSubAgentMiddleware(options) {
1290
1319
  //#endregion
1291
1320
  //#region src/middleware/patch_tool_calls.ts
1292
1321
  /**
1322
+ * Patch dangling tool calls in a messages array.
1323
+ * Returns the patched messages array and a flag indicating if patching was needed.
1324
+ *
1325
+ * @param messages - The messages array to patch
1326
+ * @returns Object with patched messages and needsPatch flag
1327
+ */
1328
+ function patchDanglingToolCalls(messages) {
1329
+ if (!messages || messages.length === 0) return {
1330
+ patchedMessages: [],
1331
+ needsPatch: false
1332
+ };
1333
+ const patchedMessages = [];
1334
+ let needsPatch = false;
1335
+ for (let i = 0; i < messages.length; i++) {
1336
+ const msg = messages[i];
1337
+ patchedMessages.push(msg);
1338
+ if (AIMessage.isInstance(msg) && msg.tool_calls != null) {
1339
+ for (const toolCall of msg.tool_calls) if (!messages.slice(i).find((m) => ToolMessage.isInstance(m) && m.tool_call_id === toolCall.id)) {
1340
+ needsPatch = true;
1341
+ const toolMsg = `Tool call ${toolCall.name} with id ${toolCall.id} was cancelled - another message came in before it could be completed.`;
1342
+ patchedMessages.push(new ToolMessage({
1343
+ content: toolMsg,
1344
+ name: toolCall.name,
1345
+ tool_call_id: toolCall.id
1346
+ }));
1347
+ }
1348
+ }
1349
+ }
1350
+ return {
1351
+ patchedMessages,
1352
+ needsPatch
1353
+ };
1354
+ }
1355
+ /**
1293
1356
  * Create middleware that patches dangling tool calls in the messages history.
1294
1357
  *
1295
1358
  * When an AI message contains tool_calls but subsequent messages don't include
1296
1359
  * the corresponding ToolMessage responses, this middleware adds synthetic
1297
1360
  * ToolMessages saying the tool call was cancelled.
1298
1361
  *
1362
+ * This middleware patches in two places:
1363
+ * 1. `beforeAgent`: Patches state at the start of the agent loop (handles most cases)
1364
+ * 2. `wrapModelCall`: Patches the request right before model invocation (handles
1365
+ * edge cases like HITL rejection during graph resume where state updates from
1366
+ * beforeAgent may not be applied in time)
1367
+ *
1299
1368
  * @returns AgentMiddleware that patches dangling tool calls
1300
1369
  *
1301
1370
  * @example
@@ -1315,26 +1384,61 @@ function createPatchToolCallsMiddleware() {
1315
1384
  beforeAgent: async (state) => {
1316
1385
  const messages = state.messages;
1317
1386
  if (!messages || messages.length === 0) return;
1318
- const patchedMessages = [];
1319
- for (let i = 0; i < messages.length; i++) {
1320
- const msg = messages[i];
1321
- patchedMessages.push(msg);
1322
- if (AIMessage.isInstance(msg) && msg.tool_calls != null) {
1323
- for (const toolCall of msg.tool_calls) if (!messages.slice(i).find((m) => ToolMessage.isInstance(m) && m.tool_call_id === toolCall.id)) {
1324
- const toolMsg = `Tool call ${toolCall.name} with id ${toolCall.id} was cancelled - another message came in before it could be completed.`;
1325
- patchedMessages.push(new ToolMessage({
1326
- content: toolMsg,
1327
- name: toolCall.name,
1328
- tool_call_id: toolCall.id
1329
- }));
1330
- }
1331
- }
1332
- }
1387
+ const { patchedMessages, needsPatch } = patchDanglingToolCalls(messages);
1388
+ /**
1389
+ * Only trigger REMOVE_ALL_MESSAGES if patching is actually needed
1390
+ */
1391
+ if (!needsPatch) return;
1333
1392
  return { messages: [new RemoveMessage({ id: REMOVE_ALL_MESSAGES }), ...patchedMessages] };
1393
+ },
1394
+ wrapModelCall: async (request, handler) => {
1395
+ const messages = request.messages;
1396
+ if (!messages || messages.length === 0) return handler(request);
1397
+ const { patchedMessages, needsPatch } = patchDanglingToolCalls(messages);
1398
+ if (!needsPatch) return handler(request);
1399
+ return handler({
1400
+ ...request,
1401
+ messages: patchedMessages
1402
+ });
1334
1403
  }
1335
1404
  });
1336
1405
  }
1337
1406
 
1407
+ //#endregion
1408
+ //#region src/values.ts
1409
+ /**
1410
+ * Shared state values for use in StateSchema definitions.
1411
+ *
1412
+ * This module provides pre-configured ReducedValue instances that can be
1413
+ * reused across different state schemas, similar to LangGraph's messagesValue.
1414
+ */
1415
+ /**
1416
+ * Shared ReducedValue for file data state management.
1417
+ *
1418
+ * This provides a reusable pattern for managing file state with automatic
1419
+ * merging of concurrent updates from parallel subagents. Files can be updated
1420
+ * or deleted (using null values) and the reducer handles the merge logic.
1421
+ *
1422
+ * Similar to LangGraph's messagesValue, this encapsulates the common pattern
1423
+ * of managing files in agent state so you don't have to manually configure
1424
+ * the ReducedValue each time.
1425
+ *
1426
+ * @example
1427
+ * ```typescript
1428
+ * import { filesValue } from "@anthropic/deepagents";
1429
+ * import { StateSchema } from "@langchain/langgraph";
1430
+ *
1431
+ * const MyStateSchema = new StateSchema({
1432
+ * files: filesValue,
1433
+ * // ... other state fields
1434
+ * });
1435
+ * ```
1436
+ */
1437
+ const filesValue = new ReducedValue(z$1.record(z$1.string(), FileDataSchema).default(() => ({})), {
1438
+ inputSchema: z$1.record(z$1.string(), FileDataSchema.nullable()).optional(),
1439
+ reducer: fileDataReducer
1440
+ });
1441
+
1338
1442
  //#endregion
1339
1443
  //#region src/middleware/memory.ts
1340
1444
  /**
@@ -1389,7 +1493,10 @@ function createPatchToolCallsMiddleware() {
1389
1493
  /**
1390
1494
  * State schema for memory middleware.
1391
1495
  */
1392
- const MemoryStateSchema = z$1.object({ memoryContents: z$1.record(z$1.string(), z$1.string()).optional() });
1496
+ const MemoryStateSchema = new StateSchema({
1497
+ memoryContents: z$1.record(z$1.string(), z$1.string()).optional(),
1498
+ files: filesValue
1499
+ });
1393
1500
  /**
1394
1501
  * Default system prompt template for memory.
1395
1502
  * Ported from Python's comprehensive memory guidelines.
@@ -1533,12 +1640,10 @@ function createMemoryMiddleware(options) {
1533
1640
  },
1534
1641
  wrapModelCall(request, handler) {
1535
1642
  const formattedContents = formatMemoryContents(request.state?.memoryContents || {}, sources);
1536
- const memorySection = MEMORY_SYSTEM_PROMPT.replace("{memory_contents}", formattedContents);
1537
- const currentSystemPrompt = request.systemPrompt || "";
1538
- const newSystemPrompt = currentSystemPrompt ? `${memorySection}\n\n${currentSystemPrompt}` : memorySection;
1643
+ const newSystemMessage = new SystemMessage(MEMORY_SYSTEM_PROMPT.replace("{memory_contents}", formattedContents)).concat(request.systemMessage);
1539
1644
  return handler({
1540
1645
  ...request,
1541
- systemPrompt: newSystemPrompt
1646
+ systemMessage: newSystemMessage
1542
1647
  });
1543
1648
  }
1544
1649
  });
@@ -1621,10 +1726,13 @@ function skillsMetadataReducer(current, update) {
1621
1726
  * State schema for skills middleware.
1622
1727
  * Uses ReducedValue for skillsMetadata to allow concurrent updates from parallel subagents.
1623
1728
  */
1624
- const SkillsStateSchema = new StateSchema({ skillsMetadata: new ReducedValue(z$1.array(SkillMetadataEntrySchema).default(() => []), {
1625
- inputSchema: z$1.array(SkillMetadataEntrySchema).optional(),
1626
- reducer: skillsMetadataReducer
1627
- }) });
1729
+ const SkillsStateSchema = new StateSchema({
1730
+ skillsMetadata: new ReducedValue(z$1.array(SkillMetadataEntrySchema).default(() => []), {
1731
+ inputSchema: z$1.array(SkillMetadataEntrySchema).optional(),
1732
+ reducer: skillsMetadataReducer
1733
+ }),
1734
+ files: filesValue
1735
+ });
1628
1736
  /**
1629
1737
  * Skills System Documentation prompt template.
1630
1738
  */
@@ -1845,7 +1953,7 @@ function createSkillsMiddleware(options) {
1845
1953
  stateSchema: SkillsStateSchema,
1846
1954
  async beforeAgent(state) {
1847
1955
  if (loadedSkills.length > 0) return;
1848
- if ("skillsMetadata" in state && state.skillsMetadata != null) {
1956
+ if ("skillsMetadata" in state && Array.isArray(state.skillsMetadata) && state.skillsMetadata.length > 0) {
1849
1957
  loadedSkills = state.skillsMetadata;
1850
1958
  return;
1851
1959
  }
@@ -3415,6 +3523,9 @@ const BASE_PROMPT = `In order to complete the objective that the user asks of yo
3415
3523
  */
3416
3524
  function createDeepAgent(params = {}) {
3417
3525
  const { model = "claude-sonnet-4-5-20250929", tools = [], systemPrompt, middleware: customMiddleware = [], subagents = [], responseFormat, contextSchema, checkpointer, store, backend, interruptOn, name, memory, skills } = params;
3526
+ /**
3527
+ * Combine system prompt with base prompt like Python implementation
3528
+ */
3418
3529
  const finalSystemPrompt = systemPrompt ? typeof systemPrompt === "string" ? `${systemPrompt}\n\n${BASE_PROMPT}` : new SystemMessage({ content: [{
3419
3530
  type: "text",
3420
3531
  text: BASE_PROMPT
@@ -3422,22 +3533,61 @@ function createDeepAgent(params = {}) {
3422
3533
  type: "text",
3423
3534
  text: systemPrompt.content
3424
3535
  }] : systemPrompt.content] }) : BASE_PROMPT;
3536
+ /**
3537
+ * Create backend configuration for filesystem middleware
3538
+ * If no backend is provided, use a factory that creates a StateBackend
3539
+ */
3425
3540
  const filesystemBackend = backend ? backend : (config) => new StateBackend(config);
3426
- const skillsMiddleware = skills != null && skills.length > 0 ? [createSkillsMiddleware({
3541
+ /**
3542
+ * Skills middleware (created conditionally for runtime use)
3543
+ */
3544
+ const skillsMiddlewareArray = skills != null && skills.length > 0 ? [createSkillsMiddleware({
3427
3545
  backend: filesystemBackend,
3428
3546
  sources: skills
3429
3547
  })] : [];
3430
- const builtInMiddleware = [
3431
- todoListMiddleware(),
3432
- ...skillsMiddleware,
3433
- createFilesystemMiddleware({ backend: filesystemBackend }),
3434
- createSubAgentMiddleware({
3435
- defaultModel: model,
3436
- defaultTools: tools,
3437
- defaultMiddleware: [
3548
+ /**
3549
+ * Memory middleware (created conditionally for runtime use)
3550
+ */
3551
+ const memoryMiddlewareArray = memory != null && memory.length > 0 ? [createMemoryMiddleware({
3552
+ backend: filesystemBackend,
3553
+ sources: memory
3554
+ })] : [];
3555
+ /**
3556
+ * Return as DeepAgent with proper DeepAgentTypeConfig
3557
+ * - Response: TResponse (from responseFormat parameter)
3558
+ * - State: undefined (state comes from middleware)
3559
+ * - Context: ContextSchema
3560
+ * - Middleware: AllMiddleware (built-in + custom + subagent middleware for state inference)
3561
+ * - Tools: TTools
3562
+ * - Subagents: TSubagents (for type-safe streaming)
3563
+ */
3564
+ return createAgent({
3565
+ model,
3566
+ systemPrompt: finalSystemPrompt,
3567
+ tools,
3568
+ middleware: [
3569
+ ...[
3438
3570
  todoListMiddleware(),
3439
- ...skillsMiddleware,
3440
3571
  createFilesystemMiddleware({ backend: filesystemBackend }),
3572
+ createSubAgentMiddleware({
3573
+ defaultModel: model,
3574
+ defaultTools: tools,
3575
+ defaultMiddleware: [
3576
+ todoListMiddleware(),
3577
+ ...skillsMiddlewareArray,
3578
+ createFilesystemMiddleware({ backend: filesystemBackend }),
3579
+ summarizationMiddleware({
3580
+ model,
3581
+ trigger: { tokens: 17e4 },
3582
+ keep: { messages: 6 }
3583
+ }),
3584
+ anthropicPromptCachingMiddleware({ unsupportedModelBehavior: "ignore" }),
3585
+ createPatchToolCallsMiddleware()
3586
+ ],
3587
+ defaultInterruptOn: interruptOn,
3588
+ subagents,
3589
+ generalPurposeAgent: true
3590
+ }),
3441
3591
  summarizationMiddleware({
3442
3592
  model,
3443
3593
  trigger: { tokens: 17e4 },
@@ -3446,34 +3596,17 @@ function createDeepAgent(params = {}) {
3446
3596
  anthropicPromptCachingMiddleware({ unsupportedModelBehavior: "ignore" }),
3447
3597
  createPatchToolCallsMiddleware()
3448
3598
  ],
3449
- defaultInterruptOn: interruptOn,
3450
- subagents,
3451
- generalPurposeAgent: true
3452
- }),
3453
- summarizationMiddleware({
3454
- model,
3455
- trigger: { tokens: 17e4 },
3456
- keep: { messages: 6 }
3457
- }),
3458
- anthropicPromptCachingMiddleware({ unsupportedModelBehavior: "ignore" }),
3459
- createPatchToolCallsMiddleware(),
3460
- ...memory != null && memory.length > 0 ? [createMemoryMiddleware({
3461
- backend: filesystemBackend,
3462
- sources: memory
3463
- })] : []
3464
- ];
3465
- if (interruptOn) builtInMiddleware.push(humanInTheLoopMiddleware({ interruptOn }));
3466
- return createAgent({
3467
- model,
3468
- systemPrompt: finalSystemPrompt,
3469
- tools,
3470
- middleware: [...builtInMiddleware, ...customMiddleware],
3599
+ ...skillsMiddlewareArray,
3600
+ ...memoryMiddlewareArray,
3601
+ ...interruptOn ? [humanInTheLoopMiddleware({ interruptOn })] : [],
3602
+ ...customMiddleware
3603
+ ],
3471
3604
  responseFormat,
3472
3605
  contextSchema,
3473
3606
  checkpointer,
3474
3607
  store,
3475
3608
  name
3476
- });
3609
+ }).withConfig({ recursionLimit: 1e4 });
3477
3610
  }
3478
3611
 
3479
3612
  //#endregion
@@ -4021,5 +4154,5 @@ function listSkills(options) {
4021
4154
  }
4022
4155
 
4023
4156
  //#endregion
4024
- export { BaseSandbox, CompositeBackend, FilesystemBackend, MAX_SKILL_DESCRIPTION_LENGTH, MAX_SKILL_FILE_SIZE, MAX_SKILL_NAME_LENGTH, StateBackend, StoreBackend, createAgentMemoryMiddleware, createDeepAgent, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSettings, createSkillsMiddleware, createSubAgentMiddleware, findProjectRoot, isSandboxBackend, listSkills, parseSkillMetadata };
4157
+ export { BaseSandbox, CompositeBackend, FilesystemBackend, MAX_SKILL_DESCRIPTION_LENGTH, MAX_SKILL_FILE_SIZE, MAX_SKILL_NAME_LENGTH, StateBackend, StoreBackend, createAgentMemoryMiddleware, createDeepAgent, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSettings, createSkillsMiddleware, createSubAgentMiddleware, filesValue, findProjectRoot, isSandboxBackend, listSkills, parseSkillMetadata };
4025
4158
  //# sourceMappingURL=index.js.map