deepagents 1.14.2 → 1.15.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/{agent-3XynVB9Z.d.cts → agent-FG1QOMBi.d.cts} +188 -15
- package/dist/{agent-BKShWAoi.d.ts → agent-rhzpV1Ew.d.ts} +192 -19
- package/dist/browser.cjs +2 -1
- package/dist/browser.d.cts +2 -2
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +2 -2
- package/dist/index.cjs +3 -2
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/{langsmith-CjOWZAS2.cjs → langsmith-DMOexB11.cjs} +768 -139
- package/dist/langsmith-DMOexB11.cjs.map +1 -0
- package/dist/{langsmith--RbtjNdQ.js → langsmith-v_uskil7.js} +758 -141
- package/dist/langsmith-v_uskil7.js.map +1 -0
- package/dist/node.cjs +3 -2
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +3 -3
- package/dist/{src-YqBMARBL.js → src-DksURH8I.js} +68 -2
- package/dist/src-DksURH8I.js.map +1 -0
- package/dist/{src-BuYhtaIW.cjs → src-L1errMkM.cjs} +68 -2
- package/dist/src-L1errMkM.cjs.map +1 -0
- package/package.json +13 -3
- package/dist/langsmith--RbtjNdQ.js.map +0 -1
- package/dist/langsmith-CjOWZAS2.cjs.map +0 -1
- package/dist/src-BuYhtaIW.cjs.map +0 -1
- package/dist/src-YqBMARBL.js.map +0 -1
|
@@ -33,9 +33,30 @@ zod = __toESM(zod, 1);
|
|
|
33
33
|
let langchain_chat_models_universal = require("langchain/chat_models/universal");
|
|
34
34
|
let yaml = require("yaml");
|
|
35
35
|
yaml = __toESM(yaml, 1);
|
|
36
|
+
let _langchain_core_runnables = require("@langchain/core/runnables");
|
|
37
|
+
let _langchain_core_tools = require("@langchain/core/tools");
|
|
38
|
+
let _langchain_core_language_models_base = require("@langchain/core/language_models/base");
|
|
39
|
+
let _langchain_core_utils_function_calling = require("@langchain/core/utils/function_calling");
|
|
40
|
+
let _langchain_core_utils_json_schema = require("@langchain/core/utils/json_schema");
|
|
41
|
+
let _langchain_core_utils_types = require("@langchain/core/utils/types");
|
|
36
42
|
let _langchain_langgraph_sdk = require("@langchain/langgraph-sdk");
|
|
37
43
|
let langsmith = require("langsmith");
|
|
38
44
|
let langsmith_experimental_sandbox = require("langsmith/experimental/sandbox");
|
|
45
|
+
//#region src/backends/realpath.ts
|
|
46
|
+
const REALPATH = Symbol.for("deepagents.realpath");
|
|
47
|
+
function getRealpath(backend) {
|
|
48
|
+
if (typeof backend !== "object" || backend === null) return;
|
|
49
|
+
const fn = backend[REALPATH];
|
|
50
|
+
return typeof fn === "function" ? fn : void 0;
|
|
51
|
+
}
|
|
52
|
+
function setRealpath(target, fn) {
|
|
53
|
+
Object.defineProperty(target, REALPATH, {
|
|
54
|
+
value: fn,
|
|
55
|
+
enumerable: false,
|
|
56
|
+
configurable: true
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
39
60
|
//#region src/backends/utils.ts
|
|
40
61
|
/**
|
|
41
62
|
* Shared utility functions for memory backend implementations.
|
|
@@ -667,6 +688,8 @@ function adaptBackendProtocol(backend) {
|
|
|
667
688
|
configurable: true
|
|
668
689
|
});
|
|
669
690
|
}
|
|
691
|
+
const realpath = getRealpath(backend);
|
|
692
|
+
if (realpath !== void 0) setRealpath(adapted, realpath);
|
|
670
693
|
const resolveBackendForPath = backend.resolveBackendForPath;
|
|
671
694
|
if (typeof resolveBackendForPath === "function") Object.defineProperty(adapted, "resolveBackendForPath", {
|
|
672
695
|
value: (path) => resolveBackendForPath.call(backend, path),
|
|
@@ -1603,11 +1626,13 @@ function validatePermissionPaths(permissions) {
|
|
|
1603
1626
|
* - Non-absolute paths (must start with `/`)
|
|
1604
1627
|
* - Paths containing `..`
|
|
1605
1628
|
* - Paths containing `~`
|
|
1629
|
+
*
|
|
1630
|
+
* Empty and `.` segments are dropped (`/a/./b` and `/a//b` become `/a/b`).
|
|
1606
1631
|
*/
|
|
1607
1632
|
function validatePath(raw) {
|
|
1608
1633
|
if (typeof raw !== "string" || raw.length === 0) throw new Error("path must be a non-empty string");
|
|
1609
1634
|
if (!raw.startsWith("/")) throw new Error(`path must be absolute: ${JSON.stringify(raw)}`);
|
|
1610
|
-
const segments = raw.split("/").filter((s) => s.length > 0);
|
|
1635
|
+
const segments = raw.split("/").filter((s) => s.length > 0 && s !== ".");
|
|
1611
1636
|
if (segments.includes("..")) throw new Error(`path must not contain "..": ${JSON.stringify(raw)}`);
|
|
1612
1637
|
if (segments.includes("~")) throw new Error(`path must not contain "~": ${JSON.stringify(raw)}`);
|
|
1613
1638
|
return `/${segments.join("/")}`;
|
|
@@ -1657,14 +1682,15 @@ var CompositeBackend = class {
|
|
|
1657
1682
|
sortedRoutes;
|
|
1658
1683
|
constructor(defaultBackend, routes) {
|
|
1659
1684
|
this.default = isSandboxProtocol(defaultBackend) ? adaptSandboxProtocol(defaultBackend) : adaptBackendProtocol(defaultBackend);
|
|
1660
|
-
this.routes = Object.fromEntries(Object.entries(routes).map(([k, v]) => [k
|
|
1685
|
+
this.routes = Object.fromEntries(Object.entries(routes).map(([k, v]) => [k.endsWith("/") ? k : `${k}/`, isSandboxProtocol(v) ? adaptSandboxProtocol(v) : adaptBackendProtocol(v)]));
|
|
1661
1686
|
this.sortedRoutes = Object.entries(this.routes).sort((a, b) => b[0].length - a[0].length);
|
|
1687
|
+
setRealpath(this, (path) => this.resolveRealPath(path));
|
|
1662
1688
|
}
|
|
1663
1689
|
/** Delegates to default backend's id if it is a sandbox, otherwise empty string. */
|
|
1664
1690
|
get id() {
|
|
1665
1691
|
return isSandboxBackend(this.default) ? this.default.id : "";
|
|
1666
1692
|
}
|
|
1667
|
-
/** Route prefixes registered on this backend (e.g. `["/workspace"]`). */
|
|
1693
|
+
/** Route prefixes registered on this backend, normalized to a trailing slash (e.g. `["/workspace/"]`). */
|
|
1668
1694
|
get routePrefixes() {
|
|
1669
1695
|
return Object.keys(this.routes);
|
|
1670
1696
|
}
|
|
@@ -1677,11 +1703,29 @@ var CompositeBackend = class {
|
|
|
1677
1703
|
static isInstance(backend) {
|
|
1678
1704
|
return typeof backend === "object" && backend !== null && Array.isArray(backend.routePrefixes);
|
|
1679
1705
|
}
|
|
1680
|
-
/**
|
|
1706
|
+
/**
|
|
1707
|
+
* Returns the backend for `path`, with the matched prefix stripped (`/foo` → `/`).
|
|
1708
|
+
* Routes match on path-segment boundaries: `/foo` matches `/foo/bar` but not `/foobar`.
|
|
1709
|
+
*/
|
|
1681
1710
|
resolveBackendForPath(path) {
|
|
1682
1711
|
return this.getBackendAndKey(path);
|
|
1683
1712
|
}
|
|
1684
1713
|
/**
|
|
1714
|
+
* Resolve symlinks via the backend that owns `path`, re-adding the route
|
|
1715
|
+
* prefix. Returns `path` unchanged when that backend has no realpath hook.
|
|
1716
|
+
*/
|
|
1717
|
+
async resolveRealPath(path) {
|
|
1718
|
+
for (const [routePrefix, backend] of this.sortedRoutes) if (this.isPathWithinRoute(path, routePrefix)) {
|
|
1719
|
+
const realpath = getRealpath(backend);
|
|
1720
|
+
if (realpath === void 0) return path;
|
|
1721
|
+
const suffix = path.substring(routePrefix.length);
|
|
1722
|
+
const resolved = await realpath(suffix ? "/" + suffix : "/");
|
|
1723
|
+
return routePrefix.slice(0, -1) + resolved;
|
|
1724
|
+
}
|
|
1725
|
+
const realpath = getRealpath(this.default);
|
|
1726
|
+
return realpath === void 0 ? path : await realpath(path);
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1685
1729
|
* Determine which backend handles this key and strip prefix.
|
|
1686
1730
|
*
|
|
1687
1731
|
* @param key - Original file path
|
|
@@ -2448,25 +2492,42 @@ function getErrorMessage$1(error) {
|
|
|
2448
2492
|
return String(error);
|
|
2449
2493
|
}
|
|
2450
2494
|
/**
|
|
2451
|
-
*
|
|
2452
|
-
*
|
|
2495
|
+
* Resolve `canonical` through the backend's realpath hook, when it has one, and
|
|
2496
|
+
* re-validate the result so it is canonical for rule matching. Returns
|
|
2497
|
+
* `canonical` unchanged for backends without path aliasing.
|
|
2498
|
+
*/
|
|
2499
|
+
async function resolveRealPath(backend, canonical) {
|
|
2500
|
+
const realpath = getRealpath(backend);
|
|
2501
|
+
return realpath ? validatePath(await realpath(canonical)) : canonical;
|
|
2502
|
+
}
|
|
2503
|
+
/**
|
|
2504
|
+
* Check whether `path` is permitted under `rules` for `operation`.
|
|
2453
2505
|
*
|
|
2454
|
-
*
|
|
2455
|
-
*
|
|
2456
|
-
*
|
|
2457
|
-
*
|
|
2506
|
+
* Returns the canonical path to hand the backend, or an error string to
|
|
2507
|
+
* surface to the model. The decision covers every form of the target: the
|
|
2508
|
+
* lexical canonical path (dot and empty segments dropped) and, when the
|
|
2509
|
+
* backend has a realpath hook, the symlink-resolved path it would actually
|
|
2510
|
+
* touch. A deny on either blocks the call.
|
|
2511
|
+
*
|
|
2512
|
+
* Never throws: an invalid path (non-absolute, or containing `..` or `~`), one
|
|
2513
|
+
* the backend cannot resolve, or a denied one is a recoverable tool error, not
|
|
2514
|
+
* a fatal run-ending one. With no rules configured, `path` is returned as-is.
|
|
2458
2515
|
*
|
|
2459
2516
|
* @internal
|
|
2460
2517
|
*/
|
|
2461
|
-
function checkPermission(rules, operation, path) {
|
|
2462
|
-
if (rules.length === 0) return;
|
|
2518
|
+
async function checkPermission(backend, rules, operation, path) {
|
|
2519
|
+
if (rules.length === 0) return { path };
|
|
2463
2520
|
let canonical;
|
|
2521
|
+
let real;
|
|
2464
2522
|
try {
|
|
2465
2523
|
canonical = validatePath(path);
|
|
2524
|
+
if (decidePathAccess(rules, operation, canonical) === "deny") return { error: `Error: permission denied for ${operation} on ${canonical}` };
|
|
2525
|
+
real = await resolveRealPath(backend, canonical);
|
|
2466
2526
|
} catch (error) {
|
|
2467
|
-
return `Error: ${getErrorMessage$1(error)}
|
|
2527
|
+
return { error: `Error: ${getErrorMessage$1(error)}` };
|
|
2468
2528
|
}
|
|
2469
|
-
if (decidePathAccess(rules, operation,
|
|
2529
|
+
if (real !== canonical && decidePathAccess(rules, operation, real) === "deny") return { error: `Error: permission denied for ${operation} on ${canonical} (resolves to ${real})` };
|
|
2530
|
+
return { path: canonical };
|
|
2470
2531
|
}
|
|
2471
2532
|
/**
|
|
2472
2533
|
* Build an error {@link ToolMessage} for a rejected or denied path. Returning a
|
|
@@ -2639,27 +2700,56 @@ function parentPath(path) {
|
|
|
2639
2700
|
if (parts.length <= 1) return "/";
|
|
2640
2701
|
return `/${parts.slice(0, -1).join("/")}`;
|
|
2641
2702
|
}
|
|
2703
|
+
/**
|
|
2704
|
+
* Resolve the path a delete of `target` would actually remove. Backends unlink
|
|
2705
|
+
* a symlink leaf rather than following it, so only the parent is resolved.
|
|
2706
|
+
*/
|
|
2707
|
+
async function resolveDeleteRealPath(backend, target) {
|
|
2708
|
+
const parts = posixParts(target);
|
|
2709
|
+
if (parts.length === 0) return resolveRealPath(backend, target);
|
|
2710
|
+
return validatePath(`${await resolveRealPath(backend, parentPath(target))}/${parts[parts.length - 1]}`);
|
|
2711
|
+
}
|
|
2642
2712
|
function supportsDelete(backend) {
|
|
2643
2713
|
return typeof backend.delete === "function";
|
|
2644
2714
|
}
|
|
2645
2715
|
/**
|
|
2646
2716
|
* Filter a list of filesystem entries to those the rules permit.
|
|
2647
2717
|
*
|
|
2648
|
-
* `getPath` extracts the absolute path from each entry.
|
|
2649
|
-
*
|
|
2650
|
-
*
|
|
2718
|
+
* `getPath` extracts the absolute path from each entry. Each entry is checked
|
|
2719
|
+
* on its canonical path and, when the backend has a realpath hook, on its
|
|
2720
|
+
* symlink-resolved path. Entries with unparsable paths are included; entries
|
|
2721
|
+
* whose path cannot be resolved are dropped.
|
|
2722
|
+
* Returns the original array unchanged when `rules` is empty.
|
|
2651
2723
|
*
|
|
2652
2724
|
* @internal
|
|
2653
2725
|
*/
|
|
2654
|
-
function filterByPermissions(entries, rules, operation, getPath) {
|
|
2726
|
+
async function filterByPermissions(backend, entries, rules, operation, getPath) {
|
|
2655
2727
|
if (rules.length === 0) return entries;
|
|
2656
|
-
|
|
2728
|
+
const decisions = /* @__PURE__ */ new Map();
|
|
2729
|
+
const isPermitted = async (rawPath) => {
|
|
2730
|
+
let canonical;
|
|
2657
2731
|
try {
|
|
2658
|
-
|
|
2732
|
+
canonical = validatePath(rawPath);
|
|
2659
2733
|
} catch {
|
|
2660
2734
|
return true;
|
|
2661
2735
|
}
|
|
2662
|
-
|
|
2736
|
+
if (decidePathAccess(rules, operation, canonical) === "deny") return false;
|
|
2737
|
+
try {
|
|
2738
|
+
return decidePathAccess(rules, operation, await resolveRealPath(backend, canonical)) !== "deny";
|
|
2739
|
+
} catch {
|
|
2740
|
+
return false;
|
|
2741
|
+
}
|
|
2742
|
+
};
|
|
2743
|
+
const permitted = await Promise.all(entries.map((entry) => {
|
|
2744
|
+
const rawPath = getPath(entry);
|
|
2745
|
+
let decision = decisions.get(rawPath);
|
|
2746
|
+
if (decision === void 0) {
|
|
2747
|
+
decision = isPermitted(rawPath);
|
|
2748
|
+
decisions.set(rawPath, decision);
|
|
2749
|
+
}
|
|
2750
|
+
return decision;
|
|
2751
|
+
}));
|
|
2752
|
+
return entries.filter((_, index) => permitted[index]);
|
|
2663
2753
|
}
|
|
2664
2754
|
const LS_TOOL_DESCRIPTION = langchain.context`
|
|
2665
2755
|
Lists all files in a directory.
|
|
@@ -2747,13 +2837,13 @@ function getExecuteToolDescription(hasGrep, hasGlob) {
|
|
|
2747
2837
|
function createLsTool(backend, options) {
|
|
2748
2838
|
const { customDescription, permissions } = options;
|
|
2749
2839
|
return (0, langchain.tool)(async (input, runtime) => {
|
|
2750
|
-
const permissionError = checkPermission(permissions, "read", input.path ?? "/");
|
|
2751
|
-
if (permissionError !== void 0) return toolError(runtime, "ls", permissionError);
|
|
2752
2840
|
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
2753
2841
|
const path = input.path || "/";
|
|
2754
|
-
const
|
|
2842
|
+
const permission = await checkPermission(resolvedBackend, permissions, "read", path);
|
|
2843
|
+
if (permission.error !== void 0) return toolError(runtime, "ls", permission.error);
|
|
2844
|
+
const lsResult = await resolvedBackend.ls(permission.path);
|
|
2755
2845
|
if (lsResult.error) return `Error listing files: ${lsResult.error}`;
|
|
2756
|
-
const infos = filterByPermissions(lsResult.files ?? [], permissions, "read", (info) => info.path);
|
|
2846
|
+
const infos = await filterByPermissions(resolvedBackend, lsResult.files ?? [], permissions, "read", (info) => info.path);
|
|
2757
2847
|
if (infos.length === 0) return `No files found in ${path}`;
|
|
2758
2848
|
const lines = [];
|
|
2759
2849
|
for (const info of infos) if (info.is_dir) lines.push(`${info.path} (directory)`);
|
|
@@ -2776,23 +2866,17 @@ function createLsTool(backend, options) {
|
|
|
2776
2866
|
function createReadFileTool(backend, options) {
|
|
2777
2867
|
const { customDescription, toolTokenLimitBeforeEvict, permissions } = options;
|
|
2778
2868
|
return (0, langchain.tool)(async (input, runtime) => {
|
|
2779
|
-
const permissionError = checkPermission(permissions, "read", input.file_path);
|
|
2780
|
-
if (permissionError !== void 0) return toolError(runtime, "read_file", permissionError);
|
|
2781
2869
|
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
2870
|
+
const permission = await checkPermission(resolvedBackend, permissions, "read", input.file_path);
|
|
2871
|
+
if (permission.error !== void 0) return toolError(runtime, "read_file", permission.error);
|
|
2782
2872
|
const { file_path, offset: requestedOffset = 0, limit: requestedLimit = 100 } = input;
|
|
2783
2873
|
const { offset, limit } = normalizeReadPagination(requestedOffset, requestedLimit);
|
|
2784
|
-
const readResult = await resolvedBackend.read(
|
|
2785
|
-
if (readResult.error) return
|
|
2786
|
-
type: "text",
|
|
2787
|
-
text: `Error: ${readResult.error}`
|
|
2788
|
-
}];
|
|
2874
|
+
const readResult = await resolvedBackend.read(permission.path, offset, limit);
|
|
2875
|
+
if (readResult.error) return toolError(runtime, "read_file", `Error: ${readResult.error}`);
|
|
2789
2876
|
const mimeType = readResult.mimeType ?? getMimeType(file_path);
|
|
2790
2877
|
if (!isTextMimeType(mimeType)) {
|
|
2791
2878
|
const binaryContent = readResult.content;
|
|
2792
|
-
if (!binaryContent) return
|
|
2793
|
-
type: "text",
|
|
2794
|
-
text: `Error: expected binary content for '${file_path}'`
|
|
2795
|
-
}];
|
|
2879
|
+
if (!binaryContent) return toolError(runtime, "read_file", `Error: expected binary content for '${file_path}'`);
|
|
2796
2880
|
let base64Data;
|
|
2797
2881
|
if (typeof binaryContent === "string") base64Data = binaryContent;
|
|
2798
2882
|
else if (ArrayBuffer.isView(binaryContent)) base64Data = Buffer.from(binaryContent).toString("base64");
|
|
@@ -2801,10 +2885,7 @@ function createReadFileTool(backend, options) {
|
|
|
2801
2885
|
base64Data = Buffer.from(new Uint8Array(values)).toString("base64");
|
|
2802
2886
|
}
|
|
2803
2887
|
const sizeBytes = Math.ceil(base64Data.length * 3 / 4);
|
|
2804
|
-
if (sizeBytes > 10485760) return
|
|
2805
|
-
type: "text",
|
|
2806
|
-
text: `Error: file too large to read (${Math.round(sizeBytes / 1048576)}MB exceeds ${MAX_BINARY_READ_SIZE_BYTES / 1048576}MB limit for binary files)`
|
|
2807
|
-
}];
|
|
2888
|
+
if (sizeBytes > 10485760) return toolError(runtime, "read_file", `Error: file too large to read (${Math.round(sizeBytes / 1048576)}MB exceeds ${MAX_BINARY_READ_SIZE_BYTES / 1048576}MB limit for binary files)`);
|
|
2808
2889
|
if (mimeType.startsWith("image/")) return [{
|
|
2809
2890
|
type: "image",
|
|
2810
2891
|
mimeType,
|
|
@@ -2870,11 +2951,11 @@ function createReadFileTool(backend, options) {
|
|
|
2870
2951
|
function createWriteFileTool(backend, options) {
|
|
2871
2952
|
const { customDescription, permissions } = options;
|
|
2872
2953
|
return (0, langchain.tool)(async (input, runtime) => {
|
|
2873
|
-
const permissionError = checkPermission(permissions, "write", input.file_path);
|
|
2874
|
-
if (permissionError !== void 0) return toolError(runtime, "write_file", permissionError);
|
|
2875
2954
|
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
2955
|
+
const permission = await checkPermission(resolvedBackend, permissions, "write", input.file_path);
|
|
2956
|
+
if (permission.error !== void 0) return toolError(runtime, "write_file", permission.error);
|
|
2876
2957
|
const { file_path, content } = input;
|
|
2877
|
-
const result = await resolvedBackend.write(
|
|
2958
|
+
const result = await resolvedBackend.write(permission.path, content);
|
|
2878
2959
|
if (result.error) return result.error;
|
|
2879
2960
|
const message = new langchain.ToolMessage({
|
|
2880
2961
|
content: `Successfully wrote to '${file_path}'`,
|
|
@@ -2902,11 +2983,11 @@ function createWriteFileTool(backend, options) {
|
|
|
2902
2983
|
function createEditFileTool(backend, options) {
|
|
2903
2984
|
const { customDescription, permissions } = options;
|
|
2904
2985
|
return (0, langchain.tool)(async (input, runtime) => {
|
|
2905
|
-
const permissionError = checkPermission(permissions, "write", input.file_path);
|
|
2906
|
-
if (permissionError !== void 0) return toolError(runtime, "edit_file", permissionError);
|
|
2907
2986
|
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
2987
|
+
const permission = await checkPermission(resolvedBackend, permissions, "write", input.file_path);
|
|
2988
|
+
if (permission.error !== void 0) return toolError(runtime, "edit_file", permission.error);
|
|
2908
2989
|
const { file_path, old_string, new_string, replace_all = false } = input;
|
|
2909
|
-
const result = await resolvedBackend.edit(
|
|
2990
|
+
const result = await resolvedBackend.edit(permission.path, old_string, new_string, replace_all);
|
|
2910
2991
|
if (result.error) return result.error;
|
|
2911
2992
|
const message = new langchain.ToolMessage({
|
|
2912
2993
|
content: `Successfully replaced ${result.occurrences} occurrence(s) in '${file_path}'`,
|
|
@@ -2946,6 +3027,17 @@ function createDeleteTool(backend, options) {
|
|
|
2946
3027
|
const hasDescendants = await deleteTargetMayHaveDescendants(resolvedBackend, validatedPath, permissions.length > 0);
|
|
2947
3028
|
const denyingPatterns = findDeleteDenyPatterns(permissions, validatedPath, hasDescendants);
|
|
2948
3029
|
if (denyingPatterns.length > 0) return toolError(runtime, "delete", `Error: permission denied for write on ${validatedPath} (matches deny rule(s): ${denyingPatterns.join(", ")})`);
|
|
3030
|
+
if (permissions.length > 0) {
|
|
3031
|
+
let realTarget;
|
|
3032
|
+
try {
|
|
3033
|
+
realTarget = await resolveDeleteRealPath(resolvedBackend, validatedPath);
|
|
3034
|
+
} catch (error) {
|
|
3035
|
+
return toolError(runtime, "delete", `Error: ${getErrorMessage$1(error)}`);
|
|
3036
|
+
}
|
|
3037
|
+
if (realTarget !== validatedPath) {
|
|
3038
|
+
if (findDeleteDenyPatterns(permissions, realTarget, hasDescendants).length > 0) return toolError(runtime, "delete", `Error: permission denied for write on ${validatedPath} (resolves to ${realTarget})`);
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
2949
3041
|
if (!supportsDelete(resolvedBackend)) return toolError(runtime, "delete", `Error: deletion is not available for '${validatedPath}'.`);
|
|
2950
3042
|
const result = await resolvedBackend.delete(validatedPath);
|
|
2951
3043
|
if (result.error) return toolError(runtime, "delete", result.error);
|
|
@@ -2972,13 +3064,13 @@ function createDeleteTool(backend, options) {
|
|
|
2972
3064
|
function createGlobTool(backend, options) {
|
|
2973
3065
|
const { customDescription, permissions } = options;
|
|
2974
3066
|
return (0, langchain.tool)(async (input, runtime) => {
|
|
2975
|
-
const permissionError = checkPermission(permissions, "read", input.path ?? "/");
|
|
2976
|
-
if (permissionError !== void 0) return toolError(runtime, "glob", permissionError);
|
|
2977
3067
|
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
2978
|
-
const { pattern
|
|
2979
|
-
const
|
|
3068
|
+
const { pattern } = input;
|
|
3069
|
+
const permission = await checkPermission(resolvedBackend, permissions, "read", input.path ?? "/");
|
|
3070
|
+
if (permission.error !== void 0) return toolError(runtime, "glob", permission.error);
|
|
3071
|
+
const globResult = await resolvedBackend.glob(pattern, input.path === void 0 ? void 0 : permission.path);
|
|
2980
3072
|
if (globResult.error) return `Error finding files: ${globResult.error}`;
|
|
2981
|
-
const infos = filterByPermissions(globResult.files ?? [], permissions, "read", (info) => info.path);
|
|
3073
|
+
const infos = await filterByPermissions(resolvedBackend, globResult.files ?? [], permissions, "read", (info) => info.path);
|
|
2982
3074
|
if (infos.length === 0) return `No files found matching pattern '${pattern}'`;
|
|
2983
3075
|
const result = truncateIfTooLong(infos.map((info) => info.path));
|
|
2984
3076
|
if (Array.isArray(result)) return result.join("\n");
|
|
@@ -2998,14 +3090,14 @@ function createGlobTool(backend, options) {
|
|
|
2998
3090
|
function createGrepTool(backend, options) {
|
|
2999
3091
|
const { customDescription, permissions, includeExecution, grepMaxCount } = options;
|
|
3000
3092
|
return (0, langchain.tool)(async (input, runtime) => {
|
|
3001
|
-
const permissionError = checkPermission(permissions, "read", input.path ?? "/");
|
|
3002
|
-
if (permissionError !== void 0) return toolError(runtime, "grep", permissionError);
|
|
3003
3093
|
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
3004
3094
|
const { pattern, path = "/", glob = null, output_mode = "content" } = input;
|
|
3095
|
+
const permission = await checkPermission(resolvedBackend, permissions, "read", path);
|
|
3096
|
+
if (permission.error !== void 0) return toolError(runtime, "grep", permission.error);
|
|
3005
3097
|
const maxCount = input.max_count ?? grepMaxCount;
|
|
3006
|
-
const result = await resolvedBackend.grep(pattern, path, glob, maxCount);
|
|
3098
|
+
const result = await resolvedBackend.grep(pattern, permission.path, glob, maxCount);
|
|
3007
3099
|
if (result.error) return result.error;
|
|
3008
|
-
const matches = filterByPermissions(result.matches ?? [], permissions, "read", (m) => m.path);
|
|
3100
|
+
const matches = await filterByPermissions(resolvedBackend, result.matches ?? [], permissions, "read", (m) => m.path);
|
|
3009
3101
|
if (matches.length === 0) return `No matches found for pattern '${pattern}'`;
|
|
3010
3102
|
const truncated = truncateIfTooLong(formatGrepMatches(matches, output_mode));
|
|
3011
3103
|
let content = typeof truncated === "string" ? truncated : truncated.join("\n");
|
|
@@ -4197,10 +4289,12 @@ const EXCLUDED_STATE_KEYS = [
|
|
|
4197
4289
|
"todos",
|
|
4198
4290
|
"structuredResponse",
|
|
4199
4291
|
"skillsMetadata",
|
|
4292
|
+
"pinnedSkills",
|
|
4200
4293
|
"memoryContents",
|
|
4201
4294
|
...CALL_COUNT_STATE_KEYS,
|
|
4202
4295
|
"_summarizationEvent",
|
|
4203
4296
|
"_summarizationSessionId",
|
|
4297
|
+
"_skillToolsDisclosed",
|
|
4204
4298
|
FORKED_CONTEXT_KEY
|
|
4205
4299
|
];
|
|
4206
4300
|
/**
|
|
@@ -4219,9 +4313,11 @@ const EXCLUDED_STATE_KEYS = [
|
|
|
4219
4313
|
*/
|
|
4220
4314
|
const FORK_EXCLUDED_STATE_KEYS = [
|
|
4221
4315
|
"structuredResponse",
|
|
4316
|
+
"pinnedSkills",
|
|
4222
4317
|
...CALL_COUNT_STATE_KEYS,
|
|
4223
4318
|
"_summarizationEvent",
|
|
4224
|
-
"_summarizationSessionId"
|
|
4319
|
+
"_summarizationSessionId",
|
|
4320
|
+
"_skillToolsDisclosed"
|
|
4225
4321
|
];
|
|
4226
4322
|
/**
|
|
4227
4323
|
* Default description for the general-purpose subagent.
|
|
@@ -4664,7 +4760,8 @@ function patchDanglingToolCalls(messages) {
|
|
|
4664
4760
|
patchedMessages.push(new langchain.ToolMessage({
|
|
4665
4761
|
content: toolMsg,
|
|
4666
4762
|
name: toolCall.name,
|
|
4667
|
-
tool_call_id: toolCall.id
|
|
4763
|
+
tool_call_id: toolCall.id,
|
|
4764
|
+
status: "error"
|
|
4668
4765
|
}));
|
|
4669
4766
|
}
|
|
4670
4767
|
}
|
|
@@ -5106,6 +5203,46 @@ function createMemoryMiddleware(options) {
|
|
|
5106
5203
|
});
|
|
5107
5204
|
}
|
|
5108
5205
|
//#endregion
|
|
5206
|
+
//#region src/errors.ts
|
|
5207
|
+
const CONFIGURATION_ERROR_SYMBOL = Symbol.for("deepagents.configuration_error");
|
|
5208
|
+
/**
|
|
5209
|
+
* Thrown when `createDeepAgent` receives invalid configuration.
|
|
5210
|
+
*
|
|
5211
|
+
* Follows the same pattern as {@link SandboxError}: a human-readable
|
|
5212
|
+
* `message`, a structured `code` for programmatic handling, and a
|
|
5213
|
+
* static `isInstance` guard that works across realms.
|
|
5214
|
+
*
|
|
5215
|
+
* @example
|
|
5216
|
+
* ```typescript
|
|
5217
|
+
* try {
|
|
5218
|
+
* createDeepAgent({ tools: [myTool] });
|
|
5219
|
+
* } catch (error) {
|
|
5220
|
+
* if (ConfigurationError.isInstance(error)) {
|
|
5221
|
+
* switch (error.code) {
|
|
5222
|
+
* case "TOOL_NAME_COLLISION":
|
|
5223
|
+
* console.error("Rename your tool:", error.message);
|
|
5224
|
+
* break;
|
|
5225
|
+
* }
|
|
5226
|
+
* }
|
|
5227
|
+
* }
|
|
5228
|
+
* ```
|
|
5229
|
+
*/
|
|
5230
|
+
var ConfigurationError = class ConfigurationError extends Error {
|
|
5231
|
+
code;
|
|
5232
|
+
cause;
|
|
5233
|
+
[CONFIGURATION_ERROR_SYMBOL] = true;
|
|
5234
|
+
name = "ConfigurationError";
|
|
5235
|
+
constructor(message, code, cause) {
|
|
5236
|
+
super(message);
|
|
5237
|
+
this.code = code;
|
|
5238
|
+
this.cause = cause;
|
|
5239
|
+
Object.setPrototypeOf(this, ConfigurationError.prototype);
|
|
5240
|
+
}
|
|
5241
|
+
static isInstance(error) {
|
|
5242
|
+
return typeof error === "object" && error !== null && error[CONFIGURATION_ERROR_SYMBOL] === true;
|
|
5243
|
+
}
|
|
5244
|
+
};
|
|
5245
|
+
//#endregion
|
|
5109
5246
|
//#region src/middleware/skills.ts
|
|
5110
5247
|
/**
|
|
5111
5248
|
* Backend-agnostic skills middleware for loading agent skills from any backend.
|
|
@@ -5165,6 +5302,10 @@ const SKILL_MODULE_EXTENSIONS = [
|
|
|
5165
5302
|
".jsx",
|
|
5166
5303
|
".tsx"
|
|
5167
5304
|
];
|
|
5305
|
+
/** `SKILL.md` frontmatter `metadata` key listing the skill's include names, space-separated. */
|
|
5306
|
+
const INCLUDE_TOOLS_KEY = "include_tools";
|
|
5307
|
+
/** YAML frontmatter between `---` delimiters at the start of a `SKILL.md`. */
|
|
5308
|
+
const FRONTMATTER_PATTERN = /^---\s*\n([\s\S]*?)\n---\s*\n/;
|
|
5168
5309
|
/**
|
|
5169
5310
|
* Zod schema for a single skill metadata entry.
|
|
5170
5311
|
*/
|
|
@@ -5206,11 +5347,52 @@ const SkillMetadataEntrySchema = zod.z.object({
|
|
|
5206
5347
|
*/
|
|
5207
5348
|
const skillsMetadataValue = zod.z.array(SkillMetadataEntrySchema).nullish();
|
|
5208
5349
|
/**
|
|
5350
|
+
* State value for a middleware's `pinnedSkills` field: the names of skills to
|
|
5351
|
+
* pin.
|
|
5352
|
+
*
|
|
5353
|
+
* The next time the skills middleware's `beforeModel` runs, before a model
|
|
5354
|
+
* call, each named skill's `SKILL.md`, without its frontmatter, is appended to
|
|
5355
|
+
* the conversation as its own `HumanMessage`, in the order named. The message
|
|
5356
|
+
* is a snapshot: editing the file later doesn't change it, and naming the
|
|
5357
|
+
* skill again appends a new copy.
|
|
5358
|
+
*
|
|
5359
|
+
* Names only, matched exactly against the loaded skills. A name that isn't
|
|
5360
|
+
* loaded, or whose `SKILL.md` can't be read, is skipped without an error. The
|
|
5361
|
+
* key is cleared once consumed, so a later run pins nothing unless it names
|
|
5362
|
+
* skills again. Writes in the same step add up, so parallel tool calls can
|
|
5363
|
+
* each pin skills by returning a `Command`.
|
|
5364
|
+
*
|
|
5365
|
+
* Like {@link skillsMetadataValue}, treat the value as opaque. A middleware
|
|
5366
|
+
* declares it on its state schema to set `pinnedSkills` from its own hooks.
|
|
5367
|
+
*
|
|
5368
|
+
* @example
|
|
5369
|
+
* ```typescript
|
|
5370
|
+
* import { createMiddleware } from "langchain";
|
|
5371
|
+
* import { StateSchema } from "@langchain/langgraph";
|
|
5372
|
+
* import { pinnedSkillsValue } from "deepagents";
|
|
5373
|
+
*
|
|
5374
|
+
* const routeSkills = createMiddleware({
|
|
5375
|
+
* name: "RouteSkills",
|
|
5376
|
+
* stateSchema: new StateSchema({ pinnedSkills: pinnedSkillsValue }),
|
|
5377
|
+
* beforeAgent: (state) => ({ pinnedSkills: skillsFor(state.messages) }),
|
|
5378
|
+
* });
|
|
5379
|
+
* ```
|
|
5380
|
+
*/
|
|
5381
|
+
const pinnedSkillsValue = new _langchain_langgraph.ReducedValue(zod.z.array(zod.z.string()).default(() => []), { reducer: (stored, written) => [...stored ?? [], ...written ?? []] });
|
|
5382
|
+
/**
|
|
5209
5383
|
* State schema for skills middleware.
|
|
5210
5384
|
*/
|
|
5211
5385
|
const SkillsStateSchema = new _langchain_langgraph.StateSchema({
|
|
5212
5386
|
skillsMetadata: skillsMetadataValue,
|
|
5213
|
-
|
|
5387
|
+
pinnedSkills: pinnedSkillsValue,
|
|
5388
|
+
files: filesValue,
|
|
5389
|
+
/**
|
|
5390
|
+
* The skill tools disclosed to the latest model call, each mapped to the
|
|
5391
|
+
* include name that produced it, for the tool-time gate. Written on every
|
|
5392
|
+
* model call of a skills middleware with skill tools. The last write wins,
|
|
5393
|
+
* so a model call retried within one step can write it again.
|
|
5394
|
+
*/
|
|
5395
|
+
_skillToolsDisclosed: new _langchain_langgraph.ReducedValue(zod.z.record(zod.z.string(), zod.z.string()).optional(), { reducer: (_previous, next) => next })
|
|
5214
5396
|
});
|
|
5215
5397
|
/**
|
|
5216
5398
|
* Skills System Documentation prompt template.
|
|
@@ -5312,7 +5494,8 @@ function validateSkillName(name, directoryName) {
|
|
|
5312
5494
|
*
|
|
5313
5495
|
* YAML parsing can return any type for the `metadata` key. This ensures the
|
|
5314
5496
|
* value in {@link SkillMetadata} is always a `Record<string, string>` by
|
|
5315
|
-
* coercing via `String()` and rejecting non-object inputs.
|
|
5497
|
+
* coercing via `String()` and rejecting non-object inputs. It also warns when
|
|
5498
|
+
* `include_tools` isn't a space-separated string of tool names.
|
|
5316
5499
|
*
|
|
5317
5500
|
* @param raw - Raw value from `frontmatterData.metadata`.
|
|
5318
5501
|
* @param skillPath - Path to the `SKILL.md` file (for warning messages).
|
|
@@ -5323,6 +5506,8 @@ function validateMetadata(raw, skillPath) {
|
|
|
5323
5506
|
if (raw) console.warn(`Ignoring non-object metadata in ${skillPath} (got ${typeof raw})`);
|
|
5324
5507
|
return {};
|
|
5325
5508
|
}
|
|
5509
|
+
const includeTools = raw[INCLUDE_TOOLS_KEY];
|
|
5510
|
+
if (Array.isArray(includeTools) || typeof includeTools === "string" && includeTools.includes(",")) console.warn(`metadata.include_tools in ${skillPath} should be a space-separated string of tool names; got ${JSON.stringify(includeTools)}`);
|
|
5326
5511
|
const result = {};
|
|
5327
5512
|
for (const [k, v] of Object.entries(raw)) result[String(k)] = String(v);
|
|
5328
5513
|
return result;
|
|
@@ -5360,7 +5545,7 @@ function parseSkillMetadataFromContent(content, skillPath, directoryName) {
|
|
|
5360
5545
|
console.warn(`Skipping ${skillPath}: content too large (${content.length} bytes)`);
|
|
5361
5546
|
return null;
|
|
5362
5547
|
}
|
|
5363
|
-
const match = content.match(
|
|
5548
|
+
const match = content.match(FRONTMATTER_PATTERN);
|
|
5364
5549
|
if (!match) {
|
|
5365
5550
|
console.warn(`Skipping ${skillPath}: no valid YAML frontmatter found`);
|
|
5366
5551
|
return null;
|
|
@@ -5479,6 +5664,61 @@ async function listSkillsFromBackend(backend, sourcePath) {
|
|
|
5479
5664
|
return skills;
|
|
5480
5665
|
}
|
|
5481
5666
|
/**
|
|
5667
|
+
* Load the skills in every source, in order. A later source's skill replaces
|
|
5668
|
+
* an earlier one of the same name.
|
|
5669
|
+
*/
|
|
5670
|
+
async function loadSkills(backend, sources) {
|
|
5671
|
+
const allSkills = /* @__PURE__ */ new Map();
|
|
5672
|
+
for (const sourcePath of sources) try {
|
|
5673
|
+
const skills = await listSkillsFromBackend(backend, sourcePath);
|
|
5674
|
+
for (const skill of skills) allSkills.set(skill.name, skill);
|
|
5675
|
+
} catch (error) {
|
|
5676
|
+
console.debug(`[BackendSkillsMiddleware] Failed to load skills from ${sourcePath}:`, error);
|
|
5677
|
+
}
|
|
5678
|
+
return Array.from(allSkills.values());
|
|
5679
|
+
}
|
|
5680
|
+
/**
|
|
5681
|
+
* Return a message pinning each skill `names` refers to, in first-seen order.
|
|
5682
|
+
*
|
|
5683
|
+
* Skips a name that isn't in `skills`, and a skill whose `SKILL.md` is
|
|
5684
|
+
* missing, unreadable, empty, or larger than {@link MAX_SKILL_FILE_SIZE}.
|
|
5685
|
+
*/
|
|
5686
|
+
async function buildPinnedSkillMessages(backend, names, skills) {
|
|
5687
|
+
const adaptedBackend = adaptBackendProtocol(backend);
|
|
5688
|
+
const skillsByName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
5689
|
+
const skillsToPin = [...new Set(names)].flatMap((name) => {
|
|
5690
|
+
const skill = skillsByName.get(name);
|
|
5691
|
+
return skill === void 0 ? [] : [skill];
|
|
5692
|
+
});
|
|
5693
|
+
const contents = await Promise.all(skillsToPin.map((skill) => readFileFromBackend(adaptedBackend, skill.path).catch(() => null)));
|
|
5694
|
+
return skillsToPin.flatMap((skill, i) => {
|
|
5695
|
+
const content = contents[i];
|
|
5696
|
+
if (!content || content.length > 10485760) return [];
|
|
5697
|
+
return [buildPinnedSkillMessage(skill, content)];
|
|
5698
|
+
});
|
|
5699
|
+
}
|
|
5700
|
+
/** Return the message pinning `skill`, carrying its `SKILL.md` `content` without frontmatter. */
|
|
5701
|
+
function buildPinnedSkillMessage(skill, content) {
|
|
5702
|
+
const body = content.replace(FRONTMATTER_PATTERN, "").trimEnd();
|
|
5703
|
+
const name = escapeXmlAttribute(skill.name);
|
|
5704
|
+
const path = escapeXmlAttribute(skill.path);
|
|
5705
|
+
return new _langchain_core_messages.HumanMessage({
|
|
5706
|
+
content: `<skill name="${name}" path="${path}">\n${body}\n</skill>`,
|
|
5707
|
+
additional_kwargs: {
|
|
5708
|
+
lc_source: "pinned_skill",
|
|
5709
|
+
skill: {
|
|
5710
|
+
name: skill.name,
|
|
5711
|
+
path: skill.path,
|
|
5712
|
+
description: skill.description
|
|
5713
|
+
}
|
|
5714
|
+
}
|
|
5715
|
+
});
|
|
5716
|
+
}
|
|
5717
|
+
/** Escape `value` for a double-quoted XML attribute. */
|
|
5718
|
+
function escapeXmlAttribute(value) {
|
|
5719
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5720
|
+
}
|
|
5721
|
+
/**
|
|
5482
5722
|
* Format skills locations for display in system prompt.
|
|
5483
5723
|
* Shows priority indicator for the last source (highest priority).
|
|
5484
5724
|
*/
|
|
@@ -5538,6 +5778,336 @@ function validateModulePath(raw) {
|
|
|
5538
5778
|
if (!endsWithModuleExtension(normalized)) return;
|
|
5539
5779
|
return normalized;
|
|
5540
5780
|
}
|
|
5781
|
+
/** Tool `extras` key (and provider field) that withholds a tool's schema until searched for. */
|
|
5782
|
+
const DEFER_LOADING = "defer_loading";
|
|
5783
|
+
/**
|
|
5784
|
+
* Model ID prefixes that accept an inline `tool_definition` mid-conversation.
|
|
5785
|
+
*
|
|
5786
|
+
* `@langchain/anthropic` sends any mid-conversation system message in place,
|
|
5787
|
+
* without gating on the model, so this list is the only guard.
|
|
5788
|
+
*/
|
|
5789
|
+
const ANTHROPIC_INLINE_TOOL_MODELS = [
|
|
5790
|
+
"claude-opus-5",
|
|
5791
|
+
"claude-fable-5",
|
|
5792
|
+
"claude-mythos-5",
|
|
5793
|
+
"claude-opus-4-8"
|
|
5794
|
+
];
|
|
5795
|
+
/** Models whose Responses API accepts an `additional_tools` input item, matched with or without a `-` suffix such as a snapshot date. */
|
|
5796
|
+
const OPENAI_INLINE_TOOL_MODELS = ["gpt-6", "gpt-5.6"];
|
|
5797
|
+
/** Root `input_schema` keys the Anthropic API rejects, failing the whole request. */
|
|
5798
|
+
const ANTHROPIC_ROOT_COMBINATORS = [
|
|
5799
|
+
"oneOf",
|
|
5800
|
+
"anyOf",
|
|
5801
|
+
"allOf"
|
|
5802
|
+
];
|
|
5803
|
+
/** `extras` keys `ChatAnthropic` copies into a tool's definition, bar the ones a disclosure must never carry. */
|
|
5804
|
+
const ANTHROPIC_DEFINITION_EXTRAS = [
|
|
5805
|
+
"input_examples",
|
|
5806
|
+
"allowed_callers",
|
|
5807
|
+
"strict"
|
|
5808
|
+
];
|
|
5809
|
+
/**
|
|
5810
|
+
* Return the resolver the skills middleware's `tools` option stands for.
|
|
5811
|
+
*
|
|
5812
|
+
* A list is validated, then resolved by exact name.
|
|
5813
|
+
*
|
|
5814
|
+
* @throws {ConfigurationError} If `tools` is neither a list nor a function,
|
|
5815
|
+
* or a list entry isn't a client tool or repeats a name.
|
|
5816
|
+
*/
|
|
5817
|
+
function toSkillToolResolver(tools) {
|
|
5818
|
+
if (tools != null && !Array.isArray(tools)) {
|
|
5819
|
+
if (typeof tools !== "function") throw new ConfigurationError(`tools must be an array of tools or a resolver function, got ${typeof tools}; wrap a single tool in an array`, "SKILL_TOOLS_UNSUPPORTED_TYPE");
|
|
5820
|
+
return tools;
|
|
5821
|
+
}
|
|
5822
|
+
const entries = tools ?? [];
|
|
5823
|
+
if (!entries.every(isClientTool)) throw new ConfigurationError("tools entries must be client tools; provider-native tool objects are not supported", "SKILL_TOOLS_UNSUPPORTED_TYPE");
|
|
5824
|
+
const names = entries.map((t) => t.name);
|
|
5825
|
+
const duplicates = [...new Set(names.filter((n, i) => names.indexOf(n) !== i))].sort();
|
|
5826
|
+
if (duplicates.length > 0) throw new ConfigurationError(`tools contains duplicate tool name(s): ${duplicates.join(", ")}`, "SKILL_TOOLS_DUPLICATE_NAME");
|
|
5827
|
+
const byName = new Map(entries.map((t) => [t.name, t]));
|
|
5828
|
+
return (name) => byName.has(name) ? [byName.get(name)] : [];
|
|
5829
|
+
}
|
|
5830
|
+
/** Whether `value` is a runnable client tool rather than a provider-native or plain object. */
|
|
5831
|
+
function isClientTool(value) {
|
|
5832
|
+
return _langchain_core_runnables.Runnable.isRunnable(value) && typeof value.name === "string";
|
|
5833
|
+
}
|
|
5834
|
+
/**
|
|
5835
|
+
* Call `resolver` for one include name, keeping the first of each tool name.
|
|
5836
|
+
*
|
|
5837
|
+
* @throws {TypeError} If the resolver returns something other than an array
|
|
5838
|
+
* of client tools.
|
|
5839
|
+
*/
|
|
5840
|
+
async function callSkillToolResolver(resolver, includeName, runtime) {
|
|
5841
|
+
const result = await resolver(includeName, runtime);
|
|
5842
|
+
if (!Array.isArray(result)) throw new TypeError(`skill tool resolver must return an array of tools for '${includeName}', got ${typeof result}`);
|
|
5843
|
+
const tools = /* @__PURE__ */ new Map();
|
|
5844
|
+
for (const item of result) {
|
|
5845
|
+
if (!isClientTool(item)) throw new TypeError(`skill tool resolver returned a non-tool ${typeof item} for '${includeName}'; expected tool instances`);
|
|
5846
|
+
if (!tools.has(item.name)) tools.set(item.name, item);
|
|
5847
|
+
}
|
|
5848
|
+
return [...tools.values()];
|
|
5849
|
+
}
|
|
5850
|
+
/**
|
|
5851
|
+
* Return every successful `read_file` or pin of a `SKILL.md` that lists
|
|
5852
|
+
* tools, in message order.
|
|
5853
|
+
*
|
|
5854
|
+
* The read's or pin's path must equal the skill's path exactly, as the skills
|
|
5855
|
+
* listing shows it. Any `offset` or `limit` counts, and so does a result whose
|
|
5856
|
+
* content was later truncated or compacted, since only the call and the
|
|
5857
|
+
* result's status are read.
|
|
5858
|
+
*/
|
|
5859
|
+
function findSkillReads(messages, skills) {
|
|
5860
|
+
const skillsByPath = /* @__PURE__ */ new Map();
|
|
5861
|
+
for (const skill of skills) {
|
|
5862
|
+
const value = skill.metadata?.[INCLUDE_TOOLS_KEY];
|
|
5863
|
+
const includeNames = typeof value === "string" ? value.split(/\s+/).filter(Boolean) : [];
|
|
5864
|
+
if (includeNames.length > 0) skillsByPath.set(skill.path, { includeNames });
|
|
5865
|
+
}
|
|
5866
|
+
if (skillsByPath.size === 0) return [];
|
|
5867
|
+
const readPaths = /* @__PURE__ */ new Map();
|
|
5868
|
+
for (const message of messages) {
|
|
5869
|
+
if (!_langchain_core_messages.AIMessage.isInstance(message)) continue;
|
|
5870
|
+
for (const toolCall of message.tool_calls ?? []) if (toolCall.name === "read_file" && toolCall.id) readPaths.set(toolCall.id, toolCall.args?.file_path ?? toolCall.args?.path);
|
|
5871
|
+
}
|
|
5872
|
+
const reads = [];
|
|
5873
|
+
messages.forEach((message, index) => {
|
|
5874
|
+
let path;
|
|
5875
|
+
if (_langchain_core_messages.ToolMessage.isInstance(message) && message.status !== "error") path = readPaths.get(message.tool_call_id);
|
|
5876
|
+
else if (_langchain_core_messages.HumanMessage.isInstance(message) && message.additional_kwargs?.lc_source === "pinned_skill") path = message.additional_kwargs.skill?.path;
|
|
5877
|
+
else return;
|
|
5878
|
+
const skill = typeof path === "string" ? skillsByPath.get(path) : void 0;
|
|
5879
|
+
if (skill !== void 0) reads.push({
|
|
5880
|
+
index,
|
|
5881
|
+
...skill
|
|
5882
|
+
});
|
|
5883
|
+
});
|
|
5884
|
+
return reads;
|
|
5885
|
+
}
|
|
5886
|
+
/** Return a request tool's name, whether it is a tool instance or a provider object. */
|
|
5887
|
+
function toolName(tool) {
|
|
5888
|
+
if ((0, _langchain_core_language_models_base.isOpenAITool)(tool)) return tool.function.name;
|
|
5889
|
+
return typeof tool.name === "string" ? tool.name : void 0;
|
|
5890
|
+
}
|
|
5891
|
+
/**
|
|
5892
|
+
* Disclose the tools the read skills' include names produce to one model call.
|
|
5893
|
+
*
|
|
5894
|
+
* Returns the request to send, and the record of the skill tools it
|
|
5895
|
+
* discloses, for the tool-time gate.
|
|
5896
|
+
*/
|
|
5897
|
+
async function discloseSkillTools(request, reads, resolver) {
|
|
5898
|
+
const requestTools = /* @__PURE__ */ new Map();
|
|
5899
|
+
for (const tool of request.tools) {
|
|
5900
|
+
const name = toolName(tool);
|
|
5901
|
+
if (name !== void 0) requestTools.set(name, tool);
|
|
5902
|
+
}
|
|
5903
|
+
const names = [...new Set(reads.flatMap((read) => read.includeNames.filter((name) => !requestTools.has(name))))];
|
|
5904
|
+
const tools = await Promise.all(names.map((name) => callSkillToolResolver(resolver, name, request.runtime)));
|
|
5905
|
+
const disclosed = planDisclosure(reads, requestTools, new Map(names.map((name, i) => [name, tools[i]])));
|
|
5906
|
+
if (disclosed.size === 0) return {
|
|
5907
|
+
request,
|
|
5908
|
+
record: {}
|
|
5909
|
+
};
|
|
5910
|
+
const chatModel = await resolveChatModel(request.model, request.runtime);
|
|
5911
|
+
if (llmType(chatModel) === "anthropic") withholdRootCombinatorTools(disclosed);
|
|
5912
|
+
const gatedNames = [...disclosed.keys()].filter((name) => disclosed.get(name).includeName !== void 0).sort();
|
|
5913
|
+
const record = Object.fromEntries(gatedNames.map((name) => [name, disclosed.get(name).includeName]));
|
|
5914
|
+
if (disclosed.size === 0) return {
|
|
5915
|
+
request,
|
|
5916
|
+
record
|
|
5917
|
+
};
|
|
5918
|
+
const build = inlineBlockBuilder(chatModel);
|
|
5919
|
+
if (build !== void 0) return {
|
|
5920
|
+
request: {
|
|
5921
|
+
...request,
|
|
5922
|
+
messages: discloseInMessages(request.messages, disclosed, build)
|
|
5923
|
+
},
|
|
5924
|
+
record
|
|
5925
|
+
};
|
|
5926
|
+
return {
|
|
5927
|
+
request: {
|
|
5928
|
+
...request,
|
|
5929
|
+
tools: discloseInTools(request.tools, disclosed, gatedNames)
|
|
5930
|
+
},
|
|
5931
|
+
record
|
|
5932
|
+
};
|
|
5933
|
+
}
|
|
5934
|
+
/**
|
|
5935
|
+
* Classify what each read skill's include names produce, anchoring each tool
|
|
5936
|
+
* at the earliest read producing it.
|
|
5937
|
+
*
|
|
5938
|
+
* An include name that matches a request tool's name produces that tool; any
|
|
5939
|
+
* other produces what the resolver returned for it. A produced tool whose name
|
|
5940
|
+
* a request tool has wins as that request tool, because it's the one that
|
|
5941
|
+
* runs: a deferred one is disclosed early, and a bound one adds nothing, since
|
|
5942
|
+
* the model already sees it. Tools are matched by name, never by identity,
|
|
5943
|
+
* because outer middleware such as tool search replaces the request's tools
|
|
5944
|
+
* with copies.
|
|
5945
|
+
*/
|
|
5946
|
+
function planDisclosure(reads, requestTools, resolved) {
|
|
5947
|
+
const disclosed = /* @__PURE__ */ new Map();
|
|
5948
|
+
const discloseIfDeferred = (name, anchor) => {
|
|
5949
|
+
const entry = requestTools.get(name);
|
|
5950
|
+
if (!disclosed.has(name) && (0, _langchain_core_tools.isLangChainTool)(entry) && entry.extras?.[DEFER_LOADING] === true) disclosed.set(name, {
|
|
5951
|
+
tool: entry,
|
|
5952
|
+
anchor
|
|
5953
|
+
});
|
|
5954
|
+
};
|
|
5955
|
+
for (const { index, includeNames } of reads) for (const includeName of includeNames) {
|
|
5956
|
+
if (requestTools.has(includeName)) {
|
|
5957
|
+
discloseIfDeferred(includeName, index);
|
|
5958
|
+
continue;
|
|
5959
|
+
}
|
|
5960
|
+
for (const tool of resolved.get(includeName) ?? []) {
|
|
5961
|
+
if (disclosed.has(tool.name)) continue;
|
|
5962
|
+
if (requestTools.has(tool.name)) discloseIfDeferred(tool.name, index);
|
|
5963
|
+
else disclosed.set(tool.name, {
|
|
5964
|
+
tool,
|
|
5965
|
+
anchor: index,
|
|
5966
|
+
includeName
|
|
5967
|
+
});
|
|
5968
|
+
}
|
|
5969
|
+
}
|
|
5970
|
+
return disclosed;
|
|
5971
|
+
}
|
|
5972
|
+
/**
|
|
5973
|
+
* Withhold each disclosed tool whose root input schema uses `oneOf`, `anyOf`
|
|
5974
|
+
* or `allOf`, which the Anthropic API rejects, failing the whole request, in
|
|
5975
|
+
* `tools` and inline alike. Withholding it on every path keeps the record
|
|
5976
|
+
* equal to what the model was shown.
|
|
5977
|
+
*/
|
|
5978
|
+
function withholdRootCombinatorTools(disclosed) {
|
|
5979
|
+
for (const [name, { tool }] of disclosed) {
|
|
5980
|
+
const schema = anthropicDefinition(tool).input_schema;
|
|
5981
|
+
const keys = ANTHROPIC_ROOT_COMBINATORS.filter((key) => typeof schema === "object" && schema !== null && Object.prototype.hasOwnProperty.call(schema, key));
|
|
5982
|
+
if (keys.length > 0) {
|
|
5983
|
+
console.warn(`Not disclosing tool '${name}': its input_schema has a top-level ${keys.join("/")}, which the Anthropic API does not support`);
|
|
5984
|
+
disclosed.delete(name);
|
|
5985
|
+
}
|
|
5986
|
+
}
|
|
5987
|
+
}
|
|
5988
|
+
/**
|
|
5989
|
+
* Insert one `SystemMessage` per insertion point, carrying the tools anchored
|
|
5990
|
+
* there, for models that accept tool definitions mid-conversation.
|
|
5991
|
+
*
|
|
5992
|
+
* Each message lands at the same index with the same bytes on every call while
|
|
5993
|
+
* its anchor stays visible, so the provider's cached prefix survives.
|
|
5994
|
+
*/
|
|
5995
|
+
function discloseInMessages(messages, disclosed, build) {
|
|
5996
|
+
const namesByIndex = /* @__PURE__ */ new Map();
|
|
5997
|
+
for (const [name, { anchor }] of disclosed) {
|
|
5998
|
+
let index = anchor + 1;
|
|
5999
|
+
while (index < messages.length) {
|
|
6000
|
+
const message = messages[index];
|
|
6001
|
+
const emptyReply = _langchain_core_messages.AIMessage.isInstance(message) && message.content.length === 0 && !message.tool_calls?.length;
|
|
6002
|
+
if (!_langchain_core_messages.ToolMessage.isInstance(message) && !_langchain_core_messages.HumanMessage.isInstance(message) && !emptyReply) break;
|
|
6003
|
+
index += 1;
|
|
6004
|
+
}
|
|
6005
|
+
namesByIndex.set(index, [...namesByIndex.get(index) ?? [], name]);
|
|
6006
|
+
}
|
|
6007
|
+
const result = [...messages];
|
|
6008
|
+
for (const index of [...namesByIndex.keys()].sort((a, b) => b - a)) {
|
|
6009
|
+
const blocks = namesByIndex.get(index).sort().map((name) => build(disclosed.get(name).tool));
|
|
6010
|
+
result.splice(index, 0, new _langchain_core_messages.SystemMessage({ content: blocks }));
|
|
6011
|
+
}
|
|
6012
|
+
return result;
|
|
6013
|
+
}
|
|
6014
|
+
/**
|
|
6015
|
+
* Return `requestTools` with deferred disclosures undeferred and skill tools
|
|
6016
|
+
* appended, for models that can't take tool definitions mid-conversation.
|
|
6017
|
+
*
|
|
6018
|
+
* A deferred tool is replaced by a plain spec rather than a copy: `createAgent`
|
|
6019
|
+
* rejects a request that swaps a registered tool for another instance of the
|
|
6020
|
+
* same name, and doesn't validate plain objects.
|
|
6021
|
+
*/
|
|
6022
|
+
function discloseInTools(requestTools, disclosed, gatedNames) {
|
|
6023
|
+
return [...requestTools.map((tool) => {
|
|
6024
|
+
const name = toolName(tool);
|
|
6025
|
+
const entry = name === void 0 ? void 0 : disclosed.get(name);
|
|
6026
|
+
if (entry?.tool !== tool || entry.includeName !== void 0) return tool;
|
|
6027
|
+
const { description, schema, extras = {} } = entry.tool;
|
|
6028
|
+
const { [DEFER_LOADING]: _deferLoading, ...rest } = extras;
|
|
6029
|
+
return {
|
|
6030
|
+
name,
|
|
6031
|
+
description,
|
|
6032
|
+
schema,
|
|
6033
|
+
extras: rest
|
|
6034
|
+
};
|
|
6035
|
+
}), ...gatedNames.map((name) => disclosed.get(name).tool)];
|
|
6036
|
+
}
|
|
6037
|
+
/** Return the model inside a `bind()` / `withConfig()` wrapper; chained calls merge into one. */
|
|
6038
|
+
function unwrapBinding(model) {
|
|
6039
|
+
return _langchain_core_runnables.RunnableBinding.isRunnableBinding(model) ? model.bound : model;
|
|
6040
|
+
}
|
|
6041
|
+
/**
|
|
6042
|
+
* Return the concrete chat model a request will call.
|
|
6043
|
+
*
|
|
6044
|
+
* Resolves a `ConfigurableModel`, which a string `model` always becomes, to
|
|
6045
|
+
* the instance it will call with this run's `configurable`. Without
|
|
6046
|
+
* configurable overrides that's the instance cached at creation: the instance
|
|
6047
|
+
* cache is keyed on the whole config, so passing `configurable`
|
|
6048
|
+
* unconditionally would build one per thread.
|
|
6049
|
+
*/
|
|
6050
|
+
async function resolveChatModel(model, runtime) {
|
|
6051
|
+
const current = unwrapBinding(model);
|
|
6052
|
+
if (current?._queuedMethodOperations === void 0 || typeof current._getModelInstance !== "function") return current;
|
|
6053
|
+
const configurable = runtime.configurable ?? {};
|
|
6054
|
+
const overrides = current._modelParams?.({ configurable }) ?? {};
|
|
6055
|
+
return unwrapBinding(Object.keys(overrides).length > 0 ? await current._getModelInstance({ configurable }) : await current._getModelInstance());
|
|
6056
|
+
}
|
|
6057
|
+
/** Return a chat model's `_llmType()`, which tells providers apart. */
|
|
6058
|
+
function llmType(model) {
|
|
6059
|
+
const fn = model?._llmType;
|
|
6060
|
+
return typeof fn === "function" ? fn.call(model) : void 0;
|
|
6061
|
+
}
|
|
6062
|
+
/**
|
|
6063
|
+
* Return how `chatModel` is given a tool mid-conversation, or `undefined` if
|
|
6064
|
+
* it can't be.
|
|
6065
|
+
*
|
|
6066
|
+
* The one place that decides support, so a model-profile capability can
|
|
6067
|
+
* replace the allowlists later.
|
|
6068
|
+
*/
|
|
6069
|
+
function inlineBlockBuilder(chatModel) {
|
|
6070
|
+
const { model: name, useResponsesApi } = chatModel;
|
|
6071
|
+
if (typeof name !== "string") return void 0;
|
|
6072
|
+
const type = llmType(chatModel);
|
|
6073
|
+
if (type === "anthropic" && ANTHROPIC_INLINE_TOOL_MODELS.some((prefix) => name.startsWith(prefix))) return anthropicToolAddition;
|
|
6074
|
+
if (type === "openai" && useResponsesApi === true && OPENAI_INLINE_TOOL_MODELS.some((model) => name === model || name.startsWith(`${model}-`))) return openaiAdditionalTools;
|
|
6075
|
+
}
|
|
6076
|
+
/**
|
|
6077
|
+
* Return the definition `ChatAnthropic` sends in `tools` for `tool`, without
|
|
6078
|
+
* `defer_loading` (a deferred definition would stay withheld) or
|
|
6079
|
+
* `cache_control` (a stray marker uses up a breakpoint).
|
|
6080
|
+
*/
|
|
6081
|
+
function anthropicDefinition(tool) {
|
|
6082
|
+
const definition = {
|
|
6083
|
+
name: tool.name,
|
|
6084
|
+
description: tool.description,
|
|
6085
|
+
input_schema: (0, _langchain_core_utils_types.isInteropZodSchema)(tool.schema) ? (0, _langchain_core_utils_json_schema.toJsonSchema)(tool.schema) : tool.schema
|
|
6086
|
+
};
|
|
6087
|
+
for (const key of ANTHROPIC_DEFINITION_EXTRAS) if (tool.extras?.[key] !== void 0) definition[key] = tool.extras[key];
|
|
6088
|
+
return definition;
|
|
6089
|
+
}
|
|
6090
|
+
/** Build the Anthropic `tool_addition` block carrying `tool`'s full definition. */
|
|
6091
|
+
function anthropicToolAddition(tool) {
|
|
6092
|
+
return {
|
|
6093
|
+
type: "tool_addition",
|
|
6094
|
+
tool: {
|
|
6095
|
+
type: "tool_definition",
|
|
6096
|
+
definition: anthropicDefinition(tool)
|
|
6097
|
+
}
|
|
6098
|
+
};
|
|
6099
|
+
}
|
|
6100
|
+
/** Build the OpenAI Responses `additional_tools` item carrying `tool`'s function schema. */
|
|
6101
|
+
function openaiAdditionalTools(tool) {
|
|
6102
|
+
return {
|
|
6103
|
+
type: "additional_tools",
|
|
6104
|
+
role: "developer",
|
|
6105
|
+
tools: [{
|
|
6106
|
+
type: "function",
|
|
6107
|
+
...(0, _langchain_core_utils_function_calling.convertToOpenAIFunction)(tool)
|
|
6108
|
+
}]
|
|
6109
|
+
};
|
|
6110
|
+
}
|
|
5541
6111
|
/**
|
|
5542
6112
|
* Create backend-agnostic middleware for loading and exposing agent skills.
|
|
5543
6113
|
*
|
|
@@ -5561,8 +6131,62 @@ function validateModulePath(raw) {
|
|
|
5561
6131
|
* invalidate from any hook — including mid-run, from `afterModel` — and see
|
|
5562
6132
|
* the fresh list on the following call. See {@link skillsMetadataValue}.
|
|
5563
6133
|
*
|
|
6134
|
+
* To guarantee the model gets a skill's instructions, for example when the
|
|
6135
|
+
* user names it, pass its name in `pinnedSkills`. Before the next model call,
|
|
6136
|
+
* each named skill's `SKILL.md`, without its frontmatter, is appended to the
|
|
6137
|
+
* conversation as its own `HumanMessage`, with `additional_kwargs.lc_source`
|
|
6138
|
+
* set to `"pinned_skill"` and the skill's `name`, `path` and `description`
|
|
6139
|
+
* under `additional_kwargs.skill`:
|
|
6140
|
+
*
|
|
6141
|
+
* ```ts
|
|
6142
|
+
* await agent.invoke(
|
|
6143
|
+
* {
|
|
6144
|
+
* messages: [new HumanMessage("/write-tests /house-style for auth.py")],
|
|
6145
|
+
* pinnedSkills: ["write-tests", "house-style"],
|
|
6146
|
+
* },
|
|
6147
|
+
* config,
|
|
6148
|
+
* );
|
|
6149
|
+
* ```
|
|
6150
|
+
*
|
|
6151
|
+
* Pins resolve against the skills loaded for the thread, so reset
|
|
6152
|
+
* `skillsMetadata` in the same input to pin a skill added since. See
|
|
6153
|
+
* {@link pinnedSkillsValue}.
|
|
6154
|
+
*
|
|
6155
|
+
* ## Skill tools
|
|
6156
|
+
*
|
|
6157
|
+
* A skill can list the tools its instructions use, separated by spaces, under
|
|
6158
|
+
* `metadata.include_tools` in its `SKILL.md` frontmatter:
|
|
6159
|
+
*
|
|
6160
|
+
* ```yaml
|
|
6161
|
+
* metadata:
|
|
6162
|
+
* include_tools: create_customer_request list_customer_requests
|
|
6163
|
+
* ```
|
|
6164
|
+
*
|
|
6165
|
+
* Pass those tools as `tools`, either as an array or as a
|
|
6166
|
+
* {@link SkillToolResolver} that looks them up by name when a skill is read
|
|
6167
|
+
* or pinned, so one name can stand for a family of tools whose real names are
|
|
6168
|
+
* only known at runtime. The model sees a skill tool only after it reads, with
|
|
6169
|
+
* `read_file`, a skill that lists it, or the skill is pinned, and only while
|
|
6170
|
+
* that read or pin stays in the conversation it is sent; compaction that drops
|
|
6171
|
+
* the read or pin withdraws the tool.
|
|
6172
|
+
* Until then, calling the tool fails with the standard invalid-tool error and
|
|
6173
|
+
* the tool doesn't run. This controls what is in the model's context; it is
|
|
6174
|
+
* not a security boundary, since the model can read any `SKILL.md` at any
|
|
6175
|
+
* time.
|
|
6176
|
+
*
|
|
6177
|
+
* `include_tools` can also name a tool passed to the agent rather than to this
|
|
6178
|
+
* middleware. That tool wins over a skill tool of the same name. If it is
|
|
6179
|
+
* deferred (`extras: { defer_loading: true }`), reading or pinning the skill
|
|
6180
|
+
* discloses it early, and it stays deferred and searchable; if it is bound,
|
|
6181
|
+
* nothing changes.
|
|
6182
|
+
*
|
|
6183
|
+
*
|
|
5564
6184
|
* @param options - Configuration options
|
|
5565
6185
|
* @returns AgentMiddleware for skills loading and injection
|
|
6186
|
+
* @throws {ConfigurationError} If `tools` is neither an array nor a
|
|
6187
|
+
* function (`SKILL_TOOLS_UNSUPPORTED_TYPE`), an entry isn't a client tool
|
|
6188
|
+
* (`SKILL_TOOLS_UNSUPPORTED_TYPE`), or two entries share a name
|
|
6189
|
+
* (`SKILL_TOOLS_DUPLICATE_NAME`).
|
|
5566
6190
|
*
|
|
5567
6191
|
* @example
|
|
5568
6192
|
* ```typescript
|
|
@@ -5574,30 +6198,60 @@ function validateModulePath(raw) {
|
|
|
5574
6198
|
*/
|
|
5575
6199
|
function createSkillsMiddleware(options) {
|
|
5576
6200
|
const { backend, sources } = options;
|
|
6201
|
+
const skillToolResolver = toSkillToolResolver(options.tools);
|
|
6202
|
+
const hasSkillTools = typeof options.tools === "function" || Array.isArray(options.tools) && options.tools.length > 0;
|
|
5577
6203
|
return (0, langchain.createMiddleware)({
|
|
5578
6204
|
name: "SkillsMiddleware",
|
|
5579
6205
|
stateSchema: SkillsStateSchema,
|
|
5580
6206
|
async beforeModel(state) {
|
|
5581
|
-
|
|
6207
|
+
const needsLoad = state.skillsMetadata === null || state.skillsMetadata === void 0;
|
|
6208
|
+
const pinnedNames = state.pinnedSkills ?? [];
|
|
6209
|
+
if (!needsLoad && pinnedNames.length === 0) return;
|
|
5582
6210
|
const resolvedBackend = await resolveBackend(backend, { state });
|
|
5583
|
-
const
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
6211
|
+
const skillsMetadata = state.skillsMetadata ?? await loadSkills(resolvedBackend, sources);
|
|
6212
|
+
if (pinnedNames.length === 0) return { skillsMetadata };
|
|
6213
|
+
const messages = await buildPinnedSkillMessages(resolvedBackend, pinnedNames, skillsMetadata);
|
|
6214
|
+
return {
|
|
6215
|
+
...needsLoad && { skillsMetadata },
|
|
6216
|
+
...messages.length > 0 && { messages },
|
|
6217
|
+
pinnedSkills: new _langchain_langgraph.Overwrite([])
|
|
6218
|
+
};
|
|
5591
6219
|
},
|
|
5592
|
-
wrapModelCall(request, handler) {
|
|
6220
|
+
async wrapModelCall(request, handler) {
|
|
5593
6221
|
const skillsMetadata = request.state?.skillsMetadata || [];
|
|
5594
6222
|
const skillsLocations = formatSkillsLocations(sources);
|
|
5595
6223
|
const skillsList = formatSkillsList(skillsMetadata, sources);
|
|
5596
6224
|
const skillsSection = SKILLS_SYSTEM_PROMPT.replace("{skills_locations}", skillsLocations).replace("{skills_list}", skillsList);
|
|
5597
6225
|
const newSystemMessage = request.systemMessage.concat(skillsSection);
|
|
5598
|
-
|
|
6226
|
+
const prompted = {
|
|
5599
6227
|
...request,
|
|
5600
6228
|
systemMessage: newSystemMessage
|
|
6229
|
+
};
|
|
6230
|
+
const reads = findSkillReads(request.messages, skillsMetadata);
|
|
6231
|
+
const { request: disclosed, record } = reads.length === 0 ? {
|
|
6232
|
+
request: prompted,
|
|
6233
|
+
record: {}
|
|
6234
|
+
} : await discloseSkillTools(prompted, reads, skillToolResolver);
|
|
6235
|
+
const response = await handler(disclosed);
|
|
6236
|
+
const stored = request.state._skillToolsDisclosed;
|
|
6237
|
+
if (!hasSkillTools && Object.keys(stored ?? {}).length === 0) return response;
|
|
6238
|
+
if (typeof response === "object" && response !== null && "structuredResponse" in response) return response;
|
|
6239
|
+
return new _langchain_langgraph.Command({ update: { _skillToolsDisclosed: record } });
|
|
6240
|
+
},
|
|
6241
|
+
async wrapToolCall(request, handler) {
|
|
6242
|
+
if (request.tool !== void 0) return handler(request);
|
|
6243
|
+
const name = request.toolCall.name;
|
|
6244
|
+
const record = request.state._skillToolsDisclosed ?? {};
|
|
6245
|
+
const includeName = Object.hasOwn(record, name) ? record[name] : void 0;
|
|
6246
|
+
if (includeName === void 0) return handler(request);
|
|
6247
|
+
const tool = (await callSkillToolResolver(skillToolResolver, includeName, request.runtime)).find((candidate) => candidate.name === name);
|
|
6248
|
+
if (tool === void 0) {
|
|
6249
|
+
console.warn(`Skill tool '${name}' was disclosed via '${includeName}', but the resolver no longer returns it`);
|
|
6250
|
+
return handler(request);
|
|
6251
|
+
}
|
|
6252
|
+
return handler({
|
|
6253
|
+
...request,
|
|
6254
|
+
tool
|
|
5601
6255
|
});
|
|
5602
6256
|
}
|
|
5603
6257
|
});
|
|
@@ -6367,46 +7021,6 @@ function createAsyncSubAgentMiddleware(options) {
|
|
|
6367
7021
|
});
|
|
6368
7022
|
}
|
|
6369
7023
|
//#endregion
|
|
6370
|
-
//#region src/errors.ts
|
|
6371
|
-
const CONFIGURATION_ERROR_SYMBOL = Symbol.for("deepagents.configuration_error");
|
|
6372
|
-
/**
|
|
6373
|
-
* Thrown when `createDeepAgent` receives invalid configuration.
|
|
6374
|
-
*
|
|
6375
|
-
* Follows the same pattern as {@link SandboxError}: a human-readable
|
|
6376
|
-
* `message`, a structured `code` for programmatic handling, and a
|
|
6377
|
-
* static `isInstance` guard that works across realms.
|
|
6378
|
-
*
|
|
6379
|
-
* @example
|
|
6380
|
-
* ```typescript
|
|
6381
|
-
* try {
|
|
6382
|
-
* createDeepAgent({ tools: [myTool] });
|
|
6383
|
-
* } catch (error) {
|
|
6384
|
-
* if (ConfigurationError.isInstance(error)) {
|
|
6385
|
-
* switch (error.code) {
|
|
6386
|
-
* case "TOOL_NAME_COLLISION":
|
|
6387
|
-
* console.error("Rename your tool:", error.message);
|
|
6388
|
-
* break;
|
|
6389
|
-
* }
|
|
6390
|
-
* }
|
|
6391
|
-
* }
|
|
6392
|
-
* ```
|
|
6393
|
-
*/
|
|
6394
|
-
var ConfigurationError = class ConfigurationError extends Error {
|
|
6395
|
-
code;
|
|
6396
|
-
cause;
|
|
6397
|
-
[CONFIGURATION_ERROR_SYMBOL] = true;
|
|
6398
|
-
name = "ConfigurationError";
|
|
6399
|
-
constructor(message, code, cause) {
|
|
6400
|
-
super(message);
|
|
6401
|
-
this.code = code;
|
|
6402
|
-
this.cause = cause;
|
|
6403
|
-
Object.setPrototypeOf(this, ConfigurationError.prototype);
|
|
6404
|
-
}
|
|
6405
|
-
static isInstance(error) {
|
|
6406
|
-
return typeof error === "object" && error !== null && error[CONFIGURATION_ERROR_SYMBOL] === true;
|
|
6407
|
-
}
|
|
6408
|
-
};
|
|
6409
|
-
//#endregion
|
|
6410
7024
|
//#region src/middleware/cache.ts
|
|
6411
7025
|
/**
|
|
6412
7026
|
* Creates a middleware that places a cache breakpoint at the end of the static
|
|
@@ -7272,13 +7886,10 @@ function createDeepAgent(params = {}) {
|
|
|
7272
7886
|
harnessProfile.systemPromptSuffix
|
|
7273
7887
|
]);
|
|
7274
7888
|
/**
|
|
7275
|
-
*
|
|
7276
|
-
*
|
|
7277
|
-
* Custom subagents do NOT inherit skills from the main agent by default.
|
|
7278
|
-
* Only the general-purpose subagent inherits the main agent's skills.
|
|
7279
|
-
* If a custom subagent needs skills, it must specify its own `skills` array.
|
|
7889
|
+
* Build the default head of a declarative subagent's middleware stack.
|
|
7890
|
+
* Skills go in the tail instead (see buildSubagentMiddleware).
|
|
7280
7891
|
*/
|
|
7281
|
-
const createSubagentDefaultMiddleware = (input, subagentProfile
|
|
7892
|
+
const createSubagentDefaultMiddleware = (input, subagentProfile) => {
|
|
7282
7893
|
const effectivePermissions = input.permissions ?? permissions;
|
|
7283
7894
|
return [
|
|
7284
7895
|
createFilesystemMiddleware({
|
|
@@ -7287,23 +7898,33 @@ function createDeepAgent(params = {}) {
|
|
|
7287
7898
|
tools: computeProfileFilesystemTools(subagentProfile)
|
|
7288
7899
|
}),
|
|
7289
7900
|
createSummarizationMiddleware({ backend }),
|
|
7290
|
-
createPatchToolCallsMiddleware()
|
|
7291
|
-
...!forked && input.skills != null && input.skills.length > 0 ? [createSkillsMiddleware({
|
|
7292
|
-
backend,
|
|
7293
|
-
sources: input.skills
|
|
7294
|
-
})] : []
|
|
7901
|
+
createPatchToolCallsMiddleware()
|
|
7295
7902
|
];
|
|
7296
7903
|
};
|
|
7297
|
-
|
|
7904
|
+
/**
|
|
7905
|
+
* The skills middleware for a stack: the last `SkillsMiddleware` among its
|
|
7906
|
+
* custom middleware, else one built from `sources`, else none. A custom one
|
|
7907
|
+
* is picked here rather than left to replace the default by name, because
|
|
7908
|
+
* without `sources` there is no default to replace and it would run with
|
|
7909
|
+
* the novel middleware instead.
|
|
7910
|
+
*/
|
|
7911
|
+
const resolveSkillsMiddleware = (sources, custom) => {
|
|
7912
|
+
const passed = custom.findLast((entry) => entry.name === "SkillsMiddleware");
|
|
7913
|
+
if (passed !== void 0) return [passed];
|
|
7914
|
+
return sources != null && sources.length > 0 ? [createSkillsMiddleware({
|
|
7915
|
+
backend,
|
|
7916
|
+
sources
|
|
7917
|
+
})] : [];
|
|
7918
|
+
};
|
|
7919
|
+
const buildSubagentMiddleware = (input, generalPurpose = false) => {
|
|
7298
7920
|
const subagentProfile = resolveSubagentProfile(input.model);
|
|
7299
7921
|
const forked = isForkedSubAgent(input);
|
|
7300
|
-
const subagentDefaultMiddleware = createSubagentDefaultMiddleware(input, subagentProfile
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
}));
|
|
7305
|
-
let subagentMiddleware = mergeMiddlewareStack(subagentDefaultMiddleware, forked && customMiddleware.length > 0 ? mergeMiddleware$1(customMiddleware, input.middleware ?? []) : input.middleware ?? [], [
|
|
7922
|
+
const subagentDefaultMiddleware = createSubagentDefaultMiddleware(input, subagentProfile);
|
|
7923
|
+
const inputMiddleware = forked && customMiddleware.length > 0 ? mergeMiddleware$1(customMiddleware, input.middleware ?? []) : input.middleware ?? [];
|
|
7924
|
+
const subagentSkillsMiddleware = generalPurpose ? resolveSkillsMiddleware(skills, customMiddleware) : resolveSkillsMiddleware(forked ? skills : input.skills, inputMiddleware);
|
|
7925
|
+
let subagentMiddleware = mergeMiddlewareStack(subagentDefaultMiddleware, inputMiddleware, [
|
|
7306
7926
|
...resolveMiddleware(subagentProfile.extraMiddleware),
|
|
7927
|
+
...subagentSkillsMiddleware,
|
|
7307
7928
|
...cacheMiddleware,
|
|
7308
7929
|
...forked && memory != null && memory.length > 0 ? [createMemoryMiddleware({
|
|
7309
7930
|
backend,
|
|
@@ -7316,9 +7937,9 @@ function createDeepAgent(params = {}) {
|
|
|
7316
7937
|
if (!subagentProfile.excludedMiddleware.has("UnsupportedContentMiddleware") && !subagentMiddleware.some((middleware) => middleware.name === "UnsupportedContentMiddleware")) subagentMiddleware.push(createUnsupportedContentMiddleware());
|
|
7317
7938
|
return subagentMiddleware;
|
|
7318
7939
|
};
|
|
7319
|
-
const normalizeSubagentSpec = (input) => ({
|
|
7940
|
+
const normalizeSubagentSpec = (input, generalPurpose = false) => ({
|
|
7320
7941
|
...input,
|
|
7321
|
-
middleware: buildSubagentMiddleware(input)
|
|
7942
|
+
middleware: buildSubagentMiddleware(input, generalPurpose)
|
|
7322
7943
|
});
|
|
7323
7944
|
const allSubagents = subagents;
|
|
7324
7945
|
const asyncSubAgents = allSubagents.filter((item) => isAsyncSubAgent(item));
|
|
@@ -7331,16 +7952,12 @@ function createDeepAgent(params = {}) {
|
|
|
7331
7952
|
description: gpConfig?.description ?? GENERAL_PURPOSE_SUBAGENT.description,
|
|
7332
7953
|
systemPrompt: gpSystemPrompt,
|
|
7333
7954
|
model,
|
|
7334
|
-
skills,
|
|
7335
7955
|
tools: effectiveTools
|
|
7336
|
-
});
|
|
7956
|
+
}, true);
|
|
7337
7957
|
generalPurposeSpec.middleware = mergeMiddlewareStack(generalPurposeSpec.middleware ?? [], customMiddleware, [], { appendNew: false });
|
|
7338
7958
|
inlineSubagents.unshift(generalPurposeSpec);
|
|
7339
7959
|
}
|
|
7340
|
-
const skillsMiddleware = skills
|
|
7341
|
-
backend,
|
|
7342
|
-
sources: skills
|
|
7343
|
-
})] : [];
|
|
7960
|
+
const skillsMiddleware = resolveSkillsMiddleware(skills, customMiddleware);
|
|
7344
7961
|
const [fsMiddleware, subagentMiddleware, summarizationMiddleware, patchToolCallsMiddleware] = [
|
|
7345
7962
|
createFilesystemMiddleware({
|
|
7346
7963
|
backend,
|
|
@@ -7359,7 +7976,6 @@ function createDeepAgent(params = {}) {
|
|
|
7359
7976
|
createPatchToolCallsMiddleware()
|
|
7360
7977
|
];
|
|
7361
7978
|
let middleware = mergeMiddlewareStack([
|
|
7362
|
-
...skillsMiddleware,
|
|
7363
7979
|
fsMiddleware,
|
|
7364
7980
|
subagentMiddleware,
|
|
7365
7981
|
summarizationMiddleware,
|
|
@@ -7367,6 +7983,7 @@ function createDeepAgent(params = {}) {
|
|
|
7367
7983
|
...asyncSubAgents.length > 0 ? [createAsyncSubAgentMiddleware({ asyncSubAgents })] : []
|
|
7368
7984
|
], customMiddleware, [
|
|
7369
7985
|
...resolveMiddleware(harnessProfile.extraMiddleware),
|
|
7986
|
+
...skillsMiddleware,
|
|
7370
7987
|
...cacheMiddleware,
|
|
7371
7988
|
...memoryMiddleware,
|
|
7372
7989
|
...interruptOn ? [(0, langchain.humanInTheLoopMiddleware)({ interruptOn })] : []
|
|
@@ -9812,6 +10429,12 @@ Object.defineProperty(exports, "performStringReplacement", {
|
|
|
9812
10429
|
return performStringReplacement;
|
|
9813
10430
|
}
|
|
9814
10431
|
});
|
|
10432
|
+
Object.defineProperty(exports, "pinnedSkillsValue", {
|
|
10433
|
+
enumerable: true,
|
|
10434
|
+
get: function() {
|
|
10435
|
+
return pinnedSkillsValue;
|
|
10436
|
+
}
|
|
10437
|
+
});
|
|
9815
10438
|
Object.defineProperty(exports, "registerHarnessProfile", {
|
|
9816
10439
|
enumerable: true,
|
|
9817
10440
|
get: function() {
|
|
@@ -9836,6 +10459,12 @@ Object.defineProperty(exports, "serializeProfile", {
|
|
|
9836
10459
|
return serializeProfile;
|
|
9837
10460
|
}
|
|
9838
10461
|
});
|
|
10462
|
+
Object.defineProperty(exports, "setRealpath", {
|
|
10463
|
+
enumerable: true,
|
|
10464
|
+
get: function() {
|
|
10465
|
+
return setRealpath;
|
|
10466
|
+
}
|
|
10467
|
+
});
|
|
9839
10468
|
Object.defineProperty(exports, "skillsMetadataValue", {
|
|
9840
10469
|
enumerable: true,
|
|
9841
10470
|
get: function() {
|
|
@@ -9843,4 +10472,4 @@ Object.defineProperty(exports, "skillsMetadataValue", {
|
|
|
9843
10472
|
}
|
|
9844
10473
|
});
|
|
9845
10474
|
|
|
9846
|
-
//# sourceMappingURL=langsmith-
|
|
10475
|
+
//# sourceMappingURL=langsmith-DMOexB11.cjs.map
|