deepagents 1.10.0 → 1.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1146 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +436 -6
- package/dist/index.d.ts +436 -6
- package/dist/index.js +1137 -25
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import fs$1 from "node:fs";
|
|
|
15
15
|
import path$1 from "node:path";
|
|
16
16
|
import cp, { spawn } from "node:child_process";
|
|
17
17
|
import fg from "fast-glob";
|
|
18
|
+
import { Client as Client$1 } from "langsmith";
|
|
18
19
|
import { LangSmithResourceNotFoundError, LangSmithSandboxError, SandboxClient } from "langsmith/experimental/sandbox";
|
|
19
20
|
import os from "node:os";
|
|
20
21
|
//#region src/backends/utils.ts
|
|
@@ -5687,6 +5688,341 @@ var FilesystemBackend = class {
|
|
|
5687
5688
|
}
|
|
5688
5689
|
};
|
|
5689
5690
|
//#endregion
|
|
5691
|
+
//#region src/backends/context-hub.ts
|
|
5692
|
+
/**
|
|
5693
|
+
* ContextHubBackend: Store files in a LangSmith Hub agent repo (persistent).
|
|
5694
|
+
*/
|
|
5695
|
+
const URL_COMMIT_SUFFIX_RE = /:([0-9a-f]{8,64})$/i;
|
|
5696
|
+
const TEXT_MIME_TYPE = "text/plain";
|
|
5697
|
+
const FNMATCH_OPTIONS = { bash: true };
|
|
5698
|
+
function getErrorMessage(error) {
|
|
5699
|
+
if (typeof error === "string") return error;
|
|
5700
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") return error.message;
|
|
5701
|
+
return String(error);
|
|
5702
|
+
}
|
|
5703
|
+
function splitLinesKeepEnds(content) {
|
|
5704
|
+
const lines = [];
|
|
5705
|
+
let lineStart = 0;
|
|
5706
|
+
for (let index = 0; index < content.length; index += 1) if (content[index] === "\n") {
|
|
5707
|
+
lines.push(content.slice(lineStart, index + 1));
|
|
5708
|
+
lineStart = index + 1;
|
|
5709
|
+
}
|
|
5710
|
+
if (lineStart < content.length) lines.push(content.slice(lineStart));
|
|
5711
|
+
return lines;
|
|
5712
|
+
}
|
|
5713
|
+
function sliceReadContent(content, offset, limit) {
|
|
5714
|
+
if (!content || content.trim() === "") return { content };
|
|
5715
|
+
const lines = splitLinesKeepEnds(content.replace(/\r\n/g, "\n").replace(/\r/g, "\n"));
|
|
5716
|
+
const startIndex = offset;
|
|
5717
|
+
const endIndex = Math.min(startIndex + limit, lines.length);
|
|
5718
|
+
if (startIndex >= lines.length) return { error: `Line offset ${offset} exceeds file length (${lines.length} lines)` };
|
|
5719
|
+
return { content: lines.slice(startIndex, endIndex).join("") };
|
|
5720
|
+
}
|
|
5721
|
+
function isLangSmithNotFoundError(error) {
|
|
5722
|
+
if (typeof error !== "object" || error === null) return false;
|
|
5723
|
+
const maybeError = error;
|
|
5724
|
+
return maybeError.name === "LangSmithNotFoundError" || maybeError.status === 404;
|
|
5725
|
+
}
|
|
5726
|
+
function isLangSmithError(error) {
|
|
5727
|
+
if (typeof error !== "object" || error === null) return false;
|
|
5728
|
+
const maybeError = error;
|
|
5729
|
+
return typeof maybeError.name === "string" && maybeError.name.startsWith("LangSmith") || typeof maybeError.status === "number";
|
|
5730
|
+
}
|
|
5731
|
+
function getLangSmithStatus(error) {
|
|
5732
|
+
if (typeof error !== "object" || error === null) return;
|
|
5733
|
+
const maybeError = error;
|
|
5734
|
+
if (typeof maybeError.status === "number") return maybeError.status;
|
|
5735
|
+
}
|
|
5736
|
+
function mapHubFileOperationError(error) {
|
|
5737
|
+
const status = getLangSmithStatus(error);
|
|
5738
|
+
if (status === 401 || status === 403) return "permission_denied";
|
|
5739
|
+
if (status === 404) return "file_not_found";
|
|
5740
|
+
return "invalid_path";
|
|
5741
|
+
}
|
|
5742
|
+
/**
|
|
5743
|
+
* Backend that stores files in a LangSmith Hub agent repo (persistent).
|
|
5744
|
+
*/
|
|
5745
|
+
var ContextHubBackend = class ContextHubBackend {
|
|
5746
|
+
identifier;
|
|
5747
|
+
client;
|
|
5748
|
+
cache = null;
|
|
5749
|
+
linkedEntries = {};
|
|
5750
|
+
commitHash = null;
|
|
5751
|
+
constructor(identifier, options = {}) {
|
|
5752
|
+
this.identifier = identifier;
|
|
5753
|
+
this.client = options.client ?? new Client$1();
|
|
5754
|
+
}
|
|
5755
|
+
static stripPrefix(path) {
|
|
5756
|
+
return path.replace(/^\/+/, "");
|
|
5757
|
+
}
|
|
5758
|
+
static toHubUnavailableError(error) {
|
|
5759
|
+
return `Hub unavailable: ${getErrorMessage(error)}`;
|
|
5760
|
+
}
|
|
5761
|
+
async loadTree() {
|
|
5762
|
+
let context;
|
|
5763
|
+
try {
|
|
5764
|
+
context = await this.client.pullAgent(this.identifier);
|
|
5765
|
+
} catch (error) {
|
|
5766
|
+
if (isLangSmithNotFoundError(error)) {
|
|
5767
|
+
this.cache = {};
|
|
5768
|
+
this.linkedEntries = {};
|
|
5769
|
+
this.commitHash = null;
|
|
5770
|
+
return;
|
|
5771
|
+
}
|
|
5772
|
+
throw error;
|
|
5773
|
+
}
|
|
5774
|
+
this.commitHash = context.commit_hash;
|
|
5775
|
+
this.cache = {};
|
|
5776
|
+
this.linkedEntries = {};
|
|
5777
|
+
for (const [path, entry] of Object.entries(context.files)) if (entry.type === "file") this.cache[path] = entry.content;
|
|
5778
|
+
else if ((entry.type === "agent" || entry.type === "skill") && typeof entry.repo_handle === "string") this.linkedEntries[path] = entry.repo_handle;
|
|
5779
|
+
}
|
|
5780
|
+
async ensureCache() {
|
|
5781
|
+
if (this.cache === null) await this.loadTree();
|
|
5782
|
+
if (this.cache === null) throw new Error("Context Hub cache failed to initialize");
|
|
5783
|
+
return this.cache;
|
|
5784
|
+
}
|
|
5785
|
+
async commit(files) {
|
|
5786
|
+
if (Object.keys(files).length === 0) return;
|
|
5787
|
+
const payload = {};
|
|
5788
|
+
for (const [path, content] of Object.entries(files)) payload[path] = {
|
|
5789
|
+
type: "file",
|
|
5790
|
+
content
|
|
5791
|
+
};
|
|
5792
|
+
const url = await this.client.pushAgent(this.identifier, {
|
|
5793
|
+
files: payload,
|
|
5794
|
+
...this.commitHash ? { parentCommit: this.commitHash } : {}
|
|
5795
|
+
});
|
|
5796
|
+
const match = URL_COMMIT_SUFFIX_RE.exec(url);
|
|
5797
|
+
if (match) this.commitHash = match[1];
|
|
5798
|
+
if (this.cache !== null) for (const [path, content] of Object.entries(files)) this.cache[path] = content;
|
|
5799
|
+
}
|
|
5800
|
+
/**
|
|
5801
|
+
* Return linked-entry paths mapped to their repo handles.
|
|
5802
|
+
*/
|
|
5803
|
+
async getLinkedEntries() {
|
|
5804
|
+
await this.ensureCache();
|
|
5805
|
+
return { ...this.linkedEntries };
|
|
5806
|
+
}
|
|
5807
|
+
/**
|
|
5808
|
+
* Return true if the hub repo already exists with at least one commit.
|
|
5809
|
+
*/
|
|
5810
|
+
async hasPriorCommits() {
|
|
5811
|
+
await this.ensureCache();
|
|
5812
|
+
return this.commitHash !== null;
|
|
5813
|
+
}
|
|
5814
|
+
async ls(path = "/") {
|
|
5815
|
+
const hubPrefix = ContextHubBackend.stripPrefix(path).replace(/\/+$/, "");
|
|
5816
|
+
let cache;
|
|
5817
|
+
try {
|
|
5818
|
+
cache = await this.ensureCache();
|
|
5819
|
+
} catch (error) {
|
|
5820
|
+
if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
|
|
5821
|
+
throw error;
|
|
5822
|
+
}
|
|
5823
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
5824
|
+
const entries = [];
|
|
5825
|
+
for (const filePath of Object.keys(cache)) {
|
|
5826
|
+
if (hubPrefix && !filePath.startsWith(`${hubPrefix}/`)) continue;
|
|
5827
|
+
const relative = hubPrefix ? filePath.slice(hubPrefix.length + 1) : filePath;
|
|
5828
|
+
if (!relative) continue;
|
|
5829
|
+
const slashIndex = relative.indexOf("/");
|
|
5830
|
+
if (slashIndex === -1) {
|
|
5831
|
+
entries.push({
|
|
5832
|
+
path: `/${filePath}`,
|
|
5833
|
+
is_dir: false
|
|
5834
|
+
});
|
|
5835
|
+
continue;
|
|
5836
|
+
}
|
|
5837
|
+
const dirName = relative.slice(0, slashIndex);
|
|
5838
|
+
const dirPath = hubPrefix ? `${hubPrefix}/${dirName}` : dirName;
|
|
5839
|
+
if (!dirs.has(dirPath)) {
|
|
5840
|
+
dirs.add(dirPath);
|
|
5841
|
+
entries.push({
|
|
5842
|
+
path: `/${dirPath}`,
|
|
5843
|
+
is_dir: true
|
|
5844
|
+
});
|
|
5845
|
+
}
|
|
5846
|
+
}
|
|
5847
|
+
return { files: entries };
|
|
5848
|
+
}
|
|
5849
|
+
async read(filePath, offset = 0, limit = 2e3) {
|
|
5850
|
+
const hubPath = ContextHubBackend.stripPrefix(filePath);
|
|
5851
|
+
let cache;
|
|
5852
|
+
try {
|
|
5853
|
+
cache = await this.ensureCache();
|
|
5854
|
+
} catch (error) {
|
|
5855
|
+
if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
|
|
5856
|
+
throw error;
|
|
5857
|
+
}
|
|
5858
|
+
const content = cache[hubPath];
|
|
5859
|
+
if (content === void 0) return { error: `File '${filePath}' not found` };
|
|
5860
|
+
const sliced = sliceReadContent(content, offset, limit);
|
|
5861
|
+
if (sliced.error) return { error: sliced.error };
|
|
5862
|
+
return {
|
|
5863
|
+
content: sliced.content ?? "",
|
|
5864
|
+
mimeType: TEXT_MIME_TYPE
|
|
5865
|
+
};
|
|
5866
|
+
}
|
|
5867
|
+
async readRaw(filePath) {
|
|
5868
|
+
const readResult = await this.read(filePath, 0, Number.MAX_SAFE_INTEGER);
|
|
5869
|
+
if (readResult.error || typeof readResult.content !== "string") return { error: readResult.error ?? `File '${filePath}' not found` };
|
|
5870
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5871
|
+
return { data: {
|
|
5872
|
+
content: readResult.content,
|
|
5873
|
+
mimeType: TEXT_MIME_TYPE,
|
|
5874
|
+
created_at: now,
|
|
5875
|
+
modified_at: now
|
|
5876
|
+
} };
|
|
5877
|
+
}
|
|
5878
|
+
async grep(pattern, path = null, glob = null) {
|
|
5879
|
+
let cache;
|
|
5880
|
+
try {
|
|
5881
|
+
cache = await this.ensureCache();
|
|
5882
|
+
} catch (error) {
|
|
5883
|
+
if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
|
|
5884
|
+
throw error;
|
|
5885
|
+
}
|
|
5886
|
+
const prefix = path ? ContextHubBackend.stripPrefix(path).replace(/\/+$/, "") : "";
|
|
5887
|
+
const matches = [];
|
|
5888
|
+
for (const [filePath, content] of Object.entries(cache)) {
|
|
5889
|
+
if (prefix && !filePath.startsWith(prefix)) continue;
|
|
5890
|
+
if (glob && !micromatch.isMatch(filePath, glob, FNMATCH_OPTIONS)) continue;
|
|
5891
|
+
const lines = content.split("\n");
|
|
5892
|
+
for (let index = 0; index < lines.length; index++) {
|
|
5893
|
+
const line = lines[index];
|
|
5894
|
+
if (line.includes(pattern)) matches.push({
|
|
5895
|
+
path: `/${filePath}`,
|
|
5896
|
+
line: index + 1,
|
|
5897
|
+
text: line
|
|
5898
|
+
});
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
return { matches };
|
|
5902
|
+
}
|
|
5903
|
+
async glob(pattern, _path = "/") {
|
|
5904
|
+
let cache;
|
|
5905
|
+
try {
|
|
5906
|
+
cache = await this.ensureCache();
|
|
5907
|
+
} catch (error) {
|
|
5908
|
+
if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
|
|
5909
|
+
throw error;
|
|
5910
|
+
}
|
|
5911
|
+
const files = [];
|
|
5912
|
+
for (const filePath of Object.keys(cache)) if (micromatch.isMatch(`/${filePath}`, pattern, FNMATCH_OPTIONS) || micromatch.isMatch(filePath, pattern, FNMATCH_OPTIONS)) files.push({
|
|
5913
|
+
path: `/${filePath}`,
|
|
5914
|
+
is_dir: false
|
|
5915
|
+
});
|
|
5916
|
+
return { files };
|
|
5917
|
+
}
|
|
5918
|
+
async write(filePath, content) {
|
|
5919
|
+
const hubPath = ContextHubBackend.stripPrefix(filePath);
|
|
5920
|
+
try {
|
|
5921
|
+
await this.ensureCache();
|
|
5922
|
+
await this.commit({ [hubPath]: content });
|
|
5923
|
+
} catch (error) {
|
|
5924
|
+
if (isLangSmithError(error)) {
|
|
5925
|
+
this.cache = null;
|
|
5926
|
+
return { error: ContextHubBackend.toHubUnavailableError(error) };
|
|
5927
|
+
}
|
|
5928
|
+
throw error;
|
|
5929
|
+
}
|
|
5930
|
+
return {
|
|
5931
|
+
path: filePath,
|
|
5932
|
+
filesUpdate: null
|
|
5933
|
+
};
|
|
5934
|
+
}
|
|
5935
|
+
async edit(filePath, oldString, newString, replaceAll = false) {
|
|
5936
|
+
const hubPath = ContextHubBackend.stripPrefix(filePath);
|
|
5937
|
+
try {
|
|
5938
|
+
const current = (await this.ensureCache())[hubPath];
|
|
5939
|
+
if (current === void 0) return { error: `Error: File '${filePath}' not found` };
|
|
5940
|
+
const replacementResult = performStringReplacement(current, oldString, newString, replaceAll);
|
|
5941
|
+
if (typeof replacementResult === "string") return { error: replacementResult };
|
|
5942
|
+
const [newContent, occurrences] = replacementResult;
|
|
5943
|
+
await this.commit({ [hubPath]: newContent });
|
|
5944
|
+
return {
|
|
5945
|
+
path: filePath,
|
|
5946
|
+
filesUpdate: null,
|
|
5947
|
+
occurrences
|
|
5948
|
+
};
|
|
5949
|
+
} catch (error) {
|
|
5950
|
+
if (isLangSmithError(error)) {
|
|
5951
|
+
this.cache = null;
|
|
5952
|
+
return { error: ContextHubBackend.toHubUnavailableError(error) };
|
|
5953
|
+
}
|
|
5954
|
+
throw error;
|
|
5955
|
+
}
|
|
5956
|
+
}
|
|
5957
|
+
async uploadFiles(files) {
|
|
5958
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
5959
|
+
const decoded = [];
|
|
5960
|
+
const validFiles = {};
|
|
5961
|
+
for (const [path, content] of files) try {
|
|
5962
|
+
const text = decoder.decode(content);
|
|
5963
|
+
decoded.push([path, text]);
|
|
5964
|
+
validFiles[ContextHubBackend.stripPrefix(path)] = text;
|
|
5965
|
+
} catch {
|
|
5966
|
+
decoded.push([path, null]);
|
|
5967
|
+
}
|
|
5968
|
+
let commitError = null;
|
|
5969
|
+
if (Object.keys(validFiles).length > 0) try {
|
|
5970
|
+
await this.ensureCache();
|
|
5971
|
+
await this.commit(validFiles);
|
|
5972
|
+
} catch (error) {
|
|
5973
|
+
if (isLangSmithError(error)) {
|
|
5974
|
+
this.cache = null;
|
|
5975
|
+
commitError = mapHubFileOperationError(error);
|
|
5976
|
+
} else throw error;
|
|
5977
|
+
}
|
|
5978
|
+
return decoded.map(([path, text]) => {
|
|
5979
|
+
if (text === null) return {
|
|
5980
|
+
path,
|
|
5981
|
+
error: "invalid_path"
|
|
5982
|
+
};
|
|
5983
|
+
if (commitError !== null) return {
|
|
5984
|
+
path,
|
|
5985
|
+
error: commitError
|
|
5986
|
+
};
|
|
5987
|
+
return {
|
|
5988
|
+
path,
|
|
5989
|
+
error: null
|
|
5990
|
+
};
|
|
5991
|
+
});
|
|
5992
|
+
}
|
|
5993
|
+
async downloadFiles(paths) {
|
|
5994
|
+
let cache;
|
|
5995
|
+
try {
|
|
5996
|
+
cache = await this.ensureCache();
|
|
5997
|
+
} catch (error) {
|
|
5998
|
+
if (isLangSmithError(error)) {
|
|
5999
|
+
const mappedError = mapHubFileOperationError(error);
|
|
6000
|
+
return paths.map((path) => ({
|
|
6001
|
+
path,
|
|
6002
|
+
content: null,
|
|
6003
|
+
error: mappedError
|
|
6004
|
+
}));
|
|
6005
|
+
}
|
|
6006
|
+
throw error;
|
|
6007
|
+
}
|
|
6008
|
+
const encoder = new TextEncoder();
|
|
6009
|
+
return paths.map((path) => {
|
|
6010
|
+
const hubPath = ContextHubBackend.stripPrefix(path);
|
|
6011
|
+
const content = cache[hubPath];
|
|
6012
|
+
if (content !== void 0) return {
|
|
6013
|
+
path,
|
|
6014
|
+
content: encoder.encode(content),
|
|
6015
|
+
error: null
|
|
6016
|
+
};
|
|
6017
|
+
return {
|
|
6018
|
+
path,
|
|
6019
|
+
content: null,
|
|
6020
|
+
error: "file_not_found"
|
|
6021
|
+
};
|
|
6022
|
+
});
|
|
6023
|
+
}
|
|
6024
|
+
};
|
|
6025
|
+
//#endregion
|
|
5690
6026
|
//#region src/backends/local-shell.ts
|
|
5691
6027
|
/**
|
|
5692
6028
|
* LocalShellBackend: Node.js implementation of the filesystem backend with unrestricted local shell execution.
|
|
@@ -6425,7 +6761,7 @@ var BaseSandbox = class {
|
|
|
6425
6761
|
* ```typescript
|
|
6426
6762
|
* import { LangSmithSandbox, createDeepAgent } from "deepagents";
|
|
6427
6763
|
*
|
|
6428
|
-
* const sandbox = await LangSmithSandbox.create({
|
|
6764
|
+
* const sandbox = await LangSmithSandbox.create({ snapshotId: "your-snapshot-id" });
|
|
6429
6765
|
*
|
|
6430
6766
|
* const agent = createDeepAgent({ model, backend: sandbox });
|
|
6431
6767
|
*
|
|
@@ -6549,6 +6885,41 @@ var LangSmithSandbox = class LangSmithSandbox extends BaseSandbox {
|
|
|
6549
6885
|
this.#isRunning = false;
|
|
6550
6886
|
}
|
|
6551
6887
|
/**
|
|
6888
|
+
* Start a stopped sandbox and wait until it is ready.
|
|
6889
|
+
*
|
|
6890
|
+
* After calling this, `isRunning` will be `true` and the sandbox
|
|
6891
|
+
* can be used for command execution and file operations again.
|
|
6892
|
+
*
|
|
6893
|
+
* @param options - Start options (timeout, signal).
|
|
6894
|
+
*/
|
|
6895
|
+
async start(options = {}) {
|
|
6896
|
+
await this.#sandbox.start(options);
|
|
6897
|
+
this.#isRunning = true;
|
|
6898
|
+
}
|
|
6899
|
+
/**
|
|
6900
|
+
* Stop the sandbox without deleting it.
|
|
6901
|
+
*
|
|
6902
|
+
* Sandbox files are preserved and the sandbox can be restarted later
|
|
6903
|
+
* with `start()`. After calling this, `isRunning` will be `false`.
|
|
6904
|
+
*/
|
|
6905
|
+
async stop() {
|
|
6906
|
+
await this.#sandbox.stop();
|
|
6907
|
+
this.#isRunning = false;
|
|
6908
|
+
}
|
|
6909
|
+
/**
|
|
6910
|
+
* Capture a snapshot from this running sandbox.
|
|
6911
|
+
*
|
|
6912
|
+
* Snapshots can be used to create new sandboxes via
|
|
6913
|
+
* `LangSmithSandbox.create({ snapshotId })`.
|
|
6914
|
+
*
|
|
6915
|
+
* @param name - Name for the snapshot.
|
|
6916
|
+
* @param options - Capture options (checkpoint, timeout).
|
|
6917
|
+
* @returns The created Snapshot in "ready" status.
|
|
6918
|
+
*/
|
|
6919
|
+
async captureSnapshot(name, options = {}) {
|
|
6920
|
+
return this.#sandbox.captureSnapshot(name, options);
|
|
6921
|
+
}
|
|
6922
|
+
/**
|
|
6552
6923
|
* Create and return a new LangSmithSandbox in one step.
|
|
6553
6924
|
*
|
|
6554
6925
|
* This is the recommended way to create a sandbox — no need to import
|
|
@@ -6556,7 +6927,10 @@ var LangSmithSandbox = class LangSmithSandbox extends BaseSandbox {
|
|
|
6556
6927
|
*
|
|
6557
6928
|
* @example
|
|
6558
6929
|
* ```typescript
|
|
6559
|
-
* const sandbox = await LangSmithSandbox.create({
|
|
6930
|
+
* const sandbox = await LangSmithSandbox.create({
|
|
6931
|
+
* snapshotId: "abc-123",
|
|
6932
|
+
* });
|
|
6933
|
+
*
|
|
6560
6934
|
* try {
|
|
6561
6935
|
* const agent = createDeepAgent({ model, backend: sandbox });
|
|
6562
6936
|
* await agent.invoke({ messages: [...] });
|
|
@@ -6565,10 +6939,14 @@ var LangSmithSandbox = class LangSmithSandbox extends BaseSandbox {
|
|
|
6565
6939
|
* }
|
|
6566
6940
|
* ```
|
|
6567
6941
|
*/
|
|
6568
|
-
static async create(options
|
|
6569
|
-
const { templateName
|
|
6942
|
+
static async create(options) {
|
|
6943
|
+
const { templateName, apiKey = process.env.LANGSMITH_API_KEY, defaultTimeout, snapshotId, ...createSandboxOptions } = options;
|
|
6944
|
+
if (snapshotId && templateName) throw new Error("snapshotId and templateName are mutually exclusive. Pass only one creation source.");
|
|
6945
|
+
if (!snapshotId && !templateName) throw new Error("Either snapshotId or templateName is required. snapshotId is recommended — template-based creation is deprecated.");
|
|
6946
|
+
const sandboxOptions = { ...createSandboxOptions };
|
|
6947
|
+
if (templateName) sandboxOptions.snapshotName = templateName;
|
|
6570
6948
|
return new LangSmithSandbox({
|
|
6571
|
-
sandbox: await new SandboxClient({ apiKey }).createSandbox(
|
|
6949
|
+
sandbox: await new SandboxClient({ apiKey }).createSandbox(snapshotId, sandboxOptions),
|
|
6572
6950
|
defaultTimeout
|
|
6573
6951
|
});
|
|
6574
6952
|
}
|
|
@@ -6920,6 +7298,717 @@ function createSubagentTransformer(path) {
|
|
|
6920
7298
|
};
|
|
6921
7299
|
}
|
|
6922
7300
|
//#endregion
|
|
7301
|
+
//#region src/profiles/keys.ts
|
|
7302
|
+
/**
|
|
7303
|
+
* Normalize and validate a profile registry key.
|
|
7304
|
+
*
|
|
7305
|
+
* Trims leading/trailing whitespace, then enforces the `"provider"` or
|
|
7306
|
+
* `"provider:model"` shape. Rejects empty strings, multiple colons, and
|
|
7307
|
+
* empty halves.
|
|
7308
|
+
*
|
|
7309
|
+
* @param key - The registry key to validate.
|
|
7310
|
+
* @returns The trimmed, validated key.
|
|
7311
|
+
* @throws {Error} When the key is malformed.
|
|
7312
|
+
*
|
|
7313
|
+
* @example
|
|
7314
|
+
* ```typescript
|
|
7315
|
+
* validateProfileKey("anthropic:claude-opus-4-7"); // "anthropic:claude-opus-4-7"
|
|
7316
|
+
* validateProfileKey(" openai "); // "openai"
|
|
7317
|
+
* validateProfileKey("openai:"); // throws
|
|
7318
|
+
* validateProfileKey(""); // throws
|
|
7319
|
+
* ```
|
|
7320
|
+
*/
|
|
7321
|
+
function validateProfileKey(key) {
|
|
7322
|
+
const trimmed = key.trim();
|
|
7323
|
+
if (!trimmed) throw new Error("Profile key must be a non-empty string");
|
|
7324
|
+
if (trimmed.split(":").length > 2) throw new Error(`Profile key "${trimmed}" has more than one ":"; expected "provider" or "provider:model"`);
|
|
7325
|
+
if (trimmed.includes(":")) {
|
|
7326
|
+
const [provider, model] = trimmed.split(":");
|
|
7327
|
+
if (!provider.trim() || !model.trim()) throw new Error(`Profile key "${trimmed}" has an empty provider or model half; expected "provider:model"`);
|
|
7328
|
+
}
|
|
7329
|
+
return trimmed;
|
|
7330
|
+
}
|
|
7331
|
+
//#endregion
|
|
7332
|
+
//#region src/profiles/harness/types.ts
|
|
7333
|
+
/**
|
|
7334
|
+
* Middleware names that provide essential agent capabilities and cannot
|
|
7335
|
+
* be excluded via `excludedMiddleware`.
|
|
7336
|
+
*
|
|
7337
|
+
* - `FilesystemMiddleware` backs all built-in file tools and enforces
|
|
7338
|
+
* filesystem permissions.
|
|
7339
|
+
* - `SubAgentMiddleware` backs the `task` tool for subagent delegation.
|
|
7340
|
+
*/
|
|
7341
|
+
const REQUIRED_MIDDLEWARE_NAMES = new Set(["FilesystemMiddleware", "SubAgentMiddleware"]);
|
|
7342
|
+
/**
|
|
7343
|
+
* Type guard: is this a fully-constructed HarnessProfile (frozen with
|
|
7344
|
+
* Set fields) or raw options?
|
|
7345
|
+
*
|
|
7346
|
+
* Options use arrays for `excludedTools`; profiles use `Set`. We
|
|
7347
|
+
* distinguish by checking whether `excludedTools` has a `.has` method
|
|
7348
|
+
* (present on Set, absent on Array).
|
|
7349
|
+
*/
|
|
7350
|
+
function isHarnessProfile(value) {
|
|
7351
|
+
return value.excludedTools != null && typeof value.excludedTools.has === "function" && !Array.isArray(value.excludedTools);
|
|
7352
|
+
}
|
|
7353
|
+
/**
|
|
7354
|
+
* Resolve middleware to a concrete array, invoking the factory if
|
|
7355
|
+
* needed.
|
|
7356
|
+
*
|
|
7357
|
+
* @internal
|
|
7358
|
+
*/
|
|
7359
|
+
function resolveMiddleware(middleware) {
|
|
7360
|
+
if (typeof middleware === "function") return middleware();
|
|
7361
|
+
return middleware;
|
|
7362
|
+
}
|
|
7363
|
+
//#endregion
|
|
7364
|
+
//#region src/profiles/harness/create.ts
|
|
7365
|
+
/**
|
|
7366
|
+
* Validate the grammar of an `excludedMiddleware` entry.
|
|
7367
|
+
*
|
|
7368
|
+
* Runs at profile construction time so malformed entries fail
|
|
7369
|
+
* immediately. Checks:
|
|
7370
|
+
*
|
|
7371
|
+
* 1. Non-empty, non-whitespace string.
|
|
7372
|
+
* 2. No colons (class-path `module:Class` syntax is reserved).
|
|
7373
|
+
* 3. No underscore prefix (private middleware is not part of the
|
|
7374
|
+
* exclusion surface).
|
|
7375
|
+
* 4. Not a required scaffolding name.
|
|
7376
|
+
*
|
|
7377
|
+
* @param name - The middleware name to validate.
|
|
7378
|
+
* @throws {Error} When the name violates any rule.
|
|
7379
|
+
*/
|
|
7380
|
+
function validateExcludedMiddlewareName(name) {
|
|
7381
|
+
if (!name || !name.trim()) throw new Error("excludedMiddleware entries must be non-empty, non-whitespace strings.");
|
|
7382
|
+
if (name.includes(":")) throw new Error(`excludedMiddleware entries must be plain middleware names; class-path syntax is not supported, got "${name}".`);
|
|
7383
|
+
if (name.startsWith("_")) throw new Error(`excludedMiddleware entry "${name}" cannot start with "_" (underscore-prefixed names refer to private middleware not part of the public exclusion surface).`);
|
|
7384
|
+
if (REQUIRED_MIDDLEWARE_NAMES.has(name)) throw new Error(`Cannot exclude required middleware "${name}" — it provides essential agent capabilities that the runtime depends on.`);
|
|
7385
|
+
}
|
|
7386
|
+
/**
|
|
7387
|
+
* Create a frozen {@link HarnessProfile} from user-provided options.
|
|
7388
|
+
*
|
|
7389
|
+
* Validates all fields, converts mutable collections to their
|
|
7390
|
+
* frozen counterparts, and returns a frozen object.
|
|
7391
|
+
* Empty options produce a no-op profile (all defaults).
|
|
7392
|
+
*
|
|
7393
|
+
* @param options - Partial profile configuration.
|
|
7394
|
+
* @returns A frozen, validated `HarnessProfile`.
|
|
7395
|
+
* @throws {Error} When any field violates validation rules (invalid
|
|
7396
|
+
* middleware names, scaffolding exclusion attempts).
|
|
7397
|
+
*
|
|
7398
|
+
* @example
|
|
7399
|
+
* ```typescript
|
|
7400
|
+
* const profile = createHarnessProfile({
|
|
7401
|
+
* systemPromptSuffix: "Think step by step.",
|
|
7402
|
+
* excludedTools: ["execute"],
|
|
7403
|
+
* });
|
|
7404
|
+
* ```
|
|
7405
|
+
*/
|
|
7406
|
+
function createHarnessProfile(options = {}) {
|
|
7407
|
+
for (const name of options.excludedMiddleware ?? []) validateExcludedMiddlewareName(name);
|
|
7408
|
+
const toolDescriptionOverrides = Object.freeze(Object.assign(Object.create(null), options.toolDescriptionOverrides));
|
|
7409
|
+
const generalPurposeSubagent = options.generalPurposeSubagent ? Object.freeze({ ...options.generalPurposeSubagent }) : void 0;
|
|
7410
|
+
const profile = {
|
|
7411
|
+
baseSystemPrompt: options.baseSystemPrompt,
|
|
7412
|
+
systemPromptSuffix: options.systemPromptSuffix,
|
|
7413
|
+
toolDescriptionOverrides,
|
|
7414
|
+
excludedTools: new Set(options.excludedTools),
|
|
7415
|
+
excludedMiddleware: new Set(options.excludedMiddleware),
|
|
7416
|
+
extraMiddleware: options.extraMiddleware ?? [],
|
|
7417
|
+
generalPurposeSubagent
|
|
7418
|
+
};
|
|
7419
|
+
return Object.freeze(profile);
|
|
7420
|
+
}
|
|
7421
|
+
/**
|
|
7422
|
+
* An empty no-op profile used as the default when no registered
|
|
7423
|
+
* profile matches. Avoids creating a new object on every miss.
|
|
7424
|
+
*/
|
|
7425
|
+
const EMPTY_HARNESS_PROFILE = createHarnessProfile();
|
|
7426
|
+
//#endregion
|
|
7427
|
+
//#region src/profiles/harness/serialization.ts
|
|
7428
|
+
const POISONED_KEYS = new Set([
|
|
7429
|
+
"__proto__",
|
|
7430
|
+
"constructor",
|
|
7431
|
+
"prototype"
|
|
7432
|
+
]);
|
|
7433
|
+
/**
|
|
7434
|
+
* Zod schema for the general-purpose subagent config section of an
|
|
7435
|
+
* external harness profile config file.
|
|
7436
|
+
*/
|
|
7437
|
+
const generalPurposeSubagentConfigSchema = z.object({
|
|
7438
|
+
enabled: z.boolean().optional(),
|
|
7439
|
+
description: z.string().optional(),
|
|
7440
|
+
systemPrompt: z.string().optional()
|
|
7441
|
+
}).strict();
|
|
7442
|
+
/**
|
|
7443
|
+
* Zod schema for parsing a harness profile from an external JSON or
|
|
7444
|
+
* YAML config file.
|
|
7445
|
+
*
|
|
7446
|
+
* Uses `.strict()` to reject unknown keys (catches typos early). Array
|
|
7447
|
+
* fields (`excludedTools`, `excludedMiddleware`) accept arrays of
|
|
7448
|
+
* strings; the result is passed to {@link createHarnessProfile} which
|
|
7449
|
+
* converts them to `Set`.
|
|
7450
|
+
*
|
|
7451
|
+
* Does not include `extraMiddleware` — middleware instances cannot be
|
|
7452
|
+
* represented in JSON/YAML.
|
|
7453
|
+
*
|
|
7454
|
+
* @example
|
|
7455
|
+
* ```typescript
|
|
7456
|
+
* import { readFileSync } from "fs";
|
|
7457
|
+
* import YAML from "yaml";
|
|
7458
|
+
*
|
|
7459
|
+
* const raw = YAML.parse(readFileSync("profile.yaml", "utf-8"));
|
|
7460
|
+
* const config = harnessProfileConfigSchema.parse(raw);
|
|
7461
|
+
* const profile = createHarnessProfile(config);
|
|
7462
|
+
* ```
|
|
7463
|
+
*/
|
|
7464
|
+
const harnessProfileConfigSchema = z.object({
|
|
7465
|
+
baseSystemPrompt: z.string().optional(),
|
|
7466
|
+
systemPromptSuffix: z.string().optional(),
|
|
7467
|
+
toolDescriptionOverrides: z.record(z.string(), z.string()).optional(),
|
|
7468
|
+
excludedTools: z.array(z.string()).optional(),
|
|
7469
|
+
excludedMiddleware: z.array(z.string()).optional(),
|
|
7470
|
+
generalPurposeSubagent: generalPurposeSubagentConfigSchema.optional()
|
|
7471
|
+
}).strict();
|
|
7472
|
+
/**
|
|
7473
|
+
* Recursively check an object for prototype-pollution keys.
|
|
7474
|
+
*
|
|
7475
|
+
* Rejects `__proto__`, `constructor`, and `prototype` at any nesting
|
|
7476
|
+
* depth. Called before Zod parsing so poisoned payloads never reach
|
|
7477
|
+
* schema validation.
|
|
7478
|
+
*/
|
|
7479
|
+
function rejectPoisonedKeys(value, path = "") {
|
|
7480
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return;
|
|
7481
|
+
for (const key of Object.keys(value)) {
|
|
7482
|
+
if (POISONED_KEYS.has(key)) throw new Error(`Rejected dangerous key "${key}" at ${path || "root"} in harness profile config.`);
|
|
7483
|
+
rejectPoisonedKeys(value[key], path ? `${path}.${key}` : key);
|
|
7484
|
+
}
|
|
7485
|
+
}
|
|
7486
|
+
/**
|
|
7487
|
+
* Parse an untrusted JSON/YAML object into a validated
|
|
7488
|
+
* {@link HarnessProfile}.
|
|
7489
|
+
*
|
|
7490
|
+
* Combines Zod schema validation with prototype-pollution protection
|
|
7491
|
+
* and profile construction validation. Use this for any config data
|
|
7492
|
+
* that originates from files, network, or user input.
|
|
7493
|
+
*
|
|
7494
|
+
* @param data - Raw object from `JSON.parse()` or `YAML.parse()`.
|
|
7495
|
+
* @returns A frozen, validated `HarnessProfile`.
|
|
7496
|
+
* @throws {z.ZodError} When the data fails schema validation.
|
|
7497
|
+
* @throws {Error} When profile-level validation fails (e.g.,
|
|
7498
|
+
* scaffolding violation in `excludedMiddleware`).
|
|
7499
|
+
*/
|
|
7500
|
+
function parseHarnessProfileConfig(data) {
|
|
7501
|
+
rejectPoisonedKeys(data);
|
|
7502
|
+
return createHarnessProfile(harnessProfileConfigSchema.parse(data));
|
|
7503
|
+
}
|
|
7504
|
+
/**
|
|
7505
|
+
* Serialize a {@link HarnessProfile} to a JSON-compatible object.
|
|
7506
|
+
*
|
|
7507
|
+
* Omits `undefined` fields and `extraMiddleware` (runtime-only).
|
|
7508
|
+
* Throws if `extraMiddleware` contains instances — callers should
|
|
7509
|
+
* strip it before serializing if they've set it.
|
|
7510
|
+
*
|
|
7511
|
+
* @param profile - The profile to serialize.
|
|
7512
|
+
* @returns A plain object matching {@link HarnessProfileConfigData}.
|
|
7513
|
+
* @throws {Error} When `extraMiddleware` is non-empty (cannot be
|
|
7514
|
+
* serialized to JSON).
|
|
7515
|
+
*/
|
|
7516
|
+
function serializeProfile(profile) {
|
|
7517
|
+
if (resolveMiddleware(profile.extraMiddleware).length > 0) throw new Error("Cannot serialize a HarnessProfile with non-empty extraMiddleware — middleware instances are runtime-only and have no JSON representation.");
|
|
7518
|
+
const result = {};
|
|
7519
|
+
if (profile.baseSystemPrompt !== void 0) result.baseSystemPrompt = profile.baseSystemPrompt;
|
|
7520
|
+
if (profile.systemPromptSuffix !== void 0) result.systemPromptSuffix = profile.systemPromptSuffix;
|
|
7521
|
+
if (Object.keys(profile.toolDescriptionOverrides).length > 0) result.toolDescriptionOverrides = { ...profile.toolDescriptionOverrides };
|
|
7522
|
+
if (profile.excludedTools.size > 0) result.excludedTools = [...profile.excludedTools];
|
|
7523
|
+
if (profile.excludedMiddleware.size > 0) result.excludedMiddleware = [...profile.excludedMiddleware];
|
|
7524
|
+
if (profile.generalPurposeSubagent !== void 0) {
|
|
7525
|
+
const gp = {};
|
|
7526
|
+
if (profile.generalPurposeSubagent.enabled !== void 0) gp.enabled = profile.generalPurposeSubagent.enabled;
|
|
7527
|
+
if (profile.generalPurposeSubagent.description !== void 0) gp.description = profile.generalPurposeSubagent.description;
|
|
7528
|
+
if (profile.generalPurposeSubagent.systemPrompt !== void 0) gp.systemPrompt = profile.generalPurposeSubagent.systemPrompt;
|
|
7529
|
+
if (Object.keys(gp).length > 0) result.generalPurposeSubagent = gp;
|
|
7530
|
+
}
|
|
7531
|
+
return result;
|
|
7532
|
+
}
|
|
7533
|
+
//#endregion
|
|
7534
|
+
//#region src/profiles/harness/merge.ts
|
|
7535
|
+
/**
|
|
7536
|
+
* Merge two middleware sequences by `.name`.
|
|
7537
|
+
*
|
|
7538
|
+
* When the override has a middleware whose `.name` already appears in
|
|
7539
|
+
* the base, the override instance replaces the base instance at the
|
|
7540
|
+
* same position. Novel names from the override are appended. If the
|
|
7541
|
+
* base has duplicates of the same name, only the first is replaced;
|
|
7542
|
+
* later duplicates are dropped.
|
|
7543
|
+
*
|
|
7544
|
+
* Returns a factory to ensure fresh resolution on each call.
|
|
7545
|
+
*/
|
|
7546
|
+
function mergeMiddleware(base, override) {
|
|
7547
|
+
const baseArr = resolveMiddleware(base);
|
|
7548
|
+
const overrideArr = resolveMiddleware(override);
|
|
7549
|
+
if (baseArr.length === 0) return override;
|
|
7550
|
+
if (overrideArr.length === 0) return base;
|
|
7551
|
+
return () => {
|
|
7552
|
+
const baseSeq = resolveMiddleware(base);
|
|
7553
|
+
const overrideSeq = resolveMiddleware(override);
|
|
7554
|
+
const overrideByName = new Map(overrideSeq.map((m) => [m.name, m]));
|
|
7555
|
+
const merged = [];
|
|
7556
|
+
const replaced = /* @__PURE__ */ new Set();
|
|
7557
|
+
for (const entry of baseSeq) {
|
|
7558
|
+
const replacement = overrideByName.get(entry.name);
|
|
7559
|
+
if (replacement) {
|
|
7560
|
+
if (!replaced.has(entry.name)) {
|
|
7561
|
+
merged.push(replacement);
|
|
7562
|
+
replaced.add(entry.name);
|
|
7563
|
+
}
|
|
7564
|
+
} else merged.push(entry);
|
|
7565
|
+
}
|
|
7566
|
+
for (const entry of overrideSeq) if (!replaced.has(entry.name)) merged.push(entry);
|
|
7567
|
+
return merged;
|
|
7568
|
+
};
|
|
7569
|
+
}
|
|
7570
|
+
/**
|
|
7571
|
+
* Merge two GP subagent configs field-wise.
|
|
7572
|
+
*
|
|
7573
|
+
* Override wins per sub-field when not `undefined`; unset fields
|
|
7574
|
+
* inherit from base. Returns `undefined` only when both inputs are
|
|
7575
|
+
* `undefined`.
|
|
7576
|
+
*/
|
|
7577
|
+
function mergeGeneralPurposeSubagentConfigs(base, override) {
|
|
7578
|
+
if (base === void 0) return override;
|
|
7579
|
+
if (override === void 0) return base;
|
|
7580
|
+
return {
|
|
7581
|
+
enabled: override.enabled ?? base.enabled,
|
|
7582
|
+
description: override.description ?? base.description,
|
|
7583
|
+
systemPrompt: override.systemPrompt ?? base.systemPrompt
|
|
7584
|
+
};
|
|
7585
|
+
}
|
|
7586
|
+
/**
|
|
7587
|
+
* Merge two harness profiles, layering `override` on top of `base`.
|
|
7588
|
+
*
|
|
7589
|
+
* Merge semantics per field:
|
|
7590
|
+
*
|
|
7591
|
+
* | Field | Strategy |
|
|
7592
|
+
* |-------|----------|
|
|
7593
|
+
* | `baseSystemPrompt` | Override wins if not `undefined` |
|
|
7594
|
+
* | `systemPromptSuffix` | Override wins if not `undefined` |
|
|
7595
|
+
* | `toolDescriptionOverrides` | Object spread merge; override wins per key |
|
|
7596
|
+
* | `excludedTools` | Set union |
|
|
7597
|
+
* | `excludedMiddleware` | Set union |
|
|
7598
|
+
* | `extraMiddleware` | Merge by `.name`; override instance replaces base at same position; novel names appended |
|
|
7599
|
+
* | `generalPurposeSubagent` | Field-wise merge; override wins per sub-field |
|
|
7600
|
+
*
|
|
7601
|
+
* @param base - Lower-priority profile (e.g., provider-wide).
|
|
7602
|
+
* @param override - Higher-priority profile (e.g., exact model).
|
|
7603
|
+
* @returns A new merged profile.
|
|
7604
|
+
*/
|
|
7605
|
+
function mergeProfiles(base, override) {
|
|
7606
|
+
return createHarnessProfile({
|
|
7607
|
+
baseSystemPrompt: override.baseSystemPrompt ?? base.baseSystemPrompt,
|
|
7608
|
+
systemPromptSuffix: override.systemPromptSuffix ?? base.systemPromptSuffix,
|
|
7609
|
+
toolDescriptionOverrides: {
|
|
7610
|
+
...base.toolDescriptionOverrides,
|
|
7611
|
+
...override.toolDescriptionOverrides
|
|
7612
|
+
},
|
|
7613
|
+
excludedTools: [...base.excludedTools, ...override.excludedTools],
|
|
7614
|
+
excludedMiddleware: [...base.excludedMiddleware, ...override.excludedMiddleware],
|
|
7615
|
+
extraMiddleware: mergeMiddleware(base.extraMiddleware, override.extraMiddleware),
|
|
7616
|
+
generalPurposeSubagent: mergeGeneralPurposeSubagentConfigs(base.generalPurposeSubagent, override.generalPurposeSubagent)
|
|
7617
|
+
});
|
|
7618
|
+
}
|
|
7619
|
+
//#endregion
|
|
7620
|
+
//#region src/profiles/harness/builtins/anthropic-opus-4-7.ts
|
|
7621
|
+
const SYSTEM_PROMPT_SUFFIX$3 = `\
|
|
7622
|
+
<use_parallel_tool_calls>
|
|
7623
|
+
If you intend to call multiple tools and there are no dependencies between the tool calls, make all of the independent tool calls in parallel. Prioritize calling tools simultaneously whenever the actions can be done in parallel rather than sequentially. For example, when reading 3 files, run 3 tool calls in parallel to read all 3 files into context at the same time. Maximize use of parallel tool calls where possible to increase speed and efficiency. However, if some tool calls depend on previous calls to inform dependent values like the parameters, do NOT call these tools in parallel and instead call them sequentially. Never use placeholders or guess missing parameters in tool calls.
|
|
7624
|
+
</use_parallel_tool_calls>
|
|
7625
|
+
|
|
7626
|
+
<investigate_before_answering>
|
|
7627
|
+
Never speculate about code you have not opened. If the user references a specific file, you MUST read the file before answering. Make sure to investigate and read relevant files BEFORE answering questions about the codebase. Never make any claims about code before investigating unless you are certain of the correct answer - give grounded and hallucination-free answers.
|
|
7628
|
+
</investigate_before_answering>
|
|
7629
|
+
|
|
7630
|
+
<tool_result_reflection>
|
|
7631
|
+
After receiving tool results, carefully reflect on their quality and determine optimal next steps before proceeding. Use your thinking to plan and iterate based on this new information, and then take the best next action.
|
|
7632
|
+
</tool_result_reflection>
|
|
7633
|
+
|
|
7634
|
+
<tool_usage>
|
|
7635
|
+
When a task depends on the state of files, tests, or system output, use tools to observe that state directly rather than reasoning from memory about what it probably contains. Read files before describing them. Run tests before claiming they pass. Search the codebase before asserting a symbol does or does not exist. Active investigation with tools is the default mode of working, not a fallback.
|
|
7636
|
+
</tool_usage>
|
|
7637
|
+
|
|
7638
|
+
<subagent_usage>
|
|
7639
|
+
Do not spawn a subagent for work you can complete directly in a single response (e.g. refactoring a function you can already see).
|
|
7640
|
+
|
|
7641
|
+
Spawn multiple subagents in the same turn when fanning out across items or reading multiple files.
|
|
7642
|
+
</subagent_usage>`;
|
|
7643
|
+
/**
|
|
7644
|
+
* Register the built-in Claude Opus 4.7 harness profile.
|
|
7645
|
+
*
|
|
7646
|
+
* Layers a system-prompt suffix onto `anthropic:claude-opus-4-7`
|
|
7647
|
+
* tuned to the model's documented behaviors: parallel tool calls,
|
|
7648
|
+
* grounded answers, post-tool reflection, active investigation, and
|
|
7649
|
+
* subagent spawning guidance.
|
|
7650
|
+
*
|
|
7651
|
+
* @internal
|
|
7652
|
+
*/
|
|
7653
|
+
function register$3() {
|
|
7654
|
+
registerHarnessProfileImpl("anthropic:claude-opus-4-7", createHarnessProfile({ systemPromptSuffix: SYSTEM_PROMPT_SUFFIX$3 }));
|
|
7655
|
+
}
|
|
7656
|
+
//#endregion
|
|
7657
|
+
//#region src/profiles/harness/builtins/anthropic-sonnet-4-6.ts
|
|
7658
|
+
const SYSTEM_PROMPT_SUFFIX$2 = `\
|
|
7659
|
+
<use_parallel_tool_calls>
|
|
7660
|
+
If you intend to call multiple tools and there are no dependencies between the tool calls, make all of the independent tool calls in parallel. Prioritize calling tools simultaneously whenever the actions can be done in parallel rather than sequentially. For example, when reading 3 files, run 3 tool calls in parallel to read all 3 files into context at the same time. Maximize use of parallel tool calls where possible to increase speed and efficiency. However, if some tool calls depend on previous calls to inform dependent values like the parameters, do NOT call these tools in parallel and instead call them sequentially. Never use placeholders or guess missing parameters in tool calls.
|
|
7661
|
+
</use_parallel_tool_calls>
|
|
7662
|
+
|
|
7663
|
+
<investigate_before_answering>
|
|
7664
|
+
Never speculate about code you have not opened. If the user references a specific file, you MUST read the file before answering. Make sure to investigate and read relevant files BEFORE answering questions about the codebase. Never make any claims about code before investigating unless you are certain of the correct answer - give grounded and hallucination-free answers.
|
|
7665
|
+
</investigate_before_answering>
|
|
7666
|
+
|
|
7667
|
+
<tool_result_reflection>
|
|
7668
|
+
After receiving tool results, carefully reflect on their quality and determine optimal next steps before proceeding. Use your thinking to plan and iterate based on this new information, and then take the best next action.
|
|
7669
|
+
</tool_result_reflection>`;
|
|
7670
|
+
/**
|
|
7671
|
+
* Register the built-in Claude Sonnet 4.6 harness profile.
|
|
7672
|
+
*
|
|
7673
|
+
* Layers universal Claude guidance (parallel tool calls, grounded
|
|
7674
|
+
* answers, post-tool reflection) onto `anthropic:claude-sonnet-4-6`.
|
|
7675
|
+
*
|
|
7676
|
+
* No Sonnet-specific overlays — Anthropic's guidance for Sonnet 4.6
|
|
7677
|
+
* centers on API-level configuration rather than system-prompt
|
|
7678
|
+
* adjustments. This module exists as the audit anchor: its presence
|
|
7679
|
+
* documents the review and justifies the absence of model-specific
|
|
7680
|
+
* content.
|
|
7681
|
+
*
|
|
7682
|
+
* @internal
|
|
7683
|
+
*/
|
|
7684
|
+
function register$2() {
|
|
7685
|
+
registerHarnessProfileImpl("anthropic:claude-sonnet-4-6", createHarnessProfile({ systemPromptSuffix: SYSTEM_PROMPT_SUFFIX$2 }));
|
|
7686
|
+
}
|
|
7687
|
+
//#endregion
|
|
7688
|
+
//#region src/profiles/harness/builtins/anthropic-haiku-4-5.ts
|
|
7689
|
+
const SYSTEM_PROMPT_SUFFIX$1 = `\
|
|
7690
|
+
<use_parallel_tool_calls>
|
|
7691
|
+
If you intend to call multiple tools and there are no dependencies between the tool calls, make all of the independent tool calls in parallel. Prioritize calling tools simultaneously whenever the actions can be done in parallel rather than sequentially. For example, when reading 3 files, run 3 tool calls in parallel to read all 3 files into context at the same time. Maximize use of parallel tool calls where possible to increase speed and efficiency. However, if some tool calls depend on previous calls to inform dependent values like the parameters, do NOT call these tools in parallel and instead call them sequentially. Never use placeholders or guess missing parameters in tool calls.
|
|
7692
|
+
</use_parallel_tool_calls>
|
|
7693
|
+
|
|
7694
|
+
<investigate_before_answering>
|
|
7695
|
+
Never speculate about code you have not opened. If the user references a specific file, you MUST read the file before answering. Make sure to investigate and read relevant files BEFORE answering questions about the codebase. Never make any claims about code before investigating unless you are certain of the correct answer - give grounded and hallucination-free answers.
|
|
7696
|
+
</investigate_before_answering>
|
|
7697
|
+
|
|
7698
|
+
<tool_result_reflection>
|
|
7699
|
+
After receiving tool results, carefully reflect on their quality and determine optimal next steps before proceeding. Use your thinking to plan and iterate based on this new information, and then take the best next action.
|
|
7700
|
+
</tool_result_reflection>`;
|
|
7701
|
+
/**
|
|
7702
|
+
* Register the built-in Claude Haiku 4.5 harness profile.
|
|
7703
|
+
*
|
|
7704
|
+
* Same universal Claude guidance as Sonnet 4.6. No Haiku-specific
|
|
7705
|
+
* overlays.
|
|
7706
|
+
*
|
|
7707
|
+
* @internal
|
|
7708
|
+
*/
|
|
7709
|
+
function register$1() {
|
|
7710
|
+
registerHarnessProfileImpl("anthropic:claude-haiku-4-5", createHarnessProfile({ systemPromptSuffix: SYSTEM_PROMPT_SUFFIX$1 }));
|
|
7711
|
+
}
|
|
7712
|
+
//#endregion
|
|
7713
|
+
//#region src/profiles/harness/builtins/openai-codex.ts
|
|
7714
|
+
/**
|
|
7715
|
+
* Model specs that receive the Codex harness profile.
|
|
7716
|
+
*
|
|
7717
|
+
* All variants share the same trained response style, so a single
|
|
7718
|
+
* suffix works across the family.
|
|
7719
|
+
*/
|
|
7720
|
+
const CODEX_MODEL_SPECS = [
|
|
7721
|
+
"openai:gpt-5.1-codex",
|
|
7722
|
+
"openai:gpt-5.2-codex",
|
|
7723
|
+
"openai:gpt-5.3-codex"
|
|
7724
|
+
];
|
|
7725
|
+
const SYSTEM_PROMPT_SUFFIX = `\
|
|
7726
|
+
## Codex-Specific Behavior
|
|
7727
|
+
|
|
7728
|
+
- You are an autonomous senior engineer. Once given a direction, proactively \
|
|
7729
|
+
gather context, plan, implement, and verify without waiting for additional \
|
|
7730
|
+
prompts at each step.
|
|
7731
|
+
- Persist until the task is fully handled end-to-end within the current turn \
|
|
7732
|
+
whenever feasible. Do not stop at analysis or partial fixes; carry changes \
|
|
7733
|
+
through implementation, verification, and a clear explanation of outcomes.
|
|
7734
|
+
- Bias to action: default to implementing with reasonable assumptions. Do not \
|
|
7735
|
+
end your turn with clarifications unless truly blocked.
|
|
7736
|
+
- Do not communicate an upfront plan or status preamble before acting. Just act.
|
|
7737
|
+
|
|
7738
|
+
## Parallel Tool Use
|
|
7739
|
+
|
|
7740
|
+
- Before any tool call, decide ALL files and resources you will need.
|
|
7741
|
+
- Batch reads, searches, and other independent operations into parallel tool \
|
|
7742
|
+
calls instead of issuing them one at a time.
|
|
7743
|
+
- Only make sequential calls when you truly cannot determine the next step \
|
|
7744
|
+
without seeing a prior result.
|
|
7745
|
+
|
|
7746
|
+
## Plan Hygiene
|
|
7747
|
+
|
|
7748
|
+
- Before finishing, reconcile every TODO or plan item created via write_todos. \
|
|
7749
|
+
Mark each as done, blocked (with a one-sentence reason), or cancelled. Do not \
|
|
7750
|
+
finish with pending items.`;
|
|
7751
|
+
/**
|
|
7752
|
+
* Register the built-in Codex harness profiles.
|
|
7753
|
+
*
|
|
7754
|
+
* Registers the same profile under each Codex model spec. Per-model
|
|
7755
|
+
* keys (not the bare `"openai"` prefix) keep the default behavior of
|
|
7756
|
+
* non-Codex OpenAI models unchanged.
|
|
7757
|
+
*
|
|
7758
|
+
* @internal
|
|
7759
|
+
*/
|
|
7760
|
+
function register() {
|
|
7761
|
+
const profile = createHarnessProfile({ systemPromptSuffix: SYSTEM_PROMPT_SUFFIX });
|
|
7762
|
+
for (const spec of CODEX_MODEL_SPECS) registerHarnessProfileImpl(spec, profile);
|
|
7763
|
+
}
|
|
7764
|
+
//#endregion
|
|
7765
|
+
//#region src/profiles/harness/builtins/index.ts
|
|
7766
|
+
/**
|
|
7767
|
+
* Register all built-in harness profiles and snapshot the resulting
|
|
7768
|
+
* registry keys as the builtin baseline.
|
|
7769
|
+
*
|
|
7770
|
+
* Called once during lazy bootstrap by `ensureBuiltinsLoaded()`.
|
|
7771
|
+
* Uses `registerHarnessProfileImpl` internally (not the public
|
|
7772
|
+
* `registerHarnessProfile`) to avoid triggering re-entrant bootstrap.
|
|
7773
|
+
*
|
|
7774
|
+
* @internal
|
|
7775
|
+
*/
|
|
7776
|
+
function loadBuiltinProfiles() {
|
|
7777
|
+
register$3();
|
|
7778
|
+
register$2();
|
|
7779
|
+
register$1();
|
|
7780
|
+
register();
|
|
7781
|
+
snapshotBuiltinKeys();
|
|
7782
|
+
}
|
|
7783
|
+
//#endregion
|
|
7784
|
+
//#region src/profiles/harness/registry.ts
|
|
7785
|
+
/**
|
|
7786
|
+
* Process-global symbol key for the harness profile registry. The `.v1`
|
|
7787
|
+
* suffix is a version gate — bump it when the {@link HarnessProfileRegistry}
|
|
7788
|
+
* shape changes in a breaking way so that incompatible versions coexist
|
|
7789
|
+
* on `globalThis` without corrupting each other.
|
|
7790
|
+
*/
|
|
7791
|
+
const PROFILE_REGISTRY_KEY = Symbol.for("deepagents.harness-profiles.v1");
|
|
7792
|
+
/**
|
|
7793
|
+
* Returns the process-global registry, creating it on first access.
|
|
7794
|
+
*/
|
|
7795
|
+
function getHarnessProfileRegistry() {
|
|
7796
|
+
const global = globalThis;
|
|
7797
|
+
if (global[PROFILE_REGISTRY_KEY] == null) global[PROFILE_REGISTRY_KEY] = {
|
|
7798
|
+
profiles: /* @__PURE__ */ new Map(),
|
|
7799
|
+
builtinKeys: /* @__PURE__ */ new Set(),
|
|
7800
|
+
builtinsLoaded: false
|
|
7801
|
+
};
|
|
7802
|
+
return global[PROFILE_REGISTRY_KEY];
|
|
7803
|
+
}
|
|
7804
|
+
/**
|
|
7805
|
+
* Ensure lazy-loaded builtin profiles have been registered.
|
|
7806
|
+
*
|
|
7807
|
+
* Called by the public `registerHarnessProfile` and lookup functions.
|
|
7808
|
+
* Built-in registration modules call `registerHarnessProfileImpl`
|
|
7809
|
+
* directly to avoid re-entrant bootstrap.
|
|
7810
|
+
*
|
|
7811
|
+
* @internal
|
|
7812
|
+
*/
|
|
7813
|
+
function ensureBuiltinsLoaded() {
|
|
7814
|
+
const registry = getHarnessProfileRegistry();
|
|
7815
|
+
if (registry.builtinsLoaded) return;
|
|
7816
|
+
registry.builtinsLoaded = true;
|
|
7817
|
+
loadBuiltinProfiles();
|
|
7818
|
+
}
|
|
7819
|
+
/**
|
|
7820
|
+
* Snapshot the current registry keys as the builtin baseline.
|
|
7821
|
+
*
|
|
7822
|
+
* Called by the builtin loader after all built-in profiles are
|
|
7823
|
+
* registered. This allows {@link hasUserRegisteredProfiles} to
|
|
7824
|
+
* distinguish user registrations from built-ins.
|
|
7825
|
+
*
|
|
7826
|
+
* @internal
|
|
7827
|
+
*/
|
|
7828
|
+
function snapshotBuiltinKeys() {
|
|
7829
|
+
const registry = getHarnessProfileRegistry();
|
|
7830
|
+
registry.builtinKeys = new Set(registry.profiles.keys());
|
|
7831
|
+
}
|
|
7832
|
+
/**
|
|
7833
|
+
* Core registration implementation. Does not trigger lazy bootstrap.
|
|
7834
|
+
*
|
|
7835
|
+
* Used by built-in profile modules during bootstrap. External callers
|
|
7836
|
+
* should use {@link registerHarnessProfile} instead.
|
|
7837
|
+
*
|
|
7838
|
+
* @internal
|
|
7839
|
+
*/
|
|
7840
|
+
function registerHarnessProfileImpl(key, profile) {
|
|
7841
|
+
key = validateProfileKey(key);
|
|
7842
|
+
const { profiles } = getHarnessProfileRegistry();
|
|
7843
|
+
const existing = profiles.get(key);
|
|
7844
|
+
if (existing !== void 0) profiles.set(key, mergeProfiles(existing, profile));
|
|
7845
|
+
else profiles.set(key, profile);
|
|
7846
|
+
}
|
|
7847
|
+
/**
|
|
7848
|
+
* Register a harness profile for a provider or specific model.
|
|
7849
|
+
*
|
|
7850
|
+
* Accepts either a pre-built {@link HarnessProfile} (from
|
|
7851
|
+
* {@link createHarnessProfile}) or raw {@link HarnessProfileOptions}
|
|
7852
|
+
* that will be validated and frozen automatically.
|
|
7853
|
+
*
|
|
7854
|
+
* Registrations are **additive**: if a profile already exists under
|
|
7855
|
+
* `key`, the new profile is merged on top. The incoming profile's
|
|
7856
|
+
* fields win on scalar conflicts; set fields union; middleware
|
|
7857
|
+
* sequences merge by name.
|
|
7858
|
+
*
|
|
7859
|
+
* @param key - Either a bare provider (`"openai"`) for provider-wide
|
|
7860
|
+
* defaults, or `"provider:model"` for a per-model override.
|
|
7861
|
+
* @param profile - A `HarnessProfile` or options to build one from.
|
|
7862
|
+
* @throws {Error} When `key` is malformed or profile validation
|
|
7863
|
+
* fails.
|
|
7864
|
+
*
|
|
7865
|
+
* @example
|
|
7866
|
+
* ```typescript
|
|
7867
|
+
* import { registerHarnessProfile } from "@langchain/deepagents";
|
|
7868
|
+
*
|
|
7869
|
+
* registerHarnessProfile("openai", {
|
|
7870
|
+
* systemPromptSuffix: "Respond concisely.",
|
|
7871
|
+
* });
|
|
7872
|
+
*
|
|
7873
|
+
* registerHarnessProfile("openai:gpt-5.4", {
|
|
7874
|
+
* excludedTools: ["execute"],
|
|
7875
|
+
* });
|
|
7876
|
+
* ```
|
|
7877
|
+
*/
|
|
7878
|
+
function registerHarnessProfile(key, profile) {
|
|
7879
|
+
ensureBuiltinsLoaded();
|
|
7880
|
+
registerHarnessProfileImpl(key, isHarnessProfile(profile) ? profile : createHarnessProfile(profile));
|
|
7881
|
+
}
|
|
7882
|
+
/**
|
|
7883
|
+
* Look up the {@link HarnessProfile} for a model spec string.
|
|
7884
|
+
*
|
|
7885
|
+
* Resolution order:
|
|
7886
|
+
*
|
|
7887
|
+
* 1. **Exact match** on `spec` (e.g., `"openai:gpt-5.4"`).
|
|
7888
|
+
* 2. **Provider prefix** (everything before `:`) when `spec` contains
|
|
7889
|
+
* a colon and both halves are non-empty.
|
|
7890
|
+
* 3. When both exist, they are **merged** (provider as base, exact as
|
|
7891
|
+
* override).
|
|
7892
|
+
* 4. `undefined` when nothing matches.
|
|
7893
|
+
*
|
|
7894
|
+
* Malformed specs (empty, multiple colons, empty halves) return
|
|
7895
|
+
* `undefined` without consulting the registry.
|
|
7896
|
+
*
|
|
7897
|
+
* @param spec - Model spec in `"provider:model"` format, or a bare
|
|
7898
|
+
* provider/model identifier.
|
|
7899
|
+
* @returns The matching profile, or `undefined`.
|
|
7900
|
+
*/
|
|
7901
|
+
function getHarnessProfile(spec) {
|
|
7902
|
+
if (spec.split(":").length > 2) return;
|
|
7903
|
+
const colonIdx = spec.indexOf(":");
|
|
7904
|
+
const hasColon = colonIdx !== -1;
|
|
7905
|
+
const provider = hasColon ? spec.slice(0, colonIdx) : void 0;
|
|
7906
|
+
const model = hasColon ? spec.slice(colonIdx + 1) : void 0;
|
|
7907
|
+
if (hasColon && (!provider || !model)) return;
|
|
7908
|
+
ensureBuiltinsLoaded();
|
|
7909
|
+
const { profiles } = getHarnessProfileRegistry();
|
|
7910
|
+
const exact = profiles.get(spec);
|
|
7911
|
+
const base = provider ? profiles.get(provider) : void 0;
|
|
7912
|
+
if (exact !== void 0 && base !== void 0) return mergeProfiles(base, exact);
|
|
7913
|
+
return exact ?? base;
|
|
7914
|
+
}
|
|
7915
|
+
/**
|
|
7916
|
+
* Resolve the harness profile for a model, falling back to the
|
|
7917
|
+
* empty default when nothing matches.
|
|
7918
|
+
*
|
|
7919
|
+
* When `spec` is set (the original model parameter), it drives the
|
|
7920
|
+
* lookup directly. When absent (pre-built model instance),
|
|
7921
|
+
* `providerHint` and `identifierHint` are used to construct lookup
|
|
7922
|
+
* keys.
|
|
7923
|
+
*
|
|
7924
|
+
* @param opts - Model metadata used to resolve the profile.
|
|
7925
|
+
* @returns The resolved profile (never `undefined`).
|
|
7926
|
+
*
|
|
7927
|
+
* @internal
|
|
7928
|
+
*/
|
|
7929
|
+
function resolveHarnessProfile(opts = {}) {
|
|
7930
|
+
const { spec, providerHint, identifierHint } = opts;
|
|
7931
|
+
if (spec !== void 0) return getHarnessProfile(spec) ?? EMPTY_HARNESS_PROFILE;
|
|
7932
|
+
if (providerHint && identifierHint && !identifierHint.includes(":")) {
|
|
7933
|
+
const profile = getHarnessProfile(`${providerHint}:${identifierHint}`);
|
|
7934
|
+
if (profile) return profile;
|
|
7935
|
+
}
|
|
7936
|
+
if (identifierHint && identifierHint.includes(":")) {
|
|
7937
|
+
const profile = getHarnessProfile(identifierHint);
|
|
7938
|
+
if (profile) return profile;
|
|
7939
|
+
}
|
|
7940
|
+
if (providerHint) {
|
|
7941
|
+
const profile = getHarnessProfile(providerHint);
|
|
7942
|
+
if (profile) return profile;
|
|
7943
|
+
}
|
|
7944
|
+
return EMPTY_HARNESS_PROFILE;
|
|
7945
|
+
}
|
|
7946
|
+
/**
|
|
7947
|
+
* Apply a profile's prompt overlay to a base prompt string.
|
|
7948
|
+
*
|
|
7949
|
+
* - `baseSystemPrompt` (when set) replaces `basePrompt` entirely.
|
|
7950
|
+
* - `systemPromptSuffix` (when set) is appended with `\n\n`.
|
|
7951
|
+
*
|
|
7952
|
+
* Both are independently optional. A profile that sets only the suffix
|
|
7953
|
+
* layers it on top of whatever base the caller passes in.
|
|
7954
|
+
*
|
|
7955
|
+
* Used uniformly for the main agent, declarative subagents, and the
|
|
7956
|
+
* auto-added general-purpose subagent.
|
|
7957
|
+
*
|
|
7958
|
+
* @param profile - The harness profile to apply.
|
|
7959
|
+
* @param basePrompt - The default base prompt (e.g., `BASE_AGENT_PROMPT`).
|
|
7960
|
+
* @returns The assembled prompt string.
|
|
7961
|
+
*/
|
|
7962
|
+
function applyProfilePrompt(profile, basePrompt) {
|
|
7963
|
+
const prompt = profile.baseSystemPrompt !== void 0 ? profile.baseSystemPrompt : basePrompt;
|
|
7964
|
+
if (profile.systemPromptSuffix !== void 0) return `${prompt}\n\n${profile.systemPromptSuffix}`;
|
|
7965
|
+
return prompt;
|
|
7966
|
+
}
|
|
7967
|
+
//#endregion
|
|
7968
|
+
//#region src/utils.ts
|
|
7969
|
+
/**
|
|
7970
|
+
* Detect whether a model is an Anthropic model.
|
|
7971
|
+
*
|
|
7972
|
+
* Used to gate Anthropic-specific prompt caching optimizations
|
|
7973
|
+
* (cache_control breakpoints).
|
|
7974
|
+
*/
|
|
7975
|
+
function isAnthropicModel(model) {
|
|
7976
|
+
if (typeof model === "string") {
|
|
7977
|
+
if (model.includes(":")) return model.split(":")[0] === "anthropic";
|
|
7978
|
+
return model.startsWith("claude");
|
|
7979
|
+
}
|
|
7980
|
+
if (model.getName() === "ConfigurableModel") return model._defaultConfig?.modelProvider === "anthropic";
|
|
7981
|
+
return model.getName() === "ChatAnthropic";
|
|
7982
|
+
}
|
|
7983
|
+
/**
|
|
7984
|
+
* Extract the provider name from a model instance for profile lookup.
|
|
7985
|
+
*
|
|
7986
|
+
* Checks `_defaultConfig.modelProvider` (ConfigurableModel) and falls
|
|
7987
|
+
* back to known model class name → provider mappings.
|
|
7988
|
+
*
|
|
7989
|
+
* @internal
|
|
7990
|
+
*/
|
|
7991
|
+
function getModelProvider(model) {
|
|
7992
|
+
if (model.getName() === "ConfigurableModel") return model._defaultConfig?.modelProvider;
|
|
7993
|
+
return {
|
|
7994
|
+
ChatAnthropic: "anthropic",
|
|
7995
|
+
ChatOpenAI: "openai",
|
|
7996
|
+
ChatGoogleGenerativeAI: "google"
|
|
7997
|
+
}[model.getName()];
|
|
7998
|
+
}
|
|
7999
|
+
/**
|
|
8000
|
+
* Extract the model identifier from a model instance for profile
|
|
8001
|
+
* lookup.
|
|
8002
|
+
*
|
|
8003
|
+
* Checks `_defaultConfig.model`, `model_name`, and `modelName` in
|
|
8004
|
+
* that order.
|
|
8005
|
+
*
|
|
8006
|
+
* @internal
|
|
8007
|
+
*/
|
|
8008
|
+
function getModelIdentifier(model) {
|
|
8009
|
+
return (model.getName() === "ConfigurableModel" ? model._defaultConfig : void 0)?.model ?? model.model_name ?? model.modelName ?? void 0;
|
|
8010
|
+
}
|
|
8011
|
+
//#endregion
|
|
6923
8012
|
//#region src/agent.ts
|
|
6924
8013
|
const BASE_AGENT_PROMPT = context`
|
|
6925
8014
|
You are a Deep Agent, an AI assistant that helps users accomplish tasks using tools. You respond with text and tool calls. The user can see your responses and tool outputs in real time.
|
|
@@ -6963,18 +8052,6 @@ const BUILTIN_TOOL_NAMES = new Set([
|
|
|
6963
8052
|
"write_todos"
|
|
6964
8053
|
]);
|
|
6965
8054
|
/**
|
|
6966
|
-
* Detect whether a model is an Anthropic model.
|
|
6967
|
-
* Used to gate Anthropic-specific prompt caching optimizations (cache_control breakpoints).
|
|
6968
|
-
*/
|
|
6969
|
-
function isAnthropicModel(model) {
|
|
6970
|
-
if (typeof model === "string") {
|
|
6971
|
-
if (model.includes(":")) return model.split(":")[0] === "anthropic";
|
|
6972
|
-
return model.startsWith("claude");
|
|
6973
|
-
}
|
|
6974
|
-
if (model.getName() === "ConfigurableModel") return model._defaultConfig?.modelProvider === "anthropic";
|
|
6975
|
-
return model.getName() === "ChatAnthropic";
|
|
6976
|
-
}
|
|
6977
|
-
/**
|
|
6978
8055
|
* Create a Deep Agent.
|
|
6979
8056
|
*
|
|
6980
8057
|
* This is the main entry point for building a production-style agent with
|
|
@@ -7008,6 +8085,12 @@ function createDeepAgent(params = {}) {
|
|
|
7008
8085
|
const { model = "anthropic:claude-sonnet-4-6", tools = [], systemPrompt, middleware: customMiddleware = [], subagents = [], responseFormat, contextSchema, checkpointer, store, backend = (config) => new StateBackend(config), interruptOn, name, memory, skills, permissions = [], streamTransformers = [] } = params;
|
|
7009
8086
|
const collidingTools = tools.map((t) => t.name).filter((n) => typeof n === "string" && BUILTIN_TOOL_NAMES.has(n));
|
|
7010
8087
|
if (collidingTools.length > 0) throw new ConfigurationError(`Tool name(s) [${collidingTools.join(", ")}] conflict with built-in tools. Rename your custom tools to avoid this.`, "TOOL_NAME_COLLISION");
|
|
8088
|
+
const harnessProfile = typeof model === "string" ? resolveHarnessProfile({ spec: model }) : resolveHarnessProfile({
|
|
8089
|
+
providerHint: getModelProvider(model),
|
|
8090
|
+
identifierHint: getModelIdentifier(model)
|
|
8091
|
+
});
|
|
8092
|
+
const toolOverrides = harnessProfile.toolDescriptionOverrides;
|
|
8093
|
+
const effectiveTools = Object.keys(toolOverrides).length > 0 ? tools.map((t) => t.name in toolOverrides ? Object.assign(Object.create(Object.getPrototypeOf(t)), t, { description: toolOverrides[t.name] }) : t) : tools;
|
|
7011
8094
|
const anthropicModel = isAnthropicModel(model);
|
|
7012
8095
|
const cacheMiddleware = anthropicModel ? [anthropicPromptCachingMiddleware({
|
|
7013
8096
|
unsupportedModelBehavior: "ignore",
|
|
@@ -7046,12 +8129,16 @@ function createDeepAgent(params = {}) {
|
|
|
7046
8129
|
const allSubagents = subagents;
|
|
7047
8130
|
const asyncSubAgents = allSubagents.filter((item) => isAsyncSubAgent(item));
|
|
7048
8131
|
const inlineSubagents = allSubagents.filter((item) => !isAsyncSubAgent(item)).map((item) => "runnable" in item ? item : normalizeSubagentSpec(item));
|
|
7049
|
-
|
|
8132
|
+
const gpConfig = harnessProfile.generalPurposeSubagent;
|
|
8133
|
+
if (!(gpConfig?.enabled === false) && !inlineSubagents.some((item) => item.name === GENERAL_PURPOSE_SUBAGENT["name"])) {
|
|
8134
|
+
const gpSystemPrompt = gpConfig?.systemPrompt ?? applyProfilePrompt(harnessProfile, GENERAL_PURPOSE_SUBAGENT.systemPrompt);
|
|
7050
8135
|
const generalPurposeSpec = normalizeSubagentSpec({
|
|
7051
8136
|
...GENERAL_PURPOSE_SUBAGENT,
|
|
8137
|
+
description: gpConfig?.description ?? GENERAL_PURPOSE_SUBAGENT.description,
|
|
8138
|
+
systemPrompt: gpSystemPrompt,
|
|
7052
8139
|
model,
|
|
7053
8140
|
skills,
|
|
7054
|
-
tools
|
|
8141
|
+
tools: effectiveTools
|
|
7055
8142
|
});
|
|
7056
8143
|
inlineSubagents.unshift(generalPurposeSpec);
|
|
7057
8144
|
}
|
|
@@ -7067,7 +8154,7 @@ function createDeepAgent(params = {}) {
|
|
|
7067
8154
|
}),
|
|
7068
8155
|
createSubAgentMiddleware({
|
|
7069
8156
|
defaultModel: model,
|
|
7070
|
-
defaultTools:
|
|
8157
|
+
defaultTools: effectiveTools,
|
|
7071
8158
|
defaultInterruptOn: interruptOn,
|
|
7072
8159
|
subagents: inlineSubagents,
|
|
7073
8160
|
generalPurposeAgent: false
|
|
@@ -7092,6 +8179,31 @@ function createDeepAgent(params = {}) {
|
|
|
7092
8179
|
})] : [],
|
|
7093
8180
|
...interruptOn ? [humanInTheLoopMiddleware({ interruptOn })] : []
|
|
7094
8181
|
];
|
|
8182
|
+
const profileMiddleware = resolveMiddleware(harnessProfile.extraMiddleware);
|
|
8183
|
+
if (profileMiddleware.length > 0) {
|
|
8184
|
+
const cacheIdx = middleware.findIndex((m) => m.name === "AnthropicPromptCachingMiddleware");
|
|
8185
|
+
if (cacheIdx !== -1) middleware.splice(cacheIdx, 0, ...profileMiddleware);
|
|
8186
|
+
else middleware.push(...profileMiddleware);
|
|
8187
|
+
}
|
|
8188
|
+
if (harnessProfile.excludedMiddleware.size > 0) {
|
|
8189
|
+
const excluded = harnessProfile.excludedMiddleware;
|
|
8190
|
+
const filtered = middleware.filter((m) => !excluded.has(m.name));
|
|
8191
|
+
middleware.length = 0;
|
|
8192
|
+
middleware.push(...filtered);
|
|
8193
|
+
}
|
|
8194
|
+
if (harnessProfile.excludedTools.size > 0) {
|
|
8195
|
+
const excludedTools = harnessProfile.excludedTools;
|
|
8196
|
+
middleware.push(createMiddleware({
|
|
8197
|
+
name: "_ToolExclusionMiddleware",
|
|
8198
|
+
wrapModelCall: async (request, handler) => {
|
|
8199
|
+
return handler({
|
|
8200
|
+
...request,
|
|
8201
|
+
tools: request.tools?.filter((t) => !excludedTools.has(t.name))
|
|
8202
|
+
});
|
|
8203
|
+
}
|
|
8204
|
+
}));
|
|
8205
|
+
}
|
|
8206
|
+
const effectiveBasePrompt = applyProfilePrompt(harnessProfile, BASE_AGENT_PROMPT);
|
|
7095
8207
|
/**
|
|
7096
8208
|
* Return as DeepAgent with proper DeepAgentTypeConfig
|
|
7097
8209
|
* - Response: InferStructuredResponse<TResponse> (unwraps ToolStrategy<T>/ProviderStrategy<T> → T)
|
|
@@ -7109,15 +8221,15 @@ function createDeepAgent(params = {}) {
|
|
|
7109
8221
|
text: systemPrompt
|
|
7110
8222
|
}, {
|
|
7111
8223
|
type: "text",
|
|
7112
|
-
text:
|
|
8224
|
+
text: effectiveBasePrompt
|
|
7113
8225
|
}] }) : SystemMessage.isInstance(systemPrompt) ? new SystemMessage({ contentBlocks: [...systemPrompt.contentBlocks, {
|
|
7114
8226
|
type: "text",
|
|
7115
|
-
text:
|
|
8227
|
+
text: effectiveBasePrompt
|
|
7116
8228
|
}] }) : new SystemMessage({ contentBlocks: [{
|
|
7117
8229
|
type: "text",
|
|
7118
|
-
text:
|
|
8230
|
+
text: effectiveBasePrompt
|
|
7119
8231
|
}] }),
|
|
7120
|
-
tools,
|
|
8232
|
+
tools: effectiveTools,
|
|
7121
8233
|
middleware,
|
|
7122
8234
|
...responseFormat !== null && { responseFormat },
|
|
7123
8235
|
contextSchema,
|
|
@@ -7646,6 +8758,6 @@ function listSkills(options) {
|
|
|
7646
8758
|
return Array.from(allSkills.values());
|
|
7647
8759
|
}
|
|
7648
8760
|
//#endregion
|
|
7649
|
-
export { BaseSandbox, CompositeBackend, ConfigurationError, DEFAULT_GENERAL_PURPOSE_DESCRIPTION, DEFAULT_SUBAGENT_PROMPT, FilesystemBackend, GENERAL_PURPOSE_SUBAGENT, LangSmithSandbox, LocalShellBackend, MAX_SKILL_DESCRIPTION_LENGTH, MAX_SKILL_FILE_SIZE, MAX_SKILL_NAME_LENGTH, SandboxError, StateBackend, StoreBackend, TASK_SYSTEM_PROMPT, adaptBackendProtocol, adaptSandboxProtocol, computeSummarizationDefaults, createAgentMemoryMiddleware, createAsyncSubAgentMiddleware, createCompletionCallbackMiddleware, createDeepAgent, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSettings, createSkillsMiddleware, createSubAgentMiddleware, createSubagentTransformer, createSummarizationMiddleware, filesValue, findProjectRoot, isAsyncSubAgent, isSandboxBackend, isSandboxProtocol, listSkills, parseSkillMetadata, resolveBackend };
|
|
8761
|
+
export { BaseSandbox, CompositeBackend, ConfigurationError, ContextHubBackend, DEFAULT_GENERAL_PURPOSE_DESCRIPTION, DEFAULT_SUBAGENT_PROMPT, EMPTY_HARNESS_PROFILE, FilesystemBackend, GENERAL_PURPOSE_SUBAGENT, LangSmithSandbox, LocalShellBackend, MAX_SKILL_DESCRIPTION_LENGTH, MAX_SKILL_FILE_SIZE, MAX_SKILL_NAME_LENGTH, REQUIRED_MIDDLEWARE_NAMES, SandboxError, StateBackend, StoreBackend, TASK_SYSTEM_PROMPT, adaptBackendProtocol, adaptSandboxProtocol, computeSummarizationDefaults, createAgentMemoryMiddleware, createAsyncSubAgentMiddleware, createCompletionCallbackMiddleware, createDeepAgent, createFilesystemMiddleware, createHarnessProfile, createMemoryMiddleware, createPatchToolCallsMiddleware, createSettings, createSkillsMiddleware, createSubAgentMiddleware, createSubagentTransformer, createSummarizationMiddleware, filesValue, findProjectRoot, generalPurposeSubagentConfigSchema, getHarnessProfile, harnessProfileConfigSchema, isAsyncSubAgent, isSandboxBackend, isSandboxProtocol, listSkills, parseHarnessProfileConfig, parseSkillMetadata, registerHarnessProfile, resolveBackend, serializeProfile };
|
|
7650
8762
|
|
|
7651
8763
|
//# sourceMappingURL=index.js.map
|