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.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { AIMessage, HumanMessage, SystemMessage, ToolMessage, anthropicPromptCachingMiddleware, countTokensApproximately, createAgent, createMiddleware, humanInTheLoopMiddleware, todoListMiddleware, tool } from "langchain";
|
|
2
|
-
import {
|
|
3
|
-
import { Command, REMOVE_ALL_MESSAGES, ReducedValue, StateSchema, getCurrentTaskInput, isCommand } from "@langchain/langgraph";
|
|
1
|
+
import { AIMessage, HumanMessage, SystemMessage, ToolMessage, anthropicPromptCachingMiddleware, context, countTokensApproximately, createAgent, createMiddleware, humanInTheLoopMiddleware, todoListMiddleware, tool } from "langchain";
|
|
2
|
+
import { ChatAnthropic } from "@langchain/anthropic";
|
|
3
|
+
import { Command, REMOVE_ALL_MESSAGES, ReducedValue, StateSchema, getConfig, getCurrentTaskInput, getStore, isCommand } from "@langchain/langgraph";
|
|
4
4
|
import { z } from "zod/v4";
|
|
5
5
|
import micromatch from "micromatch";
|
|
6
6
|
import path, { basename } from "path";
|
|
7
|
-
import { HumanMessage as HumanMessage$1, RemoveMessage, getBufferString } from "@langchain/core/messages";
|
|
7
|
+
import { AIMessage as AIMessage$1, HumanMessage as HumanMessage$1, RemoveMessage, getBufferString } from "@langchain/core/messages";
|
|
8
|
+
import * as z$2 from "zod";
|
|
8
9
|
import { z as z$1 } from "zod";
|
|
9
10
|
import yaml from "yaml";
|
|
10
11
|
import { Client } from "@langchain/langgraph-sdk";
|
|
@@ -17,78 +18,6 @@ import cp, { spawn } from "node:child_process";
|
|
|
17
18
|
import fg from "fast-glob";
|
|
18
19
|
import { LangSmithResourceNotFoundError, LangSmithSandboxError, SandboxClient } from "langsmith/experimental/sandbox";
|
|
19
20
|
import os from "node:os";
|
|
20
|
-
//#region src/backends/protocol.ts
|
|
21
|
-
/**
|
|
22
|
-
* Type guard to check if a backend supports execution.
|
|
23
|
-
*
|
|
24
|
-
* @param backend - Backend instance to check
|
|
25
|
-
* @returns True if the backend implements SandboxBackendProtocolV2
|
|
26
|
-
*/
|
|
27
|
-
function isSandboxBackend(backend) {
|
|
28
|
-
return backend != null && typeof backend === "object" && typeof backend.execute === "function" && typeof backend.id === "string" && backend.id !== "";
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Type guard to check if a backend is a sandbox protocol (v1 or v2).
|
|
32
|
-
*
|
|
33
|
-
* Checks for the presence of `execute` function and `id` string,
|
|
34
|
-
* which are the defining features of sandbox protocols.
|
|
35
|
-
*
|
|
36
|
-
* @param backend - Backend instance to check
|
|
37
|
-
* @returns True if the backend implements sandbox protocol (v1 or v2)
|
|
38
|
-
*/
|
|
39
|
-
function isSandboxProtocol(backend) {
|
|
40
|
-
return backend != null && typeof backend === "object" && typeof backend.execute === "function" && typeof backend.id === "string" && backend.id !== "";
|
|
41
|
-
}
|
|
42
|
-
const SANDBOX_ERROR_SYMBOL = Symbol.for("sandbox.error");
|
|
43
|
-
/**
|
|
44
|
-
* Custom error class for sandbox operations.
|
|
45
|
-
*
|
|
46
|
-
* @param message - Human-readable error description
|
|
47
|
-
* @param code - Structured error code for programmatic handling
|
|
48
|
-
* @returns SandboxError with message and code
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* ```typescript
|
|
52
|
-
* try {
|
|
53
|
-
* await sandbox.execute("some command");
|
|
54
|
-
* } catch (error) {
|
|
55
|
-
* if (error instanceof SandboxError) {
|
|
56
|
-
* switch (error.code) {
|
|
57
|
-
* case "NOT_INITIALIZED":
|
|
58
|
-
* await sandbox.initialize();
|
|
59
|
-
* break;
|
|
60
|
-
* case "COMMAND_TIMEOUT":
|
|
61
|
-
* console.error("Command took too long");
|
|
62
|
-
* break;
|
|
63
|
-
* default:
|
|
64
|
-
* throw error;
|
|
65
|
-
* }
|
|
66
|
-
* }
|
|
67
|
-
* }
|
|
68
|
-
* ```
|
|
69
|
-
*/
|
|
70
|
-
var SandboxError = class SandboxError extends Error {
|
|
71
|
-
/** Symbol for identifying sandbox error instances */
|
|
72
|
-
[SANDBOX_ERROR_SYMBOL] = true;
|
|
73
|
-
/** Error name for instanceof checks and logging */
|
|
74
|
-
name = "SandboxError";
|
|
75
|
-
/**
|
|
76
|
-
* Creates a new SandboxError.
|
|
77
|
-
*
|
|
78
|
-
* @param message - Human-readable error description
|
|
79
|
-
* @param code - Structured error code for programmatic handling
|
|
80
|
-
*/
|
|
81
|
-
constructor(message, code, cause) {
|
|
82
|
-
super(message);
|
|
83
|
-
this.code = code;
|
|
84
|
-
this.cause = cause;
|
|
85
|
-
Object.setPrototypeOf(this, SandboxError.prototype);
|
|
86
|
-
}
|
|
87
|
-
static isInstance(error) {
|
|
88
|
-
return typeof error === "object" && error !== null && error[SANDBOX_ERROR_SYMBOL] === true;
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
//#endregion
|
|
92
21
|
//#region src/backends/utils.ts
|
|
93
22
|
/**
|
|
94
23
|
* Shared utility functions for memory backend implementations.
|
|
@@ -98,7 +27,7 @@ var SandboxError = class SandboxError extends Error {
|
|
|
98
27
|
* enable composition without fragile string parsing.
|
|
99
28
|
*/
|
|
100
29
|
const EMPTY_CONTENT_WARNING = "System reminder: File exists but has empty contents";
|
|
101
|
-
const MAX_LINE_LENGTH =
|
|
30
|
+
const MAX_LINE_LENGTH = 5e3;
|
|
102
31
|
const TOOL_RESULT_TOKEN_LIMIT = 2e4;
|
|
103
32
|
const TRUNCATION_GUIDANCE = "... [results truncated, try being more specific with your parameters]";
|
|
104
33
|
const MIME_TYPES = {
|
|
@@ -108,11 +37,26 @@ const MIME_TYPES = {
|
|
|
108
37
|
".gif": "image/gif",
|
|
109
38
|
".webp": "image/webp",
|
|
110
39
|
".svg": "image/svg+xml",
|
|
40
|
+
".heic": "image/heic",
|
|
41
|
+
".heif": "image/heif",
|
|
111
42
|
".mp3": "audio/mpeg",
|
|
112
43
|
".wav": "audio/wav",
|
|
44
|
+
".aiff": "audio/aiff",
|
|
45
|
+
".aac": "audio/aac",
|
|
46
|
+
".ogg": "audio/ogg",
|
|
47
|
+
".flac": "audio/flac",
|
|
113
48
|
".mp4": "video/mp4",
|
|
114
49
|
".webm": "video/webm",
|
|
115
|
-
".
|
|
50
|
+
".mpeg": "video/mpeg",
|
|
51
|
+
".mov": "video/quicktime",
|
|
52
|
+
".avi": "video/x-msvideo",
|
|
53
|
+
".flv": "video/x-flv",
|
|
54
|
+
".mpg": "video/mpeg",
|
|
55
|
+
".wmv": "video/x-ms-wmv",
|
|
56
|
+
".3gpp": "video/3gpp",
|
|
57
|
+
".pdf": "application/pdf",
|
|
58
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
59
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
116
60
|
};
|
|
117
61
|
/**
|
|
118
62
|
* Sanitize tool_call_id to prevent path traversal and separator issues.
|
|
@@ -141,7 +85,7 @@ function formatContentWithLineNumbers(content, startLine = 1) {
|
|
|
141
85
|
for (let i = 0; i < lines.length; i++) {
|
|
142
86
|
const line = lines[i];
|
|
143
87
|
const lineNum = i + startLine;
|
|
144
|
-
if (line.length <=
|
|
88
|
+
if (line.length <= 5e3) resultLines.push(`${lineNum.toString().padStart(6)}\t${line}`);
|
|
145
89
|
else {
|
|
146
90
|
const numChunks = Math.ceil(line.length / MAX_LINE_LENGTH);
|
|
147
91
|
for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) {
|
|
@@ -506,7 +450,96 @@ function adaptSandboxProtocol(sandbox) {
|
|
|
506
450
|
return adapted;
|
|
507
451
|
}
|
|
508
452
|
//#endregion
|
|
453
|
+
//#region src/backends/protocol.ts
|
|
454
|
+
/**
|
|
455
|
+
* Type guard to check if a backend supports execution.
|
|
456
|
+
*
|
|
457
|
+
* @param backend - Backend instance to check
|
|
458
|
+
* @returns True if the backend implements SandboxBackendProtocolV2
|
|
459
|
+
*/
|
|
460
|
+
function isSandboxBackend(backend) {
|
|
461
|
+
return backend != null && typeof backend === "object" && typeof backend.execute === "function" && typeof backend.id === "string" && backend.id !== "";
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Type guard to check if a backend is a sandbox protocol (v1 or v2).
|
|
465
|
+
*
|
|
466
|
+
* Checks for the presence of `execute` function and `id` string,
|
|
467
|
+
* which are the defining features of sandbox protocols.
|
|
468
|
+
*
|
|
469
|
+
* @param backend - Backend instance to check
|
|
470
|
+
* @returns True if the backend implements sandbox protocol (v1 or v2)
|
|
471
|
+
*/
|
|
472
|
+
function isSandboxProtocol(backend) {
|
|
473
|
+
return backend != null && typeof backend === "object" && typeof backend.execute === "function" && typeof backend.id === "string" && backend.id !== "";
|
|
474
|
+
}
|
|
475
|
+
const SANDBOX_ERROR_SYMBOL = Symbol.for("sandbox.error");
|
|
476
|
+
/**
|
|
477
|
+
* Custom error class for sandbox operations.
|
|
478
|
+
*
|
|
479
|
+
* @param message - Human-readable error description
|
|
480
|
+
* @param code - Structured error code for programmatic handling
|
|
481
|
+
* @returns SandboxError with message and code
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* ```typescript
|
|
485
|
+
* try {
|
|
486
|
+
* await sandbox.execute("some command");
|
|
487
|
+
* } catch (error) {
|
|
488
|
+
* if (error instanceof SandboxError) {
|
|
489
|
+
* switch (error.code) {
|
|
490
|
+
* case "NOT_INITIALIZED":
|
|
491
|
+
* await sandbox.initialize();
|
|
492
|
+
* break;
|
|
493
|
+
* case "COMMAND_TIMEOUT":
|
|
494
|
+
* console.error("Command took too long");
|
|
495
|
+
* break;
|
|
496
|
+
* default:
|
|
497
|
+
* throw error;
|
|
498
|
+
* }
|
|
499
|
+
* }
|
|
500
|
+
* }
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
503
|
+
var SandboxError = class SandboxError extends Error {
|
|
504
|
+
/** Symbol for identifying sandbox error instances */
|
|
505
|
+
[SANDBOX_ERROR_SYMBOL] = true;
|
|
506
|
+
/** Error name for instanceof checks and logging */
|
|
507
|
+
name = "SandboxError";
|
|
508
|
+
/**
|
|
509
|
+
* Creates a new SandboxError.
|
|
510
|
+
*
|
|
511
|
+
* @param message - Human-readable error description
|
|
512
|
+
* @param code - Structured error code for programmatic handling
|
|
513
|
+
*/
|
|
514
|
+
constructor(message, code, cause) {
|
|
515
|
+
super(message);
|
|
516
|
+
this.code = code;
|
|
517
|
+
this.cause = cause;
|
|
518
|
+
Object.setPrototypeOf(this, SandboxError.prototype);
|
|
519
|
+
}
|
|
520
|
+
static isInstance(error) {
|
|
521
|
+
return typeof error === "object" && error !== null && error[SANDBOX_ERROR_SYMBOL] === true;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
/**
|
|
525
|
+
* Resolve a backend instance or await a {@link BackendFactory}.
|
|
526
|
+
*
|
|
527
|
+
* Accepts {@link BackendRuntime} or {@link ToolRuntime} — store typing differs
|
|
528
|
+
* between LangGraph checkpoint stores and core `ToolRuntime`; factories receive
|
|
529
|
+
* a value that is structurally compatible at runtime.
|
|
530
|
+
*
|
|
531
|
+
* @internal
|
|
532
|
+
*/
|
|
533
|
+
async function resolveBackend(backend, runtime) {
|
|
534
|
+
if (typeof backend === "function") {
|
|
535
|
+
const resolved = await backend(runtime);
|
|
536
|
+
return isSandboxProtocol(resolved) ? adaptSandboxProtocol(resolved) : adaptBackendProtocol(resolved);
|
|
537
|
+
}
|
|
538
|
+
return isSandboxProtocol(backend) ? adaptSandboxProtocol(backend) : adaptBackendProtocol(backend);
|
|
539
|
+
}
|
|
540
|
+
//#endregion
|
|
509
541
|
//#region src/backends/state.ts
|
|
542
|
+
const PREGEL_SEND_KEY = "__pregel_send";
|
|
510
543
|
/**
|
|
511
544
|
* Backend that stores files in agent state (ephemeral).
|
|
512
545
|
*
|
|
@@ -519,17 +552,52 @@ function adaptSandboxProtocol(sandbox) {
|
|
|
519
552
|
* for the middleware to apply via Command.
|
|
520
553
|
*/
|
|
521
554
|
var StateBackend = class {
|
|
522
|
-
|
|
555
|
+
runtime;
|
|
523
556
|
fileFormat;
|
|
524
|
-
constructor(
|
|
525
|
-
|
|
526
|
-
|
|
557
|
+
constructor(runtimeOrOptions, options) {
|
|
558
|
+
if (runtimeOrOptions != null && typeof runtimeOrOptions === "object" && "state" in runtimeOrOptions) {
|
|
559
|
+
this.runtime = runtimeOrOptions;
|
|
560
|
+
this.fileFormat = options?.fileFormat ?? "v2";
|
|
561
|
+
} else {
|
|
562
|
+
this.runtime = void 0;
|
|
563
|
+
this.fileFormat = runtimeOrOptions?.fileFormat ?? "v2";
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Whether this instance was constructed with the legacy factory pattern.
|
|
568
|
+
*
|
|
569
|
+
* When true, state is read from the injected `runtime` and `filesUpdate`
|
|
570
|
+
* is returned to the caller. When false, state is read from LangGraph's
|
|
571
|
+
* execution context and updates are sent via `__pregel_send`.
|
|
572
|
+
*/
|
|
573
|
+
get isLegacy() {
|
|
574
|
+
return this.runtime !== void 0;
|
|
527
575
|
}
|
|
528
576
|
/**
|
|
529
577
|
* Get files from current state.
|
|
578
|
+
*
|
|
579
|
+
* In legacy mode, reads from the injected {@link BackendRuntime}.
|
|
580
|
+
* In zero-arg mode, reads from the LangGraph execution context via
|
|
581
|
+
* {@link getCurrentTaskInput}.
|
|
530
582
|
*/
|
|
531
583
|
getFiles() {
|
|
532
|
-
return this.
|
|
584
|
+
if (this.runtime) return this.runtime.state.files || {};
|
|
585
|
+
return getCurrentTaskInput()?.files || {};
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Push a files state update through LangGraph's internal send channel.
|
|
589
|
+
*
|
|
590
|
+
* In zero-arg mode, sends the update via the `__pregel_send` function
|
|
591
|
+
* from {@link getConfig}, mirroring Python's `CONFIG_KEY_SEND`.
|
|
592
|
+
* In legacy mode, this is a no-op — the caller uses `filesUpdate`
|
|
593
|
+
* from the return value instead.
|
|
594
|
+
*
|
|
595
|
+
* @param update - Map of file paths to their updated {@link FileData}
|
|
596
|
+
*/
|
|
597
|
+
sendFilesUpdate(update) {
|
|
598
|
+
if (this.isLegacy) return;
|
|
599
|
+
const send = getConfig().configurable?.[PREGEL_SEND_KEY];
|
|
600
|
+
if (typeof send === "function") send([["files", update]]);
|
|
533
601
|
}
|
|
534
602
|
/**
|
|
535
603
|
* List files and directories in the specified directory (non-recursive).
|
|
@@ -612,6 +680,11 @@ var StateBackend = class {
|
|
|
612
680
|
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.` };
|
|
613
681
|
const mimeType = getMimeType(filePath);
|
|
614
682
|
const newFileData = createFileData(content, void 0, this.fileFormat, mimeType);
|
|
683
|
+
const update = { [filePath]: newFileData };
|
|
684
|
+
if (!this.isLegacy) {
|
|
685
|
+
this.sendFilesUpdate(update);
|
|
686
|
+
return { path: filePath };
|
|
687
|
+
}
|
|
615
688
|
return {
|
|
616
689
|
path: filePath,
|
|
617
690
|
filesUpdate: { [filePath]: newFileData }
|
|
@@ -628,6 +701,14 @@ var StateBackend = class {
|
|
|
628
701
|
if (typeof result === "string") return { error: result };
|
|
629
702
|
const [newContent, occurrences] = result;
|
|
630
703
|
const newFileData = updateFileData(fileData, newContent);
|
|
704
|
+
const update = { [filePath]: newFileData };
|
|
705
|
+
if (!this.isLegacy) {
|
|
706
|
+
this.sendFilesUpdate(update);
|
|
707
|
+
return {
|
|
708
|
+
path: filePath,
|
|
709
|
+
occurrences
|
|
710
|
+
};
|
|
711
|
+
}
|
|
631
712
|
return {
|
|
632
713
|
path: filePath,
|
|
633
714
|
filesUpdate: { [filePath]: newFileData },
|
|
@@ -688,6 +769,10 @@ var StateBackend = class {
|
|
|
688
769
|
error: "invalid_path"
|
|
689
770
|
});
|
|
690
771
|
}
|
|
772
|
+
if (!this.isLegacy) {
|
|
773
|
+
if (Object.keys(updates).length > 0) this.sendFilesUpdate(updates);
|
|
774
|
+
return responses;
|
|
775
|
+
}
|
|
691
776
|
const result = responses;
|
|
692
777
|
result.filesUpdate = updates;
|
|
693
778
|
return result;
|
|
@@ -737,6 +822,7 @@ var StateBackend = class {
|
|
|
737
822
|
* - Pluggable backends (StateBackend, StoreBackend, FilesystemBackend, CompositeBackend)
|
|
738
823
|
* - Tool result eviction for large outputs
|
|
739
824
|
*/
|
|
825
|
+
const INT_FORMATTER = new Intl.NumberFormat("en-US");
|
|
740
826
|
/**
|
|
741
827
|
* Tools that should be excluded from the large result eviction logic.
|
|
742
828
|
*
|
|
@@ -798,17 +884,75 @@ const READ_FILE_TRUNCATION_MSG = `
|
|
|
798
884
|
/**
|
|
799
885
|
* Message template for evicted tool results.
|
|
800
886
|
*/
|
|
801
|
-
const TOO_LARGE_TOOL_MSG = `
|
|
802
|
-
|
|
803
|
-
You can
|
|
804
|
-
|
|
887
|
+
const TOO_LARGE_TOOL_MSG = context`
|
|
888
|
+
Tool result too large, the result of this tool call {tool_call_id} was saved in the filesystem at this path: {file_path}
|
|
889
|
+
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.
|
|
890
|
+
You can do this by specifying an offset and limit in the read_file tool call.
|
|
891
|
+
For example, to read the first 100 lines, you can use the read_file tool with offset=0 and limit=100.
|
|
892
|
+
|
|
893
|
+
Here is a preview showing the head and tail of the result (lines of the form
|
|
894
|
+
... [N lines truncated] ...
|
|
895
|
+
indicate omitted lines in the middle of the content):
|
|
896
|
+
|
|
897
|
+
{content_sample}
|
|
898
|
+
`;
|
|
899
|
+
/**
|
|
900
|
+
* Message template for evicted HumanMessages.
|
|
901
|
+
*/
|
|
902
|
+
const TOO_LARGE_HUMAN_MSG = `Message content too large and was saved to the filesystem at: {file_path}
|
|
903
|
+
|
|
904
|
+
You can read the full content using the read_file tool with pagination (offset and limit parameters).
|
|
805
905
|
|
|
806
|
-
Here is a preview showing the head and tail of the
|
|
807
|
-
... [N lines truncated] ...
|
|
808
|
-
indicate omitted lines in the middle of the content):
|
|
906
|
+
Here is a preview showing the head and tail of the content:
|
|
809
907
|
|
|
810
908
|
{content_sample}`;
|
|
811
909
|
/**
|
|
910
|
+
* Extract text content from a message.
|
|
911
|
+
*
|
|
912
|
+
* For string content, returns it directly. For array content (mixed block types
|
|
913
|
+
* like text + image), joins all text blocks. Returns empty string if no text found.
|
|
914
|
+
*/
|
|
915
|
+
function extractTextFromMessage(message) {
|
|
916
|
+
if (typeof message.content === "string") return message.content;
|
|
917
|
+
if (Array.isArray(message.content)) return message.content.filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
|
|
918
|
+
return String(message.content);
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Build replacement content for an evicted HumanMessage, preserving non-text blocks.
|
|
922
|
+
*
|
|
923
|
+
* For plain string content, returns the replacement text directly. For list content
|
|
924
|
+
* with mixed block types (e.g., text + image), replaces all text blocks with a single
|
|
925
|
+
* text block containing the replacement text while keeping non-text blocks intact.
|
|
926
|
+
*/
|
|
927
|
+
function buildEvictedHumanContent(message, replacementText) {
|
|
928
|
+
if (typeof message.content === "string") return replacementText;
|
|
929
|
+
if (Array.isArray(message.content)) {
|
|
930
|
+
const mediaBlocks = message.content.filter((block) => typeof block === "object" && block !== null && block.type !== "text");
|
|
931
|
+
if (mediaBlocks.length === 0) return replacementText;
|
|
932
|
+
return [{
|
|
933
|
+
type: "text",
|
|
934
|
+
text: replacementText
|
|
935
|
+
}, ...mediaBlocks];
|
|
936
|
+
}
|
|
937
|
+
return replacementText;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Build a truncated HumanMessage for the model request.
|
|
941
|
+
*
|
|
942
|
+
* Computes a preview from the full content still in state and returns a
|
|
943
|
+
* lightweight replacement the model will see. Pure string computation — no
|
|
944
|
+
* backend I/O.
|
|
945
|
+
*/
|
|
946
|
+
function buildTruncatedHumanMessage(message, filePath) {
|
|
947
|
+
const contentSample = createContentPreview(extractTextFromMessage(message));
|
|
948
|
+
return new HumanMessage({
|
|
949
|
+
content: buildEvictedHumanContent(message, TOO_LARGE_HUMAN_MSG.replace("{file_path}", filePath).replace("{content_sample}", contentSample)),
|
|
950
|
+
id: message.id,
|
|
951
|
+
additional_kwargs: { ...message.additional_kwargs },
|
|
952
|
+
response_metadata: { ...message.response_metadata }
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
812
956
|
* Create a preview of content showing head and tail with truncation marker.
|
|
813
957
|
*
|
|
814
958
|
* @param contentStr - The full content string to preview.
|
|
@@ -883,138 +1027,148 @@ const FilesystemStateSchema = new StateSchema({ files: new ReducedValue(z.record
|
|
|
883
1027
|
inputSchema: z.record(z.string(), FileDataSchema.nullable()).optional(),
|
|
884
1028
|
reducer: fileDataReducer
|
|
885
1029
|
}) });
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
*
|
|
889
|
-
* @param backend - Backend instance or factory function
|
|
890
|
-
* @param stateAndStore - State and store container for backend initialization
|
|
891
|
-
*/
|
|
892
|
-
function getBackend(backend, stateAndStore) {
|
|
893
|
-
const actualBackend = typeof backend === "function" ? backend(stateAndStore) : backend;
|
|
894
|
-
return isSandboxProtocol(actualBackend) ? adaptSandboxProtocol(actualBackend) : adaptBackendProtocol(actualBackend);
|
|
895
|
-
}
|
|
896
|
-
const FILESYSTEM_SYSTEM_PROMPT = `## Filesystem Tools \`ls\`, \`read_file\`, \`write_file\`, \`edit_file\`, \`glob\`, \`grep\`
|
|
1030
|
+
const FILESYSTEM_SYSTEM_PROMPT = context`
|
|
1031
|
+
## Following Conventions
|
|
897
1032
|
|
|
898
|
-
|
|
899
|
-
|
|
1033
|
+
- Read files before editing — understand existing content before making changes
|
|
1034
|
+
- Mimic existing style, naming conventions, and patterns
|
|
900
1035
|
|
|
901
|
-
|
|
902
|
-
- read_file: read a file from the filesystem
|
|
903
|
-
- write_file: write to a file in the filesystem
|
|
904
|
-
- edit_file: edit a file in the filesystem
|
|
905
|
-
- glob: find files matching a pattern (e.g., "**/*.py")
|
|
906
|
-
- grep: search for text within files`;
|
|
907
|
-
const LS_TOOL_DESCRIPTION = `Lists all files in a directory.
|
|
1036
|
+
## Filesystem Tools \`ls\`, \`read_file\`, \`write_file\`, \`edit_file\`, \`glob\`, \`grep\`
|
|
908
1037
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
const READ_FILE_TOOL_DESCRIPTION = `Reads a file from the filesystem.
|
|
1038
|
+
You have access to a filesystem which you can interact with using these tools.
|
|
1039
|
+
All file paths must start with a /.
|
|
912
1040
|
|
|
913
|
-
|
|
1041
|
+
- ls: list files in a directory (requires absolute path)
|
|
1042
|
+
- read_file: read a file from the filesystem
|
|
1043
|
+
- write_file: write to a file in the filesystem
|
|
1044
|
+
- edit_file: edit a file in the filesystem
|
|
1045
|
+
- glob: find files matching a pattern (e.g., "**/*.py")
|
|
1046
|
+
- grep: search for text within files
|
|
1047
|
+
`;
|
|
1048
|
+
const LS_TOOL_DESCRIPTION = context`
|
|
1049
|
+
Lists all files in a directory.
|
|
914
1050
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
- Results are returned using cat -n format, with line numbers starting at 1
|
|
923
|
-
- 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.
|
|
924
|
-
- 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.
|
|
925
|
-
- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.
|
|
926
|
-
- You should ALWAYS make sure a file has been read before editing it.`;
|
|
927
|
-
const WRITE_FILE_TOOL_DESCRIPTION = `Writes to a new file in the filesystem.
|
|
1051
|
+
This is useful for exploring the filesystem and finding the right file to read or edit.
|
|
1052
|
+
You should almost ALWAYS use this tool before using the read_file or edit_file tools.
|
|
1053
|
+
`;
|
|
1054
|
+
const READ_FILE_TOOL_DESCRIPTION = context`
|
|
1055
|
+
Reads a file from the filesystem.
|
|
1056
|
+
|
|
1057
|
+
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.
|
|
928
1058
|
|
|
929
|
-
Usage:
|
|
930
|
-
-
|
|
931
|
-
-
|
|
932
|
-
|
|
1059
|
+
Usage:
|
|
1060
|
+
- By default, it reads up to 100 lines starting from the beginning of the file
|
|
1061
|
+
- **IMPORTANT for large files and codebase exploration**: Use pagination with offset and limit parameters to avoid context overflow
|
|
1062
|
+
- First scan: read_file(path, limit=100) to see file structure
|
|
1063
|
+
- Read more sections: read_file(path, offset=100, limit=200) for next 200 lines
|
|
1064
|
+
- Only omit limit (read full file) when necessary for editing
|
|
1065
|
+
- Specify offset and limit: read_file(path, offset=0, limit=100) reads first 100 lines
|
|
1066
|
+
- Results are returned using cat -n format, with line numbers starting at 1
|
|
1067
|
+
- 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.
|
|
1068
|
+
- 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.
|
|
1069
|
+
- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.
|
|
1070
|
+
- You should ALWAYS make sure a file has been read before editing it.
|
|
1071
|
+
`;
|
|
1072
|
+
const WRITE_FILE_TOOL_DESCRIPTION = context`
|
|
1073
|
+
Writes to a new file in the filesystem.
|
|
1074
|
+
|
|
1075
|
+
Usage:
|
|
1076
|
+
- The write_file tool will create a new file.
|
|
1077
|
+
- Prefer to edit existing files (with the edit_file tool) over creating new ones when possible.
|
|
1078
|
+
`;
|
|
1079
|
+
const EDIT_FILE_TOOL_DESCRIPTION = context`
|
|
1080
|
+
Performs exact string replacements in files.
|
|
933
1081
|
|
|
934
|
-
Usage:
|
|
935
|
-
- You must read the file before editing. This tool will error if you attempt an edit without reading the file first.
|
|
936
|
-
- When editing, preserve the exact indentation (tabs/spaces) from the read output. Never include line number prefixes in old_string or new_string.
|
|
937
|
-
- ALWAYS prefer editing existing files over creating new ones.
|
|
938
|
-
- Only use emojis if the user explicitly requests it
|
|
939
|
-
|
|
1082
|
+
Usage:
|
|
1083
|
+
- You must read the file before editing. This tool will error if you attempt an edit without reading the file first.
|
|
1084
|
+
- When editing, preserve the exact indentation (tabs/spaces) from the read output. Never include line number prefixes in old_string or new_string.
|
|
1085
|
+
- ALWAYS prefer editing existing files over creating new ones.
|
|
1086
|
+
- Only use emojis if the user explicitly requests it.
|
|
1087
|
+
`;
|
|
1088
|
+
const GLOB_TOOL_DESCRIPTION = context`
|
|
1089
|
+
Find files matching a glob pattern.
|
|
940
1090
|
|
|
941
|
-
Supports standard glob patterns: \`*\` (any characters), \`**\` (any directories), \`?\` (single character).
|
|
942
|
-
Returns a list of absolute file paths that match the pattern.
|
|
1091
|
+
Supports standard glob patterns: \`*\` (any characters), \`**\` (any directories), \`?\` (single character).
|
|
1092
|
+
Returns a list of absolute file paths that match the pattern.
|
|
943
1093
|
|
|
944
|
-
Examples:
|
|
945
|
-
- \`**/*.py\` - Find all Python files
|
|
946
|
-
- \`*.txt\` - Find all text files in root
|
|
947
|
-
- \`/subdir/**/*.md\` - Find all markdown files under /subdir
|
|
948
|
-
|
|
1094
|
+
Examples:
|
|
1095
|
+
- \`**/*.py\` - Find all Python files
|
|
1096
|
+
- \`*.txt\` - Find all text files in root
|
|
1097
|
+
- \`/subdir/**/*.md\` - Find all markdown files under /subdir
|
|
1098
|
+
`;
|
|
1099
|
+
const GREP_TOOL_DESCRIPTION = context`
|
|
1100
|
+
Search for a text pattern across files.
|
|
949
1101
|
|
|
950
|
-
Searches for literal text (not regex) and returns matching files or content based on output_mode.
|
|
951
|
-
Special characters like parentheses, brackets, pipes, etc. are treated as literal characters, not regex operators.
|
|
1102
|
+
Searches for literal text (not regex) and returns matching files or content based on output_mode.
|
|
1103
|
+
Special characters like parentheses, brackets, pipes, etc. are treated as literal characters, not regex operators.
|
|
952
1104
|
|
|
953
|
-
Examples:
|
|
954
|
-
- Search all files: \`grep(pattern="TODO")\`
|
|
955
|
-
- Search Python files only: \`grep(pattern="import", glob="*.py")\`
|
|
956
|
-
- Show matching lines: \`grep(pattern="error", output_mode="content")\`
|
|
957
|
-
- Search for code with special chars: \`grep(pattern="def __init__(self):")
|
|
958
|
-
|
|
1105
|
+
Examples:
|
|
1106
|
+
- Search all files: \`grep(pattern="TODO")\`
|
|
1107
|
+
- Search Python files only: \`grep(pattern="import", glob="*.py")\`
|
|
1108
|
+
- Show matching lines: \`grep(pattern="error", output_mode="content")\`
|
|
1109
|
+
- Search for code with special chars: \`grep(pattern="def __init__(self):")\`
|
|
1110
|
+
`;
|
|
1111
|
+
const EXECUTE_TOOL_DESCRIPTION = context`
|
|
1112
|
+
Executes a shell command in an isolated sandbox environment.
|
|
959
1113
|
|
|
960
|
-
Usage:
|
|
961
|
-
Executes a given command in the sandbox environment with proper handling and security measures.
|
|
962
|
-
Before executing the command, please follow these steps:
|
|
1114
|
+
Usage:
|
|
1115
|
+
Executes a given command in the sandbox environment with proper handling and security measures.
|
|
1116
|
+
Before executing the command, please follow these steps:
|
|
963
1117
|
|
|
964
|
-
1. Directory Verification:
|
|
965
|
-
|
|
966
|
-
|
|
1118
|
+
1. Directory Verification:
|
|
1119
|
+
- 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
|
|
1120
|
+
- For example, before running "mkdir foo/bar", first use ls to check that "foo" exists and is the intended parent directory
|
|
967
1121
|
|
|
968
|
-
2. Command Execution:
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1122
|
+
2. Command Execution:
|
|
1123
|
+
- Always quote file paths that contain spaces with double quotes (e.g., cd "path with spaces/file.txt")
|
|
1124
|
+
- Examples of proper quoting:
|
|
1125
|
+
- cd "/Users/name/My Documents" (correct)
|
|
1126
|
+
- cd /Users/name/My Documents (incorrect - will fail)
|
|
1127
|
+
- python "/path/with spaces/script.py" (correct)
|
|
1128
|
+
- python /path/with spaces/script.py (incorrect - will fail)
|
|
1129
|
+
- After ensuring proper quoting, execute the command
|
|
1130
|
+
- Capture the output of the command
|
|
977
1131
|
|
|
978
|
-
Usage notes:
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1132
|
+
Usage notes:
|
|
1133
|
+
- Commands run in an isolated sandbox environment
|
|
1134
|
+
- Returns combined stdout/stderr output with exit code
|
|
1135
|
+
- If the output is very large, it may be truncated
|
|
1136
|
+
- 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.
|
|
1137
|
+
- When issuing multiple commands, use the ';' or '&&' operator to separate them. DO NOT use newlines (newlines are ok in quoted strings)
|
|
1138
|
+
- Use '&&' when commands depend on each other (e.g., "mkdir dir && cd dir")
|
|
1139
|
+
- Use ';' only when you need to run commands sequentially but don't care if earlier commands fail
|
|
1140
|
+
- Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of cd
|
|
987
1141
|
|
|
988
|
-
Examples:
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1142
|
+
Examples:
|
|
1143
|
+
Good examples:
|
|
1144
|
+
- execute(command="pytest /foo/bar/tests")
|
|
1145
|
+
- execute(command="python /path/to/script.py")
|
|
1146
|
+
- execute(command="npm install && npm test")
|
|
993
1147
|
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1148
|
+
Bad examples (avoid these):
|
|
1149
|
+
- execute(command="cd /foo/bar && pytest tests") # Use absolute path instead
|
|
1150
|
+
- execute(command="cat file.txt") # Use read_file tool instead
|
|
1151
|
+
- execute(command="find . -name '*.py'") # Use glob tool instead
|
|
1152
|
+
- execute(command="grep -r 'pattern' .") # Use grep tool instead
|
|
999
1153
|
|
|
1000
|
-
Note: This tool is only available if the backend supports execution (SandboxBackendProtocol).
|
|
1001
|
-
If execution is not supported, the tool will return an error message
|
|
1002
|
-
|
|
1154
|
+
Note: This tool is only available if the backend supports execution (SandboxBackendProtocol).
|
|
1155
|
+
If execution is not supported, the tool will return an error message.
|
|
1156
|
+
`;
|
|
1157
|
+
const EXECUTION_SYSTEM_PROMPT = context`
|
|
1158
|
+
## Execute Tool \`execute\`
|
|
1003
1159
|
|
|
1004
|
-
You have access to an \`execute\` tool for running shell commands in a sandboxed environment.
|
|
1005
|
-
Use this tool to run commands, scripts, tests, builds, and other shell operations.
|
|
1160
|
+
You have access to an \`execute\` tool for running shell commands in a sandboxed environment.
|
|
1161
|
+
Use this tool to run commands, scripts, tests, builds, and other shell operations.
|
|
1006
1162
|
|
|
1007
|
-
- execute: run a shell command in the sandbox (returns output and exit code)
|
|
1163
|
+
- execute: run a shell command in the sandbox (returns output and exit code)
|
|
1164
|
+
`;
|
|
1008
1165
|
/**
|
|
1009
1166
|
* Create ls tool using backend.
|
|
1010
1167
|
*/
|
|
1011
1168
|
function createLsTool(backend, options) {
|
|
1012
1169
|
const { customDescription } = options;
|
|
1013
|
-
return tool(async (input,
|
|
1014
|
-
const resolvedBackend =
|
|
1015
|
-
state: getCurrentTaskInput(config),
|
|
1016
|
-
store: config.store
|
|
1017
|
-
});
|
|
1170
|
+
return tool(async (input, runtime) => {
|
|
1171
|
+
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
1018
1172
|
const path = input.path || "/";
|
|
1019
1173
|
const lsResult = await resolvedBackend.ls(path);
|
|
1020
1174
|
if (lsResult.error) return `Error listing files: ${lsResult.error}`;
|
|
@@ -1040,11 +1194,8 @@ function createLsTool(backend, options) {
|
|
|
1040
1194
|
*/
|
|
1041
1195
|
function createReadFileTool(backend, options) {
|
|
1042
1196
|
const { customDescription, toolTokenLimitBeforeEvict } = options;
|
|
1043
|
-
return tool(async (input,
|
|
1044
|
-
const resolvedBackend =
|
|
1045
|
-
state: getCurrentTaskInput(config),
|
|
1046
|
-
store: config.store
|
|
1047
|
-
});
|
|
1197
|
+
return tool(async (input, runtime) => {
|
|
1198
|
+
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
1048
1199
|
const { file_path, offset = 0, limit = 100 } = input;
|
|
1049
1200
|
const readResult = await resolvedBackend.read(file_path, offset, limit);
|
|
1050
1201
|
if (readResult.error) return [{
|
|
@@ -1119,17 +1270,14 @@ function createReadFileTool(backend, options) {
|
|
|
1119
1270
|
*/
|
|
1120
1271
|
function createWriteFileTool(backend, options) {
|
|
1121
1272
|
const { customDescription } = options;
|
|
1122
|
-
return tool(async (input,
|
|
1123
|
-
const resolvedBackend =
|
|
1124
|
-
state: getCurrentTaskInput(config),
|
|
1125
|
-
store: config.store
|
|
1126
|
-
});
|
|
1273
|
+
return tool(async (input, runtime) => {
|
|
1274
|
+
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
1127
1275
|
const { file_path, content } = input;
|
|
1128
1276
|
const result = await resolvedBackend.write(file_path, content);
|
|
1129
1277
|
if (result.error) return result.error;
|
|
1130
1278
|
const message = new ToolMessage({
|
|
1131
1279
|
content: `Successfully wrote to '${file_path}'`,
|
|
1132
|
-
tool_call_id:
|
|
1280
|
+
tool_call_id: runtime.toolCall?.id,
|
|
1133
1281
|
name: "write_file",
|
|
1134
1282
|
metadata: result.metadata
|
|
1135
1283
|
});
|
|
@@ -1152,17 +1300,14 @@ function createWriteFileTool(backend, options) {
|
|
|
1152
1300
|
*/
|
|
1153
1301
|
function createEditFileTool(backend, options) {
|
|
1154
1302
|
const { customDescription } = options;
|
|
1155
|
-
return tool(async (input,
|
|
1156
|
-
const resolvedBackend =
|
|
1157
|
-
state: getCurrentTaskInput(config),
|
|
1158
|
-
store: config.store
|
|
1159
|
-
});
|
|
1303
|
+
return tool(async (input, runtime) => {
|
|
1304
|
+
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
1160
1305
|
const { file_path, old_string, new_string, replace_all = false } = input;
|
|
1161
1306
|
const result = await resolvedBackend.edit(file_path, old_string, new_string, replace_all);
|
|
1162
1307
|
if (result.error) return result.error;
|
|
1163
1308
|
const message = new ToolMessage({
|
|
1164
1309
|
content: `Successfully replaced ${result.occurrences} occurrence(s) in '${file_path}'`,
|
|
1165
|
-
tool_call_id:
|
|
1310
|
+
tool_call_id: runtime.toolCall?.id,
|
|
1166
1311
|
name: "edit_file",
|
|
1167
1312
|
metadata: result.metadata
|
|
1168
1313
|
});
|
|
@@ -1187,11 +1332,8 @@ function createEditFileTool(backend, options) {
|
|
|
1187
1332
|
*/
|
|
1188
1333
|
function createGlobTool(backend, options) {
|
|
1189
1334
|
const { customDescription } = options;
|
|
1190
|
-
return tool(async (input,
|
|
1191
|
-
const resolvedBackend =
|
|
1192
|
-
state: getCurrentTaskInput(config),
|
|
1193
|
-
store: config.store
|
|
1194
|
-
});
|
|
1335
|
+
return tool(async (input, runtime) => {
|
|
1336
|
+
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
1195
1337
|
const { pattern, path = "/" } = input;
|
|
1196
1338
|
const globResult = await resolvedBackend.glob(pattern, path);
|
|
1197
1339
|
if (globResult.error) return `Error finding files: ${globResult.error}`;
|
|
@@ -1214,11 +1356,8 @@ function createGlobTool(backend, options) {
|
|
|
1214
1356
|
*/
|
|
1215
1357
|
function createGrepTool(backend, options) {
|
|
1216
1358
|
const { customDescription } = options;
|
|
1217
|
-
return tool(async (input,
|
|
1218
|
-
const resolvedBackend =
|
|
1219
|
-
state: getCurrentTaskInput(config),
|
|
1220
|
-
store: config.store
|
|
1221
|
-
});
|
|
1359
|
+
return tool(async (input, runtime) => {
|
|
1360
|
+
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
1222
1361
|
const { pattern, path = "/", glob = null } = input;
|
|
1223
1362
|
const result = await resolvedBackend.grep(pattern, path, glob);
|
|
1224
1363
|
if (result.error) return result.error;
|
|
@@ -1242,7 +1381,7 @@ function createGrepTool(backend, options) {
|
|
|
1242
1381
|
schema: z.object({
|
|
1243
1382
|
pattern: z.string().describe("Regex pattern to search for"),
|
|
1244
1383
|
path: z.string().optional().default("/").describe("Base path to search from (default: /)"),
|
|
1245
|
-
glob: z.string().optional().nullable().describe("Optional glob pattern to filter files (e.g., '*.py')")
|
|
1384
|
+
glob: z.string().optional().nullable().default(null).describe("Optional glob pattern to filter files (e.g., '*.py')")
|
|
1246
1385
|
})
|
|
1247
1386
|
});
|
|
1248
1387
|
}
|
|
@@ -1251,11 +1390,8 @@ function createGrepTool(backend, options) {
|
|
|
1251
1390
|
*/
|
|
1252
1391
|
function createExecuteTool(backend, options) {
|
|
1253
1392
|
const { customDescription } = options;
|
|
1254
|
-
return tool(async (input,
|
|
1255
|
-
const resolvedBackend =
|
|
1256
|
-
state: getCurrentTaskInput(config),
|
|
1257
|
-
store: config.store
|
|
1258
|
-
});
|
|
1393
|
+
return tool(async (input, runtime) => {
|
|
1394
|
+
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
1259
1395
|
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.";
|
|
1260
1396
|
const result = await resolvedBackend.execute(input.command);
|
|
1261
1397
|
const parts = [result.output];
|
|
@@ -1275,7 +1411,7 @@ function createExecuteTool(backend, options) {
|
|
|
1275
1411
|
* Create filesystem middleware with all tools and features.
|
|
1276
1412
|
*/
|
|
1277
1413
|
function createFilesystemMiddleware(options = {}) {
|
|
1278
|
-
const { backend = (
|
|
1414
|
+
const { backend = (runtime) => new StateBackend(runtime), systemPrompt: customSystemPrompt = null, customToolDescriptions = null, toolTokenLimitBeforeEvict = 2e4, humanMessageTokenLimitBeforeEvict = 5e4 } = options;
|
|
1279
1415
|
const baseSystemPrompt = customSystemPrompt || FILESYSTEM_SYSTEM_PROMPT;
|
|
1280
1416
|
const allToolsByName = {
|
|
1281
1417
|
ls: createLsTool(backend, { customDescription: customToolDescriptions?.ls }),
|
|
@@ -1293,19 +1429,53 @@ function createFilesystemMiddleware(options = {}) {
|
|
|
1293
1429
|
name: "FilesystemMiddleware",
|
|
1294
1430
|
stateSchema: FilesystemStateSchema,
|
|
1295
1431
|
tools: Object.values(allToolsByName),
|
|
1432
|
+
async beforeAgent(state) {
|
|
1433
|
+
if (!humanMessageTokenLimitBeforeEvict) return;
|
|
1434
|
+
const messages = state.messages;
|
|
1435
|
+
if (!messages || messages.length === 0) return;
|
|
1436
|
+
const last = messages[messages.length - 1];
|
|
1437
|
+
if (!HumanMessage.isInstance(last)) return;
|
|
1438
|
+
if (last.additional_kwargs?.lc_evicted_to) return;
|
|
1439
|
+
const contentStr = extractTextFromMessage(last);
|
|
1440
|
+
const threshold = 4 * humanMessageTokenLimitBeforeEvict;
|
|
1441
|
+
if (contentStr.length <= threshold) return;
|
|
1442
|
+
const resolvedBackend = await resolveBackend(backend, { state: state || {} });
|
|
1443
|
+
const filePath = `/conversation_history/${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
|
|
1444
|
+
const writeResult = await resolvedBackend.write(filePath, contentStr);
|
|
1445
|
+
if (writeResult.error) return;
|
|
1446
|
+
const result = { messages: [new HumanMessage({
|
|
1447
|
+
content: last.content,
|
|
1448
|
+
id: last.id,
|
|
1449
|
+
additional_kwargs: {
|
|
1450
|
+
...last.additional_kwargs,
|
|
1451
|
+
lc_evicted_to: filePath
|
|
1452
|
+
},
|
|
1453
|
+
response_metadata: { ...last.response_metadata }
|
|
1454
|
+
})] };
|
|
1455
|
+
if (writeResult.filesUpdate) result.files = writeResult.filesUpdate;
|
|
1456
|
+
return result;
|
|
1457
|
+
},
|
|
1296
1458
|
wrapModelCall: async (request, handler) => {
|
|
1297
|
-
const supportsExecution = isSandboxBackend(
|
|
1298
|
-
|
|
1299
|
-
|
|
1459
|
+
const supportsExecution = isSandboxBackend(await resolveBackend(backend, {
|
|
1460
|
+
...request.runtime,
|
|
1461
|
+
state: request.state
|
|
1300
1462
|
}));
|
|
1301
1463
|
let tools = request.tools;
|
|
1302
1464
|
if (!supportsExecution) tools = tools.filter((t) => t.name !== "execute");
|
|
1303
1465
|
let filesystemPrompt = baseSystemPrompt;
|
|
1304
1466
|
if (supportsExecution) filesystemPrompt = `${filesystemPrompt}\n\n${EXECUTION_SYSTEM_PROMPT}`;
|
|
1305
1467
|
const newSystemMessage = request.systemMessage.concat(filesystemPrompt);
|
|
1468
|
+
let messages = request.messages;
|
|
1469
|
+
if (humanMessageTokenLimitBeforeEvict && messages) {
|
|
1470
|
+
if (messages.some((msg) => HumanMessage.isInstance(msg) && msg.additional_kwargs?.lc_evicted_to)) messages = messages.map((msg) => {
|
|
1471
|
+
if (HumanMessage.isInstance(msg) && msg.additional_kwargs?.lc_evicted_to) return buildTruncatedHumanMessage(msg, msg.additional_kwargs.lc_evicted_to);
|
|
1472
|
+
return msg;
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1306
1475
|
return handler({
|
|
1307
1476
|
...request,
|
|
1308
1477
|
tools,
|
|
1478
|
+
messages,
|
|
1309
1479
|
systemMessage: newSystemMessage
|
|
1310
1480
|
});
|
|
1311
1481
|
},
|
|
@@ -1316,9 +1486,9 @@ function createFilesystemMiddleware(options = {}) {
|
|
|
1316
1486
|
const result = await handler(request);
|
|
1317
1487
|
async function processToolMessage(msg, toolTokenLimitBeforeEvict) {
|
|
1318
1488
|
if (typeof msg.content === "string" && msg.content.length > toolTokenLimitBeforeEvict * 4) {
|
|
1319
|
-
const resolvedBackend =
|
|
1320
|
-
|
|
1321
|
-
|
|
1489
|
+
const resolvedBackend = await resolveBackend(backend, {
|
|
1490
|
+
...request.runtime,
|
|
1491
|
+
state: request.state
|
|
1322
1492
|
});
|
|
1323
1493
|
const evictPath = `/large_tool_results/${sanitizeToolCallId(request.toolCall?.id || msg.tool_call_id)}`;
|
|
1324
1494
|
const writeResult = await resolvedBackend.write(evictPath, msg.content);
|
|
@@ -1411,117 +1581,117 @@ const EXCLUDED_STATE_KEYS = [
|
|
|
1411
1581
|
*/
|
|
1412
1582
|
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.";
|
|
1413
1583
|
function getTaskToolDescription(subagentDescriptions) {
|
|
1414
|
-
return `
|
|
1415
|
-
Launch an ephemeral subagent to handle complex, multi-step independent tasks with isolated context windows.
|
|
1584
|
+
return context`
|
|
1585
|
+
Launch an ephemeral subagent to handle complex, multi-step independent tasks with isolated context windows.
|
|
1416
1586
|
|
|
1417
|
-
Available agent types and the tools they have access to:
|
|
1418
|
-
${subagentDescriptions.join("\n")}
|
|
1587
|
+
Available agent types and the tools they have access to:
|
|
1588
|
+
${subagentDescriptions.join("\n")}
|
|
1419
1589
|
|
|
1420
|
-
When using the Task tool, you must specify a subagent_type parameter to select which agent type to use.
|
|
1590
|
+
When using the Task tool, you must specify a subagent_type parameter to select which agent type to use.
|
|
1421
1591
|
|
|
1422
|
-
## Usage notes:
|
|
1423
|
-
1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
|
|
1424
|
-
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.
|
|
1425
|
-
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.
|
|
1426
|
-
4. The agent's outputs should generally be trusted
|
|
1427
|
-
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
|
|
1428
|
-
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.
|
|
1429
|
-
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.
|
|
1592
|
+
## Usage notes:
|
|
1593
|
+
1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
|
|
1594
|
+
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.
|
|
1595
|
+
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.
|
|
1596
|
+
4. The agent's outputs should generally be trusted
|
|
1597
|
+
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
|
|
1598
|
+
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.
|
|
1599
|
+
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.
|
|
1430
1600
|
|
|
1431
|
-
### Example usage of the general-purpose agent:
|
|
1601
|
+
### Example usage of the general-purpose agent:
|
|
1432
1602
|
|
|
1433
|
-
<example_agent_descriptions>
|
|
1434
|
-
"general-purpose": use this agent for general purpose tasks, it has access to all tools as the main agent.
|
|
1435
|
-
</example_agent_descriptions>
|
|
1603
|
+
<example_agent_descriptions>
|
|
1604
|
+
"general-purpose": use this agent for general purpose tasks, it has access to all tools as the main agent.
|
|
1605
|
+
</example_agent_descriptions>
|
|
1436
1606
|
|
|
1437
|
-
<example>
|
|
1438
|
-
User: "I want to conduct research on the accomplishments of Lebron James, Michael Jordan, and Kobe Bryant, and then compare them."
|
|
1439
|
-
Assistant: *Uses the task tool in parallel to conduct isolated research on each of the three players*
|
|
1440
|
-
Assistant: *Synthesizes the results of the three isolated research tasks and responds to the User*
|
|
1441
|
-
<commentary>
|
|
1442
|
-
Research is a complex, multi-step task in it of itself.
|
|
1443
|
-
The research of each individual player is not dependent on the research of the other players.
|
|
1444
|
-
The assistant uses the task tool to break down the complex objective into three isolated tasks.
|
|
1445
|
-
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.
|
|
1446
|
-
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.
|
|
1447
|
-
</commentary>
|
|
1448
|
-
</example>
|
|
1607
|
+
<example>
|
|
1608
|
+
User: "I want to conduct research on the accomplishments of Lebron James, Michael Jordan, and Kobe Bryant, and then compare them."
|
|
1609
|
+
Assistant: *Uses the task tool in parallel to conduct isolated research on each of the three players*
|
|
1610
|
+
Assistant: *Synthesizes the results of the three isolated research tasks and responds to the User*
|
|
1611
|
+
<commentary>
|
|
1612
|
+
Research is a complex, multi-step task in it of itself.
|
|
1613
|
+
The research of each individual player is not dependent on the research of the other players.
|
|
1614
|
+
The assistant uses the task tool to break down the complex objective into three isolated tasks.
|
|
1615
|
+
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.
|
|
1616
|
+
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.
|
|
1617
|
+
</commentary>
|
|
1618
|
+
</example>
|
|
1449
1619
|
|
|
1450
|
-
<example>
|
|
1451
|
-
User: "Analyze a single large code repository for security vulnerabilities and generate a report."
|
|
1452
|
-
Assistant: *Launches a single \`task\` subagent for the repository analysis*
|
|
1453
|
-
Assistant: *Receives report and integrates results into final summary*
|
|
1454
|
-
<commentary>
|
|
1455
|
-
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.
|
|
1456
|
-
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.
|
|
1457
|
-
</commentary>
|
|
1458
|
-
</example>
|
|
1620
|
+
<example>
|
|
1621
|
+
User: "Analyze a single large code repository for security vulnerabilities and generate a report."
|
|
1622
|
+
Assistant: *Launches a single \`task\` subagent for the repository analysis*
|
|
1623
|
+
Assistant: *Receives report and integrates results into final summary*
|
|
1624
|
+
<commentary>
|
|
1625
|
+
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.
|
|
1626
|
+
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.
|
|
1627
|
+
</commentary>
|
|
1628
|
+
</example>
|
|
1459
1629
|
|
|
1460
|
-
<example>
|
|
1461
|
-
User: "Schedule two meetings for me and prepare agendas for each."
|
|
1462
|
-
Assistant: *Calls the task tool in parallel to launch two \`task\` subagents (one per meeting) to prepare agendas*
|
|
1463
|
-
Assistant: *Returns final schedules and agendas*
|
|
1464
|
-
<commentary>
|
|
1465
|
-
Tasks are simple individually, but subagents help silo agenda preparation.
|
|
1466
|
-
Each subagent only needs to worry about the agenda for one meeting.
|
|
1467
|
-
</commentary>
|
|
1468
|
-
</example>
|
|
1630
|
+
<example>
|
|
1631
|
+
User: "Schedule two meetings for me and prepare agendas for each."
|
|
1632
|
+
Assistant: *Calls the task tool in parallel to launch two \`task\` subagents (one per meeting) to prepare agendas*
|
|
1633
|
+
Assistant: *Returns final schedules and agendas*
|
|
1634
|
+
<commentary>
|
|
1635
|
+
Tasks are simple individually, but subagents help silo agenda preparation.
|
|
1636
|
+
Each subagent only needs to worry about the agenda for one meeting.
|
|
1637
|
+
</commentary>
|
|
1638
|
+
</example>
|
|
1469
1639
|
|
|
1470
|
-
<example>
|
|
1471
|
-
User: "I want to order a pizza from Dominos, order a burger from McDonald's, and order a salad from Subway."
|
|
1472
|
-
Assistant: *Calls tools directly in parallel to order a pizza from Dominos, a burger from McDonald's, and a salad from Subway*
|
|
1473
|
-
<commentary>
|
|
1474
|
-
The assistant did not use the task tool because the objective is super simple and clear and only requires a few trivial tool calls.
|
|
1475
|
-
It is better to just complete the task directly and NOT use the \`task\`tool.
|
|
1476
|
-
</commentary>
|
|
1477
|
-
</example>
|
|
1640
|
+
<example>
|
|
1641
|
+
User: "I want to order a pizza from Dominos, order a burger from McDonald's, and order a salad from Subway."
|
|
1642
|
+
Assistant: *Calls tools directly in parallel to order a pizza from Dominos, a burger from McDonald's, and a salad from Subway*
|
|
1643
|
+
<commentary>
|
|
1644
|
+
The assistant did not use the task tool because the objective is super simple and clear and only requires a few trivial tool calls.
|
|
1645
|
+
It is better to just complete the task directly and NOT use the \`task\`tool.
|
|
1646
|
+
</commentary>
|
|
1647
|
+
</example>
|
|
1478
1648
|
|
|
1479
|
-
### Example usage with custom agents:
|
|
1649
|
+
### Example usage with custom agents:
|
|
1480
1650
|
|
|
1481
|
-
<example_agent_descriptions>
|
|
1482
|
-
"content-reviewer": use this agent after you are done creating significant content or documents
|
|
1483
|
-
"greeting-responder": use this agent when to respond to user greetings with a friendly joke
|
|
1484
|
-
"research-analyst": use this agent to conduct thorough research on complex topics
|
|
1485
|
-
</example_agent_description>
|
|
1651
|
+
<example_agent_descriptions>
|
|
1652
|
+
"content-reviewer": use this agent after you are done creating significant content or documents
|
|
1653
|
+
"greeting-responder": use this agent when to respond to user greetings with a friendly joke
|
|
1654
|
+
"research-analyst": use this agent to conduct thorough research on complex topics
|
|
1655
|
+
</example_agent_description>
|
|
1486
1656
|
|
|
1487
|
-
<example>
|
|
1488
|
-
user: "Please write a function that checks if a number is prime"
|
|
1489
|
-
assistant: Sure let me write a function that checks if a number is prime
|
|
1490
|
-
assistant: First let me use the Write tool to write a function that checks if a number is prime
|
|
1491
|
-
assistant: I'm going to use the Write tool to write the following code:
|
|
1492
|
-
<code>
|
|
1493
|
-
function isPrime(n) {
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
}
|
|
1500
|
-
</code>
|
|
1501
|
-
<commentary>
|
|
1502
|
-
Since significant content was created and the task was completed, now use the content-reviewer agent to review the work
|
|
1503
|
-
</commentary>
|
|
1504
|
-
assistant: Now let me use the content-reviewer agent to review the code
|
|
1505
|
-
assistant: Uses the Task tool to launch with the content-reviewer agent
|
|
1506
|
-
</example>
|
|
1657
|
+
<example>
|
|
1658
|
+
user: "Please write a function that checks if a number is prime"
|
|
1659
|
+
assistant: Sure let me write a function that checks if a number is prime
|
|
1660
|
+
assistant: First let me use the Write tool to write a function that checks if a number is prime
|
|
1661
|
+
assistant: I'm going to use the Write tool to write the following code:
|
|
1662
|
+
<code>
|
|
1663
|
+
function isPrime(n) {{
|
|
1664
|
+
if (n <= 1) return false
|
|
1665
|
+
for (let i = 2; i * i <= n; i++) {{
|
|
1666
|
+
if (n % i === 0) return false
|
|
1667
|
+
}}
|
|
1668
|
+
return true
|
|
1669
|
+
}}
|
|
1670
|
+
</code>
|
|
1671
|
+
<commentary>
|
|
1672
|
+
Since significant content was created and the task was completed, now use the content-reviewer agent to review the work
|
|
1673
|
+
</commentary>
|
|
1674
|
+
assistant: Now let me use the content-reviewer agent to review the code
|
|
1675
|
+
assistant: Uses the Task tool to launch with the content-reviewer agent
|
|
1676
|
+
</example>
|
|
1507
1677
|
|
|
1508
|
-
<example>
|
|
1509
|
-
user: "Can you help me research the environmental impact of different renewable energy sources and create a comprehensive report?"
|
|
1510
|
-
<commentary>
|
|
1511
|
-
This is a complex research task that would benefit from using the research-analyst agent to conduct thorough analysis
|
|
1512
|
-
</commentary>
|
|
1513
|
-
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.
|
|
1514
|
-
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
|
|
1515
|
-
</example>
|
|
1678
|
+
<example>
|
|
1679
|
+
user: "Can you help me research the environmental impact of different renewable energy sources and create a comprehensive report?"
|
|
1680
|
+
<commentary>
|
|
1681
|
+
This is a complex research task that would benefit from using the research-analyst agent to conduct thorough analysis
|
|
1682
|
+
</commentary>
|
|
1683
|
+
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.
|
|
1684
|
+
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
|
|
1685
|
+
</example>
|
|
1516
1686
|
|
|
1517
|
-
<example>
|
|
1518
|
-
user: "Hello"
|
|
1519
|
-
<commentary>
|
|
1520
|
-
Since the user is greeting, use the greeting-responder agent to respond with a friendly joke
|
|
1521
|
-
</commentary>
|
|
1522
|
-
assistant: "I'm going to use the Task tool to launch with the greeting-responder agent"
|
|
1523
|
-
</example>
|
|
1524
|
-
|
|
1687
|
+
<example>
|
|
1688
|
+
user: "Hello"
|
|
1689
|
+
<commentary>
|
|
1690
|
+
Since the user is greeting, use the greeting-responder agent to respond with a friendly joke
|
|
1691
|
+
</commentary>
|
|
1692
|
+
assistant: "I'm going to use the Task tool to launch with the greeting-responder agent"
|
|
1693
|
+
</example>
|
|
1694
|
+
`;
|
|
1525
1695
|
}
|
|
1526
1696
|
/**
|
|
1527
1697
|
* System prompt section that explains how to use the task tool for spawning subagents.
|
|
@@ -1536,33 +1706,35 @@ assistant: "I'm going to use the Task tool to launch with the greeting-responder
|
|
|
1536
1706
|
* You can provide a custom `systemPrompt` to `createSubAgentMiddleware` to override
|
|
1537
1707
|
* or extend this default.
|
|
1538
1708
|
*/
|
|
1539
|
-
const TASK_SYSTEM_PROMPT =
|
|
1709
|
+
const TASK_SYSTEM_PROMPT = context`
|
|
1710
|
+
## \`task\` (subagent spawner)
|
|
1540
1711
|
|
|
1541
|
-
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.
|
|
1712
|
+
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.
|
|
1542
1713
|
|
|
1543
|
-
When to use the task tool:
|
|
1544
|
-
- When a task is complex and multi-step, and can be fully delegated in isolation
|
|
1545
|
-
- When a task is independent of other tasks and can run in parallel
|
|
1546
|
-
- When a task requires focused reasoning or heavy token/context usage that would bloat the orchestrator thread
|
|
1547
|
-
- When sandboxing improves reliability (e.g. code execution, structured searches, data formatting)
|
|
1548
|
-
- 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.)
|
|
1714
|
+
When to use the task tool:
|
|
1715
|
+
- When a task is complex and multi-step, and can be fully delegated in isolation
|
|
1716
|
+
- When a task is independent of other tasks and can run in parallel
|
|
1717
|
+
- When a task requires focused reasoning or heavy token/context usage that would bloat the orchestrator thread
|
|
1718
|
+
- When sandboxing improves reliability (e.g. code execution, structured searches, data formatting)
|
|
1719
|
+
- 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.)
|
|
1549
1720
|
|
|
1550
|
-
Subagent lifecycle:
|
|
1551
|
-
1. **Spawn** → Provide clear role, instructions, and expected output
|
|
1552
|
-
2. **Run** → The subagent completes the task autonomously
|
|
1553
|
-
3. **Return** → The subagent provides a single structured result
|
|
1554
|
-
4. **Reconcile** → Incorporate or synthesize the result into the main thread
|
|
1721
|
+
Subagent lifecycle:
|
|
1722
|
+
1. **Spawn** → Provide clear role, instructions, and expected output
|
|
1723
|
+
2. **Run** → The subagent completes the task autonomously
|
|
1724
|
+
3. **Return** → The subagent provides a single structured result
|
|
1725
|
+
4. **Reconcile** → Incorporate or synthesize the result into the main thread
|
|
1555
1726
|
|
|
1556
|
-
When NOT to use the task tool:
|
|
1557
|
-
- If you need to see the intermediate reasoning or steps after the subagent has completed (the task tool hides them)
|
|
1558
|
-
- If the task is trivial (a few tool calls or simple lookup)
|
|
1559
|
-
- If delegating does not reduce token usage, complexity, or context switching
|
|
1560
|
-
- If splitting would add latency without benefit
|
|
1727
|
+
When NOT to use the task tool:
|
|
1728
|
+
- If you need to see the intermediate reasoning or steps after the subagent has completed (the task tool hides them)
|
|
1729
|
+
- If the task is trivial (a few tool calls or simple lookup)
|
|
1730
|
+
- If delegating does not reduce token usage, complexity, or context switching
|
|
1731
|
+
- If splitting would add latency without benefit
|
|
1561
1732
|
|
|
1562
|
-
## Important Task Tool Usage Notes to Remember
|
|
1563
|
-
- 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.
|
|
1564
|
-
- Remember to use the \`task\` tool to silo independent tasks within a multi-part objective.
|
|
1565
|
-
- 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
|
|
1733
|
+
## Important Task Tool Usage Notes to Remember
|
|
1734
|
+
- 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.
|
|
1735
|
+
- Remember to use the \`task\` tool to silo independent tasks within a multi-part objective.
|
|
1736
|
+
- 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.
|
|
1737
|
+
`;
|
|
1566
1738
|
/**
|
|
1567
1739
|
* Base specification for the general-purpose subagent.
|
|
1568
1740
|
*
|
|
@@ -1971,65 +2143,67 @@ const MemoryStateSchema = new StateSchema({
|
|
|
1971
2143
|
* Default system prompt template for memory.
|
|
1972
2144
|
* Ported from Python's comprehensive memory guidelines.
|
|
1973
2145
|
*/
|
|
1974
|
-
const MEMORY_SYSTEM_PROMPT =
|
|
1975
|
-
|
|
1976
|
-
|
|
2146
|
+
const MEMORY_SYSTEM_PROMPT = context`
|
|
2147
|
+
<agent_memory>
|
|
2148
|
+
{memory_contents}
|
|
2149
|
+
</agent_memory>
|
|
1977
2150
|
|
|
1978
|
-
<memory_guidelines>
|
|
1979
|
-
|
|
2151
|
+
<memory_guidelines>
|
|
2152
|
+
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.
|
|
1980
2153
|
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2154
|
+
**Learning from feedback:**
|
|
2155
|
+
- 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.
|
|
2156
|
+
- 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.
|
|
2157
|
+
- When user says something is better/worse, capture WHY and encode it as a pattern.
|
|
2158
|
+
- Each correction is a chance to improve permanently - don't just fix the immediate issue, update your instructions.
|
|
2159
|
+
- 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.
|
|
2160
|
+
- Look for the underlying principle behind corrections, not just the specific mistake.
|
|
2161
|
+
- 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.
|
|
1989
2162
|
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
2163
|
+
**Asking for information:**
|
|
2164
|
+
- 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.
|
|
2165
|
+
- It is preferred for you to ask for information, don't assume anything that you do not know!
|
|
2166
|
+
- When the user provides information that is useful for future use, you should update your memories immediately.
|
|
1994
2167
|
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2168
|
+
**When to update memories:**
|
|
2169
|
+
- When the user explicitly asks you to remember something (e.g., "remember my email", "save this preference")
|
|
2170
|
+
- When the user describes your role or how you should behave (e.g., "you are a web researcher", "always do X")
|
|
2171
|
+
- When the user gives feedback on your work - capture what was wrong and how to improve
|
|
2172
|
+
- When the user provides information required for tool use (e.g., slack channel ID, email addresses)
|
|
2173
|
+
- When the user provides context useful for future tasks, such as how to use tools, or which actions to take in a particular situation
|
|
2174
|
+
- When you discover new patterns or preferences (coding styles, conventions, workflows)
|
|
2002
2175
|
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2176
|
+
**When to NOT update memories:**
|
|
2177
|
+
- When the information is temporary or transient (e.g., "I'm running late", "I'm on my phone right now")
|
|
2178
|
+
- When the information is a one-time task request (e.g., "Find me a recipe", "What's 25 * 4?")
|
|
2179
|
+
- When the information is a simple question that doesn't reveal lasting preferences (e.g., "What day is it?", "Can you explain X?")
|
|
2180
|
+
- When the information is an acknowledgment or small talk (e.g., "Sounds good!", "Hello", "Thanks for that")
|
|
2181
|
+
- When the information is stale or irrelevant in future conversations
|
|
2182
|
+
- Never store API keys, access tokens, passwords, or any other credentials in any file, memory, or system prompt.
|
|
2183
|
+
- If the user asks where to put API keys or provides an API key, do NOT echo or save it.
|
|
2011
2184
|
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2185
|
+
**Examples:**
|
|
2186
|
+
Example 1 (remembering user information):
|
|
2187
|
+
User: Can you connect to my google account?
|
|
2188
|
+
Agent: Sure, I'll connect to your google account, what's your google account email?
|
|
2189
|
+
User: john@example.com
|
|
2190
|
+
Agent: Let me save this to my memory.
|
|
2191
|
+
Tool Call: edit_file(...) -> remembers that the user's google account email is john@example.com
|
|
2019
2192
|
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2193
|
+
Example 2 (remembering implicit user preferences):
|
|
2194
|
+
User: Can you write me an example for creating a deep agent in LangChain?
|
|
2195
|
+
Agent: Sure, I'll write you an example for creating a deep agent in LangChain <example code in Python>
|
|
2196
|
+
User: Can you do this in JavaScript
|
|
2197
|
+
Agent: Let me save this to my memory.
|
|
2198
|
+
Tool Call: edit_file(...) -> remembers that the user prefers to get LangChain code examples in JavaScript
|
|
2199
|
+
Agent: Sure, here is the JavaScript example<example code in JavaScript>
|
|
2027
2200
|
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
</memory_guidelines
|
|
2201
|
+
Example 3 (do not remember transient information):
|
|
2202
|
+
User: I'm going to play basketball tonight so I will be offline for a few hours.
|
|
2203
|
+
Agent: Okay I'll add a block to your calendar.
|
|
2204
|
+
Tool Call: create_calendar_event(...) -> just calls a tool, does not commit anything to memory, as it is transient information
|
|
2205
|
+
</memory_guidelines>
|
|
2206
|
+
`;
|
|
2033
2207
|
/**
|
|
2034
2208
|
* Format loaded memory contents for injection into prompt.
|
|
2035
2209
|
* Pairs memory locations with their contents for clarity.
|
|
@@ -2088,19 +2262,12 @@ async function loadMemoryFromBackend(backend, path) {
|
|
|
2088
2262
|
*/
|
|
2089
2263
|
function createMemoryMiddleware(options) {
|
|
2090
2264
|
const { backend, sources, addCacheControl = false } = options;
|
|
2091
|
-
/**
|
|
2092
|
-
* Resolve backend from instance or factory.
|
|
2093
|
-
*/
|
|
2094
|
-
function getBackend(state) {
|
|
2095
|
-
if (typeof backend === "function") return adaptBackendProtocol(backend({ state }));
|
|
2096
|
-
return adaptBackendProtocol(backend);
|
|
2097
|
-
}
|
|
2098
2265
|
return createMiddleware({
|
|
2099
2266
|
name: "MemoryMiddleware",
|
|
2100
2267
|
stateSchema: MemoryStateSchema,
|
|
2101
2268
|
async beforeAgent(state) {
|
|
2102
2269
|
if ("memoryContents" in state && state.memoryContents != null) return;
|
|
2103
|
-
const resolvedBackend =
|
|
2270
|
+
const resolvedBackend = await resolveBackend(backend, { state });
|
|
2104
2271
|
const contents = {};
|
|
2105
2272
|
for (const path of sources) try {
|
|
2106
2273
|
const content = await loadMemoryFromBackend(resolvedBackend, path);
|
|
@@ -2234,7 +2401,7 @@ Skills follow a **progressive disclosure** pattern - you know they exist (name +
|
|
|
2234
2401
|
1. **Recognize when a skill applies**: Check if the user's task matches any skill's description
|
|
2235
2402
|
2. **Read the skill's full instructions**: The skill list above shows the exact path to use with read_file
|
|
2236
2403
|
3. **Follow the skill's instructions**: SKILL.md contains step-by-step workflows, best practices, and examples
|
|
2237
|
-
4. **Access supporting files**: Skills may include
|
|
2404
|
+
4. **Access supporting files**: Skills may include scripts, configs, or reference docs - use absolute paths
|
|
2238
2405
|
|
|
2239
2406
|
**When to Use Skills:**
|
|
2240
2407
|
- When the user's request matches a skill's domain (e.g., "research X" → web-research skill)
|
|
@@ -2246,7 +2413,7 @@ Skills follow a **progressive disclosure** pattern - you know they exist (name +
|
|
|
2246
2413
|
- The skill list above shows the full path for each skill's SKILL.md file
|
|
2247
2414
|
|
|
2248
2415
|
**Executing Skill Scripts:**
|
|
2249
|
-
Skills may contain
|
|
2416
|
+
Skills may contain scripts or other executable files. Always use absolute paths from the skill list.
|
|
2250
2417
|
|
|
2251
2418
|
**Example Workflow:**
|
|
2252
2419
|
|
|
@@ -2506,13 +2673,6 @@ function formatSkillsList(skills, sources) {
|
|
|
2506
2673
|
function createSkillsMiddleware(options) {
|
|
2507
2674
|
const { backend, sources } = options;
|
|
2508
2675
|
let loadedSkills = [];
|
|
2509
|
-
/**
|
|
2510
|
-
* Resolve backend from instance or factory.
|
|
2511
|
-
*/
|
|
2512
|
-
function getBackend(state) {
|
|
2513
|
-
if (typeof backend === "function") return adaptBackendProtocol(backend({ state }));
|
|
2514
|
-
return adaptBackendProtocol(backend);
|
|
2515
|
-
}
|
|
2516
2676
|
return createMiddleware({
|
|
2517
2677
|
name: "SkillsMiddleware",
|
|
2518
2678
|
stateSchema: SkillsStateSchema,
|
|
@@ -2522,7 +2682,7 @@ function createSkillsMiddleware(options) {
|
|
|
2522
2682
|
loadedSkills = state.skillsMetadata;
|
|
2523
2683
|
return;
|
|
2524
2684
|
}
|
|
2525
|
-
const resolvedBackend =
|
|
2685
|
+
const resolvedBackend = await resolveBackend(backend, { state });
|
|
2526
2686
|
const allSkills = /* @__PURE__ */ new Map();
|
|
2527
2687
|
for (const sourcePath of sources) try {
|
|
2528
2688
|
const skills = await listSkillsFromBackend(resolvedBackend, sourcePath);
|
|
@@ -2547,25 +2707,26 @@ function createSkillsMiddleware(options) {
|
|
|
2547
2707
|
});
|
|
2548
2708
|
}
|
|
2549
2709
|
//#endregion
|
|
2550
|
-
//#region src/middleware/
|
|
2710
|
+
//#region src/middleware/completion_callback.ts
|
|
2551
2711
|
/**
|
|
2552
|
-
*
|
|
2712
|
+
* Callback middleware for async subagents.
|
|
2553
2713
|
*
|
|
2554
|
-
*
|
|
2714
|
+
* @experimental - this middleware is experimental and may change in future releases.
|
|
2555
2715
|
*
|
|
2556
|
-
*
|
|
2557
|
-
*
|
|
2558
|
-
*
|
|
2716
|
+
* This middleware sends a notification to a callback thread when a subagent
|
|
2717
|
+
* completes successfully or raises an error. The callback agent can then
|
|
2718
|
+
* process that notification instead of relying only on polling via
|
|
2559
2719
|
* `check_async_task`.
|
|
2560
2720
|
*
|
|
2561
2721
|
* ## Architecture
|
|
2562
2722
|
*
|
|
2563
|
-
*
|
|
2564
|
-
*
|
|
2565
|
-
*
|
|
2723
|
+
* A parent agent launches a subagent with `start_async_task` and can later
|
|
2724
|
+
* inspect task state with `check_async_task`. This middleware adds an optional
|
|
2725
|
+
* completion signal by creating a run on the callback thread when the subagent
|
|
2726
|
+
* finishes.
|
|
2566
2727
|
*
|
|
2567
2728
|
* ```
|
|
2568
|
-
*
|
|
2729
|
+
* Parent Subagent
|
|
2569
2730
|
* | |
|
|
2570
2731
|
* |--- start_async_task -----> |
|
|
2571
2732
|
* |<-- task_id (immediately) - |
|
|
@@ -2573,67 +2734,74 @@ function createSkillsMiddleware(options) {
|
|
|
2573
2734
|
* | | (done!)
|
|
2574
2735
|
* | |
|
|
2575
2736
|
* |<-- runs.create( |
|
|
2576
|
-
* |
|
|
2737
|
+
* | callback_thread, |
|
|
2577
2738
|
* | "completed: ...") |
|
|
2578
2739
|
* | |
|
|
2579
|
-
* | (
|
|
2740
|
+
* | (processes result) |
|
|
2580
2741
|
* ```
|
|
2581
2742
|
*
|
|
2582
|
-
* The
|
|
2583
|
-
*
|
|
2584
|
-
*
|
|
2585
|
-
*
|
|
2586
|
-
*
|
|
2587
|
-
*
|
|
2588
|
-
*
|
|
2589
|
-
*
|
|
2590
|
-
*
|
|
2591
|
-
*
|
|
2592
|
-
* - `
|
|
2593
|
-
*
|
|
2594
|
-
*
|
|
2595
|
-
*
|
|
2596
|
-
*
|
|
2597
|
-
* supervisor's `start_async_task` tool. It survives thread interrupts and
|
|
2598
|
-
* updates because it lives in state, not config.
|
|
2599
|
-
* - If `parent_thread_id` is not present in state, the notifier silently no-ops.
|
|
2743
|
+
* The middleware calls `runs.create()` on the callback thread. From the
|
|
2744
|
+
* callback agent's perspective, this appears as a new user message containing
|
|
2745
|
+
* structured output from the subagent.
|
|
2746
|
+
*
|
|
2747
|
+
* ## Callback context
|
|
2748
|
+
*
|
|
2749
|
+
* - `callbackGraphId` identifies the callback graph or assistant. It is
|
|
2750
|
+
* provided when the middleware is constructed.
|
|
2751
|
+
* - `url` and `headers` optionally configure a remote callback destination.
|
|
2752
|
+
* Omit `url` for same-deployment ASGI transport.
|
|
2753
|
+
* - `callback_thread_id` is stored in the subagent state by the parent's
|
|
2754
|
+
* `start_async_task` tool. Because it is stored in state rather than config,
|
|
2755
|
+
* it survives thread updates and interrupts.
|
|
2756
|
+
* - If `callback_thread_id` is not present in state, the middleware does
|
|
2757
|
+
* nothing.
|
|
2600
2758
|
*
|
|
2601
2759
|
* ## Usage
|
|
2602
2760
|
*
|
|
2603
2761
|
* ```typescript
|
|
2604
|
-
* import {
|
|
2762
|
+
* import { createCompletionCallbackMiddleware } from "deepagents";
|
|
2605
2763
|
*
|
|
2606
|
-
*
|
|
2607
|
-
*
|
|
2764
|
+
* // Same deployment (callback agent and subagent share a server):
|
|
2765
|
+
* const notifier = createCompletionCallbackMiddleware({
|
|
2766
|
+
* callbackGraphId: "supervisor",
|
|
2767
|
+
* });
|
|
2768
|
+
*
|
|
2769
|
+
* // Remote deployment (callback destination on a different server):
|
|
2770
|
+
* const notifier = createCompletionCallbackMiddleware({
|
|
2771
|
+
* callbackGraphId: "supervisor",
|
|
2608
2772
|
* url: "https://my-deployment.langsmith.dev",
|
|
2609
2773
|
* });
|
|
2610
2774
|
*
|
|
2611
2775
|
* const agent = createDeepAgent({
|
|
2612
|
-
* model
|
|
2776
|
+
* model,
|
|
2613
2777
|
* middleware: [notifier],
|
|
2614
2778
|
* });
|
|
2615
2779
|
* ```
|
|
2616
2780
|
*
|
|
2617
|
-
* The middleware
|
|
2618
|
-
*
|
|
2619
|
-
*
|
|
2781
|
+
* The middleware reads `callbackThreadId` from the agent state at the end of
|
|
2782
|
+
* execution. This value is injected by the parent's `start_async_task` tool
|
|
2783
|
+
* when it creates the run.
|
|
2620
2784
|
*
|
|
2621
2785
|
* @module
|
|
2622
2786
|
*/
|
|
2623
|
-
/** State key where the supervisor's launch tool stores the parent thread ID. */
|
|
2624
|
-
const PARENT_THREAD_ID_KEY = "parent_thread_id";
|
|
2625
2787
|
/** Maximum characters to include from the last message in notifications. */
|
|
2626
|
-
const
|
|
2788
|
+
const MAX_MESSAGE_LENGTH = 500;
|
|
2789
|
+
/** Suffix appended when truncating long messages. */
|
|
2790
|
+
const TRUNCATION_SUFFIX = "... [full result truncated]";
|
|
2791
|
+
/** State key for the callback thread ID. */
|
|
2792
|
+
const CALLBACK_THREAD_ID_KEY = "callbackThreadId";
|
|
2627
2793
|
/**
|
|
2628
|
-
* State extension for subagents that use
|
|
2794
|
+
* State extension for subagents that use completion callbacks.
|
|
2629
2795
|
*
|
|
2630
|
-
*
|
|
2631
|
-
*
|
|
2632
|
-
*
|
|
2796
|
+
* @experimental - this state schema is experimental and may change in future releases.
|
|
2797
|
+
*
|
|
2798
|
+
* `callbackThreadId` is written by the parent's `start_async_task` tool
|
|
2799
|
+
* and read by `CompletionCallbackMiddleware` when sending callback
|
|
2800
|
+
* notifications.
|
|
2633
2801
|
*/
|
|
2634
|
-
const
|
|
2802
|
+
const CompletionCallbackStateSchema = z$2.object({ [CALLBACK_THREAD_ID_KEY]: z$2.string().optional() });
|
|
2635
2803
|
/**
|
|
2636
|
-
* Build headers for the
|
|
2804
|
+
* Build headers for the callback LangGraph server.
|
|
2637
2805
|
*
|
|
2638
2806
|
* Ensures `x-auth-scheme: langsmith` is present unless explicitly overridden.
|
|
2639
2807
|
*/
|
|
@@ -2643,55 +2811,63 @@ function resolveHeaders(headers) {
|
|
|
2643
2811
|
return resolved;
|
|
2644
2812
|
}
|
|
2645
2813
|
/**
|
|
2646
|
-
* Send a notification run to the
|
|
2814
|
+
* Send a notification run to the callback thread.
|
|
2815
|
+
*
|
|
2816
|
+
* @param callbackGraphId - The callback graph ID used as `assistant_id`
|
|
2817
|
+
* in the `runs.create` call.
|
|
2818
|
+
* @param callbackThreadId - The callback thread ID.
|
|
2819
|
+
* @param message - The message content to send.
|
|
2820
|
+
* @param options - Optional url and headers for the callback server.
|
|
2647
2821
|
*/
|
|
2648
|
-
async function notifyParent(
|
|
2822
|
+
async function notifyParent(callbackGraphId, callbackThreadId, message, options) {
|
|
2649
2823
|
try {
|
|
2650
2824
|
await new Client({
|
|
2651
|
-
apiUrl: options
|
|
2825
|
+
apiUrl: options?.url ?? void 0,
|
|
2652
2826
|
apiKey: null,
|
|
2653
|
-
defaultHeaders: resolveHeaders(options
|
|
2654
|
-
}).runs.create(
|
|
2827
|
+
defaultHeaders: resolveHeaders(options?.headers)
|
|
2828
|
+
}).runs.create(callbackThreadId, callbackGraphId, { input: { messages: [{
|
|
2655
2829
|
role: "user",
|
|
2656
|
-
content:
|
|
2830
|
+
content: message
|
|
2657
2831
|
}] } });
|
|
2658
2832
|
} catch (e) {
|
|
2659
|
-
console.warn(`[
|
|
2833
|
+
console.warn(`[CompletionCallbackMiddleware] Failed to notify callback thread ${callbackThreadId}:`, e);
|
|
2660
2834
|
}
|
|
2661
2835
|
}
|
|
2662
2836
|
/**
|
|
2663
2837
|
* Extract a summary from the subagent's final message.
|
|
2664
2838
|
*
|
|
2665
2839
|
* Returns at most 500 characters from the last message's content.
|
|
2840
|
+
* Throws if no messages exist or if the last message is not an AIMessage.
|
|
2841
|
+
*
|
|
2842
|
+
* @param state - The agent state dict.
|
|
2843
|
+
* @param taskId - Optional task ID to include in truncation hint.
|
|
2666
2844
|
*/
|
|
2667
|
-
function extractLastMessage(state) {
|
|
2845
|
+
function extractLastMessage(state, taskId) {
|
|
2668
2846
|
const messages = state.messages;
|
|
2669
|
-
if (!messages || messages.length === 0)
|
|
2847
|
+
if (!messages || messages.length === 0) throw new Error(`Expected at least one message in state ${JSON.stringify(state)}`);
|
|
2670
2848
|
const last = messages[messages.length - 1];
|
|
2671
|
-
if (last
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2849
|
+
if (!AIMessage$1.isInstance(last)) throw new TypeError(`Expected an AIMessage, got ${typeof last === "object" && last !== null ? last.constructor?.name ?? typeof last : typeof last} instead`);
|
|
2850
|
+
let textContent = last.text;
|
|
2851
|
+
if (textContent.length > MAX_MESSAGE_LENGTH) {
|
|
2852
|
+
textContent = textContent.slice(0, MAX_MESSAGE_LENGTH) + TRUNCATION_SUFFIX;
|
|
2853
|
+
if (taskId) textContent += ` Result truncated. Use \`check_async_task(task_id='${taskId}')\` to retrieve the full result if needed.`;
|
|
2675
2854
|
}
|
|
2676
|
-
return
|
|
2855
|
+
return textContent;
|
|
2677
2856
|
}
|
|
2678
2857
|
/**
|
|
2679
|
-
* Create a completion
|
|
2858
|
+
* Create a completion callback middleware for async subagents.
|
|
2680
2859
|
*
|
|
2681
2860
|
* **Experimental** — this middleware is experimental and may change.
|
|
2682
2861
|
*
|
|
2683
|
-
* This middleware is added to
|
|
2684
|
-
*
|
|
2685
|
-
*
|
|
2686
|
-
* proactively relay results.
|
|
2862
|
+
* This middleware is added to a subagent's middleware stack. On success or
|
|
2863
|
+
* model-call error, it sends a notification to the configured callback
|
|
2864
|
+
* thread by calling `runs.create()`.
|
|
2687
2865
|
*
|
|
2688
|
-
* The
|
|
2689
|
-
*
|
|
2690
|
-
*
|
|
2691
|
-
* configuration known at deployment time.
|
|
2866
|
+
* The callback destination is configured with `callbackGraphId` and
|
|
2867
|
+
* optional `url` and `headers`. The target thread is read from
|
|
2868
|
+
* `callbackThreadId` in the subagent state.
|
|
2692
2869
|
*
|
|
2693
|
-
* If `
|
|
2694
|
-
* launched manually without a supervisor), the middleware silently does
|
|
2870
|
+
* If `callbackThreadId` is not present in state, the middleware does
|
|
2695
2871
|
* nothing.
|
|
2696
2872
|
*
|
|
2697
2873
|
* @param options - Configuration options.
|
|
@@ -2699,11 +2875,10 @@ function extractLastMessage(state) {
|
|
|
2699
2875
|
*
|
|
2700
2876
|
* @example
|
|
2701
2877
|
* ```typescript
|
|
2702
|
-
* import {
|
|
2878
|
+
* import { createCompletionCallbackMiddleware } from "deepagents";
|
|
2703
2879
|
*
|
|
2704
|
-
* const notifier =
|
|
2705
|
-
*
|
|
2706
|
-
* url: "https://my-deployment.langsmith.dev",
|
|
2880
|
+
* const notifier = createCompletionCallbackMiddleware({
|
|
2881
|
+
* callbackGraphId: "supervisor",
|
|
2707
2882
|
* });
|
|
2708
2883
|
*
|
|
2709
2884
|
* const agent = createDeepAgent({
|
|
@@ -2712,23 +2887,13 @@ function extractLastMessage(state) {
|
|
|
2712
2887
|
* });
|
|
2713
2888
|
* ```
|
|
2714
2889
|
*/
|
|
2715
|
-
function
|
|
2716
|
-
const {
|
|
2717
|
-
let notified = false;
|
|
2890
|
+
function createCompletionCallbackMiddleware(options) {
|
|
2891
|
+
const { callbackGraphId, url, headers } = options;
|
|
2718
2892
|
/**
|
|
2719
|
-
*
|
|
2893
|
+
* Send a notification to the callback destination.
|
|
2720
2894
|
*/
|
|
2721
|
-
function
|
|
2722
|
-
|
|
2723
|
-
return Boolean(state[PARENT_THREAD_ID_KEY]);
|
|
2724
|
-
}
|
|
2725
|
-
/**
|
|
2726
|
-
* Send a notification to the parent if conditions are met.
|
|
2727
|
-
*/
|
|
2728
|
-
async function sendNotification(state, message) {
|
|
2729
|
-
if (!shouldNotify(state)) return;
|
|
2730
|
-
notified = true;
|
|
2731
|
-
await notifyParent(state[PARENT_THREAD_ID_KEY], parentGraphId, message, {
|
|
2895
|
+
async function sendNotification(callbackThreadId, message) {
|
|
2896
|
+
await notifyParent(callbackGraphId, callbackThreadId, message, {
|
|
2732
2897
|
url,
|
|
2733
2898
|
headers
|
|
2734
2899
|
});
|
|
@@ -2737,7 +2902,7 @@ function createCompletionNotifierMiddleware(options) {
|
|
|
2737
2902
|
* Read the subagent's own thread_id from runtime config.
|
|
2738
2903
|
*
|
|
2739
2904
|
* The subagent's `thread_id` is the same as the `task_id` from the
|
|
2740
|
-
*
|
|
2905
|
+
* parent's perspective.
|
|
2741
2906
|
*/
|
|
2742
2907
|
function getTaskId(runtime) {
|
|
2743
2908
|
return runtime?.configurable?.thread_id;
|
|
@@ -2750,17 +2915,20 @@ function createCompletionNotifierMiddleware(options) {
|
|
|
2750
2915
|
return `${taskId ? `[task_id=${taskId}]` : ""}${body}`;
|
|
2751
2916
|
}
|
|
2752
2917
|
return createMiddleware({
|
|
2753
|
-
name: "
|
|
2754
|
-
stateSchema:
|
|
2918
|
+
name: "CompletionCallbackMiddleware",
|
|
2919
|
+
stateSchema: CompletionCallbackStateSchema,
|
|
2755
2920
|
async afterAgent(state, runtime) {
|
|
2756
|
-
|
|
2921
|
+
const callbackThreadId = state[CALLBACK_THREAD_ID_KEY];
|
|
2922
|
+
if (callbackThreadId == null) throw new Error(`Missing required state key '${CALLBACK_THREAD_ID_KEY}'`);
|
|
2923
|
+
const taskId = getTaskId(runtime);
|
|
2924
|
+
await sendNotification(callbackThreadId, formatNotification(`Completed. Result: ${extractLastMessage(state, typeof taskId === "string" ? taskId : void 0)}`, runtime));
|
|
2757
2925
|
},
|
|
2758
2926
|
async wrapModelCall(request, handler) {
|
|
2759
2927
|
try {
|
|
2760
2928
|
return await handler(request);
|
|
2761
2929
|
} catch (e) {
|
|
2762
|
-
const
|
|
2763
|
-
await sendNotification(
|
|
2930
|
+
const callbackThreadId = request.state[CALLBACK_THREAD_ID_KEY];
|
|
2931
|
+
if (typeof callbackThreadId === "string") await sendNotification(callbackThreadId, formatNotification("The agent encountered an error while calling the model.", request.runtime));
|
|
2764
2932
|
throw e;
|
|
2765
2933
|
}
|
|
2766
2934
|
}
|
|
@@ -2959,13 +3127,6 @@ function createSummarizationMiddleware(options) {
|
|
|
2959
3127
|
let sessionId = null;
|
|
2960
3128
|
let tokenEstimationMultiplier = 1;
|
|
2961
3129
|
/**
|
|
2962
|
-
* Resolve backend from instance or factory.
|
|
2963
|
-
*/
|
|
2964
|
-
function getBackend(state) {
|
|
2965
|
-
if (typeof backend === "function") return adaptBackendProtocol(backend({ state }));
|
|
2966
|
-
return adaptBackendProtocol(backend);
|
|
2967
|
-
}
|
|
2968
|
-
/**
|
|
2969
3130
|
* Get or create session ID for history file naming.
|
|
2970
3131
|
*/
|
|
2971
3132
|
function getSessionId(state) {
|
|
@@ -3297,15 +3458,17 @@ function createSummarizationMiddleware(options) {
|
|
|
3297
3458
|
*/
|
|
3298
3459
|
function buildSummaryMessage(summary, filePath) {
|
|
3299
3460
|
let content;
|
|
3300
|
-
if (filePath) content = `
|
|
3461
|
+
if (filePath) content = context`
|
|
3462
|
+
You are in the middle of a conversation that has been summarized.
|
|
3301
3463
|
|
|
3302
|
-
The full conversation history has been saved to ${filePath} should you need to refer back to it for details.
|
|
3464
|
+
The full conversation history has been saved to ${filePath} should you need to refer back to it for details.
|
|
3303
3465
|
|
|
3304
|
-
A condensed summary follows:
|
|
3466
|
+
A condensed summary follows:
|
|
3305
3467
|
|
|
3306
|
-
<summary>
|
|
3307
|
-
${summary}
|
|
3308
|
-
</summary
|
|
3468
|
+
<summary>
|
|
3469
|
+
${summary}
|
|
3470
|
+
</summary>
|
|
3471
|
+
`;
|
|
3309
3472
|
else content = `Here is a summary of the conversation to date:\n\n${summary}`;
|
|
3310
3473
|
return new HumanMessage({
|
|
3311
3474
|
content,
|
|
@@ -3331,7 +3494,7 @@ ${summary}
|
|
|
3331
3494
|
* the file path, and the state cutoff index.
|
|
3332
3495
|
*/
|
|
3333
3496
|
async function summarizeMessages(messagesToSummarize, resolvedModel, state, previousCutoffIndex, cutoffIndex) {
|
|
3334
|
-
const filePath = await offloadToBackend(
|
|
3497
|
+
const filePath = await offloadToBackend(await resolveBackend(backend, { state }), messagesToSummarize, state);
|
|
3335
3498
|
if (filePath === null) console.warn(`[SummarizationMiddleware] Backend offload failed during summarization. Proceeding with summary generation.`);
|
|
3336
3499
|
return {
|
|
3337
3500
|
summaryMessage: buildSummaryMessage(await createSummary(messagesToSummarize, resolvedModel), filePath),
|
|
@@ -3473,6 +3636,7 @@ const AsyncTaskSchema = z.object({
|
|
|
3473
3636
|
runId: z.string(),
|
|
3474
3637
|
status: z.string(),
|
|
3475
3638
|
createdAt: z.string(),
|
|
3639
|
+
description: z.string().optional(),
|
|
3476
3640
|
updatedAt: z.string().optional(),
|
|
3477
3641
|
checkedAt: z.string().optional()
|
|
3478
3642
|
});
|
|
@@ -3510,7 +3674,7 @@ function asyncTasksReducer(existing, update) {
|
|
|
3510
3674
|
* The `{available_agents}` placeholder is replaced at middleware creation
|
|
3511
3675
|
* time with a formatted list of configured async subagent names and descriptions.
|
|
3512
3676
|
*/
|
|
3513
|
-
const ASYNC_TASK_TOOL_DESCRIPTION = `Launch an async subagent on a remote
|
|
3677
|
+
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.
|
|
3514
3678
|
|
|
3515
3679
|
Available async agent types:
|
|
3516
3680
|
{available_agents}
|
|
@@ -3520,7 +3684,7 @@ Available async agent types:
|
|
|
3520
3684
|
2. Use \`check_async_task\` only when the user asks for a status update or result.
|
|
3521
3685
|
3. Use \`update_async_task\` to send new instructions to a running task.
|
|
3522
3686
|
4. Multiple async subagents can run concurrently — launch several and let them run in the background.
|
|
3523
|
-
5. The subagent runs on a remote
|
|
3687
|
+
5. The subagent runs on a remote server, so it has its own tools and capabilities.`;
|
|
3524
3688
|
/**
|
|
3525
3689
|
* Default system prompt appended to the main agent's system message when
|
|
3526
3690
|
* async subagent middleware is active.
|
|
@@ -3530,9 +3694,9 @@ Available async agent types:
|
|
|
3530
3694
|
* critical rules about polling behavior, and guidance on when to use async
|
|
3531
3695
|
* subagents vs. synchronous delegation.
|
|
3532
3696
|
*/
|
|
3533
|
-
const ASYNC_TASK_SYSTEM_PROMPT = `## Async subagents (remote
|
|
3697
|
+
const ASYNC_TASK_SYSTEM_PROMPT = `## Async subagents (remote servers)
|
|
3534
3698
|
|
|
3535
|
-
You have access to async subagent tools that launch background tasks on remote
|
|
3699
|
+
You have access to async subagent tools that launch background tasks on remote servers.
|
|
3536
3700
|
|
|
3537
3701
|
### Tools:
|
|
3538
3702
|
- \`start_async_task\`: Start a new background task. Returns a task ID immediately.
|
|
@@ -3572,6 +3736,19 @@ You have access to async subagent tools that launch background tasks on remote L
|
|
|
3572
3736
|
* When listing tasks, live-status fetches are skipped for tasks whose
|
|
3573
3737
|
* cached status is in this set, since they are guaranteed to be final.
|
|
3574
3738
|
*/
|
|
3739
|
+
/**
|
|
3740
|
+
* Names of the tools added by the async subagent middleware.
|
|
3741
|
+
*
|
|
3742
|
+
* Exported so `agent.ts` can include them in `BUILTIN_TOOL_NAMES` and
|
|
3743
|
+
* surface a `ConfigurationError` if a user-provided tool collides.
|
|
3744
|
+
*/
|
|
3745
|
+
const ASYNC_TASK_TOOL_NAMES = [
|
|
3746
|
+
"start_async_task",
|
|
3747
|
+
"check_async_task",
|
|
3748
|
+
"update_async_task",
|
|
3749
|
+
"cancel_async_task",
|
|
3750
|
+
"list_async_tasks"
|
|
3751
|
+
];
|
|
3575
3752
|
const TERMINAL_STATUSES = new Set([
|
|
3576
3753
|
"cancelled",
|
|
3577
3754
|
"success",
|
|
@@ -3663,8 +3840,11 @@ var ClientCache = class {
|
|
|
3663
3840
|
this.agents = agents;
|
|
3664
3841
|
}
|
|
3665
3842
|
/**
|
|
3666
|
-
* Build headers for a remote
|
|
3667
|
-
*
|
|
3843
|
+
* Build headers for a remote Agent Protocol server.
|
|
3844
|
+
*
|
|
3845
|
+
* Adds `x-auth-scheme: langsmith` by default unless already provided.
|
|
3846
|
+
* For self-hosted servers that don't require this header, it is typically
|
|
3847
|
+
* ignored. Override via the `headers` field on the AsyncSubAgent config.
|
|
3668
3848
|
*/
|
|
3669
3849
|
resolveHeaders(spec) {
|
|
3670
3850
|
const headers = { ...spec.headers || {} };
|
|
@@ -3697,6 +3877,20 @@ var ClientCache = class {
|
|
|
3697
3877
|
}
|
|
3698
3878
|
};
|
|
3699
3879
|
/**
|
|
3880
|
+
* Extract the callback thread ID from the tool runtime.
|
|
3881
|
+
*
|
|
3882
|
+
* The thread ID is included in the subagent's input state so the subagent
|
|
3883
|
+
* can notify the parent when it completes (via
|
|
3884
|
+
* `CompletionCallbackMiddleware`).
|
|
3885
|
+
*
|
|
3886
|
+
* @returns Object with `callbackThreadId` if available. Empty object otherwise.
|
|
3887
|
+
*/
|
|
3888
|
+
function extractCallbackContext(runtime) {
|
|
3889
|
+
const threadId = (runtime.config?.configurable)?.thread_id;
|
|
3890
|
+
if (typeof threadId === "string" && threadId) return { callbackThreadId: threadId };
|
|
3891
|
+
return {};
|
|
3892
|
+
}
|
|
3893
|
+
/**
|
|
3700
3894
|
* Build the `start_async_task` tool.
|
|
3701
3895
|
*
|
|
3702
3896
|
* Creates a thread on the remote server, starts a run, and returns a
|
|
@@ -3709,13 +3903,17 @@ function buildStartTool(agentMap, clients, toolDescription) {
|
|
|
3709
3903
|
return `Unknown async subagent type \`${input.agentName}\`. Available types: ${allowed}`;
|
|
3710
3904
|
}
|
|
3711
3905
|
const spec = agentMap[input.agentName];
|
|
3906
|
+
const callbackContext = extractCallbackContext(runtime);
|
|
3712
3907
|
try {
|
|
3713
3908
|
const client = clients.getClient(input.agentName);
|
|
3714
3909
|
const thread = await client.threads.create();
|
|
3715
|
-
const run = await client.runs.create(thread.thread_id, spec.graphId, { input: {
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3910
|
+
const run = await client.runs.create(thread.thread_id, spec.graphId, { input: {
|
|
3911
|
+
messages: [{
|
|
3912
|
+
role: "user",
|
|
3913
|
+
content: input.description
|
|
3914
|
+
}],
|
|
3915
|
+
...callbackContext
|
|
3916
|
+
} });
|
|
3719
3917
|
const taskId = thread.thread_id;
|
|
3720
3918
|
const task = {
|
|
3721
3919
|
taskId,
|
|
@@ -3723,7 +3921,8 @@ function buildStartTool(agentMap, clients, toolDescription) {
|
|
|
3723
3921
|
threadId: taskId,
|
|
3724
3922
|
runId: run.run_id,
|
|
3725
3923
|
status: "running",
|
|
3726
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3924
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3925
|
+
description: input.description
|
|
3727
3926
|
};
|
|
3728
3927
|
return new Command({ update: {
|
|
3729
3928
|
messages: [new ToolMessage({
|
|
@@ -3773,7 +3972,7 @@ function buildCheckTool(clients) {
|
|
|
3773
3972
|
runId: task.runId,
|
|
3774
3973
|
status: result.status,
|
|
3775
3974
|
createdAt: task.createdAt,
|
|
3776
|
-
updatedAt: task.updatedAt,
|
|
3975
|
+
updatedAt: result.status !== task.status ? (/* @__PURE__ */ new Date()).toISOString() : task.updatedAt,
|
|
3777
3976
|
checkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3778
3977
|
};
|
|
3779
3978
|
return new Command({ update: {
|
|
@@ -3817,6 +4016,7 @@ function buildUpdateTool(agentMap, clients) {
|
|
|
3817
4016
|
runId: run.run_id,
|
|
3818
4017
|
status: "running",
|
|
3819
4018
|
createdAt: tracked.createdAt,
|
|
4019
|
+
description: input.message,
|
|
3820
4020
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3821
4021
|
checkedAt: tracked.checkedAt
|
|
3822
4022
|
};
|
|
@@ -3862,7 +4062,7 @@ function buildCancelTool(clients) {
|
|
|
3862
4062
|
runId: tracked.runId,
|
|
3863
4063
|
status: "cancelled",
|
|
3864
4064
|
createdAt: tracked.createdAt,
|
|
3865
|
-
updatedAt:
|
|
4065
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3866
4066
|
checkedAt: tracked.checkedAt
|
|
3867
4067
|
};
|
|
3868
4068
|
return new Command({ update: {
|
|
@@ -3903,7 +4103,7 @@ function buildListTool(clients) {
|
|
|
3903
4103
|
runId: task.runId,
|
|
3904
4104
|
status,
|
|
3905
4105
|
createdAt: task.createdAt,
|
|
3906
|
-
updatedAt: task.updatedAt,
|
|
4106
|
+
updatedAt: status !== task.status ? (/* @__PURE__ */ new Date()).toISOString() : task.updatedAt,
|
|
3907
4107
|
checkedAt: task.checkedAt
|
|
3908
4108
|
};
|
|
3909
4109
|
}
|
|
@@ -3924,10 +4124,13 @@ function buildListTool(clients) {
|
|
|
3924
4124
|
* Create middleware that adds async subagent tools to an agent.
|
|
3925
4125
|
*
|
|
3926
4126
|
* Provides five tools for launching, checking, updating, cancelling, and
|
|
3927
|
-
* listing background tasks on remote
|
|
4127
|
+
* listing background tasks on remote Agent Protocol servers. Task state is
|
|
3928
4128
|
* persisted in the `asyncTasks` state channel so it survives
|
|
3929
4129
|
* context compaction.
|
|
3930
4130
|
*
|
|
4131
|
+
* Works with any Agent Protocol-compliant server — LangGraph Platform (managed)
|
|
4132
|
+
* or self-hosted (e.g. a Hono/Express server implementing the Agent Protocol spec).
|
|
4133
|
+
*
|
|
3931
4134
|
* @throws {Error} If no async subagents are provided or names are duplicated.
|
|
3932
4135
|
*
|
|
3933
4136
|
* @example
|
|
@@ -3936,7 +4139,7 @@ function buildListTool(clients) {
|
|
|
3936
4139
|
* asyncSubAgents: [{
|
|
3937
4140
|
* name: "researcher",
|
|
3938
4141
|
* description: "Research agent for deep analysis",
|
|
3939
|
-
* url: "https://my-
|
|
4142
|
+
* url: "https://my-agent-protocol-server.example.com",
|
|
3940
4143
|
* graphId: "research_agent",
|
|
3941
4144
|
* }],
|
|
3942
4145
|
* });
|
|
@@ -3983,6 +4186,9 @@ function createAsyncSubAgentMiddleware(options) {
|
|
|
3983
4186
|
}
|
|
3984
4187
|
//#endregion
|
|
3985
4188
|
//#region src/backends/store.ts
|
|
4189
|
+
/**
|
|
4190
|
+
* StoreBackend: Adapter for LangGraph's BaseStore (persistent, cross-thread).
|
|
4191
|
+
*/
|
|
3986
4192
|
const NAMESPACE_COMPONENT_RE = /^[A-Za-z0-9\-_.@+:~]+$/;
|
|
3987
4193
|
/**
|
|
3988
4194
|
* Validate a namespace array.
|
|
@@ -4018,35 +4224,54 @@ var StoreBackend = class {
|
|
|
4018
4224
|
stateAndStore;
|
|
4019
4225
|
_namespace;
|
|
4020
4226
|
fileFormat;
|
|
4021
|
-
constructor(
|
|
4022
|
-
|
|
4023
|
-
if (
|
|
4024
|
-
|
|
4227
|
+
constructor(stateAndStoreOrOptions, options) {
|
|
4228
|
+
let opts;
|
|
4229
|
+
if (stateAndStoreOrOptions != null && typeof stateAndStoreOrOptions === "object" && "state" in stateAndStoreOrOptions) {
|
|
4230
|
+
this.stateAndStore = stateAndStoreOrOptions;
|
|
4231
|
+
opts = options;
|
|
4232
|
+
} else {
|
|
4233
|
+
this.stateAndStore = void 0;
|
|
4234
|
+
opts = stateAndStoreOrOptions;
|
|
4235
|
+
}
|
|
4236
|
+
if (opts?.namespace) this._namespace = validateNamespace(opts.namespace);
|
|
4237
|
+
this.fileFormat = opts?.fileFormat ?? "v2";
|
|
4025
4238
|
}
|
|
4026
4239
|
/**
|
|
4027
|
-
* Get the
|
|
4240
|
+
* Get the BaseStore instance for persistent storage operations.
|
|
4241
|
+
*
|
|
4242
|
+
* In legacy mode, reads from the injected {@link StateAndStore}.
|
|
4243
|
+
* In zero-arg mode, retrieves the store from the LangGraph execution
|
|
4244
|
+
* context via {@link getLangGraphStore}.
|
|
4028
4245
|
*
|
|
4029
4246
|
* @returns BaseStore instance
|
|
4030
|
-
* @throws Error if no store is available
|
|
4247
|
+
* @throws Error if no store is available in either mode
|
|
4031
4248
|
*/
|
|
4032
4249
|
getStore() {
|
|
4033
|
-
|
|
4034
|
-
|
|
4250
|
+
if (this.stateAndStore) {
|
|
4251
|
+
const store = this.stateAndStore.store;
|
|
4252
|
+
if (!store) throw new Error("Store is required but not available in runtime");
|
|
4253
|
+
return store;
|
|
4254
|
+
}
|
|
4255
|
+
const store = getStore();
|
|
4256
|
+
if (!store) throw new Error("Store is required but not available in LangGraph execution context. Ensure the graph was configured with a store.");
|
|
4035
4257
|
return store;
|
|
4036
4258
|
}
|
|
4037
4259
|
/**
|
|
4038
4260
|
* Get the namespace for store operations.
|
|
4039
4261
|
*
|
|
4040
|
-
*
|
|
4041
|
-
*
|
|
4042
|
-
*
|
|
4043
|
-
* -
|
|
4044
|
-
*
|
|
4262
|
+
* Resolution order:
|
|
4263
|
+
* 1. Explicit namespace from constructor options (both modes)
|
|
4264
|
+
* 2. Legacy mode: `[assistantId, "filesystem"]` fallback from {@link StateAndStore}
|
|
4265
|
+
* 3. Zero-arg mode without namespace: `["filesystem"]` with a deprecation warning
|
|
4266
|
+
* nudging callers to pass an explicit namespace
|
|
4267
|
+
* 4. Legacy mode without assistantId: `["filesystem"]`
|
|
4045
4268
|
*/
|
|
4046
4269
|
getNamespace() {
|
|
4047
4270
|
if (this._namespace) return this._namespace;
|
|
4048
|
-
|
|
4049
|
-
|
|
4271
|
+
if (this.stateAndStore) {
|
|
4272
|
+
const assistantId = this.stateAndStore.assistantId;
|
|
4273
|
+
if (assistantId) return [assistantId, "filesystem"];
|
|
4274
|
+
}
|
|
4050
4275
|
return ["filesystem"];
|
|
4051
4276
|
}
|
|
4052
4277
|
/**
|
|
@@ -6055,9 +6280,9 @@ var LangSmithSandbox = class LangSmithSandbox extends BaseSandbox {
|
|
|
6055
6280
|
* ```
|
|
6056
6281
|
*/
|
|
6057
6282
|
static async create(options = {}) {
|
|
6058
|
-
const { templateName = "deepagents", apiKey = process.env.LANGSMITH_API_KEY, defaultTimeout } = options;
|
|
6283
|
+
const { templateName = "deepagents", apiKey = process.env.LANGSMITH_API_KEY, defaultTimeout, ...createSandboxOptions } = options;
|
|
6059
6284
|
return new LangSmithSandbox({
|
|
6060
|
-
sandbox: await new SandboxClient({ apiKey }).createSandbox(templateName),
|
|
6285
|
+
sandbox: await new SandboxClient({ apiKey }).createSandbox(templateName, createSandboxOptions),
|
|
6061
6286
|
defaultTimeout
|
|
6062
6287
|
});
|
|
6063
6288
|
}
|
|
@@ -6145,9 +6370,44 @@ function createCacheBreakpointMiddleware() {
|
|
|
6145
6370
|
}
|
|
6146
6371
|
//#endregion
|
|
6147
6372
|
//#region src/agent.ts
|
|
6148
|
-
const
|
|
6373
|
+
const BASE_AGENT_PROMPT = context`
|
|
6374
|
+
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.
|
|
6375
|
+
|
|
6376
|
+
## Core Behavior
|
|
6377
|
+
|
|
6378
|
+
- Be concise and direct. Don't over-explain unless asked.
|
|
6379
|
+
- NEVER add unnecessary preamble (\"Sure!\", \"Great question!\", \"I'll now...\").
|
|
6380
|
+
- Don't say \"I'll now do X\" — just do it.
|
|
6381
|
+
- If the request is ambiguous, ask questions before acting.
|
|
6382
|
+
- If asked how to approach something, explain first, then act.
|
|
6383
|
+
|
|
6384
|
+
## Professional Objectivity
|
|
6385
|
+
|
|
6386
|
+
- Prioritize accuracy over validating the user's beliefs
|
|
6387
|
+
- Disagree respectfully when the user is incorrect
|
|
6388
|
+
- Avoid unnecessary superlatives, praise, or emotional validation
|
|
6389
|
+
|
|
6390
|
+
## Doing Tasks
|
|
6391
|
+
|
|
6392
|
+
When the user asks you to do something:
|
|
6393
|
+
|
|
6394
|
+
1. **Understand first** — read relevant files, check existing patterns. Quick but thorough — gather enough evidence to start, then iterate.
|
|
6395
|
+
2. **Act** — implement the solution. Work quickly but accurately.
|
|
6396
|
+
3. **Verify** — check your work against what was asked, not against your own output. Your first attempt is rarely correct — iterate.
|
|
6397
|
+
|
|
6398
|
+
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.
|
|
6399
|
+
|
|
6400
|
+
**When things go wrong:**
|
|
6401
|
+
- If something fails repeatedly, stop and analyze *why* — don't keep retrying the same approach.
|
|
6402
|
+
- If you're blocked, tell the user what's wrong and ask for guidance.
|
|
6403
|
+
|
|
6404
|
+
## Progress Updates
|
|
6405
|
+
|
|
6406
|
+
For longer tasks, provide brief progress updates at reasonable intervals — a concise sentence recapping what you've done and what's next.
|
|
6407
|
+
`;
|
|
6149
6408
|
const BUILTIN_TOOL_NAMES = new Set([
|
|
6150
6409
|
...FILESYSTEM_TOOL_NAMES,
|
|
6410
|
+
...ASYNC_TASK_TOOL_NAMES,
|
|
6151
6411
|
"task",
|
|
6152
6412
|
"write_todos"
|
|
6153
6413
|
]);
|
|
@@ -6164,19 +6424,18 @@ function isAnthropicModel(model) {
|
|
|
6164
6424
|
return model.getName() === "ChatAnthropic";
|
|
6165
6425
|
}
|
|
6166
6426
|
/**
|
|
6167
|
-
* Create a Deep Agent
|
|
6427
|
+
* Create a Deep Agent.
|
|
6428
|
+
*
|
|
6429
|
+
* This is the main entry point for building a production-style agent with
|
|
6430
|
+
* deepagents. It gives you a strong default runtime (filesystem, tasks,
|
|
6431
|
+
* subagents, summarization) and lets you opt into skills, memory,
|
|
6432
|
+
* human-in-the-loop interrupts, async subagents, and custom middleware.
|
|
6168
6433
|
*
|
|
6169
|
-
*
|
|
6170
|
-
*
|
|
6171
|
-
* - Filesystem tools (createFilesystemMiddleware)
|
|
6172
|
-
* - Subagent delegation (createSubAgentMiddleware)
|
|
6173
|
-
* - Conversation summarization (createSummarizationMiddleware) with backend offloading
|
|
6174
|
-
* - Prompt caching (anthropicPromptCachingMiddleware)
|
|
6175
|
-
* - Tool call patching (createPatchToolCallsMiddleware)
|
|
6176
|
-
* - Human-in-the-loop (humanInTheLoopMiddleware) - optional
|
|
6434
|
+
* The runtime is intentionally opinionated: defaults work out of the box, and
|
|
6435
|
+
* when you customize behavior, the middleware ordering stays deterministic.
|
|
6177
6436
|
*
|
|
6178
6437
|
* @param params Configuration parameters for the agent
|
|
6179
|
-
* @returns
|
|
6438
|
+
* @returns Deep Agent instance with inferred state/response types
|
|
6180
6439
|
*
|
|
6181
6440
|
* @example
|
|
6182
6441
|
* ```typescript
|
|
@@ -6195,98 +6454,92 @@ function isAnthropicModel(model) {
|
|
|
6195
6454
|
* ```
|
|
6196
6455
|
*/
|
|
6197
6456
|
function createDeepAgent(params = {}) {
|
|
6198
|
-
const { model = "claude-sonnet-4-
|
|
6457
|
+
const { model = new ChatAnthropic("claude-sonnet-4-6"), tools = [], systemPrompt, middleware: customMiddleware = [], subagents = [], responseFormat, contextSchema, checkpointer, store, backend = (config) => new StateBackend(config), interruptOn, name, memory, skills } = params;
|
|
6199
6458
|
const collidingTools = tools.map((t) => t.name).filter((n) => typeof n === "string" && BUILTIN_TOOL_NAMES.has(n));
|
|
6200
6459
|
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");
|
|
6201
6460
|
const anthropicModel = isAnthropicModel(model);
|
|
6202
|
-
const
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
}] : [
|
|
6206
|
-
type: "text",
|
|
6207
|
-
text: BASE_PROMPT
|
|
6208
|
-
}, ...typeof systemPrompt.content === "string" ? [{
|
|
6209
|
-
type: "text",
|
|
6210
|
-
text: systemPrompt.content
|
|
6211
|
-
}] : systemPrompt.content] : [{
|
|
6212
|
-
type: "text",
|
|
6213
|
-
text: BASE_PROMPT
|
|
6214
|
-
}] });
|
|
6215
|
-
/**
|
|
6216
|
-
* Create backend configuration for filesystem middleware
|
|
6217
|
-
* If no backend is provided, use a factory that creates a StateBackend
|
|
6218
|
-
*/
|
|
6219
|
-
const filesystemBackend = backend ? backend : (config) => new StateBackend(config);
|
|
6220
|
-
/**
|
|
6221
|
-
* Skills middleware (created conditionally for runtime use)
|
|
6222
|
-
*/
|
|
6223
|
-
const skillsMiddlewareArray = skills != null && skills.length > 0 ? [createSkillsMiddleware({
|
|
6224
|
-
backend: filesystemBackend,
|
|
6225
|
-
sources: skills
|
|
6226
|
-
})] : [];
|
|
6227
|
-
/**
|
|
6228
|
-
* Memory middleware (created conditionally for runtime use)
|
|
6229
|
-
*/
|
|
6230
|
-
const memoryMiddlewareArray = memory != null && memory.length > 0 ? [createMemoryMiddleware({
|
|
6231
|
-
backend: filesystemBackend,
|
|
6232
|
-
sources: memory,
|
|
6233
|
-
addCacheControl: anthropicModel
|
|
6234
|
-
})] : [];
|
|
6235
|
-
/**
|
|
6236
|
-
* Split the unified subagents array into sync and async subagents.
|
|
6237
|
-
* AsyncSubAgents are identified by the presence of a `graphId` field.
|
|
6238
|
-
*/
|
|
6239
|
-
const syncSubAgents = subagents.filter((a) => !isAsyncSubAgent(a));
|
|
6240
|
-
const asyncSubAgents = subagents.filter((a) => isAsyncSubAgent(a));
|
|
6461
|
+
const cacheMiddleware = anthropicModel ? [anthropicPromptCachingMiddleware({
|
|
6462
|
+
unsupportedModelBehavior: "ignore",
|
|
6463
|
+
minMessagesToCache: 1
|
|
6464
|
+
}), createCacheBreakpointMiddleware()] : [];
|
|
6241
6465
|
/**
|
|
6242
6466
|
* Process subagents to add SkillsMiddleware for those with their own skills.
|
|
6243
6467
|
*
|
|
6244
6468
|
* Custom subagents do NOT inherit skills from the main agent by default.
|
|
6245
|
-
* Only the general-purpose subagent inherits the main agent's skills
|
|
6469
|
+
* Only the general-purpose subagent inherits the main agent's skills.
|
|
6246
6470
|
* If a custom subagent needs skills, it must specify its own `skills` array.
|
|
6247
6471
|
*/
|
|
6248
|
-
const
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
sources: subagent.skills ?? []
|
|
6265
|
-
});
|
|
6472
|
+
const normalizeSubagentSpec = (input) => {
|
|
6473
|
+
const subagentMiddleware = [
|
|
6474
|
+
todoListMiddleware(),
|
|
6475
|
+
createFilesystemMiddleware({ backend }),
|
|
6476
|
+
createSummarizationMiddleware({
|
|
6477
|
+
backend,
|
|
6478
|
+
model
|
|
6479
|
+
}),
|
|
6480
|
+
createPatchToolCallsMiddleware(),
|
|
6481
|
+
...input.skills != null && input.skills.length > 0 ? [createSkillsMiddleware({
|
|
6482
|
+
backend,
|
|
6483
|
+
sources: input.skills
|
|
6484
|
+
})] : [],
|
|
6485
|
+
...input.middleware ?? [],
|
|
6486
|
+
...cacheMiddleware
|
|
6487
|
+
];
|
|
6266
6488
|
return {
|
|
6267
|
-
...
|
|
6268
|
-
|
|
6489
|
+
...input,
|
|
6490
|
+
tools: input.tools ?? [],
|
|
6491
|
+
middleware: subagentMiddleware
|
|
6269
6492
|
};
|
|
6270
|
-
}
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6493
|
+
};
|
|
6494
|
+
const allSubagents = subagents;
|
|
6495
|
+
const asyncSubAgents = allSubagents.filter((item) => isAsyncSubAgent(item));
|
|
6496
|
+
const inlineSubagents = allSubagents.filter((item) => !isAsyncSubAgent(item)).map((item) => "runnable" in item ? item : normalizeSubagentSpec(item));
|
|
6497
|
+
if (!inlineSubagents.some((item) => item.name === GENERAL_PURPOSE_SUBAGENT["name"])) {
|
|
6498
|
+
const generalPurposeSpec = normalizeSubagentSpec({
|
|
6499
|
+
...GENERAL_PURPOSE_SUBAGENT,
|
|
6500
|
+
model,
|
|
6501
|
+
skills,
|
|
6502
|
+
tools
|
|
6503
|
+
});
|
|
6504
|
+
inlineSubagents.unshift(generalPurposeSpec);
|
|
6505
|
+
}
|
|
6506
|
+
const skillsMiddleware = skills != null && skills.length > 0 ? [createSkillsMiddleware({
|
|
6507
|
+
backend,
|
|
6508
|
+
sources: skills
|
|
6509
|
+
})] : [];
|
|
6510
|
+
const [todoMiddleware, fsMiddleware, subagentMiddleware, summarizationMiddleware, patchToolCallsMiddleware] = [
|
|
6282
6511
|
todoListMiddleware(),
|
|
6283
|
-
createFilesystemMiddleware({ backend
|
|
6512
|
+
createFilesystemMiddleware({ backend }),
|
|
6513
|
+
createSubAgentMiddleware({
|
|
6514
|
+
defaultModel: model,
|
|
6515
|
+
defaultTools: tools,
|
|
6516
|
+
defaultInterruptOn: interruptOn,
|
|
6517
|
+
subagents: inlineSubagents,
|
|
6518
|
+
generalPurposeAgent: false
|
|
6519
|
+
}),
|
|
6284
6520
|
createSummarizationMiddleware({
|
|
6285
6521
|
model,
|
|
6286
|
-
backend
|
|
6522
|
+
backend
|
|
6287
6523
|
}),
|
|
6288
6524
|
createPatchToolCallsMiddleware()
|
|
6289
6525
|
];
|
|
6526
|
+
const middleware = [
|
|
6527
|
+
todoMiddleware,
|
|
6528
|
+
...skillsMiddleware,
|
|
6529
|
+
fsMiddleware,
|
|
6530
|
+
subagentMiddleware,
|
|
6531
|
+
summarizationMiddleware,
|
|
6532
|
+
patchToolCallsMiddleware,
|
|
6533
|
+
...asyncSubAgents.length > 0 ? [createAsyncSubAgentMiddleware({ asyncSubAgents })] : [],
|
|
6534
|
+
...customMiddleware,
|
|
6535
|
+
...cacheMiddleware,
|
|
6536
|
+
...memory && memory.length > 0 ? [createMemoryMiddleware({
|
|
6537
|
+
backend,
|
|
6538
|
+
sources: memory,
|
|
6539
|
+
addCacheControl: anthropicModel
|
|
6540
|
+
})] : [],
|
|
6541
|
+
...interruptOn ? [humanInTheLoopMiddleware({ interruptOn })] : []
|
|
6542
|
+
];
|
|
6290
6543
|
/**
|
|
6291
6544
|
* Return as DeepAgent with proper DeepAgentTypeConfig
|
|
6292
6545
|
* - Response: InferStructuredResponse<TResponse> (unwraps ToolStrategy<T>/ProviderStrategy<T> → T)
|
|
@@ -6298,55 +6551,32 @@ function createDeepAgent(params = {}) {
|
|
|
6298
6551
|
*/
|
|
6299
6552
|
return createAgent({
|
|
6300
6553
|
model,
|
|
6301
|
-
systemPrompt:
|
|
6554
|
+
systemPrompt: typeof systemPrompt === "string" ? new SystemMessage({ contentBlocks: [{
|
|
6555
|
+
type: "text",
|
|
6556
|
+
text: systemPrompt
|
|
6557
|
+
}, {
|
|
6558
|
+
type: "text",
|
|
6559
|
+
text: BASE_AGENT_PROMPT
|
|
6560
|
+
}] }) : SystemMessage.isInstance(systemPrompt) ? new SystemMessage({ contentBlocks: [...systemPrompt.contentBlocks, {
|
|
6561
|
+
type: "text",
|
|
6562
|
+
text: BASE_AGENT_PROMPT
|
|
6563
|
+
}] }) : new SystemMessage({ contentBlocks: [{
|
|
6564
|
+
type: "text",
|
|
6565
|
+
text: BASE_AGENT_PROMPT
|
|
6566
|
+
}] }),
|
|
6302
6567
|
tools,
|
|
6303
|
-
middleware
|
|
6304
|
-
|
|
6305
|
-
todoListMiddleware(),
|
|
6306
|
-
createFilesystemMiddleware({ backend: filesystemBackend }),
|
|
6307
|
-
createSubAgentMiddleware({
|
|
6308
|
-
defaultModel: model,
|
|
6309
|
-
defaultTools: tools,
|
|
6310
|
-
defaultMiddleware: [...subagentMiddleware, ...anthropicModel ? [anthropicPromptCachingMiddleware({
|
|
6311
|
-
unsupportedModelBehavior: "ignore",
|
|
6312
|
-
minMessagesToCache: 1
|
|
6313
|
-
}), createCacheBreakpointMiddleware()] : []],
|
|
6314
|
-
generalPurposeMiddleware: [
|
|
6315
|
-
...subagentMiddleware,
|
|
6316
|
-
...skillsMiddlewareArray,
|
|
6317
|
-
...anthropicModel ? [anthropicPromptCachingMiddleware({
|
|
6318
|
-
unsupportedModelBehavior: "ignore",
|
|
6319
|
-
minMessagesToCache: 1
|
|
6320
|
-
}), createCacheBreakpointMiddleware()] : []
|
|
6321
|
-
],
|
|
6322
|
-
defaultInterruptOn: interruptOn,
|
|
6323
|
-
subagents: processedSubagents,
|
|
6324
|
-
generalPurposeAgent: true
|
|
6325
|
-
}),
|
|
6326
|
-
createSummarizationMiddleware({
|
|
6327
|
-
model,
|
|
6328
|
-
backend: filesystemBackend
|
|
6329
|
-
}),
|
|
6330
|
-
createPatchToolCallsMiddleware()
|
|
6331
|
-
],
|
|
6332
|
-
...skillsMiddlewareArray,
|
|
6333
|
-
...customMiddleware,
|
|
6334
|
-
...anthropicModel ? [anthropicPromptCachingMiddleware({
|
|
6335
|
-
unsupportedModelBehavior: "ignore",
|
|
6336
|
-
minMessagesToCache: 1
|
|
6337
|
-
}), createCacheBreakpointMiddleware()] : [],
|
|
6338
|
-
...memoryMiddlewareArray,
|
|
6339
|
-
...interruptOn ? [humanInTheLoopMiddleware({ interruptOn })] : [],
|
|
6340
|
-
...asyncSubAgents && asyncSubAgents.length > 0 ? [createAsyncSubAgentMiddleware({ asyncSubAgents })] : []
|
|
6341
|
-
],
|
|
6342
|
-
...responseFormat != null && { responseFormat },
|
|
6568
|
+
middleware,
|
|
6569
|
+
...responseFormat !== null && { responseFormat },
|
|
6343
6570
|
contextSchema,
|
|
6344
6571
|
checkpointer,
|
|
6345
6572
|
store,
|
|
6346
6573
|
name
|
|
6347
6574
|
}).withConfig({
|
|
6348
6575
|
recursionLimit: 1e4,
|
|
6349
|
-
metadata: {
|
|
6576
|
+
metadata: {
|
|
6577
|
+
ls_integration: "deepagents",
|
|
6578
|
+
lc_agent_name: name
|
|
6579
|
+
}
|
|
6350
6580
|
});
|
|
6351
6581
|
}
|
|
6352
6582
|
//#endregion
|
|
@@ -6860,6 +7090,6 @@ function listSkills(options) {
|
|
|
6860
7090
|
return Array.from(allSkills.values());
|
|
6861
7091
|
}
|
|
6862
7092
|
//#endregion
|
|
6863
|
-
export { BaseSandbox, CompositeBackend, ConfigurationError, DEFAULT_GENERAL_PURPOSE_DESCRIPTION, DEFAULT_SUBAGENT_PROMPT, FilesystemBackend, GENERAL_PURPOSE_SUBAGENT, LangSmithSandbox, LocalShellBackend, MAX_SKILL_DESCRIPTION_LENGTH, MAX_SKILL_FILE_SIZE, MAX_SKILL_NAME_LENGTH, SandboxError, StateBackend, StoreBackend, TASK_SYSTEM_PROMPT, adaptBackendProtocol, adaptSandboxProtocol, computeSummarizationDefaults, createAgentMemoryMiddleware, createAsyncSubAgentMiddleware,
|
|
7093
|
+
export { BaseSandbox, CompositeBackend, ConfigurationError, DEFAULT_GENERAL_PURPOSE_DESCRIPTION, DEFAULT_SUBAGENT_PROMPT, FilesystemBackend, GENERAL_PURPOSE_SUBAGENT, LangSmithSandbox, LocalShellBackend, MAX_SKILL_DESCRIPTION_LENGTH, MAX_SKILL_FILE_SIZE, MAX_SKILL_NAME_LENGTH, SandboxError, StateBackend, StoreBackend, TASK_SYSTEM_PROMPT, adaptBackendProtocol, adaptSandboxProtocol, computeSummarizationDefaults, createAgentMemoryMiddleware, createAsyncSubAgentMiddleware, createCompletionCallbackMiddleware, createDeepAgent, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSettings, createSkillsMiddleware, createSubAgentMiddleware, createSummarizationMiddleware, filesValue, findProjectRoot, isAsyncSubAgent, isSandboxBackend, isSandboxProtocol, listSkills, parseSkillMetadata, resolveBackend };
|
|
6864
7094
|
|
|
6865
7095
|
//# sourceMappingURL=index.js.map
|