deepagents 1.9.0-alpha.0 → 1.9.0
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 +926 -695
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +237 -166
- package/dist/index.d.ts +238 -167
- package/dist/index.js +928 -698
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/dist/index.cjs
CHANGED
|
@@ -22,7 +22,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
//#endregion
|
|
24
24
|
let langchain = require("langchain");
|
|
25
|
-
let
|
|
25
|
+
let _langchain_anthropic = require("@langchain/anthropic");
|
|
26
26
|
let _langchain_langgraph = require("@langchain/langgraph");
|
|
27
27
|
let zod_v4 = require("zod/v4");
|
|
28
28
|
let micromatch = require("micromatch");
|
|
@@ -31,6 +31,7 @@ let path = require("path");
|
|
|
31
31
|
path = __toESM(path);
|
|
32
32
|
let _langchain_core_messages = require("@langchain/core/messages");
|
|
33
33
|
let zod = require("zod");
|
|
34
|
+
zod = __toESM(zod);
|
|
34
35
|
let yaml = require("yaml");
|
|
35
36
|
yaml = __toESM(yaml);
|
|
36
37
|
let _langchain_langgraph_sdk = require("@langchain/langgraph-sdk");
|
|
@@ -49,78 +50,6 @@ fast_glob = __toESM(fast_glob);
|
|
|
49
50
|
let langsmith_experimental_sandbox = require("langsmith/experimental/sandbox");
|
|
50
51
|
let node_os = require("node:os");
|
|
51
52
|
node_os = __toESM(node_os);
|
|
52
|
-
//#region src/backends/protocol.ts
|
|
53
|
-
/**
|
|
54
|
-
* Type guard to check if a backend supports execution.
|
|
55
|
-
*
|
|
56
|
-
* @param backend - Backend instance to check
|
|
57
|
-
* @returns True if the backend implements SandboxBackendProtocolV2
|
|
58
|
-
*/
|
|
59
|
-
function isSandboxBackend(backend) {
|
|
60
|
-
return backend != null && typeof backend === "object" && typeof backend.execute === "function" && typeof backend.id === "string" && backend.id !== "";
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Type guard to check if a backend is a sandbox protocol (v1 or v2).
|
|
64
|
-
*
|
|
65
|
-
* Checks for the presence of `execute` function and `id` string,
|
|
66
|
-
* which are the defining features of sandbox protocols.
|
|
67
|
-
*
|
|
68
|
-
* @param backend - Backend instance to check
|
|
69
|
-
* @returns True if the backend implements sandbox protocol (v1 or v2)
|
|
70
|
-
*/
|
|
71
|
-
function isSandboxProtocol(backend) {
|
|
72
|
-
return backend != null && typeof backend === "object" && typeof backend.execute === "function" && typeof backend.id === "string" && backend.id !== "";
|
|
73
|
-
}
|
|
74
|
-
const SANDBOX_ERROR_SYMBOL = Symbol.for("sandbox.error");
|
|
75
|
-
/**
|
|
76
|
-
* Custom error class for sandbox operations.
|
|
77
|
-
*
|
|
78
|
-
* @param message - Human-readable error description
|
|
79
|
-
* @param code - Structured error code for programmatic handling
|
|
80
|
-
* @returns SandboxError with message and code
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* ```typescript
|
|
84
|
-
* try {
|
|
85
|
-
* await sandbox.execute("some command");
|
|
86
|
-
* } catch (error) {
|
|
87
|
-
* if (error instanceof SandboxError) {
|
|
88
|
-
* switch (error.code) {
|
|
89
|
-
* case "NOT_INITIALIZED":
|
|
90
|
-
* await sandbox.initialize();
|
|
91
|
-
* break;
|
|
92
|
-
* case "COMMAND_TIMEOUT":
|
|
93
|
-
* console.error("Command took too long");
|
|
94
|
-
* break;
|
|
95
|
-
* default:
|
|
96
|
-
* throw error;
|
|
97
|
-
* }
|
|
98
|
-
* }
|
|
99
|
-
* }
|
|
100
|
-
* ```
|
|
101
|
-
*/
|
|
102
|
-
var SandboxError = class SandboxError extends Error {
|
|
103
|
-
/** Symbol for identifying sandbox error instances */
|
|
104
|
-
[SANDBOX_ERROR_SYMBOL] = true;
|
|
105
|
-
/** Error name for instanceof checks and logging */
|
|
106
|
-
name = "SandboxError";
|
|
107
|
-
/**
|
|
108
|
-
* Creates a new SandboxError.
|
|
109
|
-
*
|
|
110
|
-
* @param message - Human-readable error description
|
|
111
|
-
* @param code - Structured error code for programmatic handling
|
|
112
|
-
*/
|
|
113
|
-
constructor(message, code, cause) {
|
|
114
|
-
super(message);
|
|
115
|
-
this.code = code;
|
|
116
|
-
this.cause = cause;
|
|
117
|
-
Object.setPrototypeOf(this, SandboxError.prototype);
|
|
118
|
-
}
|
|
119
|
-
static isInstance(error) {
|
|
120
|
-
return typeof error === "object" && error !== null && error[SANDBOX_ERROR_SYMBOL] === true;
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
//#endregion
|
|
124
53
|
//#region src/backends/utils.ts
|
|
125
54
|
/**
|
|
126
55
|
* Shared utility functions for memory backend implementations.
|
|
@@ -130,7 +59,7 @@ var SandboxError = class SandboxError extends Error {
|
|
|
130
59
|
* enable composition without fragile string parsing.
|
|
131
60
|
*/
|
|
132
61
|
const EMPTY_CONTENT_WARNING = "System reminder: File exists but has empty contents";
|
|
133
|
-
const MAX_LINE_LENGTH =
|
|
62
|
+
const MAX_LINE_LENGTH = 5e3;
|
|
134
63
|
const TOOL_RESULT_TOKEN_LIMIT = 2e4;
|
|
135
64
|
const TRUNCATION_GUIDANCE = "... [results truncated, try being more specific with your parameters]";
|
|
136
65
|
const MIME_TYPES = {
|
|
@@ -140,11 +69,26 @@ const MIME_TYPES = {
|
|
|
140
69
|
".gif": "image/gif",
|
|
141
70
|
".webp": "image/webp",
|
|
142
71
|
".svg": "image/svg+xml",
|
|
72
|
+
".heic": "image/heic",
|
|
73
|
+
".heif": "image/heif",
|
|
143
74
|
".mp3": "audio/mpeg",
|
|
144
75
|
".wav": "audio/wav",
|
|
76
|
+
".aiff": "audio/aiff",
|
|
77
|
+
".aac": "audio/aac",
|
|
78
|
+
".ogg": "audio/ogg",
|
|
79
|
+
".flac": "audio/flac",
|
|
145
80
|
".mp4": "video/mp4",
|
|
146
81
|
".webm": "video/webm",
|
|
147
|
-
".
|
|
82
|
+
".mpeg": "video/mpeg",
|
|
83
|
+
".mov": "video/quicktime",
|
|
84
|
+
".avi": "video/x-msvideo",
|
|
85
|
+
".flv": "video/x-flv",
|
|
86
|
+
".mpg": "video/mpeg",
|
|
87
|
+
".wmv": "video/x-ms-wmv",
|
|
88
|
+
".3gpp": "video/3gpp",
|
|
89
|
+
".pdf": "application/pdf",
|
|
90
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
91
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
148
92
|
};
|
|
149
93
|
/**
|
|
150
94
|
* Sanitize tool_call_id to prevent path traversal and separator issues.
|
|
@@ -173,7 +117,7 @@ function formatContentWithLineNumbers(content, startLine = 1) {
|
|
|
173
117
|
for (let i = 0; i < lines.length; i++) {
|
|
174
118
|
const line = lines[i];
|
|
175
119
|
const lineNum = i + startLine;
|
|
176
|
-
if (line.length <=
|
|
120
|
+
if (line.length <= 5e3) resultLines.push(`${lineNum.toString().padStart(6)}\t${line}`);
|
|
177
121
|
else {
|
|
178
122
|
const numChunks = Math.ceil(line.length / MAX_LINE_LENGTH);
|
|
179
123
|
for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) {
|
|
@@ -538,7 +482,96 @@ function adaptSandboxProtocol(sandbox) {
|
|
|
538
482
|
return adapted;
|
|
539
483
|
}
|
|
540
484
|
//#endregion
|
|
485
|
+
//#region src/backends/protocol.ts
|
|
486
|
+
/**
|
|
487
|
+
* Type guard to check if a backend supports execution.
|
|
488
|
+
*
|
|
489
|
+
* @param backend - Backend instance to check
|
|
490
|
+
* @returns True if the backend implements SandboxBackendProtocolV2
|
|
491
|
+
*/
|
|
492
|
+
function isSandboxBackend(backend) {
|
|
493
|
+
return backend != null && typeof backend === "object" && typeof backend.execute === "function" && typeof backend.id === "string" && backend.id !== "";
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Type guard to check if a backend is a sandbox protocol (v1 or v2).
|
|
497
|
+
*
|
|
498
|
+
* Checks for the presence of `execute` function and `id` string,
|
|
499
|
+
* which are the defining features of sandbox protocols.
|
|
500
|
+
*
|
|
501
|
+
* @param backend - Backend instance to check
|
|
502
|
+
* @returns True if the backend implements sandbox protocol (v1 or v2)
|
|
503
|
+
*/
|
|
504
|
+
function isSandboxProtocol(backend) {
|
|
505
|
+
return backend != null && typeof backend === "object" && typeof backend.execute === "function" && typeof backend.id === "string" && backend.id !== "";
|
|
506
|
+
}
|
|
507
|
+
const SANDBOX_ERROR_SYMBOL = Symbol.for("sandbox.error");
|
|
508
|
+
/**
|
|
509
|
+
* Custom error class for sandbox operations.
|
|
510
|
+
*
|
|
511
|
+
* @param message - Human-readable error description
|
|
512
|
+
* @param code - Structured error code for programmatic handling
|
|
513
|
+
* @returns SandboxError with message and code
|
|
514
|
+
*
|
|
515
|
+
* @example
|
|
516
|
+
* ```typescript
|
|
517
|
+
* try {
|
|
518
|
+
* await sandbox.execute("some command");
|
|
519
|
+
* } catch (error) {
|
|
520
|
+
* if (error instanceof SandboxError) {
|
|
521
|
+
* switch (error.code) {
|
|
522
|
+
* case "NOT_INITIALIZED":
|
|
523
|
+
* await sandbox.initialize();
|
|
524
|
+
* break;
|
|
525
|
+
* case "COMMAND_TIMEOUT":
|
|
526
|
+
* console.error("Command took too long");
|
|
527
|
+
* break;
|
|
528
|
+
* default:
|
|
529
|
+
* throw error;
|
|
530
|
+
* }
|
|
531
|
+
* }
|
|
532
|
+
* }
|
|
533
|
+
* ```
|
|
534
|
+
*/
|
|
535
|
+
var SandboxError = class SandboxError extends Error {
|
|
536
|
+
/** Symbol for identifying sandbox error instances */
|
|
537
|
+
[SANDBOX_ERROR_SYMBOL] = true;
|
|
538
|
+
/** Error name for instanceof checks and logging */
|
|
539
|
+
name = "SandboxError";
|
|
540
|
+
/**
|
|
541
|
+
* Creates a new SandboxError.
|
|
542
|
+
*
|
|
543
|
+
* @param message - Human-readable error description
|
|
544
|
+
* @param code - Structured error code for programmatic handling
|
|
545
|
+
*/
|
|
546
|
+
constructor(message, code, cause) {
|
|
547
|
+
super(message);
|
|
548
|
+
this.code = code;
|
|
549
|
+
this.cause = cause;
|
|
550
|
+
Object.setPrototypeOf(this, SandboxError.prototype);
|
|
551
|
+
}
|
|
552
|
+
static isInstance(error) {
|
|
553
|
+
return typeof error === "object" && error !== null && error[SANDBOX_ERROR_SYMBOL] === true;
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
/**
|
|
557
|
+
* Resolve a backend instance or await a {@link BackendFactory}.
|
|
558
|
+
*
|
|
559
|
+
* Accepts {@link BackendRuntime} or {@link ToolRuntime} — store typing differs
|
|
560
|
+
* between LangGraph checkpoint stores and core `ToolRuntime`; factories receive
|
|
561
|
+
* a value that is structurally compatible at runtime.
|
|
562
|
+
*
|
|
563
|
+
* @internal
|
|
564
|
+
*/
|
|
565
|
+
async function resolveBackend(backend, runtime) {
|
|
566
|
+
if (typeof backend === "function") {
|
|
567
|
+
const resolved = await backend(runtime);
|
|
568
|
+
return isSandboxProtocol(resolved) ? adaptSandboxProtocol(resolved) : adaptBackendProtocol(resolved);
|
|
569
|
+
}
|
|
570
|
+
return isSandboxProtocol(backend) ? adaptSandboxProtocol(backend) : adaptBackendProtocol(backend);
|
|
571
|
+
}
|
|
572
|
+
//#endregion
|
|
541
573
|
//#region src/backends/state.ts
|
|
574
|
+
const PREGEL_SEND_KEY = "__pregel_send";
|
|
542
575
|
/**
|
|
543
576
|
* Backend that stores files in agent state (ephemeral).
|
|
544
577
|
*
|
|
@@ -551,17 +584,52 @@ function adaptSandboxProtocol(sandbox) {
|
|
|
551
584
|
* for the middleware to apply via Command.
|
|
552
585
|
*/
|
|
553
586
|
var StateBackend = class {
|
|
554
|
-
|
|
587
|
+
runtime;
|
|
555
588
|
fileFormat;
|
|
556
|
-
constructor(
|
|
557
|
-
|
|
558
|
-
|
|
589
|
+
constructor(runtimeOrOptions, options) {
|
|
590
|
+
if (runtimeOrOptions != null && typeof runtimeOrOptions === "object" && "state" in runtimeOrOptions) {
|
|
591
|
+
this.runtime = runtimeOrOptions;
|
|
592
|
+
this.fileFormat = options?.fileFormat ?? "v2";
|
|
593
|
+
} else {
|
|
594
|
+
this.runtime = void 0;
|
|
595
|
+
this.fileFormat = runtimeOrOptions?.fileFormat ?? "v2";
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Whether this instance was constructed with the legacy factory pattern.
|
|
600
|
+
*
|
|
601
|
+
* When true, state is read from the injected `runtime` and `filesUpdate`
|
|
602
|
+
* is returned to the caller. When false, state is read from LangGraph's
|
|
603
|
+
* execution context and updates are sent via `__pregel_send`.
|
|
604
|
+
*/
|
|
605
|
+
get isLegacy() {
|
|
606
|
+
return this.runtime !== void 0;
|
|
559
607
|
}
|
|
560
608
|
/**
|
|
561
609
|
* Get files from current state.
|
|
610
|
+
*
|
|
611
|
+
* In legacy mode, reads from the injected {@link BackendRuntime}.
|
|
612
|
+
* In zero-arg mode, reads from the LangGraph execution context via
|
|
613
|
+
* {@link getCurrentTaskInput}.
|
|
562
614
|
*/
|
|
563
615
|
getFiles() {
|
|
564
|
-
return this.
|
|
616
|
+
if (this.runtime) return this.runtime.state.files || {};
|
|
617
|
+
return (0, _langchain_langgraph.getCurrentTaskInput)()?.files || {};
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Push a files state update through LangGraph's internal send channel.
|
|
621
|
+
*
|
|
622
|
+
* In zero-arg mode, sends the update via the `__pregel_send` function
|
|
623
|
+
* from {@link getConfig}, mirroring Python's `CONFIG_KEY_SEND`.
|
|
624
|
+
* In legacy mode, this is a no-op — the caller uses `filesUpdate`
|
|
625
|
+
* from the return value instead.
|
|
626
|
+
*
|
|
627
|
+
* @param update - Map of file paths to their updated {@link FileData}
|
|
628
|
+
*/
|
|
629
|
+
sendFilesUpdate(update) {
|
|
630
|
+
if (this.isLegacy) return;
|
|
631
|
+
const send = (0, _langchain_langgraph.getConfig)().configurable?.[PREGEL_SEND_KEY];
|
|
632
|
+
if (typeof send === "function") send([["files", update]]);
|
|
565
633
|
}
|
|
566
634
|
/**
|
|
567
635
|
* List files and directories in the specified directory (non-recursive).
|
|
@@ -644,6 +712,11 @@ var StateBackend = class {
|
|
|
644
712
|
if (filePath in this.getFiles()) return { error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.` };
|
|
645
713
|
const mimeType = getMimeType(filePath);
|
|
646
714
|
const newFileData = createFileData(content, void 0, this.fileFormat, mimeType);
|
|
715
|
+
const update = { [filePath]: newFileData };
|
|
716
|
+
if (!this.isLegacy) {
|
|
717
|
+
this.sendFilesUpdate(update);
|
|
718
|
+
return { path: filePath };
|
|
719
|
+
}
|
|
647
720
|
return {
|
|
648
721
|
path: filePath,
|
|
649
722
|
filesUpdate: { [filePath]: newFileData }
|
|
@@ -660,6 +733,14 @@ var StateBackend = class {
|
|
|
660
733
|
if (typeof result === "string") return { error: result };
|
|
661
734
|
const [newContent, occurrences] = result;
|
|
662
735
|
const newFileData = updateFileData(fileData, newContent);
|
|
736
|
+
const update = { [filePath]: newFileData };
|
|
737
|
+
if (!this.isLegacy) {
|
|
738
|
+
this.sendFilesUpdate(update);
|
|
739
|
+
return {
|
|
740
|
+
path: filePath,
|
|
741
|
+
occurrences
|
|
742
|
+
};
|
|
743
|
+
}
|
|
663
744
|
return {
|
|
664
745
|
path: filePath,
|
|
665
746
|
filesUpdate: { [filePath]: newFileData },
|
|
@@ -720,6 +801,10 @@ var StateBackend = class {
|
|
|
720
801
|
error: "invalid_path"
|
|
721
802
|
});
|
|
722
803
|
}
|
|
804
|
+
if (!this.isLegacy) {
|
|
805
|
+
if (Object.keys(updates).length > 0) this.sendFilesUpdate(updates);
|
|
806
|
+
return responses;
|
|
807
|
+
}
|
|
723
808
|
const result = responses;
|
|
724
809
|
result.filesUpdate = updates;
|
|
725
810
|
return result;
|
|
@@ -769,6 +854,7 @@ var StateBackend = class {
|
|
|
769
854
|
* - Pluggable backends (StateBackend, StoreBackend, FilesystemBackend, CompositeBackend)
|
|
770
855
|
* - Tool result eviction for large outputs
|
|
771
856
|
*/
|
|
857
|
+
const INT_FORMATTER = new Intl.NumberFormat("en-US");
|
|
772
858
|
/**
|
|
773
859
|
* Tools that should be excluded from the large result eviction logic.
|
|
774
860
|
*
|
|
@@ -830,17 +916,75 @@ const READ_FILE_TRUNCATION_MSG = `
|
|
|
830
916
|
/**
|
|
831
917
|
* Message template for evicted tool results.
|
|
832
918
|
*/
|
|
833
|
-
const TOO_LARGE_TOOL_MSG = `
|
|
834
|
-
|
|
835
|
-
You can
|
|
836
|
-
|
|
919
|
+
const TOO_LARGE_TOOL_MSG = langchain.context`
|
|
920
|
+
Tool result too large, the result of this tool call {tool_call_id} was saved in the filesystem at this path: {file_path}
|
|
921
|
+
You can read the result from the filesystem by using the read_file tool, but make sure to only read part of the result at a time.
|
|
922
|
+
You can do this by specifying an offset and limit in the read_file tool call.
|
|
923
|
+
For example, to read the first 100 lines, you can use the read_file tool with offset=0 and limit=100.
|
|
924
|
+
|
|
925
|
+
Here is a preview showing the head and tail of the result (lines of the form
|
|
926
|
+
... [N lines truncated] ...
|
|
927
|
+
indicate omitted lines in the middle of the content):
|
|
928
|
+
|
|
929
|
+
{content_sample}
|
|
930
|
+
`;
|
|
931
|
+
/**
|
|
932
|
+
* Message template for evicted HumanMessages.
|
|
933
|
+
*/
|
|
934
|
+
const TOO_LARGE_HUMAN_MSG = `Message content too large and was saved to the filesystem at: {file_path}
|
|
935
|
+
|
|
936
|
+
You can read the full content using the read_file tool with pagination (offset and limit parameters).
|
|
837
937
|
|
|
838
|
-
Here is a preview showing the head and tail of the
|
|
839
|
-
... [N lines truncated] ...
|
|
840
|
-
indicate omitted lines in the middle of the content):
|
|
938
|
+
Here is a preview showing the head and tail of the content:
|
|
841
939
|
|
|
842
940
|
{content_sample}`;
|
|
843
941
|
/**
|
|
942
|
+
* Extract text content from a message.
|
|
943
|
+
*
|
|
944
|
+
* For string content, returns it directly. For array content (mixed block types
|
|
945
|
+
* like text + image), joins all text blocks. Returns empty string if no text found.
|
|
946
|
+
*/
|
|
947
|
+
function extractTextFromMessage(message) {
|
|
948
|
+
if (typeof message.content === "string") return message.content;
|
|
949
|
+
if (Array.isArray(message.content)) return message.content.filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
|
|
950
|
+
return String(message.content);
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Build replacement content for an evicted HumanMessage, preserving non-text blocks.
|
|
954
|
+
*
|
|
955
|
+
* For plain string content, returns the replacement text directly. For list content
|
|
956
|
+
* with mixed block types (e.g., text + image), replaces all text blocks with a single
|
|
957
|
+
* text block containing the replacement text while keeping non-text blocks intact.
|
|
958
|
+
*/
|
|
959
|
+
function buildEvictedHumanContent(message, replacementText) {
|
|
960
|
+
if (typeof message.content === "string") return replacementText;
|
|
961
|
+
if (Array.isArray(message.content)) {
|
|
962
|
+
const mediaBlocks = message.content.filter((block) => typeof block === "object" && block !== null && block.type !== "text");
|
|
963
|
+
if (mediaBlocks.length === 0) return replacementText;
|
|
964
|
+
return [{
|
|
965
|
+
type: "text",
|
|
966
|
+
text: replacementText
|
|
967
|
+
}, ...mediaBlocks];
|
|
968
|
+
}
|
|
969
|
+
return replacementText;
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Build a truncated HumanMessage for the model request.
|
|
973
|
+
*
|
|
974
|
+
* Computes a preview from the full content still in state and returns a
|
|
975
|
+
* lightweight replacement the model will see. Pure string computation — no
|
|
976
|
+
* backend I/O.
|
|
977
|
+
*/
|
|
978
|
+
function buildTruncatedHumanMessage(message, filePath) {
|
|
979
|
+
const contentSample = createContentPreview(extractTextFromMessage(message));
|
|
980
|
+
return new langchain.HumanMessage({
|
|
981
|
+
content: buildEvictedHumanContent(message, TOO_LARGE_HUMAN_MSG.replace("{file_path}", filePath).replace("{content_sample}", contentSample)),
|
|
982
|
+
id: message.id,
|
|
983
|
+
additional_kwargs: { ...message.additional_kwargs },
|
|
984
|
+
response_metadata: { ...message.response_metadata }
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
844
988
|
* Create a preview of content showing head and tail with truncation marker.
|
|
845
989
|
*
|
|
846
990
|
* @param contentStr - The full content string to preview.
|
|
@@ -915,138 +1059,148 @@ const FilesystemStateSchema = new _langchain_langgraph.StateSchema({ files: new
|
|
|
915
1059
|
inputSchema: zod_v4.z.record(zod_v4.z.string(), FileDataSchema.nullable()).optional(),
|
|
916
1060
|
reducer: fileDataReducer
|
|
917
1061
|
}) });
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
*
|
|
921
|
-
* @param backend - Backend instance or factory function
|
|
922
|
-
* @param stateAndStore - State and store container for backend initialization
|
|
923
|
-
*/
|
|
924
|
-
function getBackend(backend, stateAndStore) {
|
|
925
|
-
const actualBackend = typeof backend === "function" ? backend(stateAndStore) : backend;
|
|
926
|
-
return isSandboxProtocol(actualBackend) ? adaptSandboxProtocol(actualBackend) : adaptBackendProtocol(actualBackend);
|
|
927
|
-
}
|
|
928
|
-
const FILESYSTEM_SYSTEM_PROMPT = `## Filesystem Tools \`ls\`, \`read_file\`, \`write_file\`, \`edit_file\`, \`glob\`, \`grep\`
|
|
1062
|
+
const FILESYSTEM_SYSTEM_PROMPT = langchain.context`
|
|
1063
|
+
## Following Conventions
|
|
929
1064
|
|
|
930
|
-
|
|
931
|
-
|
|
1065
|
+
- Read files before editing — understand existing content before making changes
|
|
1066
|
+
- Mimic existing style, naming conventions, and patterns
|
|
932
1067
|
|
|
933
|
-
|
|
934
|
-
- read_file: read a file from the filesystem
|
|
935
|
-
- write_file: write to a file in the filesystem
|
|
936
|
-
- edit_file: edit a file in the filesystem
|
|
937
|
-
- glob: find files matching a pattern (e.g., "**/*.py")
|
|
938
|
-
- grep: search for text within files`;
|
|
939
|
-
const LS_TOOL_DESCRIPTION = `Lists all files in a directory.
|
|
1068
|
+
## Filesystem Tools \`ls\`, \`read_file\`, \`write_file\`, \`edit_file\`, \`glob\`, \`grep\`
|
|
940
1069
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
const READ_FILE_TOOL_DESCRIPTION = `Reads a file from the filesystem.
|
|
1070
|
+
You have access to a filesystem which you can interact with using these tools.
|
|
1071
|
+
All file paths must start with a /.
|
|
944
1072
|
|
|
945
|
-
|
|
1073
|
+
- ls: list files in a directory (requires absolute path)
|
|
1074
|
+
- read_file: read a file from the filesystem
|
|
1075
|
+
- write_file: write to a file in the filesystem
|
|
1076
|
+
- edit_file: edit a file in the filesystem
|
|
1077
|
+
- glob: find files matching a pattern (e.g., "**/*.py")
|
|
1078
|
+
- grep: search for text within files
|
|
1079
|
+
`;
|
|
1080
|
+
const LS_TOOL_DESCRIPTION = langchain.context`
|
|
1081
|
+
Lists all files in a directory.
|
|
946
1082
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
- Results are returned using cat -n format, with line numbers starting at 1
|
|
955
|
-
- Lines longer than 10,000 characters will be split into multiple lines with continuation markers (e.g., 5.1, 5.2, etc.). When you specify a limit, these continuation lines count towards the limit.
|
|
956
|
-
- You have the capability to call multiple tools in a single response. It is always better to speculatively read multiple files as a batch that are potentially useful.
|
|
957
|
-
- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.
|
|
958
|
-
- You should ALWAYS make sure a file has been read before editing it.`;
|
|
959
|
-
const WRITE_FILE_TOOL_DESCRIPTION = `Writes to a new file in the filesystem.
|
|
1083
|
+
This is useful for exploring the filesystem and finding the right file to read or edit.
|
|
1084
|
+
You should almost ALWAYS use this tool before using the read_file or edit_file tools.
|
|
1085
|
+
`;
|
|
1086
|
+
const READ_FILE_TOOL_DESCRIPTION = langchain.context`
|
|
1087
|
+
Reads a file from the filesystem.
|
|
1088
|
+
|
|
1089
|
+
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.
|
|
960
1090
|
|
|
961
|
-
Usage:
|
|
962
|
-
-
|
|
963
|
-
-
|
|
964
|
-
|
|
1091
|
+
Usage:
|
|
1092
|
+
- By default, it reads up to 100 lines starting from the beginning of the file
|
|
1093
|
+
- **IMPORTANT for large files and codebase exploration**: Use pagination with offset and limit parameters to avoid context overflow
|
|
1094
|
+
- First scan: read_file(path, limit=100) to see file structure
|
|
1095
|
+
- Read more sections: read_file(path, offset=100, limit=200) for next 200 lines
|
|
1096
|
+
- Only omit limit (read full file) when necessary for editing
|
|
1097
|
+
- Specify offset and limit: read_file(path, offset=0, limit=100) reads first 100 lines
|
|
1098
|
+
- Results are returned using cat -n format, with line numbers starting at 1
|
|
1099
|
+
- Lines longer than ${INT_FORMATTER.format(MAX_LINE_LENGTH)} characters will be split into multiple lines with continuation markers (e.g., 5.1, 5.2, etc.). When you specify a limit, these continuation lines count towards the limit.
|
|
1100
|
+
- You have the capability to call multiple tools in a single response. It is always better to speculatively read multiple files as a batch that are potentially useful.
|
|
1101
|
+
- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.
|
|
1102
|
+
- You should ALWAYS make sure a file has been read before editing it.
|
|
1103
|
+
`;
|
|
1104
|
+
const WRITE_FILE_TOOL_DESCRIPTION = langchain.context`
|
|
1105
|
+
Writes to a new file in the filesystem.
|
|
1106
|
+
|
|
1107
|
+
Usage:
|
|
1108
|
+
- The write_file tool will create a new file.
|
|
1109
|
+
- Prefer to edit existing files (with the edit_file tool) over creating new ones when possible.
|
|
1110
|
+
`;
|
|
1111
|
+
const EDIT_FILE_TOOL_DESCRIPTION = langchain.context`
|
|
1112
|
+
Performs exact string replacements in files.
|
|
965
1113
|
|
|
966
|
-
Usage:
|
|
967
|
-
- You must read the file before editing. This tool will error if you attempt an edit without reading the file first.
|
|
968
|
-
- When editing, preserve the exact indentation (tabs/spaces) from the read output. Never include line number prefixes in old_string or new_string.
|
|
969
|
-
- ALWAYS prefer editing existing files over creating new ones.
|
|
970
|
-
- Only use emojis if the user explicitly requests it
|
|
971
|
-
|
|
1114
|
+
Usage:
|
|
1115
|
+
- You must read the file before editing. This tool will error if you attempt an edit without reading the file first.
|
|
1116
|
+
- When editing, preserve the exact indentation (tabs/spaces) from the read output. Never include line number prefixes in old_string or new_string.
|
|
1117
|
+
- ALWAYS prefer editing existing files over creating new ones.
|
|
1118
|
+
- Only use emojis if the user explicitly requests it.
|
|
1119
|
+
`;
|
|
1120
|
+
const GLOB_TOOL_DESCRIPTION = langchain.context`
|
|
1121
|
+
Find files matching a glob pattern.
|
|
972
1122
|
|
|
973
|
-
Supports standard glob patterns: \`*\` (any characters), \`**\` (any directories), \`?\` (single character).
|
|
974
|
-
Returns a list of absolute file paths that match the pattern.
|
|
1123
|
+
Supports standard glob patterns: \`*\` (any characters), \`**\` (any directories), \`?\` (single character).
|
|
1124
|
+
Returns a list of absolute file paths that match the pattern.
|
|
975
1125
|
|
|
976
|
-
Examples:
|
|
977
|
-
- \`**/*.py\` - Find all Python files
|
|
978
|
-
- \`*.txt\` - Find all text files in root
|
|
979
|
-
- \`/subdir/**/*.md\` - Find all markdown files under /subdir
|
|
980
|
-
|
|
1126
|
+
Examples:
|
|
1127
|
+
- \`**/*.py\` - Find all Python files
|
|
1128
|
+
- \`*.txt\` - Find all text files in root
|
|
1129
|
+
- \`/subdir/**/*.md\` - Find all markdown files under /subdir
|
|
1130
|
+
`;
|
|
1131
|
+
const GREP_TOOL_DESCRIPTION = langchain.context`
|
|
1132
|
+
Search for a text pattern across files.
|
|
981
1133
|
|
|
982
|
-
Searches for literal text (not regex) and returns matching files or content based on output_mode.
|
|
983
|
-
Special characters like parentheses, brackets, pipes, etc. are treated as literal characters, not regex operators.
|
|
1134
|
+
Searches for literal text (not regex) and returns matching files or content based on output_mode.
|
|
1135
|
+
Special characters like parentheses, brackets, pipes, etc. are treated as literal characters, not regex operators.
|
|
984
1136
|
|
|
985
|
-
Examples:
|
|
986
|
-
- Search all files: \`grep(pattern="TODO")\`
|
|
987
|
-
- Search Python files only: \`grep(pattern="import", glob="*.py")\`
|
|
988
|
-
- Show matching lines: \`grep(pattern="error", output_mode="content")\`
|
|
989
|
-
- Search for code with special chars: \`grep(pattern="def __init__(self):")
|
|
990
|
-
|
|
1137
|
+
Examples:
|
|
1138
|
+
- Search all files: \`grep(pattern="TODO")\`
|
|
1139
|
+
- Search Python files only: \`grep(pattern="import", glob="*.py")\`
|
|
1140
|
+
- Show matching lines: \`grep(pattern="error", output_mode="content")\`
|
|
1141
|
+
- Search for code with special chars: \`grep(pattern="def __init__(self):")\`
|
|
1142
|
+
`;
|
|
1143
|
+
const EXECUTE_TOOL_DESCRIPTION = langchain.context`
|
|
1144
|
+
Executes a shell command in an isolated sandbox environment.
|
|
991
1145
|
|
|
992
|
-
Usage:
|
|
993
|
-
Executes a given command in the sandbox environment with proper handling and security measures.
|
|
994
|
-
Before executing the command, please follow these steps:
|
|
1146
|
+
Usage:
|
|
1147
|
+
Executes a given command in the sandbox environment with proper handling and security measures.
|
|
1148
|
+
Before executing the command, please follow these steps:
|
|
995
1149
|
|
|
996
|
-
1. Directory Verification:
|
|
997
|
-
|
|
998
|
-
|
|
1150
|
+
1. Directory Verification:
|
|
1151
|
+
- If the command will create new directories or files, first use the ls tool to verify the parent directory exists and is the correct location
|
|
1152
|
+
- For example, before running "mkdir foo/bar", first use ls to check that "foo" exists and is the intended parent directory
|
|
999
1153
|
|
|
1000
|
-
2. Command Execution:
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1154
|
+
2. Command Execution:
|
|
1155
|
+
- Always quote file paths that contain spaces with double quotes (e.g., cd "path with spaces/file.txt")
|
|
1156
|
+
- Examples of proper quoting:
|
|
1157
|
+
- cd "/Users/name/My Documents" (correct)
|
|
1158
|
+
- cd /Users/name/My Documents (incorrect - will fail)
|
|
1159
|
+
- python "/path/with spaces/script.py" (correct)
|
|
1160
|
+
- python /path/with spaces/script.py (incorrect - will fail)
|
|
1161
|
+
- After ensuring proper quoting, execute the command
|
|
1162
|
+
- Capture the output of the command
|
|
1009
1163
|
|
|
1010
|
-
Usage notes:
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1164
|
+
Usage notes:
|
|
1165
|
+
- Commands run in an isolated sandbox environment
|
|
1166
|
+
- Returns combined stdout/stderr output with exit code
|
|
1167
|
+
- If the output is very large, it may be truncated
|
|
1168
|
+
- VERY IMPORTANT: You MUST avoid using search commands like find and grep. Instead use the grep, glob tools to search. You MUST avoid read tools like cat, head, tail, and use read_file to read files.
|
|
1169
|
+
- When issuing multiple commands, use the ';' or '&&' operator to separate them. DO NOT use newlines (newlines are ok in quoted strings)
|
|
1170
|
+
- Use '&&' when commands depend on each other (e.g., "mkdir dir && cd dir")
|
|
1171
|
+
- Use ';' only when you need to run commands sequentially but don't care if earlier commands fail
|
|
1172
|
+
- Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of cd
|
|
1019
1173
|
|
|
1020
|
-
Examples:
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1174
|
+
Examples:
|
|
1175
|
+
Good examples:
|
|
1176
|
+
- execute(command="pytest /foo/bar/tests")
|
|
1177
|
+
- execute(command="python /path/to/script.py")
|
|
1178
|
+
- execute(command="npm install && npm test")
|
|
1025
1179
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1180
|
+
Bad examples (avoid these):
|
|
1181
|
+
- execute(command="cd /foo/bar && pytest tests") # Use absolute path instead
|
|
1182
|
+
- execute(command="cat file.txt") # Use read_file tool instead
|
|
1183
|
+
- execute(command="find . -name '*.py'") # Use glob tool instead
|
|
1184
|
+
- execute(command="grep -r 'pattern' .") # Use grep tool instead
|
|
1031
1185
|
|
|
1032
|
-
Note: This tool is only available if the backend supports execution (SandboxBackendProtocol).
|
|
1033
|
-
If execution is not supported, the tool will return an error message
|
|
1034
|
-
|
|
1186
|
+
Note: This tool is only available if the backend supports execution (SandboxBackendProtocol).
|
|
1187
|
+
If execution is not supported, the tool will return an error message.
|
|
1188
|
+
`;
|
|
1189
|
+
const EXECUTION_SYSTEM_PROMPT = langchain.context`
|
|
1190
|
+
## Execute Tool \`execute\`
|
|
1035
1191
|
|
|
1036
|
-
You have access to an \`execute\` tool for running shell commands in a sandboxed environment.
|
|
1037
|
-
Use this tool to run commands, scripts, tests, builds, and other shell operations.
|
|
1192
|
+
You have access to an \`execute\` tool for running shell commands in a sandboxed environment.
|
|
1193
|
+
Use this tool to run commands, scripts, tests, builds, and other shell operations.
|
|
1038
1194
|
|
|
1039
|
-
- execute: run a shell command in the sandbox (returns output and exit code)
|
|
1195
|
+
- execute: run a shell command in the sandbox (returns output and exit code)
|
|
1196
|
+
`;
|
|
1040
1197
|
/**
|
|
1041
1198
|
* Create ls tool using backend.
|
|
1042
1199
|
*/
|
|
1043
1200
|
function createLsTool(backend, options) {
|
|
1044
1201
|
const { customDescription } = options;
|
|
1045
|
-
return (0, langchain.tool)(async (input,
|
|
1046
|
-
const resolvedBackend =
|
|
1047
|
-
state: (0, _langchain_langgraph.getCurrentTaskInput)(config),
|
|
1048
|
-
store: config.store
|
|
1049
|
-
});
|
|
1202
|
+
return (0, langchain.tool)(async (input, runtime) => {
|
|
1203
|
+
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
1050
1204
|
const path = input.path || "/";
|
|
1051
1205
|
const lsResult = await resolvedBackend.ls(path);
|
|
1052
1206
|
if (lsResult.error) return `Error listing files: ${lsResult.error}`;
|
|
@@ -1072,11 +1226,8 @@ function createLsTool(backend, options) {
|
|
|
1072
1226
|
*/
|
|
1073
1227
|
function createReadFileTool(backend, options) {
|
|
1074
1228
|
const { customDescription, toolTokenLimitBeforeEvict } = options;
|
|
1075
|
-
return (0, langchain.tool)(async (input,
|
|
1076
|
-
const resolvedBackend =
|
|
1077
|
-
state: (0, _langchain_langgraph.getCurrentTaskInput)(config),
|
|
1078
|
-
store: config.store
|
|
1079
|
-
});
|
|
1229
|
+
return (0, langchain.tool)(async (input, runtime) => {
|
|
1230
|
+
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
1080
1231
|
const { file_path, offset = 0, limit = 100 } = input;
|
|
1081
1232
|
const readResult = await resolvedBackend.read(file_path, offset, limit);
|
|
1082
1233
|
if (readResult.error) return [{
|
|
@@ -1151,17 +1302,14 @@ function createReadFileTool(backend, options) {
|
|
|
1151
1302
|
*/
|
|
1152
1303
|
function createWriteFileTool(backend, options) {
|
|
1153
1304
|
const { customDescription } = options;
|
|
1154
|
-
return (0, langchain.tool)(async (input,
|
|
1155
|
-
const resolvedBackend =
|
|
1156
|
-
state: (0, _langchain_langgraph.getCurrentTaskInput)(config),
|
|
1157
|
-
store: config.store
|
|
1158
|
-
});
|
|
1305
|
+
return (0, langchain.tool)(async (input, runtime) => {
|
|
1306
|
+
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
1159
1307
|
const { file_path, content } = input;
|
|
1160
1308
|
const result = await resolvedBackend.write(file_path, content);
|
|
1161
1309
|
if (result.error) return result.error;
|
|
1162
1310
|
const message = new langchain.ToolMessage({
|
|
1163
1311
|
content: `Successfully wrote to '${file_path}'`,
|
|
1164
|
-
tool_call_id:
|
|
1312
|
+
tool_call_id: runtime.toolCall?.id,
|
|
1165
1313
|
name: "write_file",
|
|
1166
1314
|
metadata: result.metadata
|
|
1167
1315
|
});
|
|
@@ -1184,17 +1332,14 @@ function createWriteFileTool(backend, options) {
|
|
|
1184
1332
|
*/
|
|
1185
1333
|
function createEditFileTool(backend, options) {
|
|
1186
1334
|
const { customDescription } = options;
|
|
1187
|
-
return (0, langchain.tool)(async (input,
|
|
1188
|
-
const resolvedBackend =
|
|
1189
|
-
state: (0, _langchain_langgraph.getCurrentTaskInput)(config),
|
|
1190
|
-
store: config.store
|
|
1191
|
-
});
|
|
1335
|
+
return (0, langchain.tool)(async (input, runtime) => {
|
|
1336
|
+
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
1192
1337
|
const { file_path, old_string, new_string, replace_all = false } = input;
|
|
1193
1338
|
const result = await resolvedBackend.edit(file_path, old_string, new_string, replace_all);
|
|
1194
1339
|
if (result.error) return result.error;
|
|
1195
1340
|
const message = new langchain.ToolMessage({
|
|
1196
1341
|
content: `Successfully replaced ${result.occurrences} occurrence(s) in '${file_path}'`,
|
|
1197
|
-
tool_call_id:
|
|
1342
|
+
tool_call_id: runtime.toolCall?.id,
|
|
1198
1343
|
name: "edit_file",
|
|
1199
1344
|
metadata: result.metadata
|
|
1200
1345
|
});
|
|
@@ -1219,11 +1364,8 @@ function createEditFileTool(backend, options) {
|
|
|
1219
1364
|
*/
|
|
1220
1365
|
function createGlobTool(backend, options) {
|
|
1221
1366
|
const { customDescription } = options;
|
|
1222
|
-
return (0, langchain.tool)(async (input,
|
|
1223
|
-
const resolvedBackend =
|
|
1224
|
-
state: (0, _langchain_langgraph.getCurrentTaskInput)(config),
|
|
1225
|
-
store: config.store
|
|
1226
|
-
});
|
|
1367
|
+
return (0, langchain.tool)(async (input, runtime) => {
|
|
1368
|
+
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
1227
1369
|
const { pattern, path = "/" } = input;
|
|
1228
1370
|
const globResult = await resolvedBackend.glob(pattern, path);
|
|
1229
1371
|
if (globResult.error) return `Error finding files: ${globResult.error}`;
|
|
@@ -1246,11 +1388,8 @@ function createGlobTool(backend, options) {
|
|
|
1246
1388
|
*/
|
|
1247
1389
|
function createGrepTool(backend, options) {
|
|
1248
1390
|
const { customDescription } = options;
|
|
1249
|
-
return (0, langchain.tool)(async (input,
|
|
1250
|
-
const resolvedBackend =
|
|
1251
|
-
state: (0, _langchain_langgraph.getCurrentTaskInput)(config),
|
|
1252
|
-
store: config.store
|
|
1253
|
-
});
|
|
1391
|
+
return (0, langchain.tool)(async (input, runtime) => {
|
|
1392
|
+
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
1254
1393
|
const { pattern, path = "/", glob = null } = input;
|
|
1255
1394
|
const result = await resolvedBackend.grep(pattern, path, glob);
|
|
1256
1395
|
if (result.error) return result.error;
|
|
@@ -1274,7 +1413,7 @@ function createGrepTool(backend, options) {
|
|
|
1274
1413
|
schema: zod_v4.z.object({
|
|
1275
1414
|
pattern: zod_v4.z.string().describe("Regex pattern to search for"),
|
|
1276
1415
|
path: zod_v4.z.string().optional().default("/").describe("Base path to search from (default: /)"),
|
|
1277
|
-
glob: zod_v4.z.string().optional().nullable().describe("Optional glob pattern to filter files (e.g., '*.py')")
|
|
1416
|
+
glob: zod_v4.z.string().optional().nullable().default(null).describe("Optional glob pattern to filter files (e.g., '*.py')")
|
|
1278
1417
|
})
|
|
1279
1418
|
});
|
|
1280
1419
|
}
|
|
@@ -1283,11 +1422,8 @@ function createGrepTool(backend, options) {
|
|
|
1283
1422
|
*/
|
|
1284
1423
|
function createExecuteTool(backend, options) {
|
|
1285
1424
|
const { customDescription } = options;
|
|
1286
|
-
return (0, langchain.tool)(async (input,
|
|
1287
|
-
const resolvedBackend =
|
|
1288
|
-
state: (0, _langchain_langgraph.getCurrentTaskInput)(config),
|
|
1289
|
-
store: config.store
|
|
1290
|
-
});
|
|
1425
|
+
return (0, langchain.tool)(async (input, runtime) => {
|
|
1426
|
+
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
1291
1427
|
if (!isSandboxBackend(resolvedBackend)) return "Error: Execution not available. This agent's backend does not support command execution (SandboxBackendProtocol). To use the execute tool, provide a backend that implements SandboxBackendProtocol.";
|
|
1292
1428
|
const result = await resolvedBackend.execute(input.command);
|
|
1293
1429
|
const parts = [result.output];
|
|
@@ -1307,7 +1443,7 @@ function createExecuteTool(backend, options) {
|
|
|
1307
1443
|
* Create filesystem middleware with all tools and features.
|
|
1308
1444
|
*/
|
|
1309
1445
|
function createFilesystemMiddleware(options = {}) {
|
|
1310
|
-
const { backend = (
|
|
1446
|
+
const { backend = (runtime) => new StateBackend(runtime), systemPrompt: customSystemPrompt = null, customToolDescriptions = null, toolTokenLimitBeforeEvict = 2e4, humanMessageTokenLimitBeforeEvict = 5e4 } = options;
|
|
1311
1447
|
const baseSystemPrompt = customSystemPrompt || FILESYSTEM_SYSTEM_PROMPT;
|
|
1312
1448
|
const allToolsByName = {
|
|
1313
1449
|
ls: createLsTool(backend, { customDescription: customToolDescriptions?.ls }),
|
|
@@ -1325,19 +1461,53 @@ function createFilesystemMiddleware(options = {}) {
|
|
|
1325
1461
|
name: "FilesystemMiddleware",
|
|
1326
1462
|
stateSchema: FilesystemStateSchema,
|
|
1327
1463
|
tools: Object.values(allToolsByName),
|
|
1464
|
+
async beforeAgent(state) {
|
|
1465
|
+
if (!humanMessageTokenLimitBeforeEvict) return;
|
|
1466
|
+
const messages = state.messages;
|
|
1467
|
+
if (!messages || messages.length === 0) return;
|
|
1468
|
+
const last = messages[messages.length - 1];
|
|
1469
|
+
if (!langchain.HumanMessage.isInstance(last)) return;
|
|
1470
|
+
if (last.additional_kwargs?.lc_evicted_to) return;
|
|
1471
|
+
const contentStr = extractTextFromMessage(last);
|
|
1472
|
+
const threshold = 4 * humanMessageTokenLimitBeforeEvict;
|
|
1473
|
+
if (contentStr.length <= threshold) return;
|
|
1474
|
+
const resolvedBackend = await resolveBackend(backend, { state: state || {} });
|
|
1475
|
+
const filePath = `/conversation_history/${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
|
|
1476
|
+
const writeResult = await resolvedBackend.write(filePath, contentStr);
|
|
1477
|
+
if (writeResult.error) return;
|
|
1478
|
+
const result = { messages: [new langchain.HumanMessage({
|
|
1479
|
+
content: last.content,
|
|
1480
|
+
id: last.id,
|
|
1481
|
+
additional_kwargs: {
|
|
1482
|
+
...last.additional_kwargs,
|
|
1483
|
+
lc_evicted_to: filePath
|
|
1484
|
+
},
|
|
1485
|
+
response_metadata: { ...last.response_metadata }
|
|
1486
|
+
})] };
|
|
1487
|
+
if (writeResult.filesUpdate) result.files = writeResult.filesUpdate;
|
|
1488
|
+
return result;
|
|
1489
|
+
},
|
|
1328
1490
|
wrapModelCall: async (request, handler) => {
|
|
1329
|
-
const supportsExecution = isSandboxBackend(
|
|
1330
|
-
|
|
1331
|
-
|
|
1491
|
+
const supportsExecution = isSandboxBackend(await resolveBackend(backend, {
|
|
1492
|
+
...request.runtime,
|
|
1493
|
+
state: request.state
|
|
1332
1494
|
}));
|
|
1333
1495
|
let tools = request.tools;
|
|
1334
1496
|
if (!supportsExecution) tools = tools.filter((t) => t.name !== "execute");
|
|
1335
1497
|
let filesystemPrompt = baseSystemPrompt;
|
|
1336
1498
|
if (supportsExecution) filesystemPrompt = `${filesystemPrompt}\n\n${EXECUTION_SYSTEM_PROMPT}`;
|
|
1337
1499
|
const newSystemMessage = request.systemMessage.concat(filesystemPrompt);
|
|
1500
|
+
let messages = request.messages;
|
|
1501
|
+
if (humanMessageTokenLimitBeforeEvict && messages) {
|
|
1502
|
+
if (messages.some((msg) => langchain.HumanMessage.isInstance(msg) && msg.additional_kwargs?.lc_evicted_to)) messages = messages.map((msg) => {
|
|
1503
|
+
if (langchain.HumanMessage.isInstance(msg) && msg.additional_kwargs?.lc_evicted_to) return buildTruncatedHumanMessage(msg, msg.additional_kwargs.lc_evicted_to);
|
|
1504
|
+
return msg;
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1338
1507
|
return handler({
|
|
1339
1508
|
...request,
|
|
1340
1509
|
tools,
|
|
1510
|
+
messages,
|
|
1341
1511
|
systemMessage: newSystemMessage
|
|
1342
1512
|
});
|
|
1343
1513
|
},
|
|
@@ -1348,9 +1518,9 @@ function createFilesystemMiddleware(options = {}) {
|
|
|
1348
1518
|
const result = await handler(request);
|
|
1349
1519
|
async function processToolMessage(msg, toolTokenLimitBeforeEvict) {
|
|
1350
1520
|
if (typeof msg.content === "string" && msg.content.length > toolTokenLimitBeforeEvict * 4) {
|
|
1351
|
-
const resolvedBackend =
|
|
1352
|
-
|
|
1353
|
-
|
|
1521
|
+
const resolvedBackend = await resolveBackend(backend, {
|
|
1522
|
+
...request.runtime,
|
|
1523
|
+
state: request.state
|
|
1354
1524
|
});
|
|
1355
1525
|
const evictPath = `/large_tool_results/${sanitizeToolCallId(request.toolCall?.id || msg.tool_call_id)}`;
|
|
1356
1526
|
const writeResult = await resolvedBackend.write(evictPath, msg.content);
|
|
@@ -1443,117 +1613,117 @@ const EXCLUDED_STATE_KEYS = [
|
|
|
1443
1613
|
*/
|
|
1444
1614
|
const DEFAULT_GENERAL_PURPOSE_DESCRIPTION = "General-purpose agent for researching complex questions, searching for files and content, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you. This agent has access to all tools as the main agent.";
|
|
1445
1615
|
function getTaskToolDescription(subagentDescriptions) {
|
|
1446
|
-
return `
|
|
1447
|
-
Launch an ephemeral subagent to handle complex, multi-step independent tasks with isolated context windows.
|
|
1616
|
+
return langchain.context`
|
|
1617
|
+
Launch an ephemeral subagent to handle complex, multi-step independent tasks with isolated context windows.
|
|
1448
1618
|
|
|
1449
|
-
Available agent types and the tools they have access to:
|
|
1450
|
-
${subagentDescriptions.join("\n")}
|
|
1619
|
+
Available agent types and the tools they have access to:
|
|
1620
|
+
${subagentDescriptions.join("\n")}
|
|
1451
1621
|
|
|
1452
|
-
When using the Task tool, you must specify a subagent_type parameter to select which agent type to use.
|
|
1622
|
+
When using the Task tool, you must specify a subagent_type parameter to select which agent type to use.
|
|
1453
1623
|
|
|
1454
|
-
## Usage notes:
|
|
1455
|
-
1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
|
|
1456
|
-
2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
|
|
1457
|
-
3. Each agent invocation is stateless. You will not be able to send additional messages to the agent, nor will the agent be able to communicate with you outside of its final report. Therefore, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.
|
|
1458
|
-
4. The agent's outputs should generally be trusted
|
|
1459
|
-
5. Clearly tell the agent whether you expect it to create content, perform analysis, or just do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent
|
|
1460
|
-
6. If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.
|
|
1461
|
-
7. When only the general-purpose agent is provided, you should use it for all tasks. It is great for isolating context and token usage, and completing specific, complex tasks, as it has all the same capabilities as the main agent.
|
|
1624
|
+
## Usage notes:
|
|
1625
|
+
1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
|
|
1626
|
+
2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
|
|
1627
|
+
3. Each agent invocation is stateless. You will not be able to send additional messages to the agent, nor will the agent be able to communicate with you outside of its final report. Therefore, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.
|
|
1628
|
+
4. The agent's outputs should generally be trusted
|
|
1629
|
+
5. Clearly tell the agent whether you expect it to create content, perform analysis, or just do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent
|
|
1630
|
+
6. If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.
|
|
1631
|
+
7. When only the general-purpose agent is provided, you should use it for all tasks. It is great for isolating context and token usage, and completing specific, complex tasks, as it has all the same capabilities as the main agent.
|
|
1462
1632
|
|
|
1463
|
-
### Example usage of the general-purpose agent:
|
|
1633
|
+
### Example usage of the general-purpose agent:
|
|
1464
1634
|
|
|
1465
|
-
<example_agent_descriptions>
|
|
1466
|
-
"general-purpose": use this agent for general purpose tasks, it has access to all tools as the main agent.
|
|
1467
|
-
</example_agent_descriptions>
|
|
1635
|
+
<example_agent_descriptions>
|
|
1636
|
+
"general-purpose": use this agent for general purpose tasks, it has access to all tools as the main agent.
|
|
1637
|
+
</example_agent_descriptions>
|
|
1468
1638
|
|
|
1469
|
-
<example>
|
|
1470
|
-
User: "I want to conduct research on the accomplishments of Lebron James, Michael Jordan, and Kobe Bryant, and then compare them."
|
|
1471
|
-
Assistant: *Uses the task tool in parallel to conduct isolated research on each of the three players*
|
|
1472
|
-
Assistant: *Synthesizes the results of the three isolated research tasks and responds to the User*
|
|
1473
|
-
<commentary>
|
|
1474
|
-
Research is a complex, multi-step task in it of itself.
|
|
1475
|
-
The research of each individual player is not dependent on the research of the other players.
|
|
1476
|
-
The assistant uses the task tool to break down the complex objective into three isolated tasks.
|
|
1477
|
-
Each research task only needs to worry about context and tokens about one player, then returns synthesized information about each player as the Tool Result.
|
|
1478
|
-
This means each research task can dive deep and spend tokens and context deeply researching each player, but the final result is synthesized information, and saves us tokens in the long run when comparing the players to each other.
|
|
1479
|
-
</commentary>
|
|
1480
|
-
</example>
|
|
1639
|
+
<example>
|
|
1640
|
+
User: "I want to conduct research on the accomplishments of Lebron James, Michael Jordan, and Kobe Bryant, and then compare them."
|
|
1641
|
+
Assistant: *Uses the task tool in parallel to conduct isolated research on each of the three players*
|
|
1642
|
+
Assistant: *Synthesizes the results of the three isolated research tasks and responds to the User*
|
|
1643
|
+
<commentary>
|
|
1644
|
+
Research is a complex, multi-step task in it of itself.
|
|
1645
|
+
The research of each individual player is not dependent on the research of the other players.
|
|
1646
|
+
The assistant uses the task tool to break down the complex objective into three isolated tasks.
|
|
1647
|
+
Each research task only needs to worry about context and tokens about one player, then returns synthesized information about each player as the Tool Result.
|
|
1648
|
+
This means each research task can dive deep and spend tokens and context deeply researching each player, but the final result is synthesized information, and saves us tokens in the long run when comparing the players to each other.
|
|
1649
|
+
</commentary>
|
|
1650
|
+
</example>
|
|
1481
1651
|
|
|
1482
|
-
<example>
|
|
1483
|
-
User: "Analyze a single large code repository for security vulnerabilities and generate a report."
|
|
1484
|
-
Assistant: *Launches a single \`task\` subagent for the repository analysis*
|
|
1485
|
-
Assistant: *Receives report and integrates results into final summary*
|
|
1486
|
-
<commentary>
|
|
1487
|
-
Subagent is used to isolate a large, context-heavy task, even though there is only one. This prevents the main thread from being overloaded with details.
|
|
1488
|
-
If the user then asks followup questions, we have a concise report to reference instead of the entire history of analysis and tool calls, which is good and saves us time and money.
|
|
1489
|
-
</commentary>
|
|
1490
|
-
</example>
|
|
1652
|
+
<example>
|
|
1653
|
+
User: "Analyze a single large code repository for security vulnerabilities and generate a report."
|
|
1654
|
+
Assistant: *Launches a single \`task\` subagent for the repository analysis*
|
|
1655
|
+
Assistant: *Receives report and integrates results into final summary*
|
|
1656
|
+
<commentary>
|
|
1657
|
+
Subagent is used to isolate a large, context-heavy task, even though there is only one. This prevents the main thread from being overloaded with details.
|
|
1658
|
+
If the user then asks followup questions, we have a concise report to reference instead of the entire history of analysis and tool calls, which is good and saves us time and money.
|
|
1659
|
+
</commentary>
|
|
1660
|
+
</example>
|
|
1491
1661
|
|
|
1492
|
-
<example>
|
|
1493
|
-
User: "Schedule two meetings for me and prepare agendas for each."
|
|
1494
|
-
Assistant: *Calls the task tool in parallel to launch two \`task\` subagents (one per meeting) to prepare agendas*
|
|
1495
|
-
Assistant: *Returns final schedules and agendas*
|
|
1496
|
-
<commentary>
|
|
1497
|
-
Tasks are simple individually, but subagents help silo agenda preparation.
|
|
1498
|
-
Each subagent only needs to worry about the agenda for one meeting.
|
|
1499
|
-
</commentary>
|
|
1500
|
-
</example>
|
|
1662
|
+
<example>
|
|
1663
|
+
User: "Schedule two meetings for me and prepare agendas for each."
|
|
1664
|
+
Assistant: *Calls the task tool in parallel to launch two \`task\` subagents (one per meeting) to prepare agendas*
|
|
1665
|
+
Assistant: *Returns final schedules and agendas*
|
|
1666
|
+
<commentary>
|
|
1667
|
+
Tasks are simple individually, but subagents help silo agenda preparation.
|
|
1668
|
+
Each subagent only needs to worry about the agenda for one meeting.
|
|
1669
|
+
</commentary>
|
|
1670
|
+
</example>
|
|
1501
1671
|
|
|
1502
|
-
<example>
|
|
1503
|
-
User: "I want to order a pizza from Dominos, order a burger from McDonald's, and order a salad from Subway."
|
|
1504
|
-
Assistant: *Calls tools directly in parallel to order a pizza from Dominos, a burger from McDonald's, and a salad from Subway*
|
|
1505
|
-
<commentary>
|
|
1506
|
-
The assistant did not use the task tool because the objective is super simple and clear and only requires a few trivial tool calls.
|
|
1507
|
-
It is better to just complete the task directly and NOT use the \`task\`tool.
|
|
1508
|
-
</commentary>
|
|
1509
|
-
</example>
|
|
1672
|
+
<example>
|
|
1673
|
+
User: "I want to order a pizza from Dominos, order a burger from McDonald's, and order a salad from Subway."
|
|
1674
|
+
Assistant: *Calls tools directly in parallel to order a pizza from Dominos, a burger from McDonald's, and a salad from Subway*
|
|
1675
|
+
<commentary>
|
|
1676
|
+
The assistant did not use the task tool because the objective is super simple and clear and only requires a few trivial tool calls.
|
|
1677
|
+
It is better to just complete the task directly and NOT use the \`task\`tool.
|
|
1678
|
+
</commentary>
|
|
1679
|
+
</example>
|
|
1510
1680
|
|
|
1511
|
-
### Example usage with custom agents:
|
|
1681
|
+
### Example usage with custom agents:
|
|
1512
1682
|
|
|
1513
|
-
<example_agent_descriptions>
|
|
1514
|
-
"content-reviewer": use this agent after you are done creating significant content or documents
|
|
1515
|
-
"greeting-responder": use this agent when to respond to user greetings with a friendly joke
|
|
1516
|
-
"research-analyst": use this agent to conduct thorough research on complex topics
|
|
1517
|
-
</example_agent_description>
|
|
1683
|
+
<example_agent_descriptions>
|
|
1684
|
+
"content-reviewer": use this agent after you are done creating significant content or documents
|
|
1685
|
+
"greeting-responder": use this agent when to respond to user greetings with a friendly joke
|
|
1686
|
+
"research-analyst": use this agent to conduct thorough research on complex topics
|
|
1687
|
+
</example_agent_description>
|
|
1518
1688
|
|
|
1519
|
-
<example>
|
|
1520
|
-
user: "Please write a function that checks if a number is prime"
|
|
1521
|
-
assistant: Sure let me write a function that checks if a number is prime
|
|
1522
|
-
assistant: First let me use the Write tool to write a function that checks if a number is prime
|
|
1523
|
-
assistant: I'm going to use the Write tool to write the following code:
|
|
1524
|
-
<code>
|
|
1525
|
-
function isPrime(n) {
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
}
|
|
1532
|
-
</code>
|
|
1533
|
-
<commentary>
|
|
1534
|
-
Since significant content was created and the task was completed, now use the content-reviewer agent to review the work
|
|
1535
|
-
</commentary>
|
|
1536
|
-
assistant: Now let me use the content-reviewer agent to review the code
|
|
1537
|
-
assistant: Uses the Task tool to launch with the content-reviewer agent
|
|
1538
|
-
</example>
|
|
1689
|
+
<example>
|
|
1690
|
+
user: "Please write a function that checks if a number is prime"
|
|
1691
|
+
assistant: Sure let me write a function that checks if a number is prime
|
|
1692
|
+
assistant: First let me use the Write tool to write a function that checks if a number is prime
|
|
1693
|
+
assistant: I'm going to use the Write tool to write the following code:
|
|
1694
|
+
<code>
|
|
1695
|
+
function isPrime(n) {{
|
|
1696
|
+
if (n <= 1) return false
|
|
1697
|
+
for (let i = 2; i * i <= n; i++) {{
|
|
1698
|
+
if (n % i === 0) return false
|
|
1699
|
+
}}
|
|
1700
|
+
return true
|
|
1701
|
+
}}
|
|
1702
|
+
</code>
|
|
1703
|
+
<commentary>
|
|
1704
|
+
Since significant content was created and the task was completed, now use the content-reviewer agent to review the work
|
|
1705
|
+
</commentary>
|
|
1706
|
+
assistant: Now let me use the content-reviewer agent to review the code
|
|
1707
|
+
assistant: Uses the Task tool to launch with the content-reviewer agent
|
|
1708
|
+
</example>
|
|
1539
1709
|
|
|
1540
|
-
<example>
|
|
1541
|
-
user: "Can you help me research the environmental impact of different renewable energy sources and create a comprehensive report?"
|
|
1542
|
-
<commentary>
|
|
1543
|
-
This is a complex research task that would benefit from using the research-analyst agent to conduct thorough analysis
|
|
1544
|
-
</commentary>
|
|
1545
|
-
assistant: I'll help you research the environmental impact of renewable energy sources. Let me use the research-analyst agent to conduct comprehensive research on this topic.
|
|
1546
|
-
assistant: Uses the Task tool to launch with the research-analyst agent, providing detailed instructions about what research to conduct and what format the report should take
|
|
1547
|
-
</example>
|
|
1710
|
+
<example>
|
|
1711
|
+
user: "Can you help me research the environmental impact of different renewable energy sources and create a comprehensive report?"
|
|
1712
|
+
<commentary>
|
|
1713
|
+
This is a complex research task that would benefit from using the research-analyst agent to conduct thorough analysis
|
|
1714
|
+
</commentary>
|
|
1715
|
+
assistant: I'll help you research the environmental impact of renewable energy sources. Let me use the research-analyst agent to conduct comprehensive research on this topic.
|
|
1716
|
+
assistant: Uses the Task tool to launch with the research-analyst agent, providing detailed instructions about what research to conduct and what format the report should take
|
|
1717
|
+
</example>
|
|
1548
1718
|
|
|
1549
|
-
<example>
|
|
1550
|
-
user: "Hello"
|
|
1551
|
-
<commentary>
|
|
1552
|
-
Since the user is greeting, use the greeting-responder agent to respond with a friendly joke
|
|
1553
|
-
</commentary>
|
|
1554
|
-
assistant: "I'm going to use the Task tool to launch with the greeting-responder agent"
|
|
1555
|
-
</example>
|
|
1556
|
-
|
|
1719
|
+
<example>
|
|
1720
|
+
user: "Hello"
|
|
1721
|
+
<commentary>
|
|
1722
|
+
Since the user is greeting, use the greeting-responder agent to respond with a friendly joke
|
|
1723
|
+
</commentary>
|
|
1724
|
+
assistant: "I'm going to use the Task tool to launch with the greeting-responder agent"
|
|
1725
|
+
</example>
|
|
1726
|
+
`;
|
|
1557
1727
|
}
|
|
1558
1728
|
/**
|
|
1559
1729
|
* System prompt section that explains how to use the task tool for spawning subagents.
|
|
@@ -1568,33 +1738,35 @@ assistant: "I'm going to use the Task tool to launch with the greeting-responder
|
|
|
1568
1738
|
* You can provide a custom `systemPrompt` to `createSubAgentMiddleware` to override
|
|
1569
1739
|
* or extend this default.
|
|
1570
1740
|
*/
|
|
1571
|
-
const TASK_SYSTEM_PROMPT =
|
|
1741
|
+
const TASK_SYSTEM_PROMPT = langchain.context`
|
|
1742
|
+
## \`task\` (subagent spawner)
|
|
1572
1743
|
|
|
1573
|
-
You have access to a \`task\` tool to launch short-lived subagents that handle isolated tasks. These agents are ephemeral — they live only for the duration of the task and return a single result.
|
|
1744
|
+
You have access to a \`task\` tool to launch short-lived subagents that handle isolated tasks. These agents are ephemeral — they live only for the duration of the task and return a single result.
|
|
1574
1745
|
|
|
1575
|
-
When to use the task tool:
|
|
1576
|
-
- When a task is complex and multi-step, and can be fully delegated in isolation
|
|
1577
|
-
- When a task is independent of other tasks and can run in parallel
|
|
1578
|
-
- When a task requires focused reasoning or heavy token/context usage that would bloat the orchestrator thread
|
|
1579
|
-
- When sandboxing improves reliability (e.g. code execution, structured searches, data formatting)
|
|
1580
|
-
- When you only care about the output of the subagent, and not the intermediate steps (ex. performing a lot of research and then returned a synthesized report, performing a series of computations or lookups to achieve a concise, relevant answer.)
|
|
1746
|
+
When to use the task tool:
|
|
1747
|
+
- When a task is complex and multi-step, and can be fully delegated in isolation
|
|
1748
|
+
- When a task is independent of other tasks and can run in parallel
|
|
1749
|
+
- When a task requires focused reasoning or heavy token/context usage that would bloat the orchestrator thread
|
|
1750
|
+
- When sandboxing improves reliability (e.g. code execution, structured searches, data formatting)
|
|
1751
|
+
- When you only care about the output of the subagent, and not the intermediate steps (ex. performing a lot of research and then returned a synthesized report, performing a series of computations or lookups to achieve a concise, relevant answer.)
|
|
1581
1752
|
|
|
1582
|
-
Subagent lifecycle:
|
|
1583
|
-
1. **Spawn** → Provide clear role, instructions, and expected output
|
|
1584
|
-
2. **Run** → The subagent completes the task autonomously
|
|
1585
|
-
3. **Return** → The subagent provides a single structured result
|
|
1586
|
-
4. **Reconcile** → Incorporate or synthesize the result into the main thread
|
|
1753
|
+
Subagent lifecycle:
|
|
1754
|
+
1. **Spawn** → Provide clear role, instructions, and expected output
|
|
1755
|
+
2. **Run** → The subagent completes the task autonomously
|
|
1756
|
+
3. **Return** → The subagent provides a single structured result
|
|
1757
|
+
4. **Reconcile** → Incorporate or synthesize the result into the main thread
|
|
1587
1758
|
|
|
1588
|
-
When NOT to use the task tool:
|
|
1589
|
-
- If you need to see the intermediate reasoning or steps after the subagent has completed (the task tool hides them)
|
|
1590
|
-
- If the task is trivial (a few tool calls or simple lookup)
|
|
1591
|
-
- If delegating does not reduce token usage, complexity, or context switching
|
|
1592
|
-
- If splitting would add latency without benefit
|
|
1759
|
+
When NOT to use the task tool:
|
|
1760
|
+
- If you need to see the intermediate reasoning or steps after the subagent has completed (the task tool hides them)
|
|
1761
|
+
- If the task is trivial (a few tool calls or simple lookup)
|
|
1762
|
+
- If delegating does not reduce token usage, complexity, or context switching
|
|
1763
|
+
- If splitting would add latency without benefit
|
|
1593
1764
|
|
|
1594
|
-
## Important Task Tool Usage Notes to Remember
|
|
1595
|
-
- Whenever possible, parallelize the work that you do. This is true for both tool_calls, and for tasks. Whenever you have independent steps to complete - make tool_calls, or kick off tasks (subagents) in parallel to accomplish them faster. This saves time for the user, which is incredibly important.
|
|
1596
|
-
- Remember to use the \`task\` tool to silo independent tasks within a multi-part objective.
|
|
1597
|
-
- You should use the \`task\` tool whenever you have a complex task that will take multiple steps, and is independent from other tasks that the agent needs to complete. These agents are highly competent and efficient
|
|
1765
|
+
## Important Task Tool Usage Notes to Remember
|
|
1766
|
+
- Whenever possible, parallelize the work that you do. This is true for both tool_calls, and for tasks. Whenever you have independent steps to complete - make tool_calls, or kick off tasks (subagents) in parallel to accomplish them faster. This saves time for the user, which is incredibly important.
|
|
1767
|
+
- Remember to use the \`task\` tool to silo independent tasks within a multi-part objective.
|
|
1768
|
+
- You should use the \`task\` tool whenever you have a complex task that will take multiple steps, and is independent from other tasks that the agent needs to complete. These agents are highly competent and efficient.
|
|
1769
|
+
`;
|
|
1598
1770
|
/**
|
|
1599
1771
|
* Base specification for the general-purpose subagent.
|
|
1600
1772
|
*
|
|
@@ -2003,65 +2175,67 @@ const MemoryStateSchema = new _langchain_langgraph.StateSchema({
|
|
|
2003
2175
|
* Default system prompt template for memory.
|
|
2004
2176
|
* Ported from Python's comprehensive memory guidelines.
|
|
2005
2177
|
*/
|
|
2006
|
-
const MEMORY_SYSTEM_PROMPT =
|
|
2007
|
-
|
|
2008
|
-
|
|
2178
|
+
const MEMORY_SYSTEM_PROMPT = langchain.context`
|
|
2179
|
+
<agent_memory>
|
|
2180
|
+
{memory_contents}
|
|
2181
|
+
</agent_memory>
|
|
2009
2182
|
|
|
2010
|
-
<memory_guidelines>
|
|
2011
|
-
|
|
2183
|
+
<memory_guidelines>
|
|
2184
|
+
The above <agent_memory> was loaded in from files in your filesystem. As you learn from your interactions with the user, you can save new knowledge by calling the \`edit_file\` tool.
|
|
2012
2185
|
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2186
|
+
**Learning from feedback:**
|
|
2187
|
+
- One of your MAIN PRIORITIES is to learn from your interactions with the user. These learnings can be implicit or explicit. This means that in the future, you will remember this important information.
|
|
2188
|
+
- When you need to remember something, updating memory must be your FIRST, IMMEDIATE action - before responding to the user, before calling other tools, before doing anything else. Just update memory immediately.
|
|
2189
|
+
- When user says something is better/worse, capture WHY and encode it as a pattern.
|
|
2190
|
+
- Each correction is a chance to improve permanently - don't just fix the immediate issue, update your instructions.
|
|
2191
|
+
- A great opportunity to update your memories is when the user interrupts a tool call and provides feedback. You should update your memories immediately before revising the tool call.
|
|
2192
|
+
- Look for the underlying principle behind corrections, not just the specific mistake.
|
|
2193
|
+
- The user might not explicitly ask you to remember something, but if they provide information that is useful for future use, you should update your memories immediately.
|
|
2021
2194
|
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2195
|
+
**Asking for information:**
|
|
2196
|
+
- If you lack context to perform an action (e.g. send a Slack DM, requires a user ID/email) you should explicitly ask the user for this information.
|
|
2197
|
+
- It is preferred for you to ask for information, don't assume anything that you do not know!
|
|
2198
|
+
- When the user provides information that is useful for future use, you should update your memories immediately.
|
|
2026
2199
|
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2200
|
+
**When to update memories:**
|
|
2201
|
+
- When the user explicitly asks you to remember something (e.g., "remember my email", "save this preference")
|
|
2202
|
+
- When the user describes your role or how you should behave (e.g., "you are a web researcher", "always do X")
|
|
2203
|
+
- When the user gives feedback on your work - capture what was wrong and how to improve
|
|
2204
|
+
- When the user provides information required for tool use (e.g., slack channel ID, email addresses)
|
|
2205
|
+
- When the user provides context useful for future tasks, such as how to use tools, or which actions to take in a particular situation
|
|
2206
|
+
- When you discover new patterns or preferences (coding styles, conventions, workflows)
|
|
2034
2207
|
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2208
|
+
**When to NOT update memories:**
|
|
2209
|
+
- When the information is temporary or transient (e.g., "I'm running late", "I'm on my phone right now")
|
|
2210
|
+
- When the information is a one-time task request (e.g., "Find me a recipe", "What's 25 * 4?")
|
|
2211
|
+
- When the information is a simple question that doesn't reveal lasting preferences (e.g., "What day is it?", "Can you explain X?")
|
|
2212
|
+
- When the information is an acknowledgment or small talk (e.g., "Sounds good!", "Hello", "Thanks for that")
|
|
2213
|
+
- When the information is stale or irrelevant in future conversations
|
|
2214
|
+
- Never store API keys, access tokens, passwords, or any other credentials in any file, memory, or system prompt.
|
|
2215
|
+
- If the user asks where to put API keys or provides an API key, do NOT echo or save it.
|
|
2043
2216
|
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2217
|
+
**Examples:**
|
|
2218
|
+
Example 1 (remembering user information):
|
|
2219
|
+
User: Can you connect to my google account?
|
|
2220
|
+
Agent: Sure, I'll connect to your google account, what's your google account email?
|
|
2221
|
+
User: john@example.com
|
|
2222
|
+
Agent: Let me save this to my memory.
|
|
2223
|
+
Tool Call: edit_file(...) -> remembers that the user's google account email is john@example.com
|
|
2051
2224
|
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2225
|
+
Example 2 (remembering implicit user preferences):
|
|
2226
|
+
User: Can you write me an example for creating a deep agent in LangChain?
|
|
2227
|
+
Agent: Sure, I'll write you an example for creating a deep agent in LangChain <example code in Python>
|
|
2228
|
+
User: Can you do this in JavaScript
|
|
2229
|
+
Agent: Let me save this to my memory.
|
|
2230
|
+
Tool Call: edit_file(...) -> remembers that the user prefers to get LangChain code examples in JavaScript
|
|
2231
|
+
Agent: Sure, here is the JavaScript example<example code in JavaScript>
|
|
2059
2232
|
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
</memory_guidelines
|
|
2233
|
+
Example 3 (do not remember transient information):
|
|
2234
|
+
User: I'm going to play basketball tonight so I will be offline for a few hours.
|
|
2235
|
+
Agent: Okay I'll add a block to your calendar.
|
|
2236
|
+
Tool Call: create_calendar_event(...) -> just calls a tool, does not commit anything to memory, as it is transient information
|
|
2237
|
+
</memory_guidelines>
|
|
2238
|
+
`;
|
|
2065
2239
|
/**
|
|
2066
2240
|
* Format loaded memory contents for injection into prompt.
|
|
2067
2241
|
* Pairs memory locations with their contents for clarity.
|
|
@@ -2120,19 +2294,12 @@ async function loadMemoryFromBackend(backend, path) {
|
|
|
2120
2294
|
*/
|
|
2121
2295
|
function createMemoryMiddleware(options) {
|
|
2122
2296
|
const { backend, sources, addCacheControl = false } = options;
|
|
2123
|
-
/**
|
|
2124
|
-
* Resolve backend from instance or factory.
|
|
2125
|
-
*/
|
|
2126
|
-
function getBackend(state) {
|
|
2127
|
-
if (typeof backend === "function") return adaptBackendProtocol(backend({ state }));
|
|
2128
|
-
return adaptBackendProtocol(backend);
|
|
2129
|
-
}
|
|
2130
2297
|
return (0, langchain.createMiddleware)({
|
|
2131
2298
|
name: "MemoryMiddleware",
|
|
2132
2299
|
stateSchema: MemoryStateSchema,
|
|
2133
2300
|
async beforeAgent(state) {
|
|
2134
2301
|
if ("memoryContents" in state && state.memoryContents != null) return;
|
|
2135
|
-
const resolvedBackend =
|
|
2302
|
+
const resolvedBackend = await resolveBackend(backend, { state });
|
|
2136
2303
|
const contents = {};
|
|
2137
2304
|
for (const path of sources) try {
|
|
2138
2305
|
const content = await loadMemoryFromBackend(resolvedBackend, path);
|
|
@@ -2266,7 +2433,7 @@ Skills follow a **progressive disclosure** pattern - you know they exist (name +
|
|
|
2266
2433
|
1. **Recognize when a skill applies**: Check if the user's task matches any skill's description
|
|
2267
2434
|
2. **Read the skill's full instructions**: The skill list above shows the exact path to use with read_file
|
|
2268
2435
|
3. **Follow the skill's instructions**: SKILL.md contains step-by-step workflows, best practices, and examples
|
|
2269
|
-
4. **Access supporting files**: Skills may include
|
|
2436
|
+
4. **Access supporting files**: Skills may include scripts, configs, or reference docs - use absolute paths
|
|
2270
2437
|
|
|
2271
2438
|
**When to Use Skills:**
|
|
2272
2439
|
- When the user's request matches a skill's domain (e.g., "research X" → web-research skill)
|
|
@@ -2278,7 +2445,7 @@ Skills follow a **progressive disclosure** pattern - you know they exist (name +
|
|
|
2278
2445
|
- The skill list above shows the full path for each skill's SKILL.md file
|
|
2279
2446
|
|
|
2280
2447
|
**Executing Skill Scripts:**
|
|
2281
|
-
Skills may contain
|
|
2448
|
+
Skills may contain scripts or other executable files. Always use absolute paths from the skill list.
|
|
2282
2449
|
|
|
2283
2450
|
**Example Workflow:**
|
|
2284
2451
|
|
|
@@ -2538,13 +2705,6 @@ function formatSkillsList(skills, sources) {
|
|
|
2538
2705
|
function createSkillsMiddleware(options) {
|
|
2539
2706
|
const { backend, sources } = options;
|
|
2540
2707
|
let loadedSkills = [];
|
|
2541
|
-
/**
|
|
2542
|
-
* Resolve backend from instance or factory.
|
|
2543
|
-
*/
|
|
2544
|
-
function getBackend(state) {
|
|
2545
|
-
if (typeof backend === "function") return adaptBackendProtocol(backend({ state }));
|
|
2546
|
-
return adaptBackendProtocol(backend);
|
|
2547
|
-
}
|
|
2548
2708
|
return (0, langchain.createMiddleware)({
|
|
2549
2709
|
name: "SkillsMiddleware",
|
|
2550
2710
|
stateSchema: SkillsStateSchema,
|
|
@@ -2554,7 +2714,7 @@ function createSkillsMiddleware(options) {
|
|
|
2554
2714
|
loadedSkills = state.skillsMetadata;
|
|
2555
2715
|
return;
|
|
2556
2716
|
}
|
|
2557
|
-
const resolvedBackend =
|
|
2717
|
+
const resolvedBackend = await resolveBackend(backend, { state });
|
|
2558
2718
|
const allSkills = /* @__PURE__ */ new Map();
|
|
2559
2719
|
for (const sourcePath of sources) try {
|
|
2560
2720
|
const skills = await listSkillsFromBackend(resolvedBackend, sourcePath);
|
|
@@ -2586,25 +2746,26 @@ function createSkillsMiddleware(options) {
|
|
|
2586
2746
|
* This module provides shared helpers used across middleware implementations.
|
|
2587
2747
|
*/
|
|
2588
2748
|
//#endregion
|
|
2589
|
-
//#region src/middleware/
|
|
2749
|
+
//#region src/middleware/completion_callback.ts
|
|
2590
2750
|
/**
|
|
2591
|
-
*
|
|
2751
|
+
* Callback middleware for async subagents.
|
|
2592
2752
|
*
|
|
2593
|
-
*
|
|
2753
|
+
* @experimental - this middleware is experimental and may change in future releases.
|
|
2594
2754
|
*
|
|
2595
|
-
*
|
|
2596
|
-
*
|
|
2597
|
-
*
|
|
2755
|
+
* This middleware sends a notification to a callback thread when a subagent
|
|
2756
|
+
* completes successfully or raises an error. The callback agent can then
|
|
2757
|
+
* process that notification instead of relying only on polling via
|
|
2598
2758
|
* `check_async_task`.
|
|
2599
2759
|
*
|
|
2600
2760
|
* ## Architecture
|
|
2601
2761
|
*
|
|
2602
|
-
*
|
|
2603
|
-
*
|
|
2604
|
-
*
|
|
2762
|
+
* A parent agent launches a subagent with `start_async_task` and can later
|
|
2763
|
+
* inspect task state with `check_async_task`. This middleware adds an optional
|
|
2764
|
+
* completion signal by creating a run on the callback thread when the subagent
|
|
2765
|
+
* finishes.
|
|
2605
2766
|
*
|
|
2606
2767
|
* ```
|
|
2607
|
-
*
|
|
2768
|
+
* Parent Subagent
|
|
2608
2769
|
* | |
|
|
2609
2770
|
* |--- start_async_task -----> |
|
|
2610
2771
|
* |<-- task_id (immediately) - |
|
|
@@ -2612,67 +2773,74 @@ function createSkillsMiddleware(options) {
|
|
|
2612
2773
|
* | | (done!)
|
|
2613
2774
|
* | |
|
|
2614
2775
|
* |<-- runs.create( |
|
|
2615
|
-
* |
|
|
2776
|
+
* | callback_thread, |
|
|
2616
2777
|
* | "completed: ...") |
|
|
2617
2778
|
* | |
|
|
2618
|
-
* | (
|
|
2779
|
+
* | (processes result) |
|
|
2619
2780
|
* ```
|
|
2620
2781
|
*
|
|
2621
|
-
* The
|
|
2622
|
-
*
|
|
2623
|
-
*
|
|
2624
|
-
*
|
|
2625
|
-
*
|
|
2626
|
-
*
|
|
2627
|
-
*
|
|
2628
|
-
*
|
|
2629
|
-
*
|
|
2630
|
-
*
|
|
2631
|
-
* - `
|
|
2632
|
-
*
|
|
2633
|
-
*
|
|
2634
|
-
*
|
|
2635
|
-
*
|
|
2636
|
-
* supervisor's `start_async_task` tool. It survives thread interrupts and
|
|
2637
|
-
* updates because it lives in state, not config.
|
|
2638
|
-
* - If `parent_thread_id` is not present in state, the notifier silently no-ops.
|
|
2782
|
+
* The middleware calls `runs.create()` on the callback thread. From the
|
|
2783
|
+
* callback agent's perspective, this appears as a new user message containing
|
|
2784
|
+
* structured output from the subagent.
|
|
2785
|
+
*
|
|
2786
|
+
* ## Callback context
|
|
2787
|
+
*
|
|
2788
|
+
* - `callbackGraphId` identifies the callback graph or assistant. It is
|
|
2789
|
+
* provided when the middleware is constructed.
|
|
2790
|
+
* - `url` and `headers` optionally configure a remote callback destination.
|
|
2791
|
+
* Omit `url` for same-deployment ASGI transport.
|
|
2792
|
+
* - `callback_thread_id` is stored in the subagent state by the parent's
|
|
2793
|
+
* `start_async_task` tool. Because it is stored in state rather than config,
|
|
2794
|
+
* it survives thread updates and interrupts.
|
|
2795
|
+
* - If `callback_thread_id` is not present in state, the middleware does
|
|
2796
|
+
* nothing.
|
|
2639
2797
|
*
|
|
2640
2798
|
* ## Usage
|
|
2641
2799
|
*
|
|
2642
2800
|
* ```typescript
|
|
2643
|
-
* import {
|
|
2801
|
+
* import { createCompletionCallbackMiddleware } from "deepagents";
|
|
2644
2802
|
*
|
|
2645
|
-
*
|
|
2646
|
-
*
|
|
2803
|
+
* // Same deployment (callback agent and subagent share a server):
|
|
2804
|
+
* const notifier = createCompletionCallbackMiddleware({
|
|
2805
|
+
* callbackGraphId: "supervisor",
|
|
2806
|
+
* });
|
|
2807
|
+
*
|
|
2808
|
+
* // Remote deployment (callback destination on a different server):
|
|
2809
|
+
* const notifier = createCompletionCallbackMiddleware({
|
|
2810
|
+
* callbackGraphId: "supervisor",
|
|
2647
2811
|
* url: "https://my-deployment.langsmith.dev",
|
|
2648
2812
|
* });
|
|
2649
2813
|
*
|
|
2650
2814
|
* const agent = createDeepAgent({
|
|
2651
|
-
* model
|
|
2815
|
+
* model,
|
|
2652
2816
|
* middleware: [notifier],
|
|
2653
2817
|
* });
|
|
2654
2818
|
* ```
|
|
2655
2819
|
*
|
|
2656
|
-
* The middleware
|
|
2657
|
-
*
|
|
2658
|
-
*
|
|
2820
|
+
* The middleware reads `callbackThreadId` from the agent state at the end of
|
|
2821
|
+
* execution. This value is injected by the parent's `start_async_task` tool
|
|
2822
|
+
* when it creates the run.
|
|
2659
2823
|
*
|
|
2660
2824
|
* @module
|
|
2661
2825
|
*/
|
|
2662
|
-
/** State key where the supervisor's launch tool stores the parent thread ID. */
|
|
2663
|
-
const PARENT_THREAD_ID_KEY = "parent_thread_id";
|
|
2664
2826
|
/** Maximum characters to include from the last message in notifications. */
|
|
2665
|
-
const
|
|
2827
|
+
const MAX_MESSAGE_LENGTH = 500;
|
|
2828
|
+
/** Suffix appended when truncating long messages. */
|
|
2829
|
+
const TRUNCATION_SUFFIX = "... [full result truncated]";
|
|
2830
|
+
/** State key for the callback thread ID. */
|
|
2831
|
+
const CALLBACK_THREAD_ID_KEY = "callbackThreadId";
|
|
2666
2832
|
/**
|
|
2667
|
-
* State extension for subagents that use
|
|
2833
|
+
* State extension for subagents that use completion callbacks.
|
|
2668
2834
|
*
|
|
2669
|
-
*
|
|
2670
|
-
*
|
|
2671
|
-
*
|
|
2835
|
+
* @experimental - this state schema is experimental and may change in future releases.
|
|
2836
|
+
*
|
|
2837
|
+
* `callbackThreadId` is written by the parent's `start_async_task` tool
|
|
2838
|
+
* and read by `CompletionCallbackMiddleware` when sending callback
|
|
2839
|
+
* notifications.
|
|
2672
2840
|
*/
|
|
2673
|
-
const
|
|
2841
|
+
const CompletionCallbackStateSchema = zod.object({ [CALLBACK_THREAD_ID_KEY]: zod.string().optional() });
|
|
2674
2842
|
/**
|
|
2675
|
-
* Build headers for the
|
|
2843
|
+
* Build headers for the callback LangGraph server.
|
|
2676
2844
|
*
|
|
2677
2845
|
* Ensures `x-auth-scheme: langsmith` is present unless explicitly overridden.
|
|
2678
2846
|
*/
|
|
@@ -2682,55 +2850,63 @@ function resolveHeaders(headers) {
|
|
|
2682
2850
|
return resolved;
|
|
2683
2851
|
}
|
|
2684
2852
|
/**
|
|
2685
|
-
* Send a notification run to the
|
|
2853
|
+
* Send a notification run to the callback thread.
|
|
2854
|
+
*
|
|
2855
|
+
* @param callbackGraphId - The callback graph ID used as `assistant_id`
|
|
2856
|
+
* in the `runs.create` call.
|
|
2857
|
+
* @param callbackThreadId - The callback thread ID.
|
|
2858
|
+
* @param message - The message content to send.
|
|
2859
|
+
* @param options - Optional url and headers for the callback server.
|
|
2686
2860
|
*/
|
|
2687
|
-
async function notifyParent(
|
|
2861
|
+
async function notifyParent(callbackGraphId, callbackThreadId, message, options) {
|
|
2688
2862
|
try {
|
|
2689
2863
|
await new _langchain_langgraph_sdk.Client({
|
|
2690
|
-
apiUrl: options
|
|
2864
|
+
apiUrl: options?.url ?? void 0,
|
|
2691
2865
|
apiKey: null,
|
|
2692
|
-
defaultHeaders: resolveHeaders(options
|
|
2693
|
-
}).runs.create(
|
|
2866
|
+
defaultHeaders: resolveHeaders(options?.headers)
|
|
2867
|
+
}).runs.create(callbackThreadId, callbackGraphId, { input: { messages: [{
|
|
2694
2868
|
role: "user",
|
|
2695
|
-
content:
|
|
2869
|
+
content: message
|
|
2696
2870
|
}] } });
|
|
2697
2871
|
} catch (e) {
|
|
2698
|
-
console.warn(`[
|
|
2872
|
+
console.warn(`[CompletionCallbackMiddleware] Failed to notify callback thread ${callbackThreadId}:`, e);
|
|
2699
2873
|
}
|
|
2700
2874
|
}
|
|
2701
2875
|
/**
|
|
2702
2876
|
* Extract a summary from the subagent's final message.
|
|
2703
2877
|
*
|
|
2704
2878
|
* Returns at most 500 characters from the last message's content.
|
|
2879
|
+
* Throws if no messages exist or if the last message is not an AIMessage.
|
|
2880
|
+
*
|
|
2881
|
+
* @param state - The agent state dict.
|
|
2882
|
+
* @param taskId - Optional task ID to include in truncation hint.
|
|
2705
2883
|
*/
|
|
2706
|
-
function extractLastMessage(state) {
|
|
2884
|
+
function extractLastMessage(state, taskId) {
|
|
2707
2885
|
const messages = state.messages;
|
|
2708
|
-
if (!messages || messages.length === 0)
|
|
2886
|
+
if (!messages || messages.length === 0) throw new Error(`Expected at least one message in state ${JSON.stringify(state)}`);
|
|
2709
2887
|
const last = messages[messages.length - 1];
|
|
2710
|
-
if (last
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2888
|
+
if (!_langchain_core_messages.AIMessage.isInstance(last)) throw new TypeError(`Expected an AIMessage, got ${typeof last === "object" && last !== null ? last.constructor?.name ?? typeof last : typeof last} instead`);
|
|
2889
|
+
let textContent = last.text;
|
|
2890
|
+
if (textContent.length > MAX_MESSAGE_LENGTH) {
|
|
2891
|
+
textContent = textContent.slice(0, MAX_MESSAGE_LENGTH) + TRUNCATION_SUFFIX;
|
|
2892
|
+
if (taskId) textContent += ` Result truncated. Use \`check_async_task(task_id='${taskId}')\` to retrieve the full result if needed.`;
|
|
2714
2893
|
}
|
|
2715
|
-
return
|
|
2894
|
+
return textContent;
|
|
2716
2895
|
}
|
|
2717
2896
|
/**
|
|
2718
|
-
* Create a completion
|
|
2897
|
+
* Create a completion callback middleware for async subagents.
|
|
2719
2898
|
*
|
|
2720
2899
|
* **Experimental** — this middleware is experimental and may change.
|
|
2721
2900
|
*
|
|
2722
|
-
* This middleware is added to
|
|
2723
|
-
*
|
|
2724
|
-
*
|
|
2725
|
-
* proactively relay results.
|
|
2901
|
+
* This middleware is added to a subagent's middleware stack. On success or
|
|
2902
|
+
* model-call error, it sends a notification to the configured callback
|
|
2903
|
+
* thread by calling `runs.create()`.
|
|
2726
2904
|
*
|
|
2727
|
-
* The
|
|
2728
|
-
*
|
|
2729
|
-
*
|
|
2730
|
-
* configuration known at deployment time.
|
|
2905
|
+
* The callback destination is configured with `callbackGraphId` and
|
|
2906
|
+
* optional `url` and `headers`. The target thread is read from
|
|
2907
|
+
* `callbackThreadId` in the subagent state.
|
|
2731
2908
|
*
|
|
2732
|
-
* If `
|
|
2733
|
-
* launched manually without a supervisor), the middleware silently does
|
|
2909
|
+
* If `callbackThreadId` is not present in state, the middleware does
|
|
2734
2910
|
* nothing.
|
|
2735
2911
|
*
|
|
2736
2912
|
* @param options - Configuration options.
|
|
@@ -2738,11 +2914,10 @@ function extractLastMessage(state) {
|
|
|
2738
2914
|
*
|
|
2739
2915
|
* @example
|
|
2740
2916
|
* ```typescript
|
|
2741
|
-
* import {
|
|
2917
|
+
* import { createCompletionCallbackMiddleware } from "deepagents";
|
|
2742
2918
|
*
|
|
2743
|
-
* const notifier =
|
|
2744
|
-
*
|
|
2745
|
-
* url: "https://my-deployment.langsmith.dev",
|
|
2919
|
+
* const notifier = createCompletionCallbackMiddleware({
|
|
2920
|
+
* callbackGraphId: "supervisor",
|
|
2746
2921
|
* });
|
|
2747
2922
|
*
|
|
2748
2923
|
* const agent = createDeepAgent({
|
|
@@ -2751,23 +2926,13 @@ function extractLastMessage(state) {
|
|
|
2751
2926
|
* });
|
|
2752
2927
|
* ```
|
|
2753
2928
|
*/
|
|
2754
|
-
function
|
|
2755
|
-
const {
|
|
2756
|
-
let notified = false;
|
|
2929
|
+
function createCompletionCallbackMiddleware(options) {
|
|
2930
|
+
const { callbackGraphId, url, headers } = options;
|
|
2757
2931
|
/**
|
|
2758
|
-
*
|
|
2932
|
+
* Send a notification to the callback destination.
|
|
2759
2933
|
*/
|
|
2760
|
-
function
|
|
2761
|
-
|
|
2762
|
-
return Boolean(state[PARENT_THREAD_ID_KEY]);
|
|
2763
|
-
}
|
|
2764
|
-
/**
|
|
2765
|
-
* Send a notification to the parent if conditions are met.
|
|
2766
|
-
*/
|
|
2767
|
-
async function sendNotification(state, message) {
|
|
2768
|
-
if (!shouldNotify(state)) return;
|
|
2769
|
-
notified = true;
|
|
2770
|
-
await notifyParent(state[PARENT_THREAD_ID_KEY], parentGraphId, message, {
|
|
2934
|
+
async function sendNotification(callbackThreadId, message) {
|
|
2935
|
+
await notifyParent(callbackGraphId, callbackThreadId, message, {
|
|
2771
2936
|
url,
|
|
2772
2937
|
headers
|
|
2773
2938
|
});
|
|
@@ -2776,7 +2941,7 @@ function createCompletionNotifierMiddleware(options) {
|
|
|
2776
2941
|
* Read the subagent's own thread_id from runtime config.
|
|
2777
2942
|
*
|
|
2778
2943
|
* The subagent's `thread_id` is the same as the `task_id` from the
|
|
2779
|
-
*
|
|
2944
|
+
* parent's perspective.
|
|
2780
2945
|
*/
|
|
2781
2946
|
function getTaskId(runtime) {
|
|
2782
2947
|
return runtime?.configurable?.thread_id;
|
|
@@ -2789,17 +2954,20 @@ function createCompletionNotifierMiddleware(options) {
|
|
|
2789
2954
|
return `${taskId ? `[task_id=${taskId}]` : ""}${body}`;
|
|
2790
2955
|
}
|
|
2791
2956
|
return (0, langchain.createMiddleware)({
|
|
2792
|
-
name: "
|
|
2793
|
-
stateSchema:
|
|
2957
|
+
name: "CompletionCallbackMiddleware",
|
|
2958
|
+
stateSchema: CompletionCallbackStateSchema,
|
|
2794
2959
|
async afterAgent(state, runtime) {
|
|
2795
|
-
|
|
2960
|
+
const callbackThreadId = state[CALLBACK_THREAD_ID_KEY];
|
|
2961
|
+
if (callbackThreadId == null) throw new Error(`Missing required state key '${CALLBACK_THREAD_ID_KEY}'`);
|
|
2962
|
+
const taskId = getTaskId(runtime);
|
|
2963
|
+
await sendNotification(callbackThreadId, formatNotification(`Completed. Result: ${extractLastMessage(state, typeof taskId === "string" ? taskId : void 0)}`, runtime));
|
|
2796
2964
|
},
|
|
2797
2965
|
async wrapModelCall(request, handler) {
|
|
2798
2966
|
try {
|
|
2799
2967
|
return await handler(request);
|
|
2800
2968
|
} catch (e) {
|
|
2801
|
-
const
|
|
2802
|
-
await sendNotification(
|
|
2969
|
+
const callbackThreadId = request.state[CALLBACK_THREAD_ID_KEY];
|
|
2970
|
+
if (typeof callbackThreadId === "string") await sendNotification(callbackThreadId, formatNotification("The agent encountered an error while calling the model.", request.runtime));
|
|
2803
2971
|
throw e;
|
|
2804
2972
|
}
|
|
2805
2973
|
}
|
|
@@ -2998,13 +3166,6 @@ function createSummarizationMiddleware(options) {
|
|
|
2998
3166
|
let sessionId = null;
|
|
2999
3167
|
let tokenEstimationMultiplier = 1;
|
|
3000
3168
|
/**
|
|
3001
|
-
* Resolve backend from instance or factory.
|
|
3002
|
-
*/
|
|
3003
|
-
function getBackend(state) {
|
|
3004
|
-
if (typeof backend === "function") return adaptBackendProtocol(backend({ state }));
|
|
3005
|
-
return adaptBackendProtocol(backend);
|
|
3006
|
-
}
|
|
3007
|
-
/**
|
|
3008
3169
|
* Get or create session ID for history file naming.
|
|
3009
3170
|
*/
|
|
3010
3171
|
function getSessionId(state) {
|
|
@@ -3336,15 +3497,17 @@ function createSummarizationMiddleware(options) {
|
|
|
3336
3497
|
*/
|
|
3337
3498
|
function buildSummaryMessage(summary, filePath) {
|
|
3338
3499
|
let content;
|
|
3339
|
-
if (filePath) content = `
|
|
3500
|
+
if (filePath) content = langchain.context`
|
|
3501
|
+
You are in the middle of a conversation that has been summarized.
|
|
3340
3502
|
|
|
3341
|
-
The full conversation history has been saved to ${filePath} should you need to refer back to it for details.
|
|
3503
|
+
The full conversation history has been saved to ${filePath} should you need to refer back to it for details.
|
|
3342
3504
|
|
|
3343
|
-
A condensed summary follows:
|
|
3505
|
+
A condensed summary follows:
|
|
3344
3506
|
|
|
3345
|
-
<summary>
|
|
3346
|
-
${summary}
|
|
3347
|
-
</summary
|
|
3507
|
+
<summary>
|
|
3508
|
+
${summary}
|
|
3509
|
+
</summary>
|
|
3510
|
+
`;
|
|
3348
3511
|
else content = `Here is a summary of the conversation to date:\n\n${summary}`;
|
|
3349
3512
|
return new langchain.HumanMessage({
|
|
3350
3513
|
content,
|
|
@@ -3370,7 +3533,7 @@ ${summary}
|
|
|
3370
3533
|
* the file path, and the state cutoff index.
|
|
3371
3534
|
*/
|
|
3372
3535
|
async function summarizeMessages(messagesToSummarize, resolvedModel, state, previousCutoffIndex, cutoffIndex) {
|
|
3373
|
-
const filePath = await offloadToBackend(
|
|
3536
|
+
const filePath = await offloadToBackend(await resolveBackend(backend, { state }), messagesToSummarize, state);
|
|
3374
3537
|
if (filePath === null) console.warn(`[SummarizationMiddleware] Backend offload failed during summarization. Proceeding with summary generation.`);
|
|
3375
3538
|
return {
|
|
3376
3539
|
summaryMessage: buildSummaryMessage(await createSummary(messagesToSummarize, resolvedModel), filePath),
|
|
@@ -3512,6 +3675,7 @@ const AsyncTaskSchema = zod_v4.z.object({
|
|
|
3512
3675
|
runId: zod_v4.z.string(),
|
|
3513
3676
|
status: zod_v4.z.string(),
|
|
3514
3677
|
createdAt: zod_v4.z.string(),
|
|
3678
|
+
description: zod_v4.z.string().optional(),
|
|
3515
3679
|
updatedAt: zod_v4.z.string().optional(),
|
|
3516
3680
|
checkedAt: zod_v4.z.string().optional()
|
|
3517
3681
|
});
|
|
@@ -3549,7 +3713,7 @@ function asyncTasksReducer(existing, update) {
|
|
|
3549
3713
|
* The `{available_agents}` placeholder is replaced at middleware creation
|
|
3550
3714
|
* time with a formatted list of configured async subagent names and descriptions.
|
|
3551
3715
|
*/
|
|
3552
|
-
const ASYNC_TASK_TOOL_DESCRIPTION = `Launch an async subagent on a remote
|
|
3716
|
+
const ASYNC_TASK_TOOL_DESCRIPTION = `Launch an async subagent on a remote server. The subagent runs in the background and returns a task ID immediately.
|
|
3553
3717
|
|
|
3554
3718
|
Available async agent types:
|
|
3555
3719
|
{available_agents}
|
|
@@ -3559,7 +3723,7 @@ Available async agent types:
|
|
|
3559
3723
|
2. Use \`check_async_task\` only when the user asks for a status update or result.
|
|
3560
3724
|
3. Use \`update_async_task\` to send new instructions to a running task.
|
|
3561
3725
|
4. Multiple async subagents can run concurrently — launch several and let them run in the background.
|
|
3562
|
-
5. The subagent runs on a remote
|
|
3726
|
+
5. The subagent runs on a remote server, so it has its own tools and capabilities.`;
|
|
3563
3727
|
/**
|
|
3564
3728
|
* Default system prompt appended to the main agent's system message when
|
|
3565
3729
|
* async subagent middleware is active.
|
|
@@ -3569,9 +3733,9 @@ Available async agent types:
|
|
|
3569
3733
|
* critical rules about polling behavior, and guidance on when to use async
|
|
3570
3734
|
* subagents vs. synchronous delegation.
|
|
3571
3735
|
*/
|
|
3572
|
-
const ASYNC_TASK_SYSTEM_PROMPT = `## Async subagents (remote
|
|
3736
|
+
const ASYNC_TASK_SYSTEM_PROMPT = `## Async subagents (remote servers)
|
|
3573
3737
|
|
|
3574
|
-
You have access to async subagent tools that launch background tasks on remote
|
|
3738
|
+
You have access to async subagent tools that launch background tasks on remote servers.
|
|
3575
3739
|
|
|
3576
3740
|
### Tools:
|
|
3577
3741
|
- \`start_async_task\`: Start a new background task. Returns a task ID immediately.
|
|
@@ -3611,6 +3775,19 @@ You have access to async subagent tools that launch background tasks on remote L
|
|
|
3611
3775
|
* When listing tasks, live-status fetches are skipped for tasks whose
|
|
3612
3776
|
* cached status is in this set, since they are guaranteed to be final.
|
|
3613
3777
|
*/
|
|
3778
|
+
/**
|
|
3779
|
+
* Names of the tools added by the async subagent middleware.
|
|
3780
|
+
*
|
|
3781
|
+
* Exported so `agent.ts` can include them in `BUILTIN_TOOL_NAMES` and
|
|
3782
|
+
* surface a `ConfigurationError` if a user-provided tool collides.
|
|
3783
|
+
*/
|
|
3784
|
+
const ASYNC_TASK_TOOL_NAMES = [
|
|
3785
|
+
"start_async_task",
|
|
3786
|
+
"check_async_task",
|
|
3787
|
+
"update_async_task",
|
|
3788
|
+
"cancel_async_task",
|
|
3789
|
+
"list_async_tasks"
|
|
3790
|
+
];
|
|
3614
3791
|
const TERMINAL_STATUSES = new Set([
|
|
3615
3792
|
"cancelled",
|
|
3616
3793
|
"success",
|
|
@@ -3702,8 +3879,11 @@ var ClientCache = class {
|
|
|
3702
3879
|
this.agents = agents;
|
|
3703
3880
|
}
|
|
3704
3881
|
/**
|
|
3705
|
-
* Build headers for a remote
|
|
3706
|
-
*
|
|
3882
|
+
* Build headers for a remote Agent Protocol server.
|
|
3883
|
+
*
|
|
3884
|
+
* Adds `x-auth-scheme: langsmith` by default unless already provided.
|
|
3885
|
+
* For self-hosted servers that don't require this header, it is typically
|
|
3886
|
+
* ignored. Override via the `headers` field on the AsyncSubAgent config.
|
|
3707
3887
|
*/
|
|
3708
3888
|
resolveHeaders(spec) {
|
|
3709
3889
|
const headers = { ...spec.headers || {} };
|
|
@@ -3736,6 +3916,20 @@ var ClientCache = class {
|
|
|
3736
3916
|
}
|
|
3737
3917
|
};
|
|
3738
3918
|
/**
|
|
3919
|
+
* Extract the callback thread ID from the tool runtime.
|
|
3920
|
+
*
|
|
3921
|
+
* The thread ID is included in the subagent's input state so the subagent
|
|
3922
|
+
* can notify the parent when it completes (via
|
|
3923
|
+
* `CompletionCallbackMiddleware`).
|
|
3924
|
+
*
|
|
3925
|
+
* @returns Object with `callbackThreadId` if available. Empty object otherwise.
|
|
3926
|
+
*/
|
|
3927
|
+
function extractCallbackContext(runtime) {
|
|
3928
|
+
const threadId = (runtime.config?.configurable)?.thread_id;
|
|
3929
|
+
if (typeof threadId === "string" && threadId) return { callbackThreadId: threadId };
|
|
3930
|
+
return {};
|
|
3931
|
+
}
|
|
3932
|
+
/**
|
|
3739
3933
|
* Build the `start_async_task` tool.
|
|
3740
3934
|
*
|
|
3741
3935
|
* Creates a thread on the remote server, starts a run, and returns a
|
|
@@ -3748,13 +3942,17 @@ function buildStartTool(agentMap, clients, toolDescription) {
|
|
|
3748
3942
|
return `Unknown async subagent type \`${input.agentName}\`. Available types: ${allowed}`;
|
|
3749
3943
|
}
|
|
3750
3944
|
const spec = agentMap[input.agentName];
|
|
3945
|
+
const callbackContext = extractCallbackContext(runtime);
|
|
3751
3946
|
try {
|
|
3752
3947
|
const client = clients.getClient(input.agentName);
|
|
3753
3948
|
const thread = await client.threads.create();
|
|
3754
|
-
const run = await client.runs.create(thread.thread_id, spec.graphId, { input: {
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3949
|
+
const run = await client.runs.create(thread.thread_id, spec.graphId, { input: {
|
|
3950
|
+
messages: [{
|
|
3951
|
+
role: "user",
|
|
3952
|
+
content: input.description
|
|
3953
|
+
}],
|
|
3954
|
+
...callbackContext
|
|
3955
|
+
} });
|
|
3758
3956
|
const taskId = thread.thread_id;
|
|
3759
3957
|
const task = {
|
|
3760
3958
|
taskId,
|
|
@@ -3762,7 +3960,8 @@ function buildStartTool(agentMap, clients, toolDescription) {
|
|
|
3762
3960
|
threadId: taskId,
|
|
3763
3961
|
runId: run.run_id,
|
|
3764
3962
|
status: "running",
|
|
3765
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3963
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3964
|
+
description: input.description
|
|
3766
3965
|
};
|
|
3767
3966
|
return new _langchain_langgraph.Command({ update: {
|
|
3768
3967
|
messages: [new langchain.ToolMessage({
|
|
@@ -3812,7 +4011,7 @@ function buildCheckTool(clients) {
|
|
|
3812
4011
|
runId: task.runId,
|
|
3813
4012
|
status: result.status,
|
|
3814
4013
|
createdAt: task.createdAt,
|
|
3815
|
-
updatedAt: task.updatedAt,
|
|
4014
|
+
updatedAt: result.status !== task.status ? (/* @__PURE__ */ new Date()).toISOString() : task.updatedAt,
|
|
3816
4015
|
checkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3817
4016
|
};
|
|
3818
4017
|
return new _langchain_langgraph.Command({ update: {
|
|
@@ -3856,6 +4055,7 @@ function buildUpdateTool(agentMap, clients) {
|
|
|
3856
4055
|
runId: run.run_id,
|
|
3857
4056
|
status: "running",
|
|
3858
4057
|
createdAt: tracked.createdAt,
|
|
4058
|
+
description: input.message,
|
|
3859
4059
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3860
4060
|
checkedAt: tracked.checkedAt
|
|
3861
4061
|
};
|
|
@@ -3901,7 +4101,7 @@ function buildCancelTool(clients) {
|
|
|
3901
4101
|
runId: tracked.runId,
|
|
3902
4102
|
status: "cancelled",
|
|
3903
4103
|
createdAt: tracked.createdAt,
|
|
3904
|
-
updatedAt:
|
|
4104
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3905
4105
|
checkedAt: tracked.checkedAt
|
|
3906
4106
|
};
|
|
3907
4107
|
return new _langchain_langgraph.Command({ update: {
|
|
@@ -3942,7 +4142,7 @@ function buildListTool(clients) {
|
|
|
3942
4142
|
runId: task.runId,
|
|
3943
4143
|
status,
|
|
3944
4144
|
createdAt: task.createdAt,
|
|
3945
|
-
updatedAt: task.updatedAt,
|
|
4145
|
+
updatedAt: status !== task.status ? (/* @__PURE__ */ new Date()).toISOString() : task.updatedAt,
|
|
3946
4146
|
checkedAt: task.checkedAt
|
|
3947
4147
|
};
|
|
3948
4148
|
}
|
|
@@ -3963,10 +4163,13 @@ function buildListTool(clients) {
|
|
|
3963
4163
|
* Create middleware that adds async subagent tools to an agent.
|
|
3964
4164
|
*
|
|
3965
4165
|
* Provides five tools for launching, checking, updating, cancelling, and
|
|
3966
|
-
* listing background tasks on remote
|
|
4166
|
+
* listing background tasks on remote Agent Protocol servers. Task state is
|
|
3967
4167
|
* persisted in the `asyncTasks` state channel so it survives
|
|
3968
4168
|
* context compaction.
|
|
3969
4169
|
*
|
|
4170
|
+
* Works with any Agent Protocol-compliant server — LangGraph Platform (managed)
|
|
4171
|
+
* or self-hosted (e.g. a Hono/Express server implementing the Agent Protocol spec).
|
|
4172
|
+
*
|
|
3970
4173
|
* @throws {Error} If no async subagents are provided or names are duplicated.
|
|
3971
4174
|
*
|
|
3972
4175
|
* @example
|
|
@@ -3975,7 +4178,7 @@ function buildListTool(clients) {
|
|
|
3975
4178
|
* asyncSubAgents: [{
|
|
3976
4179
|
* name: "researcher",
|
|
3977
4180
|
* description: "Research agent for deep analysis",
|
|
3978
|
-
* url: "https://my-
|
|
4181
|
+
* url: "https://my-agent-protocol-server.example.com",
|
|
3979
4182
|
* graphId: "research_agent",
|
|
3980
4183
|
* }],
|
|
3981
4184
|
* });
|
|
@@ -4022,6 +4225,9 @@ function createAsyncSubAgentMiddleware(options) {
|
|
|
4022
4225
|
}
|
|
4023
4226
|
//#endregion
|
|
4024
4227
|
//#region src/backends/store.ts
|
|
4228
|
+
/**
|
|
4229
|
+
* StoreBackend: Adapter for LangGraph's BaseStore (persistent, cross-thread).
|
|
4230
|
+
*/
|
|
4025
4231
|
const NAMESPACE_COMPONENT_RE = /^[A-Za-z0-9\-_.@+:~]+$/;
|
|
4026
4232
|
/**
|
|
4027
4233
|
* Validate a namespace array.
|
|
@@ -4057,35 +4263,54 @@ var StoreBackend = class {
|
|
|
4057
4263
|
stateAndStore;
|
|
4058
4264
|
_namespace;
|
|
4059
4265
|
fileFormat;
|
|
4060
|
-
constructor(
|
|
4061
|
-
|
|
4062
|
-
if (
|
|
4063
|
-
|
|
4266
|
+
constructor(stateAndStoreOrOptions, options) {
|
|
4267
|
+
let opts;
|
|
4268
|
+
if (stateAndStoreOrOptions != null && typeof stateAndStoreOrOptions === "object" && "state" in stateAndStoreOrOptions) {
|
|
4269
|
+
this.stateAndStore = stateAndStoreOrOptions;
|
|
4270
|
+
opts = options;
|
|
4271
|
+
} else {
|
|
4272
|
+
this.stateAndStore = void 0;
|
|
4273
|
+
opts = stateAndStoreOrOptions;
|
|
4274
|
+
}
|
|
4275
|
+
if (opts?.namespace) this._namespace = validateNamespace(opts.namespace);
|
|
4276
|
+
this.fileFormat = opts?.fileFormat ?? "v2";
|
|
4064
4277
|
}
|
|
4065
4278
|
/**
|
|
4066
|
-
* Get the
|
|
4279
|
+
* Get the BaseStore instance for persistent storage operations.
|
|
4280
|
+
*
|
|
4281
|
+
* In legacy mode, reads from the injected {@link StateAndStore}.
|
|
4282
|
+
* In zero-arg mode, retrieves the store from the LangGraph execution
|
|
4283
|
+
* context via {@link getLangGraphStore}.
|
|
4067
4284
|
*
|
|
4068
4285
|
* @returns BaseStore instance
|
|
4069
|
-
* @throws Error if no store is available
|
|
4286
|
+
* @throws Error if no store is available in either mode
|
|
4070
4287
|
*/
|
|
4071
4288
|
getStore() {
|
|
4072
|
-
|
|
4073
|
-
|
|
4289
|
+
if (this.stateAndStore) {
|
|
4290
|
+
const store = this.stateAndStore.store;
|
|
4291
|
+
if (!store) throw new Error("Store is required but not available in runtime");
|
|
4292
|
+
return store;
|
|
4293
|
+
}
|
|
4294
|
+
const store = (0, _langchain_langgraph.getStore)();
|
|
4295
|
+
if (!store) throw new Error("Store is required but not available in LangGraph execution context. Ensure the graph was configured with a store.");
|
|
4074
4296
|
return store;
|
|
4075
4297
|
}
|
|
4076
4298
|
/**
|
|
4077
4299
|
* Get the namespace for store operations.
|
|
4078
4300
|
*
|
|
4079
|
-
*
|
|
4080
|
-
*
|
|
4081
|
-
*
|
|
4082
|
-
* -
|
|
4083
|
-
*
|
|
4301
|
+
* Resolution order:
|
|
4302
|
+
* 1. Explicit namespace from constructor options (both modes)
|
|
4303
|
+
* 2. Legacy mode: `[assistantId, "filesystem"]` fallback from {@link StateAndStore}
|
|
4304
|
+
* 3. Zero-arg mode without namespace: `["filesystem"]` with a deprecation warning
|
|
4305
|
+
* nudging callers to pass an explicit namespace
|
|
4306
|
+
* 4. Legacy mode without assistantId: `["filesystem"]`
|
|
4084
4307
|
*/
|
|
4085
4308
|
getNamespace() {
|
|
4086
4309
|
if (this._namespace) return this._namespace;
|
|
4087
|
-
|
|
4088
|
-
|
|
4310
|
+
if (this.stateAndStore) {
|
|
4311
|
+
const assistantId = this.stateAndStore.assistantId;
|
|
4312
|
+
if (assistantId) return [assistantId, "filesystem"];
|
|
4313
|
+
}
|
|
4089
4314
|
return ["filesystem"];
|
|
4090
4315
|
}
|
|
4091
4316
|
/**
|
|
@@ -6094,9 +6319,9 @@ var LangSmithSandbox = class LangSmithSandbox extends BaseSandbox {
|
|
|
6094
6319
|
* ```
|
|
6095
6320
|
*/
|
|
6096
6321
|
static async create(options = {}) {
|
|
6097
|
-
const { templateName = "deepagents", apiKey = process.env.LANGSMITH_API_KEY, defaultTimeout } = options;
|
|
6322
|
+
const { templateName = "deepagents", apiKey = process.env.LANGSMITH_API_KEY, defaultTimeout, ...createSandboxOptions } = options;
|
|
6098
6323
|
return new LangSmithSandbox({
|
|
6099
|
-
sandbox: await new langsmith_experimental_sandbox.SandboxClient({ apiKey }).createSandbox(templateName),
|
|
6324
|
+
sandbox: await new langsmith_experimental_sandbox.SandboxClient({ apiKey }).createSandbox(templateName, createSandboxOptions),
|
|
6100
6325
|
defaultTimeout
|
|
6101
6326
|
});
|
|
6102
6327
|
}
|
|
@@ -6184,9 +6409,44 @@ function createCacheBreakpointMiddleware() {
|
|
|
6184
6409
|
}
|
|
6185
6410
|
//#endregion
|
|
6186
6411
|
//#region src/agent.ts
|
|
6187
|
-
const
|
|
6412
|
+
const BASE_AGENT_PROMPT = langchain.context`
|
|
6413
|
+
You are a Deep Agent, an AI assistant that helps users accomplish tasks using tools. You respond with text and tool calls. The user can see your responses and tool outputs in real time.
|
|
6414
|
+
|
|
6415
|
+
## Core Behavior
|
|
6416
|
+
|
|
6417
|
+
- Be concise and direct. Don't over-explain unless asked.
|
|
6418
|
+
- NEVER add unnecessary preamble (\"Sure!\", \"Great question!\", \"I'll now...\").
|
|
6419
|
+
- Don't say \"I'll now do X\" — just do it.
|
|
6420
|
+
- If the request is ambiguous, ask questions before acting.
|
|
6421
|
+
- If asked how to approach something, explain first, then act.
|
|
6422
|
+
|
|
6423
|
+
## Professional Objectivity
|
|
6424
|
+
|
|
6425
|
+
- Prioritize accuracy over validating the user's beliefs
|
|
6426
|
+
- Disagree respectfully when the user is incorrect
|
|
6427
|
+
- Avoid unnecessary superlatives, praise, or emotional validation
|
|
6428
|
+
|
|
6429
|
+
## Doing Tasks
|
|
6430
|
+
|
|
6431
|
+
When the user asks you to do something:
|
|
6432
|
+
|
|
6433
|
+
1. **Understand first** — read relevant files, check existing patterns. Quick but thorough — gather enough evidence to start, then iterate.
|
|
6434
|
+
2. **Act** — implement the solution. Work quickly but accurately.
|
|
6435
|
+
3. **Verify** — check your work against what was asked, not against your own output. Your first attempt is rarely correct — iterate.
|
|
6436
|
+
|
|
6437
|
+
Keep working until the task is fully complete. Don't stop partway and explain what you would do — just do it. Only yield back to the user when the task is done or you're genuinely blocked.
|
|
6438
|
+
|
|
6439
|
+
**When things go wrong:**
|
|
6440
|
+
- If something fails repeatedly, stop and analyze *why* — don't keep retrying the same approach.
|
|
6441
|
+
- If you're blocked, tell the user what's wrong and ask for guidance.
|
|
6442
|
+
|
|
6443
|
+
## Progress Updates
|
|
6444
|
+
|
|
6445
|
+
For longer tasks, provide brief progress updates at reasonable intervals — a concise sentence recapping what you've done and what's next.
|
|
6446
|
+
`;
|
|
6188
6447
|
const BUILTIN_TOOL_NAMES = new Set([
|
|
6189
6448
|
...FILESYSTEM_TOOL_NAMES,
|
|
6449
|
+
...ASYNC_TASK_TOOL_NAMES,
|
|
6190
6450
|
"task",
|
|
6191
6451
|
"write_todos"
|
|
6192
6452
|
]);
|
|
@@ -6203,19 +6463,18 @@ function isAnthropicModel(model) {
|
|
|
6203
6463
|
return model.getName() === "ChatAnthropic";
|
|
6204
6464
|
}
|
|
6205
6465
|
/**
|
|
6206
|
-
* Create a Deep Agent
|
|
6466
|
+
* Create a Deep Agent.
|
|
6467
|
+
*
|
|
6468
|
+
* This is the main entry point for building a production-style agent with
|
|
6469
|
+
* deepagents. It gives you a strong default runtime (filesystem, tasks,
|
|
6470
|
+
* subagents, summarization) and lets you opt into skills, memory,
|
|
6471
|
+
* human-in-the-loop interrupts, async subagents, and custom middleware.
|
|
6207
6472
|
*
|
|
6208
|
-
*
|
|
6209
|
-
*
|
|
6210
|
-
* - Filesystem tools (createFilesystemMiddleware)
|
|
6211
|
-
* - Subagent delegation (createSubAgentMiddleware)
|
|
6212
|
-
* - Conversation summarization (createSummarizationMiddleware) with backend offloading
|
|
6213
|
-
* - Prompt caching (anthropicPromptCachingMiddleware)
|
|
6214
|
-
* - Tool call patching (createPatchToolCallsMiddleware)
|
|
6215
|
-
* - Human-in-the-loop (humanInTheLoopMiddleware) - optional
|
|
6473
|
+
* The runtime is intentionally opinionated: defaults work out of the box, and
|
|
6474
|
+
* when you customize behavior, the middleware ordering stays deterministic.
|
|
6216
6475
|
*
|
|
6217
6476
|
* @param params Configuration parameters for the agent
|
|
6218
|
-
* @returns
|
|
6477
|
+
* @returns Deep Agent instance with inferred state/response types
|
|
6219
6478
|
*
|
|
6220
6479
|
* @example
|
|
6221
6480
|
* ```typescript
|
|
@@ -6234,98 +6493,92 @@ function isAnthropicModel(model) {
|
|
|
6234
6493
|
* ```
|
|
6235
6494
|
*/
|
|
6236
6495
|
function createDeepAgent(params = {}) {
|
|
6237
|
-
const { model = "claude-sonnet-4-
|
|
6496
|
+
const { model = new _langchain_anthropic.ChatAnthropic("claude-sonnet-4-6"), tools = [], systemPrompt, middleware: customMiddleware = [], subagents = [], responseFormat, contextSchema, checkpointer, store, backend = (config) => new StateBackend(config), interruptOn, name, memory, skills } = params;
|
|
6238
6497
|
const collidingTools = tools.map((t) => t.name).filter((n) => typeof n === "string" && BUILTIN_TOOL_NAMES.has(n));
|
|
6239
6498
|
if (collidingTools.length > 0) throw new ConfigurationError(`Tool name(s) [${collidingTools.join(", ")}] conflict with built-in tools. Rename your custom tools to avoid this.`, "TOOL_NAME_COLLISION");
|
|
6240
6499
|
const anthropicModel = isAnthropicModel(model);
|
|
6241
|
-
const
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
}] : [
|
|
6245
|
-
type: "text",
|
|
6246
|
-
text: BASE_PROMPT
|
|
6247
|
-
}, ...typeof systemPrompt.content === "string" ? [{
|
|
6248
|
-
type: "text",
|
|
6249
|
-
text: systemPrompt.content
|
|
6250
|
-
}] : systemPrompt.content] : [{
|
|
6251
|
-
type: "text",
|
|
6252
|
-
text: BASE_PROMPT
|
|
6253
|
-
}] });
|
|
6254
|
-
/**
|
|
6255
|
-
* Create backend configuration for filesystem middleware
|
|
6256
|
-
* If no backend is provided, use a factory that creates a StateBackend
|
|
6257
|
-
*/
|
|
6258
|
-
const filesystemBackend = backend ? backend : (config) => new StateBackend(config);
|
|
6259
|
-
/**
|
|
6260
|
-
* Skills middleware (created conditionally for runtime use)
|
|
6261
|
-
*/
|
|
6262
|
-
const skillsMiddlewareArray = skills != null && skills.length > 0 ? [createSkillsMiddleware({
|
|
6263
|
-
backend: filesystemBackend,
|
|
6264
|
-
sources: skills
|
|
6265
|
-
})] : [];
|
|
6266
|
-
/**
|
|
6267
|
-
* Memory middleware (created conditionally for runtime use)
|
|
6268
|
-
*/
|
|
6269
|
-
const memoryMiddlewareArray = memory != null && memory.length > 0 ? [createMemoryMiddleware({
|
|
6270
|
-
backend: filesystemBackend,
|
|
6271
|
-
sources: memory,
|
|
6272
|
-
addCacheControl: anthropicModel
|
|
6273
|
-
})] : [];
|
|
6274
|
-
/**
|
|
6275
|
-
* Split the unified subagents array into sync and async subagents.
|
|
6276
|
-
* AsyncSubAgents are identified by the presence of a `graphId` field.
|
|
6277
|
-
*/
|
|
6278
|
-
const syncSubAgents = subagents.filter((a) => !isAsyncSubAgent(a));
|
|
6279
|
-
const asyncSubAgents = subagents.filter((a) => isAsyncSubAgent(a));
|
|
6500
|
+
const cacheMiddleware = anthropicModel ? [(0, langchain.anthropicPromptCachingMiddleware)({
|
|
6501
|
+
unsupportedModelBehavior: "ignore",
|
|
6502
|
+
minMessagesToCache: 1
|
|
6503
|
+
}), createCacheBreakpointMiddleware()] : [];
|
|
6280
6504
|
/**
|
|
6281
6505
|
* Process subagents to add SkillsMiddleware for those with their own skills.
|
|
6282
6506
|
*
|
|
6283
6507
|
* Custom subagents do NOT inherit skills from the main agent by default.
|
|
6284
|
-
* Only the general-purpose subagent inherits the main agent's skills
|
|
6508
|
+
* Only the general-purpose subagent inherits the main agent's skills.
|
|
6285
6509
|
* If a custom subagent needs skills, it must specify its own `skills` array.
|
|
6286
6510
|
*/
|
|
6287
|
-
const
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
sources: subagent.skills ?? []
|
|
6304
|
-
});
|
|
6511
|
+
const normalizeSubagentSpec = (input) => {
|
|
6512
|
+
const subagentMiddleware = [
|
|
6513
|
+
(0, langchain.todoListMiddleware)(),
|
|
6514
|
+
createFilesystemMiddleware({ backend }),
|
|
6515
|
+
createSummarizationMiddleware({
|
|
6516
|
+
backend,
|
|
6517
|
+
model
|
|
6518
|
+
}),
|
|
6519
|
+
createPatchToolCallsMiddleware(),
|
|
6520
|
+
...input.skills != null && input.skills.length > 0 ? [createSkillsMiddleware({
|
|
6521
|
+
backend,
|
|
6522
|
+
sources: input.skills
|
|
6523
|
+
})] : [],
|
|
6524
|
+
...input.middleware ?? [],
|
|
6525
|
+
...cacheMiddleware
|
|
6526
|
+
];
|
|
6305
6527
|
return {
|
|
6306
|
-
...
|
|
6307
|
-
|
|
6528
|
+
...input,
|
|
6529
|
+
tools: input.tools ?? [],
|
|
6530
|
+
middleware: subagentMiddleware
|
|
6308
6531
|
};
|
|
6309
|
-
}
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6532
|
+
};
|
|
6533
|
+
const allSubagents = subagents;
|
|
6534
|
+
const asyncSubAgents = allSubagents.filter((item) => isAsyncSubAgent(item));
|
|
6535
|
+
const inlineSubagents = allSubagents.filter((item) => !isAsyncSubAgent(item)).map((item) => "runnable" in item ? item : normalizeSubagentSpec(item));
|
|
6536
|
+
if (!inlineSubagents.some((item) => item.name === GENERAL_PURPOSE_SUBAGENT["name"])) {
|
|
6537
|
+
const generalPurposeSpec = normalizeSubagentSpec({
|
|
6538
|
+
...GENERAL_PURPOSE_SUBAGENT,
|
|
6539
|
+
model,
|
|
6540
|
+
skills,
|
|
6541
|
+
tools
|
|
6542
|
+
});
|
|
6543
|
+
inlineSubagents.unshift(generalPurposeSpec);
|
|
6544
|
+
}
|
|
6545
|
+
const skillsMiddleware = skills != null && skills.length > 0 ? [createSkillsMiddleware({
|
|
6546
|
+
backend,
|
|
6547
|
+
sources: skills
|
|
6548
|
+
})] : [];
|
|
6549
|
+
const [todoMiddleware, fsMiddleware, subagentMiddleware, summarizationMiddleware, patchToolCallsMiddleware] = [
|
|
6321
6550
|
(0, langchain.todoListMiddleware)(),
|
|
6322
|
-
createFilesystemMiddleware({ backend
|
|
6551
|
+
createFilesystemMiddleware({ backend }),
|
|
6552
|
+
createSubAgentMiddleware({
|
|
6553
|
+
defaultModel: model,
|
|
6554
|
+
defaultTools: tools,
|
|
6555
|
+
defaultInterruptOn: interruptOn,
|
|
6556
|
+
subagents: inlineSubagents,
|
|
6557
|
+
generalPurposeAgent: false
|
|
6558
|
+
}),
|
|
6323
6559
|
createSummarizationMiddleware({
|
|
6324
6560
|
model,
|
|
6325
|
-
backend
|
|
6561
|
+
backend
|
|
6326
6562
|
}),
|
|
6327
6563
|
createPatchToolCallsMiddleware()
|
|
6328
6564
|
];
|
|
6565
|
+
const middleware = [
|
|
6566
|
+
todoMiddleware,
|
|
6567
|
+
...skillsMiddleware,
|
|
6568
|
+
fsMiddleware,
|
|
6569
|
+
subagentMiddleware,
|
|
6570
|
+
summarizationMiddleware,
|
|
6571
|
+
patchToolCallsMiddleware,
|
|
6572
|
+
...asyncSubAgents.length > 0 ? [createAsyncSubAgentMiddleware({ asyncSubAgents })] : [],
|
|
6573
|
+
...customMiddleware,
|
|
6574
|
+
...cacheMiddleware,
|
|
6575
|
+
...memory && memory.length > 0 ? [createMemoryMiddleware({
|
|
6576
|
+
backend,
|
|
6577
|
+
sources: memory,
|
|
6578
|
+
addCacheControl: anthropicModel
|
|
6579
|
+
})] : [],
|
|
6580
|
+
...interruptOn ? [(0, langchain.humanInTheLoopMiddleware)({ interruptOn })] : []
|
|
6581
|
+
];
|
|
6329
6582
|
/**
|
|
6330
6583
|
* Return as DeepAgent with proper DeepAgentTypeConfig
|
|
6331
6584
|
* - Response: InferStructuredResponse<TResponse> (unwraps ToolStrategy<T>/ProviderStrategy<T> → T)
|
|
@@ -6337,55 +6590,32 @@ function createDeepAgent(params = {}) {
|
|
|
6337
6590
|
*/
|
|
6338
6591
|
return (0, langchain.createAgent)({
|
|
6339
6592
|
model,
|
|
6340
|
-
systemPrompt:
|
|
6593
|
+
systemPrompt: typeof systemPrompt === "string" ? new langchain.SystemMessage({ contentBlocks: [{
|
|
6594
|
+
type: "text",
|
|
6595
|
+
text: systemPrompt
|
|
6596
|
+
}, {
|
|
6597
|
+
type: "text",
|
|
6598
|
+
text: BASE_AGENT_PROMPT
|
|
6599
|
+
}] }) : langchain.SystemMessage.isInstance(systemPrompt) ? new langchain.SystemMessage({ contentBlocks: [...systemPrompt.contentBlocks, {
|
|
6600
|
+
type: "text",
|
|
6601
|
+
text: BASE_AGENT_PROMPT
|
|
6602
|
+
}] }) : new langchain.SystemMessage({ contentBlocks: [{
|
|
6603
|
+
type: "text",
|
|
6604
|
+
text: BASE_AGENT_PROMPT
|
|
6605
|
+
}] }),
|
|
6341
6606
|
tools,
|
|
6342
|
-
middleware
|
|
6343
|
-
|
|
6344
|
-
(0, langchain.todoListMiddleware)(),
|
|
6345
|
-
createFilesystemMiddleware({ backend: filesystemBackend }),
|
|
6346
|
-
createSubAgentMiddleware({
|
|
6347
|
-
defaultModel: model,
|
|
6348
|
-
defaultTools: tools,
|
|
6349
|
-
defaultMiddleware: [...subagentMiddleware, ...anthropicModel ? [(0, langchain.anthropicPromptCachingMiddleware)({
|
|
6350
|
-
unsupportedModelBehavior: "ignore",
|
|
6351
|
-
minMessagesToCache: 1
|
|
6352
|
-
}), createCacheBreakpointMiddleware()] : []],
|
|
6353
|
-
generalPurposeMiddleware: [
|
|
6354
|
-
...subagentMiddleware,
|
|
6355
|
-
...skillsMiddlewareArray,
|
|
6356
|
-
...anthropicModel ? [(0, langchain.anthropicPromptCachingMiddleware)({
|
|
6357
|
-
unsupportedModelBehavior: "ignore",
|
|
6358
|
-
minMessagesToCache: 1
|
|
6359
|
-
}), createCacheBreakpointMiddleware()] : []
|
|
6360
|
-
],
|
|
6361
|
-
defaultInterruptOn: interruptOn,
|
|
6362
|
-
subagents: processedSubagents,
|
|
6363
|
-
generalPurposeAgent: true
|
|
6364
|
-
}),
|
|
6365
|
-
createSummarizationMiddleware({
|
|
6366
|
-
model,
|
|
6367
|
-
backend: filesystemBackend
|
|
6368
|
-
}),
|
|
6369
|
-
createPatchToolCallsMiddleware()
|
|
6370
|
-
],
|
|
6371
|
-
...skillsMiddlewareArray,
|
|
6372
|
-
...customMiddleware,
|
|
6373
|
-
...anthropicModel ? [(0, langchain.anthropicPromptCachingMiddleware)({
|
|
6374
|
-
unsupportedModelBehavior: "ignore",
|
|
6375
|
-
minMessagesToCache: 1
|
|
6376
|
-
}), createCacheBreakpointMiddleware()] : [],
|
|
6377
|
-
...memoryMiddlewareArray,
|
|
6378
|
-
...interruptOn ? [(0, langchain.humanInTheLoopMiddleware)({ interruptOn })] : [],
|
|
6379
|
-
...asyncSubAgents && asyncSubAgents.length > 0 ? [createAsyncSubAgentMiddleware({ asyncSubAgents })] : []
|
|
6380
|
-
],
|
|
6381
|
-
...responseFormat != null && { responseFormat },
|
|
6607
|
+
middleware,
|
|
6608
|
+
...responseFormat !== null && { responseFormat },
|
|
6382
6609
|
contextSchema,
|
|
6383
6610
|
checkpointer,
|
|
6384
6611
|
store,
|
|
6385
6612
|
name
|
|
6386
6613
|
}).withConfig({
|
|
6387
6614
|
recursionLimit: 1e4,
|
|
6388
|
-
metadata: {
|
|
6615
|
+
metadata: {
|
|
6616
|
+
ls_integration: "deepagents",
|
|
6617
|
+
lc_agent_name: name
|
|
6618
|
+
}
|
|
6389
6619
|
});
|
|
6390
6620
|
}
|
|
6391
6621
|
//#endregion
|
|
@@ -6920,7 +7150,7 @@ exports.adaptSandboxProtocol = adaptSandboxProtocol;
|
|
|
6920
7150
|
exports.computeSummarizationDefaults = computeSummarizationDefaults;
|
|
6921
7151
|
exports.createAgentMemoryMiddleware = createAgentMemoryMiddleware;
|
|
6922
7152
|
exports.createAsyncSubAgentMiddleware = createAsyncSubAgentMiddleware;
|
|
6923
|
-
exports.
|
|
7153
|
+
exports.createCompletionCallbackMiddleware = createCompletionCallbackMiddleware;
|
|
6924
7154
|
exports.createDeepAgent = createDeepAgent;
|
|
6925
7155
|
exports.createFilesystemMiddleware = createFilesystemMiddleware;
|
|
6926
7156
|
exports.createMemoryMiddleware = createMemoryMiddleware;
|
|
@@ -6936,5 +7166,6 @@ exports.isSandboxBackend = isSandboxBackend;
|
|
|
6936
7166
|
exports.isSandboxProtocol = isSandboxProtocol;
|
|
6937
7167
|
exports.listSkills = listSkills;
|
|
6938
7168
|
exports.parseSkillMetadata = parseSkillMetadata;
|
|
7169
|
+
exports.resolveBackend = resolveBackend;
|
|
6939
7170
|
|
|
6940
7171
|
//# sourceMappingURL=index.cjs.map
|