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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AIMessage, HumanMessage, SystemMessage, ToolMessage, anthropicPromptCachingMiddleware, bedrockPromptCachingMiddleware, context, countTokensApproximately, createAgent, createMiddleware, humanInTheLoopMiddleware, todoListMiddleware, tool } from "langchain";
|
|
2
2
|
import { ContextOverflowError } from "@langchain/core/errors";
|
|
3
|
-
import { Command, REMOVE_ALL_MESSAGES, ReducedValue, StateSchema, getConfig, getCurrentTaskInput, getStore, isCommand } from "@langchain/langgraph";
|
|
3
|
+
import { Command, Overwrite, 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 { createHash } from "node:crypto";
|
|
@@ -9,9 +9,30 @@ import * as z$2 from "zod";
|
|
|
9
9
|
import { z as z$1 } from "zod";
|
|
10
10
|
import { initChatModel } from "langchain/chat_models/universal";
|
|
11
11
|
import yaml from "yaml";
|
|
12
|
+
import { Runnable, RunnableBinding } from "@langchain/core/runnables";
|
|
13
|
+
import { isLangChainTool } from "@langchain/core/tools";
|
|
14
|
+
import { isOpenAITool } from "@langchain/core/language_models/base";
|
|
15
|
+
import { convertToOpenAIFunction } from "@langchain/core/utils/function_calling";
|
|
16
|
+
import { toJsonSchema } from "@langchain/core/utils/json_schema";
|
|
17
|
+
import { isInteropZodSchema } from "@langchain/core/utils/types";
|
|
12
18
|
import { Client } from "@langchain/langgraph-sdk";
|
|
13
19
|
import { Client as Client$1 } from "langsmith";
|
|
14
20
|
import { LangSmithResourceNotFoundError, LangSmithSandboxError, SandboxClient } from "langsmith/experimental/sandbox";
|
|
21
|
+
//#region src/backends/realpath.ts
|
|
22
|
+
const REALPATH = Symbol.for("deepagents.realpath");
|
|
23
|
+
function getRealpath(backend) {
|
|
24
|
+
if (typeof backend !== "object" || backend === null) return;
|
|
25
|
+
const fn = backend[REALPATH];
|
|
26
|
+
return typeof fn === "function" ? fn : void 0;
|
|
27
|
+
}
|
|
28
|
+
function setRealpath(target, fn) {
|
|
29
|
+
Object.defineProperty(target, REALPATH, {
|
|
30
|
+
value: fn,
|
|
31
|
+
enumerable: false,
|
|
32
|
+
configurable: true
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
15
36
|
//#region src/backends/utils.ts
|
|
16
37
|
/**
|
|
17
38
|
* Shared utility functions for memory backend implementations.
|
|
@@ -643,6 +664,8 @@ function adaptBackendProtocol(backend) {
|
|
|
643
664
|
configurable: true
|
|
644
665
|
});
|
|
645
666
|
}
|
|
667
|
+
const realpath = getRealpath(backend);
|
|
668
|
+
if (realpath !== void 0) setRealpath(adapted, realpath);
|
|
646
669
|
const resolveBackendForPath = backend.resolveBackendForPath;
|
|
647
670
|
if (typeof resolveBackendForPath === "function") Object.defineProperty(adapted, "resolveBackendForPath", {
|
|
648
671
|
value: (path) => resolveBackendForPath.call(backend, path),
|
|
@@ -1579,11 +1602,13 @@ function validatePermissionPaths(permissions) {
|
|
|
1579
1602
|
* - Non-absolute paths (must start with `/`)
|
|
1580
1603
|
* - Paths containing `..`
|
|
1581
1604
|
* - Paths containing `~`
|
|
1605
|
+
*
|
|
1606
|
+
* Empty and `.` segments are dropped (`/a/./b` and `/a//b` become `/a/b`).
|
|
1582
1607
|
*/
|
|
1583
1608
|
function validatePath(raw) {
|
|
1584
1609
|
if (typeof raw !== "string" || raw.length === 0) throw new Error("path must be a non-empty string");
|
|
1585
1610
|
if (!raw.startsWith("/")) throw new Error(`path must be absolute: ${JSON.stringify(raw)}`);
|
|
1586
|
-
const segments = raw.split("/").filter((s) => s.length > 0);
|
|
1611
|
+
const segments = raw.split("/").filter((s) => s.length > 0 && s !== ".");
|
|
1587
1612
|
if (segments.includes("..")) throw new Error(`path must not contain "..": ${JSON.stringify(raw)}`);
|
|
1588
1613
|
if (segments.includes("~")) throw new Error(`path must not contain "~": ${JSON.stringify(raw)}`);
|
|
1589
1614
|
return `/${segments.join("/")}`;
|
|
@@ -1633,14 +1658,15 @@ var CompositeBackend = class {
|
|
|
1633
1658
|
sortedRoutes;
|
|
1634
1659
|
constructor(defaultBackend, routes) {
|
|
1635
1660
|
this.default = isSandboxProtocol(defaultBackend) ? adaptSandboxProtocol(defaultBackend) : adaptBackendProtocol(defaultBackend);
|
|
1636
|
-
this.routes = Object.fromEntries(Object.entries(routes).map(([k, v]) => [k
|
|
1661
|
+
this.routes = Object.fromEntries(Object.entries(routes).map(([k, v]) => [k.endsWith("/") ? k : `${k}/`, isSandboxProtocol(v) ? adaptSandboxProtocol(v) : adaptBackendProtocol(v)]));
|
|
1637
1662
|
this.sortedRoutes = Object.entries(this.routes).sort((a, b) => b[0].length - a[0].length);
|
|
1663
|
+
setRealpath(this, (path) => this.resolveRealPath(path));
|
|
1638
1664
|
}
|
|
1639
1665
|
/** Delegates to default backend's id if it is a sandbox, otherwise empty string. */
|
|
1640
1666
|
get id() {
|
|
1641
1667
|
return isSandboxBackend(this.default) ? this.default.id : "";
|
|
1642
1668
|
}
|
|
1643
|
-
/** Route prefixes registered on this backend (e.g. `["/workspace"]`). */
|
|
1669
|
+
/** Route prefixes registered on this backend, normalized to a trailing slash (e.g. `["/workspace/"]`). */
|
|
1644
1670
|
get routePrefixes() {
|
|
1645
1671
|
return Object.keys(this.routes);
|
|
1646
1672
|
}
|
|
@@ -1653,11 +1679,29 @@ var CompositeBackend = class {
|
|
|
1653
1679
|
static isInstance(backend) {
|
|
1654
1680
|
return typeof backend === "object" && backend !== null && Array.isArray(backend.routePrefixes);
|
|
1655
1681
|
}
|
|
1656
|
-
/**
|
|
1682
|
+
/**
|
|
1683
|
+
* Returns the backend for `path`, with the matched prefix stripped (`/foo` → `/`).
|
|
1684
|
+
* Routes match on path-segment boundaries: `/foo` matches `/foo/bar` but not `/foobar`.
|
|
1685
|
+
*/
|
|
1657
1686
|
resolveBackendForPath(path) {
|
|
1658
1687
|
return this.getBackendAndKey(path);
|
|
1659
1688
|
}
|
|
1660
1689
|
/**
|
|
1690
|
+
* Resolve symlinks via the backend that owns `path`, re-adding the route
|
|
1691
|
+
* prefix. Returns `path` unchanged when that backend has no realpath hook.
|
|
1692
|
+
*/
|
|
1693
|
+
async resolveRealPath(path) {
|
|
1694
|
+
for (const [routePrefix, backend] of this.sortedRoutes) if (this.isPathWithinRoute(path, routePrefix)) {
|
|
1695
|
+
const realpath = getRealpath(backend);
|
|
1696
|
+
if (realpath === void 0) return path;
|
|
1697
|
+
const suffix = path.substring(routePrefix.length);
|
|
1698
|
+
const resolved = await realpath(suffix ? "/" + suffix : "/");
|
|
1699
|
+
return routePrefix.slice(0, -1) + resolved;
|
|
1700
|
+
}
|
|
1701
|
+
const realpath = getRealpath(this.default);
|
|
1702
|
+
return realpath === void 0 ? path : await realpath(path);
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1661
1705
|
* Determine which backend handles this key and strip prefix.
|
|
1662
1706
|
*
|
|
1663
1707
|
* @param key - Original file path
|
|
@@ -2424,25 +2468,42 @@ function getErrorMessage$1(error) {
|
|
|
2424
2468
|
return String(error);
|
|
2425
2469
|
}
|
|
2426
2470
|
/**
|
|
2427
|
-
*
|
|
2428
|
-
*
|
|
2471
|
+
* Resolve `canonical` through the backend's realpath hook, when it has one, and
|
|
2472
|
+
* re-validate the result so it is canonical for rule matching. Returns
|
|
2473
|
+
* `canonical` unchanged for backends without path aliasing.
|
|
2474
|
+
*/
|
|
2475
|
+
async function resolveRealPath(backend, canonical) {
|
|
2476
|
+
const realpath = getRealpath(backend);
|
|
2477
|
+
return realpath ? validatePath(await realpath(canonical)) : canonical;
|
|
2478
|
+
}
|
|
2479
|
+
/**
|
|
2480
|
+
* Check whether `path` is permitted under `rules` for `operation`.
|
|
2429
2481
|
*
|
|
2430
|
-
*
|
|
2431
|
-
*
|
|
2432
|
-
*
|
|
2433
|
-
*
|
|
2482
|
+
* Returns the canonical path to hand the backend, or an error string to
|
|
2483
|
+
* surface to the model. The decision covers every form of the target: the
|
|
2484
|
+
* lexical canonical path (dot and empty segments dropped) and, when the
|
|
2485
|
+
* backend has a realpath hook, the symlink-resolved path it would actually
|
|
2486
|
+
* touch. A deny on either blocks the call.
|
|
2487
|
+
*
|
|
2488
|
+
* Never throws: an invalid path (non-absolute, or containing `..` or `~`), one
|
|
2489
|
+
* the backend cannot resolve, or a denied one is a recoverable tool error, not
|
|
2490
|
+
* a fatal run-ending one. With no rules configured, `path` is returned as-is.
|
|
2434
2491
|
*
|
|
2435
2492
|
* @internal
|
|
2436
2493
|
*/
|
|
2437
|
-
function checkPermission(rules, operation, path) {
|
|
2438
|
-
if (rules.length === 0) return;
|
|
2494
|
+
async function checkPermission(backend, rules, operation, path) {
|
|
2495
|
+
if (rules.length === 0) return { path };
|
|
2439
2496
|
let canonical;
|
|
2497
|
+
let real;
|
|
2440
2498
|
try {
|
|
2441
2499
|
canonical = validatePath(path);
|
|
2500
|
+
if (decidePathAccess(rules, operation, canonical) === "deny") return { error: `Error: permission denied for ${operation} on ${canonical}` };
|
|
2501
|
+
real = await resolveRealPath(backend, canonical);
|
|
2442
2502
|
} catch (error) {
|
|
2443
|
-
return `Error: ${getErrorMessage$1(error)}
|
|
2503
|
+
return { error: `Error: ${getErrorMessage$1(error)}` };
|
|
2444
2504
|
}
|
|
2445
|
-
if (decidePathAccess(rules, operation,
|
|
2505
|
+
if (real !== canonical && decidePathAccess(rules, operation, real) === "deny") return { error: `Error: permission denied for ${operation} on ${canonical} (resolves to ${real})` };
|
|
2506
|
+
return { path: canonical };
|
|
2446
2507
|
}
|
|
2447
2508
|
/**
|
|
2448
2509
|
* Build an error {@link ToolMessage} for a rejected or denied path. Returning a
|
|
@@ -2615,27 +2676,56 @@ function parentPath(path) {
|
|
|
2615
2676
|
if (parts.length <= 1) return "/";
|
|
2616
2677
|
return `/${parts.slice(0, -1).join("/")}`;
|
|
2617
2678
|
}
|
|
2679
|
+
/**
|
|
2680
|
+
* Resolve the path a delete of `target` would actually remove. Backends unlink
|
|
2681
|
+
* a symlink leaf rather than following it, so only the parent is resolved.
|
|
2682
|
+
*/
|
|
2683
|
+
async function resolveDeleteRealPath(backend, target) {
|
|
2684
|
+
const parts = posixParts(target);
|
|
2685
|
+
if (parts.length === 0) return resolveRealPath(backend, target);
|
|
2686
|
+
return validatePath(`${await resolveRealPath(backend, parentPath(target))}/${parts[parts.length - 1]}`);
|
|
2687
|
+
}
|
|
2618
2688
|
function supportsDelete(backend) {
|
|
2619
2689
|
return typeof backend.delete === "function";
|
|
2620
2690
|
}
|
|
2621
2691
|
/**
|
|
2622
2692
|
* Filter a list of filesystem entries to those the rules permit.
|
|
2623
2693
|
*
|
|
2624
|
-
* `getPath` extracts the absolute path from each entry.
|
|
2625
|
-
*
|
|
2626
|
-
*
|
|
2694
|
+
* `getPath` extracts the absolute path from each entry. Each entry is checked
|
|
2695
|
+
* on its canonical path and, when the backend has a realpath hook, on its
|
|
2696
|
+
* symlink-resolved path. Entries with unparsable paths are included; entries
|
|
2697
|
+
* whose path cannot be resolved are dropped.
|
|
2698
|
+
* Returns the original array unchanged when `rules` is empty.
|
|
2627
2699
|
*
|
|
2628
2700
|
* @internal
|
|
2629
2701
|
*/
|
|
2630
|
-
function filterByPermissions(entries, rules, operation, getPath) {
|
|
2702
|
+
async function filterByPermissions(backend, entries, rules, operation, getPath) {
|
|
2631
2703
|
if (rules.length === 0) return entries;
|
|
2632
|
-
|
|
2704
|
+
const decisions = /* @__PURE__ */ new Map();
|
|
2705
|
+
const isPermitted = async (rawPath) => {
|
|
2706
|
+
let canonical;
|
|
2633
2707
|
try {
|
|
2634
|
-
|
|
2708
|
+
canonical = validatePath(rawPath);
|
|
2635
2709
|
} catch {
|
|
2636
2710
|
return true;
|
|
2637
2711
|
}
|
|
2638
|
-
|
|
2712
|
+
if (decidePathAccess(rules, operation, canonical) === "deny") return false;
|
|
2713
|
+
try {
|
|
2714
|
+
return decidePathAccess(rules, operation, await resolveRealPath(backend, canonical)) !== "deny";
|
|
2715
|
+
} catch {
|
|
2716
|
+
return false;
|
|
2717
|
+
}
|
|
2718
|
+
};
|
|
2719
|
+
const permitted = await Promise.all(entries.map((entry) => {
|
|
2720
|
+
const rawPath = getPath(entry);
|
|
2721
|
+
let decision = decisions.get(rawPath);
|
|
2722
|
+
if (decision === void 0) {
|
|
2723
|
+
decision = isPermitted(rawPath);
|
|
2724
|
+
decisions.set(rawPath, decision);
|
|
2725
|
+
}
|
|
2726
|
+
return decision;
|
|
2727
|
+
}));
|
|
2728
|
+
return entries.filter((_, index) => permitted[index]);
|
|
2639
2729
|
}
|
|
2640
2730
|
const LS_TOOL_DESCRIPTION = context`
|
|
2641
2731
|
Lists all files in a directory.
|
|
@@ -2723,13 +2813,13 @@ function getExecuteToolDescription(hasGrep, hasGlob) {
|
|
|
2723
2813
|
function createLsTool(backend, options) {
|
|
2724
2814
|
const { customDescription, permissions } = options;
|
|
2725
2815
|
return tool(async (input, runtime) => {
|
|
2726
|
-
const permissionError = checkPermission(permissions, "read", input.path ?? "/");
|
|
2727
|
-
if (permissionError !== void 0) return toolError(runtime, "ls", permissionError);
|
|
2728
2816
|
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
2729
2817
|
const path = input.path || "/";
|
|
2730
|
-
const
|
|
2818
|
+
const permission = await checkPermission(resolvedBackend, permissions, "read", path);
|
|
2819
|
+
if (permission.error !== void 0) return toolError(runtime, "ls", permission.error);
|
|
2820
|
+
const lsResult = await resolvedBackend.ls(permission.path);
|
|
2731
2821
|
if (lsResult.error) return `Error listing files: ${lsResult.error}`;
|
|
2732
|
-
const infos = filterByPermissions(lsResult.files ?? [], permissions, "read", (info) => info.path);
|
|
2822
|
+
const infos = await filterByPermissions(resolvedBackend, lsResult.files ?? [], permissions, "read", (info) => info.path);
|
|
2733
2823
|
if (infos.length === 0) return `No files found in ${path}`;
|
|
2734
2824
|
const lines = [];
|
|
2735
2825
|
for (const info of infos) if (info.is_dir) lines.push(`${info.path} (directory)`);
|
|
@@ -2752,23 +2842,17 @@ function createLsTool(backend, options) {
|
|
|
2752
2842
|
function createReadFileTool(backend, options) {
|
|
2753
2843
|
const { customDescription, toolTokenLimitBeforeEvict, permissions } = options;
|
|
2754
2844
|
return tool(async (input, runtime) => {
|
|
2755
|
-
const permissionError = checkPermission(permissions, "read", input.file_path);
|
|
2756
|
-
if (permissionError !== void 0) return toolError(runtime, "read_file", permissionError);
|
|
2757
2845
|
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
2846
|
+
const permission = await checkPermission(resolvedBackend, permissions, "read", input.file_path);
|
|
2847
|
+
if (permission.error !== void 0) return toolError(runtime, "read_file", permission.error);
|
|
2758
2848
|
const { file_path, offset: requestedOffset = 0, limit: requestedLimit = 100 } = input;
|
|
2759
2849
|
const { offset, limit } = normalizeReadPagination(requestedOffset, requestedLimit);
|
|
2760
|
-
const readResult = await resolvedBackend.read(
|
|
2761
|
-
if (readResult.error) return
|
|
2762
|
-
type: "text",
|
|
2763
|
-
text: `Error: ${readResult.error}`
|
|
2764
|
-
}];
|
|
2850
|
+
const readResult = await resolvedBackend.read(permission.path, offset, limit);
|
|
2851
|
+
if (readResult.error) return toolError(runtime, "read_file", `Error: ${readResult.error}`);
|
|
2765
2852
|
const mimeType = readResult.mimeType ?? getMimeType(file_path);
|
|
2766
2853
|
if (!isTextMimeType(mimeType)) {
|
|
2767
2854
|
const binaryContent = readResult.content;
|
|
2768
|
-
if (!binaryContent) return
|
|
2769
|
-
type: "text",
|
|
2770
|
-
text: `Error: expected binary content for '${file_path}'`
|
|
2771
|
-
}];
|
|
2855
|
+
if (!binaryContent) return toolError(runtime, "read_file", `Error: expected binary content for '${file_path}'`);
|
|
2772
2856
|
let base64Data;
|
|
2773
2857
|
if (typeof binaryContent === "string") base64Data = binaryContent;
|
|
2774
2858
|
else if (ArrayBuffer.isView(binaryContent)) base64Data = Buffer.from(binaryContent).toString("base64");
|
|
@@ -2777,10 +2861,7 @@ function createReadFileTool(backend, options) {
|
|
|
2777
2861
|
base64Data = Buffer.from(new Uint8Array(values)).toString("base64");
|
|
2778
2862
|
}
|
|
2779
2863
|
const sizeBytes = Math.ceil(base64Data.length * 3 / 4);
|
|
2780
|
-
if (sizeBytes > 10485760) return
|
|
2781
|
-
type: "text",
|
|
2782
|
-
text: `Error: file too large to read (${Math.round(sizeBytes / 1048576)}MB exceeds ${MAX_BINARY_READ_SIZE_BYTES / 1048576}MB limit for binary files)`
|
|
2783
|
-
}];
|
|
2864
|
+
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)`);
|
|
2784
2865
|
if (mimeType.startsWith("image/")) return [{
|
|
2785
2866
|
type: "image",
|
|
2786
2867
|
mimeType,
|
|
@@ -2846,11 +2927,11 @@ function createReadFileTool(backend, options) {
|
|
|
2846
2927
|
function createWriteFileTool(backend, options) {
|
|
2847
2928
|
const { customDescription, permissions } = options;
|
|
2848
2929
|
return tool(async (input, runtime) => {
|
|
2849
|
-
const permissionError = checkPermission(permissions, "write", input.file_path);
|
|
2850
|
-
if (permissionError !== void 0) return toolError(runtime, "write_file", permissionError);
|
|
2851
2930
|
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
2931
|
+
const permission = await checkPermission(resolvedBackend, permissions, "write", input.file_path);
|
|
2932
|
+
if (permission.error !== void 0) return toolError(runtime, "write_file", permission.error);
|
|
2852
2933
|
const { file_path, content } = input;
|
|
2853
|
-
const result = await resolvedBackend.write(
|
|
2934
|
+
const result = await resolvedBackend.write(permission.path, content);
|
|
2854
2935
|
if (result.error) return result.error;
|
|
2855
2936
|
const message = new ToolMessage({
|
|
2856
2937
|
content: `Successfully wrote to '${file_path}'`,
|
|
@@ -2878,11 +2959,11 @@ function createWriteFileTool(backend, options) {
|
|
|
2878
2959
|
function createEditFileTool(backend, options) {
|
|
2879
2960
|
const { customDescription, permissions } = options;
|
|
2880
2961
|
return tool(async (input, runtime) => {
|
|
2881
|
-
const permissionError = checkPermission(permissions, "write", input.file_path);
|
|
2882
|
-
if (permissionError !== void 0) return toolError(runtime, "edit_file", permissionError);
|
|
2883
2962
|
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
2963
|
+
const permission = await checkPermission(resolvedBackend, permissions, "write", input.file_path);
|
|
2964
|
+
if (permission.error !== void 0) return toolError(runtime, "edit_file", permission.error);
|
|
2884
2965
|
const { file_path, old_string, new_string, replace_all = false } = input;
|
|
2885
|
-
const result = await resolvedBackend.edit(
|
|
2966
|
+
const result = await resolvedBackend.edit(permission.path, old_string, new_string, replace_all);
|
|
2886
2967
|
if (result.error) return result.error;
|
|
2887
2968
|
const message = new ToolMessage({
|
|
2888
2969
|
content: `Successfully replaced ${result.occurrences} occurrence(s) in '${file_path}'`,
|
|
@@ -2922,6 +3003,17 @@ function createDeleteTool(backend, options) {
|
|
|
2922
3003
|
const hasDescendants = await deleteTargetMayHaveDescendants(resolvedBackend, validatedPath, permissions.length > 0);
|
|
2923
3004
|
const denyingPatterns = findDeleteDenyPatterns(permissions, validatedPath, hasDescendants);
|
|
2924
3005
|
if (denyingPatterns.length > 0) return toolError(runtime, "delete", `Error: permission denied for write on ${validatedPath} (matches deny rule(s): ${denyingPatterns.join(", ")})`);
|
|
3006
|
+
if (permissions.length > 0) {
|
|
3007
|
+
let realTarget;
|
|
3008
|
+
try {
|
|
3009
|
+
realTarget = await resolveDeleteRealPath(resolvedBackend, validatedPath);
|
|
3010
|
+
} catch (error) {
|
|
3011
|
+
return toolError(runtime, "delete", `Error: ${getErrorMessage$1(error)}`);
|
|
3012
|
+
}
|
|
3013
|
+
if (realTarget !== validatedPath) {
|
|
3014
|
+
if (findDeleteDenyPatterns(permissions, realTarget, hasDescendants).length > 0) return toolError(runtime, "delete", `Error: permission denied for write on ${validatedPath} (resolves to ${realTarget})`);
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
2925
3017
|
if (!supportsDelete(resolvedBackend)) return toolError(runtime, "delete", `Error: deletion is not available for '${validatedPath}'.`);
|
|
2926
3018
|
const result = await resolvedBackend.delete(validatedPath);
|
|
2927
3019
|
if (result.error) return toolError(runtime, "delete", result.error);
|
|
@@ -2948,13 +3040,13 @@ function createDeleteTool(backend, options) {
|
|
|
2948
3040
|
function createGlobTool(backend, options) {
|
|
2949
3041
|
const { customDescription, permissions } = options;
|
|
2950
3042
|
return tool(async (input, runtime) => {
|
|
2951
|
-
const permissionError = checkPermission(permissions, "read", input.path ?? "/");
|
|
2952
|
-
if (permissionError !== void 0) return toolError(runtime, "glob", permissionError);
|
|
2953
3043
|
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
2954
|
-
const { pattern
|
|
2955
|
-
const
|
|
3044
|
+
const { pattern } = input;
|
|
3045
|
+
const permission = await checkPermission(resolvedBackend, permissions, "read", input.path ?? "/");
|
|
3046
|
+
if (permission.error !== void 0) return toolError(runtime, "glob", permission.error);
|
|
3047
|
+
const globResult = await resolvedBackend.glob(pattern, input.path === void 0 ? void 0 : permission.path);
|
|
2956
3048
|
if (globResult.error) return `Error finding files: ${globResult.error}`;
|
|
2957
|
-
const infos = filterByPermissions(globResult.files ?? [], permissions, "read", (info) => info.path);
|
|
3049
|
+
const infos = await filterByPermissions(resolvedBackend, globResult.files ?? [], permissions, "read", (info) => info.path);
|
|
2958
3050
|
if (infos.length === 0) return `No files found matching pattern '${pattern}'`;
|
|
2959
3051
|
const result = truncateIfTooLong(infos.map((info) => info.path));
|
|
2960
3052
|
if (Array.isArray(result)) return result.join("\n");
|
|
@@ -2974,14 +3066,14 @@ function createGlobTool(backend, options) {
|
|
|
2974
3066
|
function createGrepTool(backend, options) {
|
|
2975
3067
|
const { customDescription, permissions, includeExecution, grepMaxCount } = options;
|
|
2976
3068
|
return tool(async (input, runtime) => {
|
|
2977
|
-
const permissionError = checkPermission(permissions, "read", input.path ?? "/");
|
|
2978
|
-
if (permissionError !== void 0) return toolError(runtime, "grep", permissionError);
|
|
2979
3069
|
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
2980
3070
|
const { pattern, path = "/", glob = null, output_mode = "content" } = input;
|
|
3071
|
+
const permission = await checkPermission(resolvedBackend, permissions, "read", path);
|
|
3072
|
+
if (permission.error !== void 0) return toolError(runtime, "grep", permission.error);
|
|
2981
3073
|
const maxCount = input.max_count ?? grepMaxCount;
|
|
2982
|
-
const result = await resolvedBackend.grep(pattern, path, glob, maxCount);
|
|
3074
|
+
const result = await resolvedBackend.grep(pattern, permission.path, glob, maxCount);
|
|
2983
3075
|
if (result.error) return result.error;
|
|
2984
|
-
const matches = filterByPermissions(result.matches ?? [], permissions, "read", (m) => m.path);
|
|
3076
|
+
const matches = await filterByPermissions(resolvedBackend, result.matches ?? [], permissions, "read", (m) => m.path);
|
|
2985
3077
|
if (matches.length === 0) return `No matches found for pattern '${pattern}'`;
|
|
2986
3078
|
const truncated = truncateIfTooLong(formatGrepMatches(matches, output_mode));
|
|
2987
3079
|
let content = typeof truncated === "string" ? truncated : truncated.join("\n");
|
|
@@ -4173,10 +4265,12 @@ const EXCLUDED_STATE_KEYS = [
|
|
|
4173
4265
|
"todos",
|
|
4174
4266
|
"structuredResponse",
|
|
4175
4267
|
"skillsMetadata",
|
|
4268
|
+
"pinnedSkills",
|
|
4176
4269
|
"memoryContents",
|
|
4177
4270
|
...CALL_COUNT_STATE_KEYS,
|
|
4178
4271
|
"_summarizationEvent",
|
|
4179
4272
|
"_summarizationSessionId",
|
|
4273
|
+
"_skillToolsDisclosed",
|
|
4180
4274
|
FORKED_CONTEXT_KEY
|
|
4181
4275
|
];
|
|
4182
4276
|
/**
|
|
@@ -4195,9 +4289,11 @@ const EXCLUDED_STATE_KEYS = [
|
|
|
4195
4289
|
*/
|
|
4196
4290
|
const FORK_EXCLUDED_STATE_KEYS = [
|
|
4197
4291
|
"structuredResponse",
|
|
4292
|
+
"pinnedSkills",
|
|
4198
4293
|
...CALL_COUNT_STATE_KEYS,
|
|
4199
4294
|
"_summarizationEvent",
|
|
4200
|
-
"_summarizationSessionId"
|
|
4295
|
+
"_summarizationSessionId",
|
|
4296
|
+
"_skillToolsDisclosed"
|
|
4201
4297
|
];
|
|
4202
4298
|
/**
|
|
4203
4299
|
* Default description for the general-purpose subagent.
|
|
@@ -4640,7 +4736,8 @@ function patchDanglingToolCalls(messages) {
|
|
|
4640
4736
|
patchedMessages.push(new ToolMessage({
|
|
4641
4737
|
content: toolMsg,
|
|
4642
4738
|
name: toolCall.name,
|
|
4643
|
-
tool_call_id: toolCall.id
|
|
4739
|
+
tool_call_id: toolCall.id,
|
|
4740
|
+
status: "error"
|
|
4644
4741
|
}));
|
|
4645
4742
|
}
|
|
4646
4743
|
}
|
|
@@ -5082,6 +5179,46 @@ function createMemoryMiddleware(options) {
|
|
|
5082
5179
|
});
|
|
5083
5180
|
}
|
|
5084
5181
|
//#endregion
|
|
5182
|
+
//#region src/errors.ts
|
|
5183
|
+
const CONFIGURATION_ERROR_SYMBOL = Symbol.for("deepagents.configuration_error");
|
|
5184
|
+
/**
|
|
5185
|
+
* Thrown when `createDeepAgent` receives invalid configuration.
|
|
5186
|
+
*
|
|
5187
|
+
* Follows the same pattern as {@link SandboxError}: a human-readable
|
|
5188
|
+
* `message`, a structured `code` for programmatic handling, and a
|
|
5189
|
+
* static `isInstance` guard that works across realms.
|
|
5190
|
+
*
|
|
5191
|
+
* @example
|
|
5192
|
+
* ```typescript
|
|
5193
|
+
* try {
|
|
5194
|
+
* createDeepAgent({ tools: [myTool] });
|
|
5195
|
+
* } catch (error) {
|
|
5196
|
+
* if (ConfigurationError.isInstance(error)) {
|
|
5197
|
+
* switch (error.code) {
|
|
5198
|
+
* case "TOOL_NAME_COLLISION":
|
|
5199
|
+
* console.error("Rename your tool:", error.message);
|
|
5200
|
+
* break;
|
|
5201
|
+
* }
|
|
5202
|
+
* }
|
|
5203
|
+
* }
|
|
5204
|
+
* ```
|
|
5205
|
+
*/
|
|
5206
|
+
var ConfigurationError = class ConfigurationError extends Error {
|
|
5207
|
+
code;
|
|
5208
|
+
cause;
|
|
5209
|
+
[CONFIGURATION_ERROR_SYMBOL] = true;
|
|
5210
|
+
name = "ConfigurationError";
|
|
5211
|
+
constructor(message, code, cause) {
|
|
5212
|
+
super(message);
|
|
5213
|
+
this.code = code;
|
|
5214
|
+
this.cause = cause;
|
|
5215
|
+
Object.setPrototypeOf(this, ConfigurationError.prototype);
|
|
5216
|
+
}
|
|
5217
|
+
static isInstance(error) {
|
|
5218
|
+
return typeof error === "object" && error !== null && error[CONFIGURATION_ERROR_SYMBOL] === true;
|
|
5219
|
+
}
|
|
5220
|
+
};
|
|
5221
|
+
//#endregion
|
|
5085
5222
|
//#region src/middleware/skills.ts
|
|
5086
5223
|
/**
|
|
5087
5224
|
* Backend-agnostic skills middleware for loading agent skills from any backend.
|
|
@@ -5141,6 +5278,10 @@ const SKILL_MODULE_EXTENSIONS = [
|
|
|
5141
5278
|
".jsx",
|
|
5142
5279
|
".tsx"
|
|
5143
5280
|
];
|
|
5281
|
+
/** `SKILL.md` frontmatter `metadata` key listing the skill's include names, space-separated. */
|
|
5282
|
+
const INCLUDE_TOOLS_KEY = "include_tools";
|
|
5283
|
+
/** YAML frontmatter between `---` delimiters at the start of a `SKILL.md`. */
|
|
5284
|
+
const FRONTMATTER_PATTERN = /^---\s*\n([\s\S]*?)\n---\s*\n/;
|
|
5144
5285
|
/**
|
|
5145
5286
|
* Zod schema for a single skill metadata entry.
|
|
5146
5287
|
*/
|
|
@@ -5182,11 +5323,52 @@ const SkillMetadataEntrySchema = z$1.object({
|
|
|
5182
5323
|
*/
|
|
5183
5324
|
const skillsMetadataValue = z$1.array(SkillMetadataEntrySchema).nullish();
|
|
5184
5325
|
/**
|
|
5326
|
+
* State value for a middleware's `pinnedSkills` field: the names of skills to
|
|
5327
|
+
* pin.
|
|
5328
|
+
*
|
|
5329
|
+
* The next time the skills middleware's `beforeModel` runs, before a model
|
|
5330
|
+
* call, each named skill's `SKILL.md`, without its frontmatter, is appended to
|
|
5331
|
+
* the conversation as its own `HumanMessage`, in the order named. The message
|
|
5332
|
+
* is a snapshot: editing the file later doesn't change it, and naming the
|
|
5333
|
+
* skill again appends a new copy.
|
|
5334
|
+
*
|
|
5335
|
+
* Names only, matched exactly against the loaded skills. A name that isn't
|
|
5336
|
+
* loaded, or whose `SKILL.md` can't be read, is skipped without an error. The
|
|
5337
|
+
* key is cleared once consumed, so a later run pins nothing unless it names
|
|
5338
|
+
* skills again. Writes in the same step add up, so parallel tool calls can
|
|
5339
|
+
* each pin skills by returning a `Command`.
|
|
5340
|
+
*
|
|
5341
|
+
* Like {@link skillsMetadataValue}, treat the value as opaque. A middleware
|
|
5342
|
+
* declares it on its state schema to set `pinnedSkills` from its own hooks.
|
|
5343
|
+
*
|
|
5344
|
+
* @example
|
|
5345
|
+
* ```typescript
|
|
5346
|
+
* import { createMiddleware } from "langchain";
|
|
5347
|
+
* import { StateSchema } from "@langchain/langgraph";
|
|
5348
|
+
* import { pinnedSkillsValue } from "deepagents";
|
|
5349
|
+
*
|
|
5350
|
+
* const routeSkills = createMiddleware({
|
|
5351
|
+
* name: "RouteSkills",
|
|
5352
|
+
* stateSchema: new StateSchema({ pinnedSkills: pinnedSkillsValue }),
|
|
5353
|
+
* beforeAgent: (state) => ({ pinnedSkills: skillsFor(state.messages) }),
|
|
5354
|
+
* });
|
|
5355
|
+
* ```
|
|
5356
|
+
*/
|
|
5357
|
+
const pinnedSkillsValue = new ReducedValue(z$1.array(z$1.string()).default(() => []), { reducer: (stored, written) => [...stored ?? [], ...written ?? []] });
|
|
5358
|
+
/**
|
|
5185
5359
|
* State schema for skills middleware.
|
|
5186
5360
|
*/
|
|
5187
5361
|
const SkillsStateSchema = new StateSchema({
|
|
5188
5362
|
skillsMetadata: skillsMetadataValue,
|
|
5189
|
-
|
|
5363
|
+
pinnedSkills: pinnedSkillsValue,
|
|
5364
|
+
files: filesValue,
|
|
5365
|
+
/**
|
|
5366
|
+
* The skill tools disclosed to the latest model call, each mapped to the
|
|
5367
|
+
* include name that produced it, for the tool-time gate. Written on every
|
|
5368
|
+
* model call of a skills middleware with skill tools. The last write wins,
|
|
5369
|
+
* so a model call retried within one step can write it again.
|
|
5370
|
+
*/
|
|
5371
|
+
_skillToolsDisclosed: new ReducedValue(z$1.record(z$1.string(), z$1.string()).optional(), { reducer: (_previous, next) => next })
|
|
5190
5372
|
});
|
|
5191
5373
|
/**
|
|
5192
5374
|
* Skills System Documentation prompt template.
|
|
@@ -5288,7 +5470,8 @@ function validateSkillName(name, directoryName) {
|
|
|
5288
5470
|
*
|
|
5289
5471
|
* YAML parsing can return any type for the `metadata` key. This ensures the
|
|
5290
5472
|
* value in {@link SkillMetadata} is always a `Record<string, string>` by
|
|
5291
|
-
* coercing via `String()` and rejecting non-object inputs.
|
|
5473
|
+
* coercing via `String()` and rejecting non-object inputs. It also warns when
|
|
5474
|
+
* `include_tools` isn't a space-separated string of tool names.
|
|
5292
5475
|
*
|
|
5293
5476
|
* @param raw - Raw value from `frontmatterData.metadata`.
|
|
5294
5477
|
* @param skillPath - Path to the `SKILL.md` file (for warning messages).
|
|
@@ -5299,6 +5482,8 @@ function validateMetadata(raw, skillPath) {
|
|
|
5299
5482
|
if (raw) console.warn(`Ignoring non-object metadata in ${skillPath} (got ${typeof raw})`);
|
|
5300
5483
|
return {};
|
|
5301
5484
|
}
|
|
5485
|
+
const includeTools = raw[INCLUDE_TOOLS_KEY];
|
|
5486
|
+
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)}`);
|
|
5302
5487
|
const result = {};
|
|
5303
5488
|
for (const [k, v] of Object.entries(raw)) result[String(k)] = String(v);
|
|
5304
5489
|
return result;
|
|
@@ -5336,7 +5521,7 @@ function parseSkillMetadataFromContent(content, skillPath, directoryName) {
|
|
|
5336
5521
|
console.warn(`Skipping ${skillPath}: content too large (${content.length} bytes)`);
|
|
5337
5522
|
return null;
|
|
5338
5523
|
}
|
|
5339
|
-
const match = content.match(
|
|
5524
|
+
const match = content.match(FRONTMATTER_PATTERN);
|
|
5340
5525
|
if (!match) {
|
|
5341
5526
|
console.warn(`Skipping ${skillPath}: no valid YAML frontmatter found`);
|
|
5342
5527
|
return null;
|
|
@@ -5455,6 +5640,61 @@ async function listSkillsFromBackend(backend, sourcePath) {
|
|
|
5455
5640
|
return skills;
|
|
5456
5641
|
}
|
|
5457
5642
|
/**
|
|
5643
|
+
* Load the skills in every source, in order. A later source's skill replaces
|
|
5644
|
+
* an earlier one of the same name.
|
|
5645
|
+
*/
|
|
5646
|
+
async function loadSkills(backend, sources) {
|
|
5647
|
+
const allSkills = /* @__PURE__ */ new Map();
|
|
5648
|
+
for (const sourcePath of sources) try {
|
|
5649
|
+
const skills = await listSkillsFromBackend(backend, sourcePath);
|
|
5650
|
+
for (const skill of skills) allSkills.set(skill.name, skill);
|
|
5651
|
+
} catch (error) {
|
|
5652
|
+
console.debug(`[BackendSkillsMiddleware] Failed to load skills from ${sourcePath}:`, error);
|
|
5653
|
+
}
|
|
5654
|
+
return Array.from(allSkills.values());
|
|
5655
|
+
}
|
|
5656
|
+
/**
|
|
5657
|
+
* Return a message pinning each skill `names` refers to, in first-seen order.
|
|
5658
|
+
*
|
|
5659
|
+
* Skips a name that isn't in `skills`, and a skill whose `SKILL.md` is
|
|
5660
|
+
* missing, unreadable, empty, or larger than {@link MAX_SKILL_FILE_SIZE}.
|
|
5661
|
+
*/
|
|
5662
|
+
async function buildPinnedSkillMessages(backend, names, skills) {
|
|
5663
|
+
const adaptedBackend = adaptBackendProtocol(backend);
|
|
5664
|
+
const skillsByName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
5665
|
+
const skillsToPin = [...new Set(names)].flatMap((name) => {
|
|
5666
|
+
const skill = skillsByName.get(name);
|
|
5667
|
+
return skill === void 0 ? [] : [skill];
|
|
5668
|
+
});
|
|
5669
|
+
const contents = await Promise.all(skillsToPin.map((skill) => readFileFromBackend(adaptedBackend, skill.path).catch(() => null)));
|
|
5670
|
+
return skillsToPin.flatMap((skill, i) => {
|
|
5671
|
+
const content = contents[i];
|
|
5672
|
+
if (!content || content.length > 10485760) return [];
|
|
5673
|
+
return [buildPinnedSkillMessage(skill, content)];
|
|
5674
|
+
});
|
|
5675
|
+
}
|
|
5676
|
+
/** Return the message pinning `skill`, carrying its `SKILL.md` `content` without frontmatter. */
|
|
5677
|
+
function buildPinnedSkillMessage(skill, content) {
|
|
5678
|
+
const body = content.replace(FRONTMATTER_PATTERN, "").trimEnd();
|
|
5679
|
+
const name = escapeXmlAttribute(skill.name);
|
|
5680
|
+
const path = escapeXmlAttribute(skill.path);
|
|
5681
|
+
return new HumanMessage$1({
|
|
5682
|
+
content: `<skill name="${name}" path="${path}">\n${body}\n</skill>`,
|
|
5683
|
+
additional_kwargs: {
|
|
5684
|
+
lc_source: "pinned_skill",
|
|
5685
|
+
skill: {
|
|
5686
|
+
name: skill.name,
|
|
5687
|
+
path: skill.path,
|
|
5688
|
+
description: skill.description
|
|
5689
|
+
}
|
|
5690
|
+
}
|
|
5691
|
+
});
|
|
5692
|
+
}
|
|
5693
|
+
/** Escape `value` for a double-quoted XML attribute. */
|
|
5694
|
+
function escapeXmlAttribute(value) {
|
|
5695
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5696
|
+
}
|
|
5697
|
+
/**
|
|
5458
5698
|
* Format skills locations for display in system prompt.
|
|
5459
5699
|
* Shows priority indicator for the last source (highest priority).
|
|
5460
5700
|
*/
|
|
@@ -5514,6 +5754,336 @@ function validateModulePath(raw) {
|
|
|
5514
5754
|
if (!endsWithModuleExtension(normalized)) return;
|
|
5515
5755
|
return normalized;
|
|
5516
5756
|
}
|
|
5757
|
+
/** Tool `extras` key (and provider field) that withholds a tool's schema until searched for. */
|
|
5758
|
+
const DEFER_LOADING = "defer_loading";
|
|
5759
|
+
/**
|
|
5760
|
+
* Model ID prefixes that accept an inline `tool_definition` mid-conversation.
|
|
5761
|
+
*
|
|
5762
|
+
* `@langchain/anthropic` sends any mid-conversation system message in place,
|
|
5763
|
+
* without gating on the model, so this list is the only guard.
|
|
5764
|
+
*/
|
|
5765
|
+
const ANTHROPIC_INLINE_TOOL_MODELS = [
|
|
5766
|
+
"claude-opus-5",
|
|
5767
|
+
"claude-fable-5",
|
|
5768
|
+
"claude-mythos-5",
|
|
5769
|
+
"claude-opus-4-8"
|
|
5770
|
+
];
|
|
5771
|
+
/** Models whose Responses API accepts an `additional_tools` input item, matched with or without a `-` suffix such as a snapshot date. */
|
|
5772
|
+
const OPENAI_INLINE_TOOL_MODELS = ["gpt-6", "gpt-5.6"];
|
|
5773
|
+
/** Root `input_schema` keys the Anthropic API rejects, failing the whole request. */
|
|
5774
|
+
const ANTHROPIC_ROOT_COMBINATORS = [
|
|
5775
|
+
"oneOf",
|
|
5776
|
+
"anyOf",
|
|
5777
|
+
"allOf"
|
|
5778
|
+
];
|
|
5779
|
+
/** `extras` keys `ChatAnthropic` copies into a tool's definition, bar the ones a disclosure must never carry. */
|
|
5780
|
+
const ANTHROPIC_DEFINITION_EXTRAS = [
|
|
5781
|
+
"input_examples",
|
|
5782
|
+
"allowed_callers",
|
|
5783
|
+
"strict"
|
|
5784
|
+
];
|
|
5785
|
+
/**
|
|
5786
|
+
* Return the resolver the skills middleware's `tools` option stands for.
|
|
5787
|
+
*
|
|
5788
|
+
* A list is validated, then resolved by exact name.
|
|
5789
|
+
*
|
|
5790
|
+
* @throws {ConfigurationError} If `tools` is neither a list nor a function,
|
|
5791
|
+
* or a list entry isn't a client tool or repeats a name.
|
|
5792
|
+
*/
|
|
5793
|
+
function toSkillToolResolver(tools) {
|
|
5794
|
+
if (tools != null && !Array.isArray(tools)) {
|
|
5795
|
+
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");
|
|
5796
|
+
return tools;
|
|
5797
|
+
}
|
|
5798
|
+
const entries = tools ?? [];
|
|
5799
|
+
if (!entries.every(isClientTool)) throw new ConfigurationError("tools entries must be client tools; provider-native tool objects are not supported", "SKILL_TOOLS_UNSUPPORTED_TYPE");
|
|
5800
|
+
const names = entries.map((t) => t.name);
|
|
5801
|
+
const duplicates = [...new Set(names.filter((n, i) => names.indexOf(n) !== i))].sort();
|
|
5802
|
+
if (duplicates.length > 0) throw new ConfigurationError(`tools contains duplicate tool name(s): ${duplicates.join(", ")}`, "SKILL_TOOLS_DUPLICATE_NAME");
|
|
5803
|
+
const byName = new Map(entries.map((t) => [t.name, t]));
|
|
5804
|
+
return (name) => byName.has(name) ? [byName.get(name)] : [];
|
|
5805
|
+
}
|
|
5806
|
+
/** Whether `value` is a runnable client tool rather than a provider-native or plain object. */
|
|
5807
|
+
function isClientTool(value) {
|
|
5808
|
+
return Runnable.isRunnable(value) && typeof value.name === "string";
|
|
5809
|
+
}
|
|
5810
|
+
/**
|
|
5811
|
+
* Call `resolver` for one include name, keeping the first of each tool name.
|
|
5812
|
+
*
|
|
5813
|
+
* @throws {TypeError} If the resolver returns something other than an array
|
|
5814
|
+
* of client tools.
|
|
5815
|
+
*/
|
|
5816
|
+
async function callSkillToolResolver(resolver, includeName, runtime) {
|
|
5817
|
+
const result = await resolver(includeName, runtime);
|
|
5818
|
+
if (!Array.isArray(result)) throw new TypeError(`skill tool resolver must return an array of tools for '${includeName}', got ${typeof result}`);
|
|
5819
|
+
const tools = /* @__PURE__ */ new Map();
|
|
5820
|
+
for (const item of result) {
|
|
5821
|
+
if (!isClientTool(item)) throw new TypeError(`skill tool resolver returned a non-tool ${typeof item} for '${includeName}'; expected tool instances`);
|
|
5822
|
+
if (!tools.has(item.name)) tools.set(item.name, item);
|
|
5823
|
+
}
|
|
5824
|
+
return [...tools.values()];
|
|
5825
|
+
}
|
|
5826
|
+
/**
|
|
5827
|
+
* Return every successful `read_file` or pin of a `SKILL.md` that lists
|
|
5828
|
+
* tools, in message order.
|
|
5829
|
+
*
|
|
5830
|
+
* The read's or pin's path must equal the skill's path exactly, as the skills
|
|
5831
|
+
* listing shows it. Any `offset` or `limit` counts, and so does a result whose
|
|
5832
|
+
* content was later truncated or compacted, since only the call and the
|
|
5833
|
+
* result's status are read.
|
|
5834
|
+
*/
|
|
5835
|
+
function findSkillReads(messages, skills) {
|
|
5836
|
+
const skillsByPath = /* @__PURE__ */ new Map();
|
|
5837
|
+
for (const skill of skills) {
|
|
5838
|
+
const value = skill.metadata?.[INCLUDE_TOOLS_KEY];
|
|
5839
|
+
const includeNames = typeof value === "string" ? value.split(/\s+/).filter(Boolean) : [];
|
|
5840
|
+
if (includeNames.length > 0) skillsByPath.set(skill.path, { includeNames });
|
|
5841
|
+
}
|
|
5842
|
+
if (skillsByPath.size === 0) return [];
|
|
5843
|
+
const readPaths = /* @__PURE__ */ new Map();
|
|
5844
|
+
for (const message of messages) {
|
|
5845
|
+
if (!AIMessage$1.isInstance(message)) continue;
|
|
5846
|
+
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);
|
|
5847
|
+
}
|
|
5848
|
+
const reads = [];
|
|
5849
|
+
messages.forEach((message, index) => {
|
|
5850
|
+
let path;
|
|
5851
|
+
if (ToolMessage$1.isInstance(message) && message.status !== "error") path = readPaths.get(message.tool_call_id);
|
|
5852
|
+
else if (HumanMessage$1.isInstance(message) && message.additional_kwargs?.lc_source === "pinned_skill") path = message.additional_kwargs.skill?.path;
|
|
5853
|
+
else return;
|
|
5854
|
+
const skill = typeof path === "string" ? skillsByPath.get(path) : void 0;
|
|
5855
|
+
if (skill !== void 0) reads.push({
|
|
5856
|
+
index,
|
|
5857
|
+
...skill
|
|
5858
|
+
});
|
|
5859
|
+
});
|
|
5860
|
+
return reads;
|
|
5861
|
+
}
|
|
5862
|
+
/** Return a request tool's name, whether it is a tool instance or a provider object. */
|
|
5863
|
+
function toolName(tool) {
|
|
5864
|
+
if (isOpenAITool(tool)) return tool.function.name;
|
|
5865
|
+
return typeof tool.name === "string" ? tool.name : void 0;
|
|
5866
|
+
}
|
|
5867
|
+
/**
|
|
5868
|
+
* Disclose the tools the read skills' include names produce to one model call.
|
|
5869
|
+
*
|
|
5870
|
+
* Returns the request to send, and the record of the skill tools it
|
|
5871
|
+
* discloses, for the tool-time gate.
|
|
5872
|
+
*/
|
|
5873
|
+
async function discloseSkillTools(request, reads, resolver) {
|
|
5874
|
+
const requestTools = /* @__PURE__ */ new Map();
|
|
5875
|
+
for (const tool of request.tools) {
|
|
5876
|
+
const name = toolName(tool);
|
|
5877
|
+
if (name !== void 0) requestTools.set(name, tool);
|
|
5878
|
+
}
|
|
5879
|
+
const names = [...new Set(reads.flatMap((read) => read.includeNames.filter((name) => !requestTools.has(name))))];
|
|
5880
|
+
const tools = await Promise.all(names.map((name) => callSkillToolResolver(resolver, name, request.runtime)));
|
|
5881
|
+
const disclosed = planDisclosure(reads, requestTools, new Map(names.map((name, i) => [name, tools[i]])));
|
|
5882
|
+
if (disclosed.size === 0) return {
|
|
5883
|
+
request,
|
|
5884
|
+
record: {}
|
|
5885
|
+
};
|
|
5886
|
+
const chatModel = await resolveChatModel(request.model, request.runtime);
|
|
5887
|
+
if (llmType(chatModel) === "anthropic") withholdRootCombinatorTools(disclosed);
|
|
5888
|
+
const gatedNames = [...disclosed.keys()].filter((name) => disclosed.get(name).includeName !== void 0).sort();
|
|
5889
|
+
const record = Object.fromEntries(gatedNames.map((name) => [name, disclosed.get(name).includeName]));
|
|
5890
|
+
if (disclosed.size === 0) return {
|
|
5891
|
+
request,
|
|
5892
|
+
record
|
|
5893
|
+
};
|
|
5894
|
+
const build = inlineBlockBuilder(chatModel);
|
|
5895
|
+
if (build !== void 0) return {
|
|
5896
|
+
request: {
|
|
5897
|
+
...request,
|
|
5898
|
+
messages: discloseInMessages(request.messages, disclosed, build)
|
|
5899
|
+
},
|
|
5900
|
+
record
|
|
5901
|
+
};
|
|
5902
|
+
return {
|
|
5903
|
+
request: {
|
|
5904
|
+
...request,
|
|
5905
|
+
tools: discloseInTools(request.tools, disclosed, gatedNames)
|
|
5906
|
+
},
|
|
5907
|
+
record
|
|
5908
|
+
};
|
|
5909
|
+
}
|
|
5910
|
+
/**
|
|
5911
|
+
* Classify what each read skill's include names produce, anchoring each tool
|
|
5912
|
+
* at the earliest read producing it.
|
|
5913
|
+
*
|
|
5914
|
+
* An include name that matches a request tool's name produces that tool; any
|
|
5915
|
+
* other produces what the resolver returned for it. A produced tool whose name
|
|
5916
|
+
* a request tool has wins as that request tool, because it's the one that
|
|
5917
|
+
* runs: a deferred one is disclosed early, and a bound one adds nothing, since
|
|
5918
|
+
* the model already sees it. Tools are matched by name, never by identity,
|
|
5919
|
+
* because outer middleware such as tool search replaces the request's tools
|
|
5920
|
+
* with copies.
|
|
5921
|
+
*/
|
|
5922
|
+
function planDisclosure(reads, requestTools, resolved) {
|
|
5923
|
+
const disclosed = /* @__PURE__ */ new Map();
|
|
5924
|
+
const discloseIfDeferred = (name, anchor) => {
|
|
5925
|
+
const entry = requestTools.get(name);
|
|
5926
|
+
if (!disclosed.has(name) && isLangChainTool(entry) && entry.extras?.[DEFER_LOADING] === true) disclosed.set(name, {
|
|
5927
|
+
tool: entry,
|
|
5928
|
+
anchor
|
|
5929
|
+
});
|
|
5930
|
+
};
|
|
5931
|
+
for (const { index, includeNames } of reads) for (const includeName of includeNames) {
|
|
5932
|
+
if (requestTools.has(includeName)) {
|
|
5933
|
+
discloseIfDeferred(includeName, index);
|
|
5934
|
+
continue;
|
|
5935
|
+
}
|
|
5936
|
+
for (const tool of resolved.get(includeName) ?? []) {
|
|
5937
|
+
if (disclosed.has(tool.name)) continue;
|
|
5938
|
+
if (requestTools.has(tool.name)) discloseIfDeferred(tool.name, index);
|
|
5939
|
+
else disclosed.set(tool.name, {
|
|
5940
|
+
tool,
|
|
5941
|
+
anchor: index,
|
|
5942
|
+
includeName
|
|
5943
|
+
});
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5946
|
+
return disclosed;
|
|
5947
|
+
}
|
|
5948
|
+
/**
|
|
5949
|
+
* Withhold each disclosed tool whose root input schema uses `oneOf`, `anyOf`
|
|
5950
|
+
* or `allOf`, which the Anthropic API rejects, failing the whole request, in
|
|
5951
|
+
* `tools` and inline alike. Withholding it on every path keeps the record
|
|
5952
|
+
* equal to what the model was shown.
|
|
5953
|
+
*/
|
|
5954
|
+
function withholdRootCombinatorTools(disclosed) {
|
|
5955
|
+
for (const [name, { tool }] of disclosed) {
|
|
5956
|
+
const schema = anthropicDefinition(tool).input_schema;
|
|
5957
|
+
const keys = ANTHROPIC_ROOT_COMBINATORS.filter((key) => typeof schema === "object" && schema !== null && Object.prototype.hasOwnProperty.call(schema, key));
|
|
5958
|
+
if (keys.length > 0) {
|
|
5959
|
+
console.warn(`Not disclosing tool '${name}': its input_schema has a top-level ${keys.join("/")}, which the Anthropic API does not support`);
|
|
5960
|
+
disclosed.delete(name);
|
|
5961
|
+
}
|
|
5962
|
+
}
|
|
5963
|
+
}
|
|
5964
|
+
/**
|
|
5965
|
+
* Insert one `SystemMessage` per insertion point, carrying the tools anchored
|
|
5966
|
+
* there, for models that accept tool definitions mid-conversation.
|
|
5967
|
+
*
|
|
5968
|
+
* Each message lands at the same index with the same bytes on every call while
|
|
5969
|
+
* its anchor stays visible, so the provider's cached prefix survives.
|
|
5970
|
+
*/
|
|
5971
|
+
function discloseInMessages(messages, disclosed, build) {
|
|
5972
|
+
const namesByIndex = /* @__PURE__ */ new Map();
|
|
5973
|
+
for (const [name, { anchor }] of disclosed) {
|
|
5974
|
+
let index = anchor + 1;
|
|
5975
|
+
while (index < messages.length) {
|
|
5976
|
+
const message = messages[index];
|
|
5977
|
+
const emptyReply = AIMessage$1.isInstance(message) && message.content.length === 0 && !message.tool_calls?.length;
|
|
5978
|
+
if (!ToolMessage$1.isInstance(message) && !HumanMessage$1.isInstance(message) && !emptyReply) break;
|
|
5979
|
+
index += 1;
|
|
5980
|
+
}
|
|
5981
|
+
namesByIndex.set(index, [...namesByIndex.get(index) ?? [], name]);
|
|
5982
|
+
}
|
|
5983
|
+
const result = [...messages];
|
|
5984
|
+
for (const index of [...namesByIndex.keys()].sort((a, b) => b - a)) {
|
|
5985
|
+
const blocks = namesByIndex.get(index).sort().map((name) => build(disclosed.get(name).tool));
|
|
5986
|
+
result.splice(index, 0, new SystemMessage$1({ content: blocks }));
|
|
5987
|
+
}
|
|
5988
|
+
return result;
|
|
5989
|
+
}
|
|
5990
|
+
/**
|
|
5991
|
+
* Return `requestTools` with deferred disclosures undeferred and skill tools
|
|
5992
|
+
* appended, for models that can't take tool definitions mid-conversation.
|
|
5993
|
+
*
|
|
5994
|
+
* A deferred tool is replaced by a plain spec rather than a copy: `createAgent`
|
|
5995
|
+
* rejects a request that swaps a registered tool for another instance of the
|
|
5996
|
+
* same name, and doesn't validate plain objects.
|
|
5997
|
+
*/
|
|
5998
|
+
function discloseInTools(requestTools, disclosed, gatedNames) {
|
|
5999
|
+
return [...requestTools.map((tool) => {
|
|
6000
|
+
const name = toolName(tool);
|
|
6001
|
+
const entry = name === void 0 ? void 0 : disclosed.get(name);
|
|
6002
|
+
if (entry?.tool !== tool || entry.includeName !== void 0) return tool;
|
|
6003
|
+
const { description, schema, extras = {} } = entry.tool;
|
|
6004
|
+
const { [DEFER_LOADING]: _deferLoading, ...rest } = extras;
|
|
6005
|
+
return {
|
|
6006
|
+
name,
|
|
6007
|
+
description,
|
|
6008
|
+
schema,
|
|
6009
|
+
extras: rest
|
|
6010
|
+
};
|
|
6011
|
+
}), ...gatedNames.map((name) => disclosed.get(name).tool)];
|
|
6012
|
+
}
|
|
6013
|
+
/** Return the model inside a `bind()` / `withConfig()` wrapper; chained calls merge into one. */
|
|
6014
|
+
function unwrapBinding(model) {
|
|
6015
|
+
return RunnableBinding.isRunnableBinding(model) ? model.bound : model;
|
|
6016
|
+
}
|
|
6017
|
+
/**
|
|
6018
|
+
* Return the concrete chat model a request will call.
|
|
6019
|
+
*
|
|
6020
|
+
* Resolves a `ConfigurableModel`, which a string `model` always becomes, to
|
|
6021
|
+
* the instance it will call with this run's `configurable`. Without
|
|
6022
|
+
* configurable overrides that's the instance cached at creation: the instance
|
|
6023
|
+
* cache is keyed on the whole config, so passing `configurable`
|
|
6024
|
+
* unconditionally would build one per thread.
|
|
6025
|
+
*/
|
|
6026
|
+
async function resolveChatModel(model, runtime) {
|
|
6027
|
+
const current = unwrapBinding(model);
|
|
6028
|
+
if (current?._queuedMethodOperations === void 0 || typeof current._getModelInstance !== "function") return current;
|
|
6029
|
+
const configurable = runtime.configurable ?? {};
|
|
6030
|
+
const overrides = current._modelParams?.({ configurable }) ?? {};
|
|
6031
|
+
return unwrapBinding(Object.keys(overrides).length > 0 ? await current._getModelInstance({ configurable }) : await current._getModelInstance());
|
|
6032
|
+
}
|
|
6033
|
+
/** Return a chat model's `_llmType()`, which tells providers apart. */
|
|
6034
|
+
function llmType(model) {
|
|
6035
|
+
const fn = model?._llmType;
|
|
6036
|
+
return typeof fn === "function" ? fn.call(model) : void 0;
|
|
6037
|
+
}
|
|
6038
|
+
/**
|
|
6039
|
+
* Return how `chatModel` is given a tool mid-conversation, or `undefined` if
|
|
6040
|
+
* it can't be.
|
|
6041
|
+
*
|
|
6042
|
+
* The one place that decides support, so a model-profile capability can
|
|
6043
|
+
* replace the allowlists later.
|
|
6044
|
+
*/
|
|
6045
|
+
function inlineBlockBuilder(chatModel) {
|
|
6046
|
+
const { model: name, useResponsesApi } = chatModel;
|
|
6047
|
+
if (typeof name !== "string") return void 0;
|
|
6048
|
+
const type = llmType(chatModel);
|
|
6049
|
+
if (type === "anthropic" && ANTHROPIC_INLINE_TOOL_MODELS.some((prefix) => name.startsWith(prefix))) return anthropicToolAddition;
|
|
6050
|
+
if (type === "openai" && useResponsesApi === true && OPENAI_INLINE_TOOL_MODELS.some((model) => name === model || name.startsWith(`${model}-`))) return openaiAdditionalTools;
|
|
6051
|
+
}
|
|
6052
|
+
/**
|
|
6053
|
+
* Return the definition `ChatAnthropic` sends in `tools` for `tool`, without
|
|
6054
|
+
* `defer_loading` (a deferred definition would stay withheld) or
|
|
6055
|
+
* `cache_control` (a stray marker uses up a breakpoint).
|
|
6056
|
+
*/
|
|
6057
|
+
function anthropicDefinition(tool) {
|
|
6058
|
+
const definition = {
|
|
6059
|
+
name: tool.name,
|
|
6060
|
+
description: tool.description,
|
|
6061
|
+
input_schema: isInteropZodSchema(tool.schema) ? toJsonSchema(tool.schema) : tool.schema
|
|
6062
|
+
};
|
|
6063
|
+
for (const key of ANTHROPIC_DEFINITION_EXTRAS) if (tool.extras?.[key] !== void 0) definition[key] = tool.extras[key];
|
|
6064
|
+
return definition;
|
|
6065
|
+
}
|
|
6066
|
+
/** Build the Anthropic `tool_addition` block carrying `tool`'s full definition. */
|
|
6067
|
+
function anthropicToolAddition(tool) {
|
|
6068
|
+
return {
|
|
6069
|
+
type: "tool_addition",
|
|
6070
|
+
tool: {
|
|
6071
|
+
type: "tool_definition",
|
|
6072
|
+
definition: anthropicDefinition(tool)
|
|
6073
|
+
}
|
|
6074
|
+
};
|
|
6075
|
+
}
|
|
6076
|
+
/** Build the OpenAI Responses `additional_tools` item carrying `tool`'s function schema. */
|
|
6077
|
+
function openaiAdditionalTools(tool) {
|
|
6078
|
+
return {
|
|
6079
|
+
type: "additional_tools",
|
|
6080
|
+
role: "developer",
|
|
6081
|
+
tools: [{
|
|
6082
|
+
type: "function",
|
|
6083
|
+
...convertToOpenAIFunction(tool)
|
|
6084
|
+
}]
|
|
6085
|
+
};
|
|
6086
|
+
}
|
|
5517
6087
|
/**
|
|
5518
6088
|
* Create backend-agnostic middleware for loading and exposing agent skills.
|
|
5519
6089
|
*
|
|
@@ -5537,8 +6107,62 @@ function validateModulePath(raw) {
|
|
|
5537
6107
|
* invalidate from any hook — including mid-run, from `afterModel` — and see
|
|
5538
6108
|
* the fresh list on the following call. See {@link skillsMetadataValue}.
|
|
5539
6109
|
*
|
|
6110
|
+
* To guarantee the model gets a skill's instructions, for example when the
|
|
6111
|
+
* user names it, pass its name in `pinnedSkills`. Before the next model call,
|
|
6112
|
+
* each named skill's `SKILL.md`, without its frontmatter, is appended to the
|
|
6113
|
+
* conversation as its own `HumanMessage`, with `additional_kwargs.lc_source`
|
|
6114
|
+
* set to `"pinned_skill"` and the skill's `name`, `path` and `description`
|
|
6115
|
+
* under `additional_kwargs.skill`:
|
|
6116
|
+
*
|
|
6117
|
+
* ```ts
|
|
6118
|
+
* await agent.invoke(
|
|
6119
|
+
* {
|
|
6120
|
+
* messages: [new HumanMessage("/write-tests /house-style for auth.py")],
|
|
6121
|
+
* pinnedSkills: ["write-tests", "house-style"],
|
|
6122
|
+
* },
|
|
6123
|
+
* config,
|
|
6124
|
+
* );
|
|
6125
|
+
* ```
|
|
6126
|
+
*
|
|
6127
|
+
* Pins resolve against the skills loaded for the thread, so reset
|
|
6128
|
+
* `skillsMetadata` in the same input to pin a skill added since. See
|
|
6129
|
+
* {@link pinnedSkillsValue}.
|
|
6130
|
+
*
|
|
6131
|
+
* ## Skill tools
|
|
6132
|
+
*
|
|
6133
|
+
* A skill can list the tools its instructions use, separated by spaces, under
|
|
6134
|
+
* `metadata.include_tools` in its `SKILL.md` frontmatter:
|
|
6135
|
+
*
|
|
6136
|
+
* ```yaml
|
|
6137
|
+
* metadata:
|
|
6138
|
+
* include_tools: create_customer_request list_customer_requests
|
|
6139
|
+
* ```
|
|
6140
|
+
*
|
|
6141
|
+
* Pass those tools as `tools`, either as an array or as a
|
|
6142
|
+
* {@link SkillToolResolver} that looks them up by name when a skill is read
|
|
6143
|
+
* or pinned, so one name can stand for a family of tools whose real names are
|
|
6144
|
+
* only known at runtime. The model sees a skill tool only after it reads, with
|
|
6145
|
+
* `read_file`, a skill that lists it, or the skill is pinned, and only while
|
|
6146
|
+
* that read or pin stays in the conversation it is sent; compaction that drops
|
|
6147
|
+
* the read or pin withdraws the tool.
|
|
6148
|
+
* Until then, calling the tool fails with the standard invalid-tool error and
|
|
6149
|
+
* the tool doesn't run. This controls what is in the model's context; it is
|
|
6150
|
+
* not a security boundary, since the model can read any `SKILL.md` at any
|
|
6151
|
+
* time.
|
|
6152
|
+
*
|
|
6153
|
+
* `include_tools` can also name a tool passed to the agent rather than to this
|
|
6154
|
+
* middleware. That tool wins over a skill tool of the same name. If it is
|
|
6155
|
+
* deferred (`extras: { defer_loading: true }`), reading or pinning the skill
|
|
6156
|
+
* discloses it early, and it stays deferred and searchable; if it is bound,
|
|
6157
|
+
* nothing changes.
|
|
6158
|
+
*
|
|
6159
|
+
*
|
|
5540
6160
|
* @param options - Configuration options
|
|
5541
6161
|
* @returns AgentMiddleware for skills loading and injection
|
|
6162
|
+
* @throws {ConfigurationError} If `tools` is neither an array nor a
|
|
6163
|
+
* function (`SKILL_TOOLS_UNSUPPORTED_TYPE`), an entry isn't a client tool
|
|
6164
|
+
* (`SKILL_TOOLS_UNSUPPORTED_TYPE`), or two entries share a name
|
|
6165
|
+
* (`SKILL_TOOLS_DUPLICATE_NAME`).
|
|
5542
6166
|
*
|
|
5543
6167
|
* @example
|
|
5544
6168
|
* ```typescript
|
|
@@ -5550,30 +6174,60 @@ function validateModulePath(raw) {
|
|
|
5550
6174
|
*/
|
|
5551
6175
|
function createSkillsMiddleware(options) {
|
|
5552
6176
|
const { backend, sources } = options;
|
|
6177
|
+
const skillToolResolver = toSkillToolResolver(options.tools);
|
|
6178
|
+
const hasSkillTools = typeof options.tools === "function" || Array.isArray(options.tools) && options.tools.length > 0;
|
|
5553
6179
|
return createMiddleware({
|
|
5554
6180
|
name: "SkillsMiddleware",
|
|
5555
6181
|
stateSchema: SkillsStateSchema,
|
|
5556
6182
|
async beforeModel(state) {
|
|
5557
|
-
|
|
6183
|
+
const needsLoad = state.skillsMetadata === null || state.skillsMetadata === void 0;
|
|
6184
|
+
const pinnedNames = state.pinnedSkills ?? [];
|
|
6185
|
+
if (!needsLoad && pinnedNames.length === 0) return;
|
|
5558
6186
|
const resolvedBackend = await resolveBackend(backend, { state });
|
|
5559
|
-
const
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
6187
|
+
const skillsMetadata = state.skillsMetadata ?? await loadSkills(resolvedBackend, sources);
|
|
6188
|
+
if (pinnedNames.length === 0) return { skillsMetadata };
|
|
6189
|
+
const messages = await buildPinnedSkillMessages(resolvedBackend, pinnedNames, skillsMetadata);
|
|
6190
|
+
return {
|
|
6191
|
+
...needsLoad && { skillsMetadata },
|
|
6192
|
+
...messages.length > 0 && { messages },
|
|
6193
|
+
pinnedSkills: new Overwrite([])
|
|
6194
|
+
};
|
|
5567
6195
|
},
|
|
5568
|
-
wrapModelCall(request, handler) {
|
|
6196
|
+
async wrapModelCall(request, handler) {
|
|
5569
6197
|
const skillsMetadata = request.state?.skillsMetadata || [];
|
|
5570
6198
|
const skillsLocations = formatSkillsLocations(sources);
|
|
5571
6199
|
const skillsList = formatSkillsList(skillsMetadata, sources);
|
|
5572
6200
|
const skillsSection = SKILLS_SYSTEM_PROMPT.replace("{skills_locations}", skillsLocations).replace("{skills_list}", skillsList);
|
|
5573
6201
|
const newSystemMessage = request.systemMessage.concat(skillsSection);
|
|
5574
|
-
|
|
6202
|
+
const prompted = {
|
|
5575
6203
|
...request,
|
|
5576
6204
|
systemMessage: newSystemMessage
|
|
6205
|
+
};
|
|
6206
|
+
const reads = findSkillReads(request.messages, skillsMetadata);
|
|
6207
|
+
const { request: disclosed, record } = reads.length === 0 ? {
|
|
6208
|
+
request: prompted,
|
|
6209
|
+
record: {}
|
|
6210
|
+
} : await discloseSkillTools(prompted, reads, skillToolResolver);
|
|
6211
|
+
const response = await handler(disclosed);
|
|
6212
|
+
const stored = request.state._skillToolsDisclosed;
|
|
6213
|
+
if (!hasSkillTools && Object.keys(stored ?? {}).length === 0) return response;
|
|
6214
|
+
if (typeof response === "object" && response !== null && "structuredResponse" in response) return response;
|
|
6215
|
+
return new Command({ update: { _skillToolsDisclosed: record } });
|
|
6216
|
+
},
|
|
6217
|
+
async wrapToolCall(request, handler) {
|
|
6218
|
+
if (request.tool !== void 0) return handler(request);
|
|
6219
|
+
const name = request.toolCall.name;
|
|
6220
|
+
const record = request.state._skillToolsDisclosed ?? {};
|
|
6221
|
+
const includeName = Object.hasOwn(record, name) ? record[name] : void 0;
|
|
6222
|
+
if (includeName === void 0) return handler(request);
|
|
6223
|
+
const tool = (await callSkillToolResolver(skillToolResolver, includeName, request.runtime)).find((candidate) => candidate.name === name);
|
|
6224
|
+
if (tool === void 0) {
|
|
6225
|
+
console.warn(`Skill tool '${name}' was disclosed via '${includeName}', but the resolver no longer returns it`);
|
|
6226
|
+
return handler(request);
|
|
6227
|
+
}
|
|
6228
|
+
return handler({
|
|
6229
|
+
...request,
|
|
6230
|
+
tool
|
|
5577
6231
|
});
|
|
5578
6232
|
}
|
|
5579
6233
|
});
|
|
@@ -6343,46 +6997,6 @@ function createAsyncSubAgentMiddleware(options) {
|
|
|
6343
6997
|
});
|
|
6344
6998
|
}
|
|
6345
6999
|
//#endregion
|
|
6346
|
-
//#region src/errors.ts
|
|
6347
|
-
const CONFIGURATION_ERROR_SYMBOL = Symbol.for("deepagents.configuration_error");
|
|
6348
|
-
/**
|
|
6349
|
-
* Thrown when `createDeepAgent` receives invalid configuration.
|
|
6350
|
-
*
|
|
6351
|
-
* Follows the same pattern as {@link SandboxError}: a human-readable
|
|
6352
|
-
* `message`, a structured `code` for programmatic handling, and a
|
|
6353
|
-
* static `isInstance` guard that works across realms.
|
|
6354
|
-
*
|
|
6355
|
-
* @example
|
|
6356
|
-
* ```typescript
|
|
6357
|
-
* try {
|
|
6358
|
-
* createDeepAgent({ tools: [myTool] });
|
|
6359
|
-
* } catch (error) {
|
|
6360
|
-
* if (ConfigurationError.isInstance(error)) {
|
|
6361
|
-
* switch (error.code) {
|
|
6362
|
-
* case "TOOL_NAME_COLLISION":
|
|
6363
|
-
* console.error("Rename your tool:", error.message);
|
|
6364
|
-
* break;
|
|
6365
|
-
* }
|
|
6366
|
-
* }
|
|
6367
|
-
* }
|
|
6368
|
-
* ```
|
|
6369
|
-
*/
|
|
6370
|
-
var ConfigurationError = class ConfigurationError extends Error {
|
|
6371
|
-
code;
|
|
6372
|
-
cause;
|
|
6373
|
-
[CONFIGURATION_ERROR_SYMBOL] = true;
|
|
6374
|
-
name = "ConfigurationError";
|
|
6375
|
-
constructor(message, code, cause) {
|
|
6376
|
-
super(message);
|
|
6377
|
-
this.code = code;
|
|
6378
|
-
this.cause = cause;
|
|
6379
|
-
Object.setPrototypeOf(this, ConfigurationError.prototype);
|
|
6380
|
-
}
|
|
6381
|
-
static isInstance(error) {
|
|
6382
|
-
return typeof error === "object" && error !== null && error[CONFIGURATION_ERROR_SYMBOL] === true;
|
|
6383
|
-
}
|
|
6384
|
-
};
|
|
6385
|
-
//#endregion
|
|
6386
7000
|
//#region src/middleware/cache.ts
|
|
6387
7001
|
/**
|
|
6388
7002
|
* Creates a middleware that places a cache breakpoint at the end of the static
|
|
@@ -7248,13 +7862,10 @@ function createDeepAgent(params = {}) {
|
|
|
7248
7862
|
harnessProfile.systemPromptSuffix
|
|
7249
7863
|
]);
|
|
7250
7864
|
/**
|
|
7251
|
-
*
|
|
7252
|
-
*
|
|
7253
|
-
* Custom subagents do NOT inherit skills from the main agent by default.
|
|
7254
|
-
* Only the general-purpose subagent inherits the main agent's skills.
|
|
7255
|
-
* If a custom subagent needs skills, it must specify its own `skills` array.
|
|
7865
|
+
* Build the default head of a declarative subagent's middleware stack.
|
|
7866
|
+
* Skills go in the tail instead (see buildSubagentMiddleware).
|
|
7256
7867
|
*/
|
|
7257
|
-
const createSubagentDefaultMiddleware = (input, subagentProfile
|
|
7868
|
+
const createSubagentDefaultMiddleware = (input, subagentProfile) => {
|
|
7258
7869
|
const effectivePermissions = input.permissions ?? permissions;
|
|
7259
7870
|
return [
|
|
7260
7871
|
createFilesystemMiddleware({
|
|
@@ -7263,23 +7874,33 @@ function createDeepAgent(params = {}) {
|
|
|
7263
7874
|
tools: computeProfileFilesystemTools(subagentProfile)
|
|
7264
7875
|
}),
|
|
7265
7876
|
createSummarizationMiddleware({ backend }),
|
|
7266
|
-
createPatchToolCallsMiddleware()
|
|
7267
|
-
...!forked && input.skills != null && input.skills.length > 0 ? [createSkillsMiddleware({
|
|
7268
|
-
backend,
|
|
7269
|
-
sources: input.skills
|
|
7270
|
-
})] : []
|
|
7877
|
+
createPatchToolCallsMiddleware()
|
|
7271
7878
|
];
|
|
7272
7879
|
};
|
|
7273
|
-
|
|
7880
|
+
/**
|
|
7881
|
+
* The skills middleware for a stack: the last `SkillsMiddleware` among its
|
|
7882
|
+
* custom middleware, else one built from `sources`, else none. A custom one
|
|
7883
|
+
* is picked here rather than left to replace the default by name, because
|
|
7884
|
+
* without `sources` there is no default to replace and it would run with
|
|
7885
|
+
* the novel middleware instead.
|
|
7886
|
+
*/
|
|
7887
|
+
const resolveSkillsMiddleware = (sources, custom) => {
|
|
7888
|
+
const passed = custom.findLast((entry) => entry.name === "SkillsMiddleware");
|
|
7889
|
+
if (passed !== void 0) return [passed];
|
|
7890
|
+
return sources != null && sources.length > 0 ? [createSkillsMiddleware({
|
|
7891
|
+
backend,
|
|
7892
|
+
sources
|
|
7893
|
+
})] : [];
|
|
7894
|
+
};
|
|
7895
|
+
const buildSubagentMiddleware = (input, generalPurpose = false) => {
|
|
7274
7896
|
const subagentProfile = resolveSubagentProfile(input.model);
|
|
7275
7897
|
const forked = isForkedSubAgent(input);
|
|
7276
|
-
const subagentDefaultMiddleware = createSubagentDefaultMiddleware(input, subagentProfile
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
}));
|
|
7281
|
-
let subagentMiddleware = mergeMiddlewareStack(subagentDefaultMiddleware, forked && customMiddleware.length > 0 ? mergeMiddleware$1(customMiddleware, input.middleware ?? []) : input.middleware ?? [], [
|
|
7898
|
+
const subagentDefaultMiddleware = createSubagentDefaultMiddleware(input, subagentProfile);
|
|
7899
|
+
const inputMiddleware = forked && customMiddleware.length > 0 ? mergeMiddleware$1(customMiddleware, input.middleware ?? []) : input.middleware ?? [];
|
|
7900
|
+
const subagentSkillsMiddleware = generalPurpose ? resolveSkillsMiddleware(skills, customMiddleware) : resolveSkillsMiddleware(forked ? skills : input.skills, inputMiddleware);
|
|
7901
|
+
let subagentMiddleware = mergeMiddlewareStack(subagentDefaultMiddleware, inputMiddleware, [
|
|
7282
7902
|
...resolveMiddleware(subagentProfile.extraMiddleware),
|
|
7903
|
+
...subagentSkillsMiddleware,
|
|
7283
7904
|
...cacheMiddleware,
|
|
7284
7905
|
...forked && memory != null && memory.length > 0 ? [createMemoryMiddleware({
|
|
7285
7906
|
backend,
|
|
@@ -7292,9 +7913,9 @@ function createDeepAgent(params = {}) {
|
|
|
7292
7913
|
if (!subagentProfile.excludedMiddleware.has("UnsupportedContentMiddleware") && !subagentMiddleware.some((middleware) => middleware.name === "UnsupportedContentMiddleware")) subagentMiddleware.push(createUnsupportedContentMiddleware());
|
|
7293
7914
|
return subagentMiddleware;
|
|
7294
7915
|
};
|
|
7295
|
-
const normalizeSubagentSpec = (input) => ({
|
|
7916
|
+
const normalizeSubagentSpec = (input, generalPurpose = false) => ({
|
|
7296
7917
|
...input,
|
|
7297
|
-
middleware: buildSubagentMiddleware(input)
|
|
7918
|
+
middleware: buildSubagentMiddleware(input, generalPurpose)
|
|
7298
7919
|
});
|
|
7299
7920
|
const allSubagents = subagents;
|
|
7300
7921
|
const asyncSubAgents = allSubagents.filter((item) => isAsyncSubAgent(item));
|
|
@@ -7307,16 +7928,12 @@ function createDeepAgent(params = {}) {
|
|
|
7307
7928
|
description: gpConfig?.description ?? GENERAL_PURPOSE_SUBAGENT.description,
|
|
7308
7929
|
systemPrompt: gpSystemPrompt,
|
|
7309
7930
|
model,
|
|
7310
|
-
skills,
|
|
7311
7931
|
tools: effectiveTools
|
|
7312
|
-
});
|
|
7932
|
+
}, true);
|
|
7313
7933
|
generalPurposeSpec.middleware = mergeMiddlewareStack(generalPurposeSpec.middleware ?? [], customMiddleware, [], { appendNew: false });
|
|
7314
7934
|
inlineSubagents.unshift(generalPurposeSpec);
|
|
7315
7935
|
}
|
|
7316
|
-
const skillsMiddleware = skills
|
|
7317
|
-
backend,
|
|
7318
|
-
sources: skills
|
|
7319
|
-
})] : [];
|
|
7936
|
+
const skillsMiddleware = resolveSkillsMiddleware(skills, customMiddleware);
|
|
7320
7937
|
const [fsMiddleware, subagentMiddleware, summarizationMiddleware, patchToolCallsMiddleware] = [
|
|
7321
7938
|
createFilesystemMiddleware({
|
|
7322
7939
|
backend,
|
|
@@ -7335,7 +7952,6 @@ function createDeepAgent(params = {}) {
|
|
|
7335
7952
|
createPatchToolCallsMiddleware()
|
|
7336
7953
|
];
|
|
7337
7954
|
let middleware = mergeMiddlewareStack([
|
|
7338
|
-
...skillsMiddleware,
|
|
7339
7955
|
fsMiddleware,
|
|
7340
7956
|
subagentMiddleware,
|
|
7341
7957
|
summarizationMiddleware,
|
|
@@ -7343,6 +7959,7 @@ function createDeepAgent(params = {}) {
|
|
|
7343
7959
|
...asyncSubAgents.length > 0 ? [createAsyncSubAgentMiddleware({ asyncSubAgents })] : []
|
|
7344
7960
|
], customMiddleware, [
|
|
7345
7961
|
...resolveMiddleware(harnessProfile.extraMiddleware),
|
|
7962
|
+
...skillsMiddleware,
|
|
7346
7963
|
...cacheMiddleware,
|
|
7347
7964
|
...memoryMiddleware,
|
|
7348
7965
|
...interruptOn ? [humanInTheLoopMiddleware({ interruptOn })] : []
|
|
@@ -9470,6 +10087,6 @@ var LangSmithSandbox = class LangSmithSandbox extends BaseSandbox {
|
|
|
9470
10087
|
}
|
|
9471
10088
|
};
|
|
9472
10089
|
//#endregion
|
|
9473
|
-
export {
|
|
10090
|
+
export { adaptSandboxProtocol as $, filesValue as A, createFilesystemMiddleware as B, MAX_SKILL_FILE_SIZE as C, skillsMetadataValue as D, pinnedSkillsValue as E, SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY as F, scrubUnsupportedMultimodalContent as G, MULTIMODAL_BLOCK_TYPES as H, createSubAgent as I, applyGrepMaxCount as J, StateBackend as K, createSubAgentMiddleware as L, DEFAULT_GENERAL_PURPOSE_DESCRIPTION as M, DEFAULT_SUBAGENT_PROMPT as N, ConfigurationError as O, GENERAL_PURPOSE_SUBAGENT as P, adaptBackendProtocol as Q, computeSummarizationDefaults as R, MAX_SKILL_DESCRIPTION_LENGTH as S, createSkillsMiddleware as T, createUnsupportedContentMiddleware as U, CompositeBackend as V, multimodalBlockSupported as W, isSandboxProtocol as X, isSandboxBackend as Y, resolveBackend as Z, createHarnessProfile as _, ASYNC_TASK_SYSTEM_PROMPT as a, setRealpath as at, isAsyncSubAgent as b, TASK_SYSTEM_PROMPT as c, registerHarnessProfile as d, checkEmptyContent as et, generalPurposeSubagentConfigSchema as f, EMPTY_HARNESS_PROFILE as g, serializeProfile as h, StoreBackend as i, performStringReplacement as it, createPatchToolCallsMiddleware as j, createMemoryMiddleware as k, createDeepAgent as l, parseHarnessProfileConfig as m, BaseSandbox as n, isTextMimeType as nt, BASE_AGENT_PROMPT as o, harnessProfileConfigSchema as p, SandboxError as q, ContextHubBackend as r, normalizeReadPagination as rt, EXECUTION_SYSTEM_PROMPT as s, LangSmithSandbox as t, getMimeType as tt, getHarnessProfile as u, REQUIRED_MIDDLEWARE_NAMES as v, MAX_SKILL_NAME_LENGTH as w, createCompletionCallbackMiddleware as x, createAsyncSubAgentMiddleware as y, createSummarizationMiddleware as z };
|
|
9474
10091
|
|
|
9475
|
-
//# sourceMappingURL=langsmith
|
|
10092
|
+
//# sourceMappingURL=langsmith-v_uskil7.js.map
|