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 CHANGED
@@ -46,6 +46,7 @@ let node_child_process = require("node:child_process");
46
46
  node_child_process = __toESM(node_child_process, 1);
47
47
  let fast_glob = require("fast-glob");
48
48
  fast_glob = __toESM(fast_glob, 1);
49
+ let langsmith = require("langsmith");
49
50
  let langsmith_experimental_sandbox = require("langsmith/experimental/sandbox");
50
51
  let node_os = require("node:os");
51
52
  node_os = __toESM(node_os, 1);
@@ -5726,6 +5727,341 @@ var FilesystemBackend = class {
5726
5727
  }
5727
5728
  };
5728
5729
  //#endregion
5730
+ //#region src/backends/context-hub.ts
5731
+ /**
5732
+ * ContextHubBackend: Store files in a LangSmith Hub agent repo (persistent).
5733
+ */
5734
+ const URL_COMMIT_SUFFIX_RE = /:([0-9a-f]{8,64})$/i;
5735
+ const TEXT_MIME_TYPE = "text/plain";
5736
+ const FNMATCH_OPTIONS = { bash: true };
5737
+ function getErrorMessage(error) {
5738
+ if (typeof error === "string") return error;
5739
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") return error.message;
5740
+ return String(error);
5741
+ }
5742
+ function splitLinesKeepEnds(content) {
5743
+ const lines = [];
5744
+ let lineStart = 0;
5745
+ for (let index = 0; index < content.length; index += 1) if (content[index] === "\n") {
5746
+ lines.push(content.slice(lineStart, index + 1));
5747
+ lineStart = index + 1;
5748
+ }
5749
+ if (lineStart < content.length) lines.push(content.slice(lineStart));
5750
+ return lines;
5751
+ }
5752
+ function sliceReadContent(content, offset, limit) {
5753
+ if (!content || content.trim() === "") return { content };
5754
+ const lines = splitLinesKeepEnds(content.replace(/\r\n/g, "\n").replace(/\r/g, "\n"));
5755
+ const startIndex = offset;
5756
+ const endIndex = Math.min(startIndex + limit, lines.length);
5757
+ if (startIndex >= lines.length) return { error: `Line offset ${offset} exceeds file length (${lines.length} lines)` };
5758
+ return { content: lines.slice(startIndex, endIndex).join("") };
5759
+ }
5760
+ function isLangSmithNotFoundError(error) {
5761
+ if (typeof error !== "object" || error === null) return false;
5762
+ const maybeError = error;
5763
+ return maybeError.name === "LangSmithNotFoundError" || maybeError.status === 404;
5764
+ }
5765
+ function isLangSmithError(error) {
5766
+ if (typeof error !== "object" || error === null) return false;
5767
+ const maybeError = error;
5768
+ return typeof maybeError.name === "string" && maybeError.name.startsWith("LangSmith") || typeof maybeError.status === "number";
5769
+ }
5770
+ function getLangSmithStatus(error) {
5771
+ if (typeof error !== "object" || error === null) return;
5772
+ const maybeError = error;
5773
+ if (typeof maybeError.status === "number") return maybeError.status;
5774
+ }
5775
+ function mapHubFileOperationError(error) {
5776
+ const status = getLangSmithStatus(error);
5777
+ if (status === 401 || status === 403) return "permission_denied";
5778
+ if (status === 404) return "file_not_found";
5779
+ return "invalid_path";
5780
+ }
5781
+ /**
5782
+ * Backend that stores files in a LangSmith Hub agent repo (persistent).
5783
+ */
5784
+ var ContextHubBackend = class ContextHubBackend {
5785
+ identifier;
5786
+ client;
5787
+ cache = null;
5788
+ linkedEntries = {};
5789
+ commitHash = null;
5790
+ constructor(identifier, options = {}) {
5791
+ this.identifier = identifier;
5792
+ this.client = options.client ?? new langsmith.Client();
5793
+ }
5794
+ static stripPrefix(path) {
5795
+ return path.replace(/^\/+/, "");
5796
+ }
5797
+ static toHubUnavailableError(error) {
5798
+ return `Hub unavailable: ${getErrorMessage(error)}`;
5799
+ }
5800
+ async loadTree() {
5801
+ let context;
5802
+ try {
5803
+ context = await this.client.pullAgent(this.identifier);
5804
+ } catch (error) {
5805
+ if (isLangSmithNotFoundError(error)) {
5806
+ this.cache = {};
5807
+ this.linkedEntries = {};
5808
+ this.commitHash = null;
5809
+ return;
5810
+ }
5811
+ throw error;
5812
+ }
5813
+ this.commitHash = context.commit_hash;
5814
+ this.cache = {};
5815
+ this.linkedEntries = {};
5816
+ for (const [path, entry] of Object.entries(context.files)) if (entry.type === "file") this.cache[path] = entry.content;
5817
+ else if ((entry.type === "agent" || entry.type === "skill") && typeof entry.repo_handle === "string") this.linkedEntries[path] = entry.repo_handle;
5818
+ }
5819
+ async ensureCache() {
5820
+ if (this.cache === null) await this.loadTree();
5821
+ if (this.cache === null) throw new Error("Context Hub cache failed to initialize");
5822
+ return this.cache;
5823
+ }
5824
+ async commit(files) {
5825
+ if (Object.keys(files).length === 0) return;
5826
+ const payload = {};
5827
+ for (const [path, content] of Object.entries(files)) payload[path] = {
5828
+ type: "file",
5829
+ content
5830
+ };
5831
+ const url = await this.client.pushAgent(this.identifier, {
5832
+ files: payload,
5833
+ ...this.commitHash ? { parentCommit: this.commitHash } : {}
5834
+ });
5835
+ const match = URL_COMMIT_SUFFIX_RE.exec(url);
5836
+ if (match) this.commitHash = match[1];
5837
+ if (this.cache !== null) for (const [path, content] of Object.entries(files)) this.cache[path] = content;
5838
+ }
5839
+ /**
5840
+ * Return linked-entry paths mapped to their repo handles.
5841
+ */
5842
+ async getLinkedEntries() {
5843
+ await this.ensureCache();
5844
+ return { ...this.linkedEntries };
5845
+ }
5846
+ /**
5847
+ * Return true if the hub repo already exists with at least one commit.
5848
+ */
5849
+ async hasPriorCommits() {
5850
+ await this.ensureCache();
5851
+ return this.commitHash !== null;
5852
+ }
5853
+ async ls(path = "/") {
5854
+ const hubPrefix = ContextHubBackend.stripPrefix(path).replace(/\/+$/, "");
5855
+ let cache;
5856
+ try {
5857
+ cache = await this.ensureCache();
5858
+ } catch (error) {
5859
+ if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
5860
+ throw error;
5861
+ }
5862
+ const dirs = /* @__PURE__ */ new Set();
5863
+ const entries = [];
5864
+ for (const filePath of Object.keys(cache)) {
5865
+ if (hubPrefix && !filePath.startsWith(`${hubPrefix}/`)) continue;
5866
+ const relative = hubPrefix ? filePath.slice(hubPrefix.length + 1) : filePath;
5867
+ if (!relative) continue;
5868
+ const slashIndex = relative.indexOf("/");
5869
+ if (slashIndex === -1) {
5870
+ entries.push({
5871
+ path: `/${filePath}`,
5872
+ is_dir: false
5873
+ });
5874
+ continue;
5875
+ }
5876
+ const dirName = relative.slice(0, slashIndex);
5877
+ const dirPath = hubPrefix ? `${hubPrefix}/${dirName}` : dirName;
5878
+ if (!dirs.has(dirPath)) {
5879
+ dirs.add(dirPath);
5880
+ entries.push({
5881
+ path: `/${dirPath}`,
5882
+ is_dir: true
5883
+ });
5884
+ }
5885
+ }
5886
+ return { files: entries };
5887
+ }
5888
+ async read(filePath, offset = 0, limit = 2e3) {
5889
+ const hubPath = ContextHubBackend.stripPrefix(filePath);
5890
+ let cache;
5891
+ try {
5892
+ cache = await this.ensureCache();
5893
+ } catch (error) {
5894
+ if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
5895
+ throw error;
5896
+ }
5897
+ const content = cache[hubPath];
5898
+ if (content === void 0) return { error: `File '${filePath}' not found` };
5899
+ const sliced = sliceReadContent(content, offset, limit);
5900
+ if (sliced.error) return { error: sliced.error };
5901
+ return {
5902
+ content: sliced.content ?? "",
5903
+ mimeType: TEXT_MIME_TYPE
5904
+ };
5905
+ }
5906
+ async readRaw(filePath) {
5907
+ const readResult = await this.read(filePath, 0, Number.MAX_SAFE_INTEGER);
5908
+ if (readResult.error || typeof readResult.content !== "string") return { error: readResult.error ?? `File '${filePath}' not found` };
5909
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5910
+ return { data: {
5911
+ content: readResult.content,
5912
+ mimeType: TEXT_MIME_TYPE,
5913
+ created_at: now,
5914
+ modified_at: now
5915
+ } };
5916
+ }
5917
+ async grep(pattern, path = null, glob = null) {
5918
+ let cache;
5919
+ try {
5920
+ cache = await this.ensureCache();
5921
+ } catch (error) {
5922
+ if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
5923
+ throw error;
5924
+ }
5925
+ const prefix = path ? ContextHubBackend.stripPrefix(path).replace(/\/+$/, "") : "";
5926
+ const matches = [];
5927
+ for (const [filePath, content] of Object.entries(cache)) {
5928
+ if (prefix && !filePath.startsWith(prefix)) continue;
5929
+ if (glob && !micromatch.default.isMatch(filePath, glob, FNMATCH_OPTIONS)) continue;
5930
+ const lines = content.split("\n");
5931
+ for (let index = 0; index < lines.length; index++) {
5932
+ const line = lines[index];
5933
+ if (line.includes(pattern)) matches.push({
5934
+ path: `/${filePath}`,
5935
+ line: index + 1,
5936
+ text: line
5937
+ });
5938
+ }
5939
+ }
5940
+ return { matches };
5941
+ }
5942
+ async glob(pattern, _path = "/") {
5943
+ let cache;
5944
+ try {
5945
+ cache = await this.ensureCache();
5946
+ } catch (error) {
5947
+ if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
5948
+ throw error;
5949
+ }
5950
+ const files = [];
5951
+ for (const filePath of Object.keys(cache)) if (micromatch.default.isMatch(`/${filePath}`, pattern, FNMATCH_OPTIONS) || micromatch.default.isMatch(filePath, pattern, FNMATCH_OPTIONS)) files.push({
5952
+ path: `/${filePath}`,
5953
+ is_dir: false
5954
+ });
5955
+ return { files };
5956
+ }
5957
+ async write(filePath, content) {
5958
+ const hubPath = ContextHubBackend.stripPrefix(filePath);
5959
+ try {
5960
+ await this.ensureCache();
5961
+ await this.commit({ [hubPath]: content });
5962
+ } catch (error) {
5963
+ if (isLangSmithError(error)) {
5964
+ this.cache = null;
5965
+ return { error: ContextHubBackend.toHubUnavailableError(error) };
5966
+ }
5967
+ throw error;
5968
+ }
5969
+ return {
5970
+ path: filePath,
5971
+ filesUpdate: null
5972
+ };
5973
+ }
5974
+ async edit(filePath, oldString, newString, replaceAll = false) {
5975
+ const hubPath = ContextHubBackend.stripPrefix(filePath);
5976
+ try {
5977
+ const current = (await this.ensureCache())[hubPath];
5978
+ if (current === void 0) return { error: `Error: File '${filePath}' not found` };
5979
+ const replacementResult = performStringReplacement(current, oldString, newString, replaceAll);
5980
+ if (typeof replacementResult === "string") return { error: replacementResult };
5981
+ const [newContent, occurrences] = replacementResult;
5982
+ await this.commit({ [hubPath]: newContent });
5983
+ return {
5984
+ path: filePath,
5985
+ filesUpdate: null,
5986
+ occurrences
5987
+ };
5988
+ } catch (error) {
5989
+ if (isLangSmithError(error)) {
5990
+ this.cache = null;
5991
+ return { error: ContextHubBackend.toHubUnavailableError(error) };
5992
+ }
5993
+ throw error;
5994
+ }
5995
+ }
5996
+ async uploadFiles(files) {
5997
+ const decoder = new TextDecoder("utf-8", { fatal: true });
5998
+ const decoded = [];
5999
+ const validFiles = {};
6000
+ for (const [path, content] of files) try {
6001
+ const text = decoder.decode(content);
6002
+ decoded.push([path, text]);
6003
+ validFiles[ContextHubBackend.stripPrefix(path)] = text;
6004
+ } catch {
6005
+ decoded.push([path, null]);
6006
+ }
6007
+ let commitError = null;
6008
+ if (Object.keys(validFiles).length > 0) try {
6009
+ await this.ensureCache();
6010
+ await this.commit(validFiles);
6011
+ } catch (error) {
6012
+ if (isLangSmithError(error)) {
6013
+ this.cache = null;
6014
+ commitError = mapHubFileOperationError(error);
6015
+ } else throw error;
6016
+ }
6017
+ return decoded.map(([path, text]) => {
6018
+ if (text === null) return {
6019
+ path,
6020
+ error: "invalid_path"
6021
+ };
6022
+ if (commitError !== null) return {
6023
+ path,
6024
+ error: commitError
6025
+ };
6026
+ return {
6027
+ path,
6028
+ error: null
6029
+ };
6030
+ });
6031
+ }
6032
+ async downloadFiles(paths) {
6033
+ let cache;
6034
+ try {
6035
+ cache = await this.ensureCache();
6036
+ } catch (error) {
6037
+ if (isLangSmithError(error)) {
6038
+ const mappedError = mapHubFileOperationError(error);
6039
+ return paths.map((path) => ({
6040
+ path,
6041
+ content: null,
6042
+ error: mappedError
6043
+ }));
6044
+ }
6045
+ throw error;
6046
+ }
6047
+ const encoder = new TextEncoder();
6048
+ return paths.map((path) => {
6049
+ const hubPath = ContextHubBackend.stripPrefix(path);
6050
+ const content = cache[hubPath];
6051
+ if (content !== void 0) return {
6052
+ path,
6053
+ content: encoder.encode(content),
6054
+ error: null
6055
+ };
6056
+ return {
6057
+ path,
6058
+ content: null,
6059
+ error: "file_not_found"
6060
+ };
6061
+ });
6062
+ }
6063
+ };
6064
+ //#endregion
5729
6065
  //#region src/backends/local-shell.ts
5730
6066
  /**
5731
6067
  * LocalShellBackend: Node.js implementation of the filesystem backend with unrestricted local shell execution.
@@ -6464,7 +6800,7 @@ var BaseSandbox = class {
6464
6800
  * ```typescript
6465
6801
  * import { LangSmithSandbox, createDeepAgent } from "deepagents";
6466
6802
  *
6467
- * const sandbox = await LangSmithSandbox.create({ templateName: "deepagents-cli" });
6803
+ * const sandbox = await LangSmithSandbox.create({ snapshotId: "your-snapshot-id" });
6468
6804
  *
6469
6805
  * const agent = createDeepAgent({ model, backend: sandbox });
6470
6806
  *
@@ -6588,6 +6924,41 @@ var LangSmithSandbox = class LangSmithSandbox extends BaseSandbox {
6588
6924
  this.#isRunning = false;
6589
6925
  }
6590
6926
  /**
6927
+ * Start a stopped sandbox and wait until it is ready.
6928
+ *
6929
+ * After calling this, `isRunning` will be `true` and the sandbox
6930
+ * can be used for command execution and file operations again.
6931
+ *
6932
+ * @param options - Start options (timeout, signal).
6933
+ */
6934
+ async start(options = {}) {
6935
+ await this.#sandbox.start(options);
6936
+ this.#isRunning = true;
6937
+ }
6938
+ /**
6939
+ * Stop the sandbox without deleting it.
6940
+ *
6941
+ * Sandbox files are preserved and the sandbox can be restarted later
6942
+ * with `start()`. After calling this, `isRunning` will be `false`.
6943
+ */
6944
+ async stop() {
6945
+ await this.#sandbox.stop();
6946
+ this.#isRunning = false;
6947
+ }
6948
+ /**
6949
+ * Capture a snapshot from this running sandbox.
6950
+ *
6951
+ * Snapshots can be used to create new sandboxes via
6952
+ * `LangSmithSandbox.create({ snapshotId })`.
6953
+ *
6954
+ * @param name - Name for the snapshot.
6955
+ * @param options - Capture options (checkpoint, timeout).
6956
+ * @returns The created Snapshot in "ready" status.
6957
+ */
6958
+ async captureSnapshot(name, options = {}) {
6959
+ return this.#sandbox.captureSnapshot(name, options);
6960
+ }
6961
+ /**
6591
6962
  * Create and return a new LangSmithSandbox in one step.
6592
6963
  *
6593
6964
  * This is the recommended way to create a sandbox — no need to import
@@ -6595,7 +6966,10 @@ var LangSmithSandbox = class LangSmithSandbox extends BaseSandbox {
6595
6966
  *
6596
6967
  * @example
6597
6968
  * ```typescript
6598
- * const sandbox = await LangSmithSandbox.create({ templateName: "deepagents" });
6969
+ * const sandbox = await LangSmithSandbox.create({
6970
+ * snapshotId: "abc-123",
6971
+ * });
6972
+ *
6599
6973
  * try {
6600
6974
  * const agent = createDeepAgent({ model, backend: sandbox });
6601
6975
  * await agent.invoke({ messages: [...] });
@@ -6604,10 +6978,14 @@ var LangSmithSandbox = class LangSmithSandbox extends BaseSandbox {
6604
6978
  * }
6605
6979
  * ```
6606
6980
  */
6607
- static async create(options = {}) {
6608
- const { templateName = "deepagents", apiKey = process.env.LANGSMITH_API_KEY, defaultTimeout, ...createSandboxOptions } = options;
6981
+ static async create(options) {
6982
+ const { templateName, apiKey = process.env.LANGSMITH_API_KEY, defaultTimeout, snapshotId, ...createSandboxOptions } = options;
6983
+ if (snapshotId && templateName) throw new Error("snapshotId and templateName are mutually exclusive. Pass only one creation source.");
6984
+ if (!snapshotId && !templateName) throw new Error("Either snapshotId or templateName is required. snapshotId is recommended — template-based creation is deprecated.");
6985
+ const sandboxOptions = { ...createSandboxOptions };
6986
+ if (templateName) sandboxOptions.snapshotName = templateName;
6609
6987
  return new LangSmithSandbox({
6610
- sandbox: await new langsmith_experimental_sandbox.SandboxClient({ apiKey }).createSandbox(templateName, createSandboxOptions),
6988
+ sandbox: await new langsmith_experimental_sandbox.SandboxClient({ apiKey }).createSandbox(snapshotId, sandboxOptions),
6611
6989
  defaultTimeout
6612
6990
  });
6613
6991
  }
@@ -6959,6 +7337,717 @@ function createSubagentTransformer(path) {
6959
7337
  };
6960
7338
  }
6961
7339
  //#endregion
7340
+ //#region src/profiles/keys.ts
7341
+ /**
7342
+ * Normalize and validate a profile registry key.
7343
+ *
7344
+ * Trims leading/trailing whitespace, then enforces the `"provider"` or
7345
+ * `"provider:model"` shape. Rejects empty strings, multiple colons, and
7346
+ * empty halves.
7347
+ *
7348
+ * @param key - The registry key to validate.
7349
+ * @returns The trimmed, validated key.
7350
+ * @throws {Error} When the key is malformed.
7351
+ *
7352
+ * @example
7353
+ * ```typescript
7354
+ * validateProfileKey("anthropic:claude-opus-4-7"); // "anthropic:claude-opus-4-7"
7355
+ * validateProfileKey(" openai "); // "openai"
7356
+ * validateProfileKey("openai:"); // throws
7357
+ * validateProfileKey(""); // throws
7358
+ * ```
7359
+ */
7360
+ function validateProfileKey(key) {
7361
+ const trimmed = key.trim();
7362
+ if (!trimmed) throw new Error("Profile key must be a non-empty string");
7363
+ if (trimmed.split(":").length > 2) throw new Error(`Profile key "${trimmed}" has more than one ":"; expected "provider" or "provider:model"`);
7364
+ if (trimmed.includes(":")) {
7365
+ const [provider, model] = trimmed.split(":");
7366
+ if (!provider.trim() || !model.trim()) throw new Error(`Profile key "${trimmed}" has an empty provider or model half; expected "provider:model"`);
7367
+ }
7368
+ return trimmed;
7369
+ }
7370
+ //#endregion
7371
+ //#region src/profiles/harness/types.ts
7372
+ /**
7373
+ * Middleware names that provide essential agent capabilities and cannot
7374
+ * be excluded via `excludedMiddleware`.
7375
+ *
7376
+ * - `FilesystemMiddleware` backs all built-in file tools and enforces
7377
+ * filesystem permissions.
7378
+ * - `SubAgentMiddleware` backs the `task` tool for subagent delegation.
7379
+ */
7380
+ const REQUIRED_MIDDLEWARE_NAMES = new Set(["FilesystemMiddleware", "SubAgentMiddleware"]);
7381
+ /**
7382
+ * Type guard: is this a fully-constructed HarnessProfile (frozen with
7383
+ * Set fields) or raw options?
7384
+ *
7385
+ * Options use arrays for `excludedTools`; profiles use `Set`. We
7386
+ * distinguish by checking whether `excludedTools` has a `.has` method
7387
+ * (present on Set, absent on Array).
7388
+ */
7389
+ function isHarnessProfile(value) {
7390
+ return value.excludedTools != null && typeof value.excludedTools.has === "function" && !Array.isArray(value.excludedTools);
7391
+ }
7392
+ /**
7393
+ * Resolve middleware to a concrete array, invoking the factory if
7394
+ * needed.
7395
+ *
7396
+ * @internal
7397
+ */
7398
+ function resolveMiddleware(middleware) {
7399
+ if (typeof middleware === "function") return middleware();
7400
+ return middleware;
7401
+ }
7402
+ //#endregion
7403
+ //#region src/profiles/harness/create.ts
7404
+ /**
7405
+ * Validate the grammar of an `excludedMiddleware` entry.
7406
+ *
7407
+ * Runs at profile construction time so malformed entries fail
7408
+ * immediately. Checks:
7409
+ *
7410
+ * 1. Non-empty, non-whitespace string.
7411
+ * 2. No colons (class-path `module:Class` syntax is reserved).
7412
+ * 3. No underscore prefix (private middleware is not part of the
7413
+ * exclusion surface).
7414
+ * 4. Not a required scaffolding name.
7415
+ *
7416
+ * @param name - The middleware name to validate.
7417
+ * @throws {Error} When the name violates any rule.
7418
+ */
7419
+ function validateExcludedMiddlewareName(name) {
7420
+ if (!name || !name.trim()) throw new Error("excludedMiddleware entries must be non-empty, non-whitespace strings.");
7421
+ if (name.includes(":")) throw new Error(`excludedMiddleware entries must be plain middleware names; class-path syntax is not supported, got "${name}".`);
7422
+ 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).`);
7423
+ if (REQUIRED_MIDDLEWARE_NAMES.has(name)) throw new Error(`Cannot exclude required middleware "${name}" — it provides essential agent capabilities that the runtime depends on.`);
7424
+ }
7425
+ /**
7426
+ * Create a frozen {@link HarnessProfile} from user-provided options.
7427
+ *
7428
+ * Validates all fields, converts mutable collections to their
7429
+ * frozen counterparts, and returns a frozen object.
7430
+ * Empty options produce a no-op profile (all defaults).
7431
+ *
7432
+ * @param options - Partial profile configuration.
7433
+ * @returns A frozen, validated `HarnessProfile`.
7434
+ * @throws {Error} When any field violates validation rules (invalid
7435
+ * middleware names, scaffolding exclusion attempts).
7436
+ *
7437
+ * @example
7438
+ * ```typescript
7439
+ * const profile = createHarnessProfile({
7440
+ * systemPromptSuffix: "Think step by step.",
7441
+ * excludedTools: ["execute"],
7442
+ * });
7443
+ * ```
7444
+ */
7445
+ function createHarnessProfile(options = {}) {
7446
+ for (const name of options.excludedMiddleware ?? []) validateExcludedMiddlewareName(name);
7447
+ const toolDescriptionOverrides = Object.freeze(Object.assign(Object.create(null), options.toolDescriptionOverrides));
7448
+ const generalPurposeSubagent = options.generalPurposeSubagent ? Object.freeze({ ...options.generalPurposeSubagent }) : void 0;
7449
+ const profile = {
7450
+ baseSystemPrompt: options.baseSystemPrompt,
7451
+ systemPromptSuffix: options.systemPromptSuffix,
7452
+ toolDescriptionOverrides,
7453
+ excludedTools: new Set(options.excludedTools),
7454
+ excludedMiddleware: new Set(options.excludedMiddleware),
7455
+ extraMiddleware: options.extraMiddleware ?? [],
7456
+ generalPurposeSubagent
7457
+ };
7458
+ return Object.freeze(profile);
7459
+ }
7460
+ /**
7461
+ * An empty no-op profile used as the default when no registered
7462
+ * profile matches. Avoids creating a new object on every miss.
7463
+ */
7464
+ const EMPTY_HARNESS_PROFILE = createHarnessProfile();
7465
+ //#endregion
7466
+ //#region src/profiles/harness/serialization.ts
7467
+ const POISONED_KEYS = new Set([
7468
+ "__proto__",
7469
+ "constructor",
7470
+ "prototype"
7471
+ ]);
7472
+ /**
7473
+ * Zod schema for the general-purpose subagent config section of an
7474
+ * external harness profile config file.
7475
+ */
7476
+ const generalPurposeSubagentConfigSchema = zod_v4.z.object({
7477
+ enabled: zod_v4.z.boolean().optional(),
7478
+ description: zod_v4.z.string().optional(),
7479
+ systemPrompt: zod_v4.z.string().optional()
7480
+ }).strict();
7481
+ /**
7482
+ * Zod schema for parsing a harness profile from an external JSON or
7483
+ * YAML config file.
7484
+ *
7485
+ * Uses `.strict()` to reject unknown keys (catches typos early). Array
7486
+ * fields (`excludedTools`, `excludedMiddleware`) accept arrays of
7487
+ * strings; the result is passed to {@link createHarnessProfile} which
7488
+ * converts them to `Set`.
7489
+ *
7490
+ * Does not include `extraMiddleware` — middleware instances cannot be
7491
+ * represented in JSON/YAML.
7492
+ *
7493
+ * @example
7494
+ * ```typescript
7495
+ * import { readFileSync } from "fs";
7496
+ * import YAML from "yaml";
7497
+ *
7498
+ * const raw = YAML.parse(readFileSync("profile.yaml", "utf-8"));
7499
+ * const config = harnessProfileConfigSchema.parse(raw);
7500
+ * const profile = createHarnessProfile(config);
7501
+ * ```
7502
+ */
7503
+ const harnessProfileConfigSchema = zod_v4.z.object({
7504
+ baseSystemPrompt: zod_v4.z.string().optional(),
7505
+ systemPromptSuffix: zod_v4.z.string().optional(),
7506
+ toolDescriptionOverrides: zod_v4.z.record(zod_v4.z.string(), zod_v4.z.string()).optional(),
7507
+ excludedTools: zod_v4.z.array(zod_v4.z.string()).optional(),
7508
+ excludedMiddleware: zod_v4.z.array(zod_v4.z.string()).optional(),
7509
+ generalPurposeSubagent: generalPurposeSubagentConfigSchema.optional()
7510
+ }).strict();
7511
+ /**
7512
+ * Recursively check an object for prototype-pollution keys.
7513
+ *
7514
+ * Rejects `__proto__`, `constructor`, and `prototype` at any nesting
7515
+ * depth. Called before Zod parsing so poisoned payloads never reach
7516
+ * schema validation.
7517
+ */
7518
+ function rejectPoisonedKeys(value, path = "") {
7519
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return;
7520
+ for (const key of Object.keys(value)) {
7521
+ if (POISONED_KEYS.has(key)) throw new Error(`Rejected dangerous key "${key}" at ${path || "root"} in harness profile config.`);
7522
+ rejectPoisonedKeys(value[key], path ? `${path}.${key}` : key);
7523
+ }
7524
+ }
7525
+ /**
7526
+ * Parse an untrusted JSON/YAML object into a validated
7527
+ * {@link HarnessProfile}.
7528
+ *
7529
+ * Combines Zod schema validation with prototype-pollution protection
7530
+ * and profile construction validation. Use this for any config data
7531
+ * that originates from files, network, or user input.
7532
+ *
7533
+ * @param data - Raw object from `JSON.parse()` or `YAML.parse()`.
7534
+ * @returns A frozen, validated `HarnessProfile`.
7535
+ * @throws {z.ZodError} When the data fails schema validation.
7536
+ * @throws {Error} When profile-level validation fails (e.g.,
7537
+ * scaffolding violation in `excludedMiddleware`).
7538
+ */
7539
+ function parseHarnessProfileConfig(data) {
7540
+ rejectPoisonedKeys(data);
7541
+ return createHarnessProfile(harnessProfileConfigSchema.parse(data));
7542
+ }
7543
+ /**
7544
+ * Serialize a {@link HarnessProfile} to a JSON-compatible object.
7545
+ *
7546
+ * Omits `undefined` fields and `extraMiddleware` (runtime-only).
7547
+ * Throws if `extraMiddleware` contains instances — callers should
7548
+ * strip it before serializing if they've set it.
7549
+ *
7550
+ * @param profile - The profile to serialize.
7551
+ * @returns A plain object matching {@link HarnessProfileConfigData}.
7552
+ * @throws {Error} When `extraMiddleware` is non-empty (cannot be
7553
+ * serialized to JSON).
7554
+ */
7555
+ function serializeProfile(profile) {
7556
+ 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.");
7557
+ const result = {};
7558
+ if (profile.baseSystemPrompt !== void 0) result.baseSystemPrompt = profile.baseSystemPrompt;
7559
+ if (profile.systemPromptSuffix !== void 0) result.systemPromptSuffix = profile.systemPromptSuffix;
7560
+ if (Object.keys(profile.toolDescriptionOverrides).length > 0) result.toolDescriptionOverrides = { ...profile.toolDescriptionOverrides };
7561
+ if (profile.excludedTools.size > 0) result.excludedTools = [...profile.excludedTools];
7562
+ if (profile.excludedMiddleware.size > 0) result.excludedMiddleware = [...profile.excludedMiddleware];
7563
+ if (profile.generalPurposeSubagent !== void 0) {
7564
+ const gp = {};
7565
+ if (profile.generalPurposeSubagent.enabled !== void 0) gp.enabled = profile.generalPurposeSubagent.enabled;
7566
+ if (profile.generalPurposeSubagent.description !== void 0) gp.description = profile.generalPurposeSubagent.description;
7567
+ if (profile.generalPurposeSubagent.systemPrompt !== void 0) gp.systemPrompt = profile.generalPurposeSubagent.systemPrompt;
7568
+ if (Object.keys(gp).length > 0) result.generalPurposeSubagent = gp;
7569
+ }
7570
+ return result;
7571
+ }
7572
+ //#endregion
7573
+ //#region src/profiles/harness/merge.ts
7574
+ /**
7575
+ * Merge two middleware sequences by `.name`.
7576
+ *
7577
+ * When the override has a middleware whose `.name` already appears in
7578
+ * the base, the override instance replaces the base instance at the
7579
+ * same position. Novel names from the override are appended. If the
7580
+ * base has duplicates of the same name, only the first is replaced;
7581
+ * later duplicates are dropped.
7582
+ *
7583
+ * Returns a factory to ensure fresh resolution on each call.
7584
+ */
7585
+ function mergeMiddleware(base, override) {
7586
+ const baseArr = resolveMiddleware(base);
7587
+ const overrideArr = resolveMiddleware(override);
7588
+ if (baseArr.length === 0) return override;
7589
+ if (overrideArr.length === 0) return base;
7590
+ return () => {
7591
+ const baseSeq = resolveMiddleware(base);
7592
+ const overrideSeq = resolveMiddleware(override);
7593
+ const overrideByName = new Map(overrideSeq.map((m) => [m.name, m]));
7594
+ const merged = [];
7595
+ const replaced = /* @__PURE__ */ new Set();
7596
+ for (const entry of baseSeq) {
7597
+ const replacement = overrideByName.get(entry.name);
7598
+ if (replacement) {
7599
+ if (!replaced.has(entry.name)) {
7600
+ merged.push(replacement);
7601
+ replaced.add(entry.name);
7602
+ }
7603
+ } else merged.push(entry);
7604
+ }
7605
+ for (const entry of overrideSeq) if (!replaced.has(entry.name)) merged.push(entry);
7606
+ return merged;
7607
+ };
7608
+ }
7609
+ /**
7610
+ * Merge two GP subagent configs field-wise.
7611
+ *
7612
+ * Override wins per sub-field when not `undefined`; unset fields
7613
+ * inherit from base. Returns `undefined` only when both inputs are
7614
+ * `undefined`.
7615
+ */
7616
+ function mergeGeneralPurposeSubagentConfigs(base, override) {
7617
+ if (base === void 0) return override;
7618
+ if (override === void 0) return base;
7619
+ return {
7620
+ enabled: override.enabled ?? base.enabled,
7621
+ description: override.description ?? base.description,
7622
+ systemPrompt: override.systemPrompt ?? base.systemPrompt
7623
+ };
7624
+ }
7625
+ /**
7626
+ * Merge two harness profiles, layering `override` on top of `base`.
7627
+ *
7628
+ * Merge semantics per field:
7629
+ *
7630
+ * | Field | Strategy |
7631
+ * |-------|----------|
7632
+ * | `baseSystemPrompt` | Override wins if not `undefined` |
7633
+ * | `systemPromptSuffix` | Override wins if not `undefined` |
7634
+ * | `toolDescriptionOverrides` | Object spread merge; override wins per key |
7635
+ * | `excludedTools` | Set union |
7636
+ * | `excludedMiddleware` | Set union |
7637
+ * | `extraMiddleware` | Merge by `.name`; override instance replaces base at same position; novel names appended |
7638
+ * | `generalPurposeSubagent` | Field-wise merge; override wins per sub-field |
7639
+ *
7640
+ * @param base - Lower-priority profile (e.g., provider-wide).
7641
+ * @param override - Higher-priority profile (e.g., exact model).
7642
+ * @returns A new merged profile.
7643
+ */
7644
+ function mergeProfiles(base, override) {
7645
+ return createHarnessProfile({
7646
+ baseSystemPrompt: override.baseSystemPrompt ?? base.baseSystemPrompt,
7647
+ systemPromptSuffix: override.systemPromptSuffix ?? base.systemPromptSuffix,
7648
+ toolDescriptionOverrides: {
7649
+ ...base.toolDescriptionOverrides,
7650
+ ...override.toolDescriptionOverrides
7651
+ },
7652
+ excludedTools: [...base.excludedTools, ...override.excludedTools],
7653
+ excludedMiddleware: [...base.excludedMiddleware, ...override.excludedMiddleware],
7654
+ extraMiddleware: mergeMiddleware(base.extraMiddleware, override.extraMiddleware),
7655
+ generalPurposeSubagent: mergeGeneralPurposeSubagentConfigs(base.generalPurposeSubagent, override.generalPurposeSubagent)
7656
+ });
7657
+ }
7658
+ //#endregion
7659
+ //#region src/profiles/harness/builtins/anthropic-opus-4-7.ts
7660
+ const SYSTEM_PROMPT_SUFFIX$3 = `\
7661
+ <use_parallel_tool_calls>
7662
+ 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.
7663
+ </use_parallel_tool_calls>
7664
+
7665
+ <investigate_before_answering>
7666
+ 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.
7667
+ </investigate_before_answering>
7668
+
7669
+ <tool_result_reflection>
7670
+ 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.
7671
+ </tool_result_reflection>
7672
+
7673
+ <tool_usage>
7674
+ 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.
7675
+ </tool_usage>
7676
+
7677
+ <subagent_usage>
7678
+ Do not spawn a subagent for work you can complete directly in a single response (e.g. refactoring a function you can already see).
7679
+
7680
+ Spawn multiple subagents in the same turn when fanning out across items or reading multiple files.
7681
+ </subagent_usage>`;
7682
+ /**
7683
+ * Register the built-in Claude Opus 4.7 harness profile.
7684
+ *
7685
+ * Layers a system-prompt suffix onto `anthropic:claude-opus-4-7`
7686
+ * tuned to the model's documented behaviors: parallel tool calls,
7687
+ * grounded answers, post-tool reflection, active investigation, and
7688
+ * subagent spawning guidance.
7689
+ *
7690
+ * @internal
7691
+ */
7692
+ function register$3() {
7693
+ registerHarnessProfileImpl("anthropic:claude-opus-4-7", createHarnessProfile({ systemPromptSuffix: SYSTEM_PROMPT_SUFFIX$3 }));
7694
+ }
7695
+ //#endregion
7696
+ //#region src/profiles/harness/builtins/anthropic-sonnet-4-6.ts
7697
+ const SYSTEM_PROMPT_SUFFIX$2 = `\
7698
+ <use_parallel_tool_calls>
7699
+ 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.
7700
+ </use_parallel_tool_calls>
7701
+
7702
+ <investigate_before_answering>
7703
+ 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.
7704
+ </investigate_before_answering>
7705
+
7706
+ <tool_result_reflection>
7707
+ 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.
7708
+ </tool_result_reflection>`;
7709
+ /**
7710
+ * Register the built-in Claude Sonnet 4.6 harness profile.
7711
+ *
7712
+ * Layers universal Claude guidance (parallel tool calls, grounded
7713
+ * answers, post-tool reflection) onto `anthropic:claude-sonnet-4-6`.
7714
+ *
7715
+ * No Sonnet-specific overlays — Anthropic's guidance for Sonnet 4.6
7716
+ * centers on API-level configuration rather than system-prompt
7717
+ * adjustments. This module exists as the audit anchor: its presence
7718
+ * documents the review and justifies the absence of model-specific
7719
+ * content.
7720
+ *
7721
+ * @internal
7722
+ */
7723
+ function register$2() {
7724
+ registerHarnessProfileImpl("anthropic:claude-sonnet-4-6", createHarnessProfile({ systemPromptSuffix: SYSTEM_PROMPT_SUFFIX$2 }));
7725
+ }
7726
+ //#endregion
7727
+ //#region src/profiles/harness/builtins/anthropic-haiku-4-5.ts
7728
+ const SYSTEM_PROMPT_SUFFIX$1 = `\
7729
+ <use_parallel_tool_calls>
7730
+ 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.
7731
+ </use_parallel_tool_calls>
7732
+
7733
+ <investigate_before_answering>
7734
+ 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.
7735
+ </investigate_before_answering>
7736
+
7737
+ <tool_result_reflection>
7738
+ 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.
7739
+ </tool_result_reflection>`;
7740
+ /**
7741
+ * Register the built-in Claude Haiku 4.5 harness profile.
7742
+ *
7743
+ * Same universal Claude guidance as Sonnet 4.6. No Haiku-specific
7744
+ * overlays.
7745
+ *
7746
+ * @internal
7747
+ */
7748
+ function register$1() {
7749
+ registerHarnessProfileImpl("anthropic:claude-haiku-4-5", createHarnessProfile({ systemPromptSuffix: SYSTEM_PROMPT_SUFFIX$1 }));
7750
+ }
7751
+ //#endregion
7752
+ //#region src/profiles/harness/builtins/openai-codex.ts
7753
+ /**
7754
+ * Model specs that receive the Codex harness profile.
7755
+ *
7756
+ * All variants share the same trained response style, so a single
7757
+ * suffix works across the family.
7758
+ */
7759
+ const CODEX_MODEL_SPECS = [
7760
+ "openai:gpt-5.1-codex",
7761
+ "openai:gpt-5.2-codex",
7762
+ "openai:gpt-5.3-codex"
7763
+ ];
7764
+ const SYSTEM_PROMPT_SUFFIX = `\
7765
+ ## Codex-Specific Behavior
7766
+
7767
+ - You are an autonomous senior engineer. Once given a direction, proactively \
7768
+ gather context, plan, implement, and verify without waiting for additional \
7769
+ prompts at each step.
7770
+ - Persist until the task is fully handled end-to-end within the current turn \
7771
+ whenever feasible. Do not stop at analysis or partial fixes; carry changes \
7772
+ through implementation, verification, and a clear explanation of outcomes.
7773
+ - Bias to action: default to implementing with reasonable assumptions. Do not \
7774
+ end your turn with clarifications unless truly blocked.
7775
+ - Do not communicate an upfront plan or status preamble before acting. Just act.
7776
+
7777
+ ## Parallel Tool Use
7778
+
7779
+ - Before any tool call, decide ALL files and resources you will need.
7780
+ - Batch reads, searches, and other independent operations into parallel tool \
7781
+ calls instead of issuing them one at a time.
7782
+ - Only make sequential calls when you truly cannot determine the next step \
7783
+ without seeing a prior result.
7784
+
7785
+ ## Plan Hygiene
7786
+
7787
+ - Before finishing, reconcile every TODO or plan item created via write_todos. \
7788
+ Mark each as done, blocked (with a one-sentence reason), or cancelled. Do not \
7789
+ finish with pending items.`;
7790
+ /**
7791
+ * Register the built-in Codex harness profiles.
7792
+ *
7793
+ * Registers the same profile under each Codex model spec. Per-model
7794
+ * keys (not the bare `"openai"` prefix) keep the default behavior of
7795
+ * non-Codex OpenAI models unchanged.
7796
+ *
7797
+ * @internal
7798
+ */
7799
+ function register() {
7800
+ const profile = createHarnessProfile({ systemPromptSuffix: SYSTEM_PROMPT_SUFFIX });
7801
+ for (const spec of CODEX_MODEL_SPECS) registerHarnessProfileImpl(spec, profile);
7802
+ }
7803
+ //#endregion
7804
+ //#region src/profiles/harness/builtins/index.ts
7805
+ /**
7806
+ * Register all built-in harness profiles and snapshot the resulting
7807
+ * registry keys as the builtin baseline.
7808
+ *
7809
+ * Called once during lazy bootstrap by `ensureBuiltinsLoaded()`.
7810
+ * Uses `registerHarnessProfileImpl` internally (not the public
7811
+ * `registerHarnessProfile`) to avoid triggering re-entrant bootstrap.
7812
+ *
7813
+ * @internal
7814
+ */
7815
+ function loadBuiltinProfiles() {
7816
+ register$3();
7817
+ register$2();
7818
+ register$1();
7819
+ register();
7820
+ snapshotBuiltinKeys();
7821
+ }
7822
+ //#endregion
7823
+ //#region src/profiles/harness/registry.ts
7824
+ /**
7825
+ * Process-global symbol key for the harness profile registry. The `.v1`
7826
+ * suffix is a version gate — bump it when the {@link HarnessProfileRegistry}
7827
+ * shape changes in a breaking way so that incompatible versions coexist
7828
+ * on `globalThis` without corrupting each other.
7829
+ */
7830
+ const PROFILE_REGISTRY_KEY = Symbol.for("deepagents.harness-profiles.v1");
7831
+ /**
7832
+ * Returns the process-global registry, creating it on first access.
7833
+ */
7834
+ function getHarnessProfileRegistry() {
7835
+ const global = globalThis;
7836
+ if (global[PROFILE_REGISTRY_KEY] == null) global[PROFILE_REGISTRY_KEY] = {
7837
+ profiles: /* @__PURE__ */ new Map(),
7838
+ builtinKeys: /* @__PURE__ */ new Set(),
7839
+ builtinsLoaded: false
7840
+ };
7841
+ return global[PROFILE_REGISTRY_KEY];
7842
+ }
7843
+ /**
7844
+ * Ensure lazy-loaded builtin profiles have been registered.
7845
+ *
7846
+ * Called by the public `registerHarnessProfile` and lookup functions.
7847
+ * Built-in registration modules call `registerHarnessProfileImpl`
7848
+ * directly to avoid re-entrant bootstrap.
7849
+ *
7850
+ * @internal
7851
+ */
7852
+ function ensureBuiltinsLoaded() {
7853
+ const registry = getHarnessProfileRegistry();
7854
+ if (registry.builtinsLoaded) return;
7855
+ registry.builtinsLoaded = true;
7856
+ loadBuiltinProfiles();
7857
+ }
7858
+ /**
7859
+ * Snapshot the current registry keys as the builtin baseline.
7860
+ *
7861
+ * Called by the builtin loader after all built-in profiles are
7862
+ * registered. This allows {@link hasUserRegisteredProfiles} to
7863
+ * distinguish user registrations from built-ins.
7864
+ *
7865
+ * @internal
7866
+ */
7867
+ function snapshotBuiltinKeys() {
7868
+ const registry = getHarnessProfileRegistry();
7869
+ registry.builtinKeys = new Set(registry.profiles.keys());
7870
+ }
7871
+ /**
7872
+ * Core registration implementation. Does not trigger lazy bootstrap.
7873
+ *
7874
+ * Used by built-in profile modules during bootstrap. External callers
7875
+ * should use {@link registerHarnessProfile} instead.
7876
+ *
7877
+ * @internal
7878
+ */
7879
+ function registerHarnessProfileImpl(key, profile) {
7880
+ key = validateProfileKey(key);
7881
+ const { profiles } = getHarnessProfileRegistry();
7882
+ const existing = profiles.get(key);
7883
+ if (existing !== void 0) profiles.set(key, mergeProfiles(existing, profile));
7884
+ else profiles.set(key, profile);
7885
+ }
7886
+ /**
7887
+ * Register a harness profile for a provider or specific model.
7888
+ *
7889
+ * Accepts either a pre-built {@link HarnessProfile} (from
7890
+ * {@link createHarnessProfile}) or raw {@link HarnessProfileOptions}
7891
+ * that will be validated and frozen automatically.
7892
+ *
7893
+ * Registrations are **additive**: if a profile already exists under
7894
+ * `key`, the new profile is merged on top. The incoming profile's
7895
+ * fields win on scalar conflicts; set fields union; middleware
7896
+ * sequences merge by name.
7897
+ *
7898
+ * @param key - Either a bare provider (`"openai"`) for provider-wide
7899
+ * defaults, or `"provider:model"` for a per-model override.
7900
+ * @param profile - A `HarnessProfile` or options to build one from.
7901
+ * @throws {Error} When `key` is malformed or profile validation
7902
+ * fails.
7903
+ *
7904
+ * @example
7905
+ * ```typescript
7906
+ * import { registerHarnessProfile } from "@langchain/deepagents";
7907
+ *
7908
+ * registerHarnessProfile("openai", {
7909
+ * systemPromptSuffix: "Respond concisely.",
7910
+ * });
7911
+ *
7912
+ * registerHarnessProfile("openai:gpt-5.4", {
7913
+ * excludedTools: ["execute"],
7914
+ * });
7915
+ * ```
7916
+ */
7917
+ function registerHarnessProfile(key, profile) {
7918
+ ensureBuiltinsLoaded();
7919
+ registerHarnessProfileImpl(key, isHarnessProfile(profile) ? profile : createHarnessProfile(profile));
7920
+ }
7921
+ /**
7922
+ * Look up the {@link HarnessProfile} for a model spec string.
7923
+ *
7924
+ * Resolution order:
7925
+ *
7926
+ * 1. **Exact match** on `spec` (e.g., `"openai:gpt-5.4"`).
7927
+ * 2. **Provider prefix** (everything before `:`) when `spec` contains
7928
+ * a colon and both halves are non-empty.
7929
+ * 3. When both exist, they are **merged** (provider as base, exact as
7930
+ * override).
7931
+ * 4. `undefined` when nothing matches.
7932
+ *
7933
+ * Malformed specs (empty, multiple colons, empty halves) return
7934
+ * `undefined` without consulting the registry.
7935
+ *
7936
+ * @param spec - Model spec in `"provider:model"` format, or a bare
7937
+ * provider/model identifier.
7938
+ * @returns The matching profile, or `undefined`.
7939
+ */
7940
+ function getHarnessProfile(spec) {
7941
+ if (spec.split(":").length > 2) return;
7942
+ const colonIdx = spec.indexOf(":");
7943
+ const hasColon = colonIdx !== -1;
7944
+ const provider = hasColon ? spec.slice(0, colonIdx) : void 0;
7945
+ const model = hasColon ? spec.slice(colonIdx + 1) : void 0;
7946
+ if (hasColon && (!provider || !model)) return;
7947
+ ensureBuiltinsLoaded();
7948
+ const { profiles } = getHarnessProfileRegistry();
7949
+ const exact = profiles.get(spec);
7950
+ const base = provider ? profiles.get(provider) : void 0;
7951
+ if (exact !== void 0 && base !== void 0) return mergeProfiles(base, exact);
7952
+ return exact ?? base;
7953
+ }
7954
+ /**
7955
+ * Resolve the harness profile for a model, falling back to the
7956
+ * empty default when nothing matches.
7957
+ *
7958
+ * When `spec` is set (the original model parameter), it drives the
7959
+ * lookup directly. When absent (pre-built model instance),
7960
+ * `providerHint` and `identifierHint` are used to construct lookup
7961
+ * keys.
7962
+ *
7963
+ * @param opts - Model metadata used to resolve the profile.
7964
+ * @returns The resolved profile (never `undefined`).
7965
+ *
7966
+ * @internal
7967
+ */
7968
+ function resolveHarnessProfile(opts = {}) {
7969
+ const { spec, providerHint, identifierHint } = opts;
7970
+ if (spec !== void 0) return getHarnessProfile(spec) ?? EMPTY_HARNESS_PROFILE;
7971
+ if (providerHint && identifierHint && !identifierHint.includes(":")) {
7972
+ const profile = getHarnessProfile(`${providerHint}:${identifierHint}`);
7973
+ if (profile) return profile;
7974
+ }
7975
+ if (identifierHint && identifierHint.includes(":")) {
7976
+ const profile = getHarnessProfile(identifierHint);
7977
+ if (profile) return profile;
7978
+ }
7979
+ if (providerHint) {
7980
+ const profile = getHarnessProfile(providerHint);
7981
+ if (profile) return profile;
7982
+ }
7983
+ return EMPTY_HARNESS_PROFILE;
7984
+ }
7985
+ /**
7986
+ * Apply a profile's prompt overlay to a base prompt string.
7987
+ *
7988
+ * - `baseSystemPrompt` (when set) replaces `basePrompt` entirely.
7989
+ * - `systemPromptSuffix` (when set) is appended with `\n\n`.
7990
+ *
7991
+ * Both are independently optional. A profile that sets only the suffix
7992
+ * layers it on top of whatever base the caller passes in.
7993
+ *
7994
+ * Used uniformly for the main agent, declarative subagents, and the
7995
+ * auto-added general-purpose subagent.
7996
+ *
7997
+ * @param profile - The harness profile to apply.
7998
+ * @param basePrompt - The default base prompt (e.g., `BASE_AGENT_PROMPT`).
7999
+ * @returns The assembled prompt string.
8000
+ */
8001
+ function applyProfilePrompt(profile, basePrompt) {
8002
+ const prompt = profile.baseSystemPrompt !== void 0 ? profile.baseSystemPrompt : basePrompt;
8003
+ if (profile.systemPromptSuffix !== void 0) return `${prompt}\n\n${profile.systemPromptSuffix}`;
8004
+ return prompt;
8005
+ }
8006
+ //#endregion
8007
+ //#region src/utils.ts
8008
+ /**
8009
+ * Detect whether a model is an Anthropic model.
8010
+ *
8011
+ * Used to gate Anthropic-specific prompt caching optimizations
8012
+ * (cache_control breakpoints).
8013
+ */
8014
+ function isAnthropicModel(model) {
8015
+ if (typeof model === "string") {
8016
+ if (model.includes(":")) return model.split(":")[0] === "anthropic";
8017
+ return model.startsWith("claude");
8018
+ }
8019
+ if (model.getName() === "ConfigurableModel") return model._defaultConfig?.modelProvider === "anthropic";
8020
+ return model.getName() === "ChatAnthropic";
8021
+ }
8022
+ /**
8023
+ * Extract the provider name from a model instance for profile lookup.
8024
+ *
8025
+ * Checks `_defaultConfig.modelProvider` (ConfigurableModel) and falls
8026
+ * back to known model class name → provider mappings.
8027
+ *
8028
+ * @internal
8029
+ */
8030
+ function getModelProvider(model) {
8031
+ if (model.getName() === "ConfigurableModel") return model._defaultConfig?.modelProvider;
8032
+ return {
8033
+ ChatAnthropic: "anthropic",
8034
+ ChatOpenAI: "openai",
8035
+ ChatGoogleGenerativeAI: "google"
8036
+ }[model.getName()];
8037
+ }
8038
+ /**
8039
+ * Extract the model identifier from a model instance for profile
8040
+ * lookup.
8041
+ *
8042
+ * Checks `_defaultConfig.model`, `model_name`, and `modelName` in
8043
+ * that order.
8044
+ *
8045
+ * @internal
8046
+ */
8047
+ function getModelIdentifier(model) {
8048
+ return (model.getName() === "ConfigurableModel" ? model._defaultConfig : void 0)?.model ?? model.model_name ?? model.modelName ?? void 0;
8049
+ }
8050
+ //#endregion
6962
8051
  //#region src/agent.ts
6963
8052
  const BASE_AGENT_PROMPT = langchain.context`
6964
8053
  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.
@@ -7002,18 +8091,6 @@ const BUILTIN_TOOL_NAMES = new Set([
7002
8091
  "write_todos"
7003
8092
  ]);
7004
8093
  /**
7005
- * Detect whether a model is an Anthropic model.
7006
- * Used to gate Anthropic-specific prompt caching optimizations (cache_control breakpoints).
7007
- */
7008
- function isAnthropicModel(model) {
7009
- if (typeof model === "string") {
7010
- if (model.includes(":")) return model.split(":")[0] === "anthropic";
7011
- return model.startsWith("claude");
7012
- }
7013
- if (model.getName() === "ConfigurableModel") return model._defaultConfig?.modelProvider === "anthropic";
7014
- return model.getName() === "ChatAnthropic";
7015
- }
7016
- /**
7017
8094
  * Create a Deep Agent.
7018
8095
  *
7019
8096
  * This is the main entry point for building a production-style agent with
@@ -7047,6 +8124,12 @@ function createDeepAgent(params = {}) {
7047
8124
  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;
7048
8125
  const collidingTools = tools.map((t) => t.name).filter((n) => typeof n === "string" && BUILTIN_TOOL_NAMES.has(n));
7049
8126
  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");
8127
+ const harnessProfile = typeof model === "string" ? resolveHarnessProfile({ spec: model }) : resolveHarnessProfile({
8128
+ providerHint: getModelProvider(model),
8129
+ identifierHint: getModelIdentifier(model)
8130
+ });
8131
+ const toolOverrides = harnessProfile.toolDescriptionOverrides;
8132
+ 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;
7050
8133
  const anthropicModel = isAnthropicModel(model);
7051
8134
  const cacheMiddleware = anthropicModel ? [(0, langchain.anthropicPromptCachingMiddleware)({
7052
8135
  unsupportedModelBehavior: "ignore",
@@ -7085,12 +8168,16 @@ function createDeepAgent(params = {}) {
7085
8168
  const allSubagents = subagents;
7086
8169
  const asyncSubAgents = allSubagents.filter((item) => isAsyncSubAgent(item));
7087
8170
  const inlineSubagents = allSubagents.filter((item) => !isAsyncSubAgent(item)).map((item) => "runnable" in item ? item : normalizeSubagentSpec(item));
7088
- if (!inlineSubagents.some((item) => item.name === GENERAL_PURPOSE_SUBAGENT["name"])) {
8171
+ const gpConfig = harnessProfile.generalPurposeSubagent;
8172
+ if (!(gpConfig?.enabled === false) && !inlineSubagents.some((item) => item.name === GENERAL_PURPOSE_SUBAGENT["name"])) {
8173
+ const gpSystemPrompt = gpConfig?.systemPrompt ?? applyProfilePrompt(harnessProfile, GENERAL_PURPOSE_SUBAGENT.systemPrompt);
7089
8174
  const generalPurposeSpec = normalizeSubagentSpec({
7090
8175
  ...GENERAL_PURPOSE_SUBAGENT,
8176
+ description: gpConfig?.description ?? GENERAL_PURPOSE_SUBAGENT.description,
8177
+ systemPrompt: gpSystemPrompt,
7091
8178
  model,
7092
8179
  skills,
7093
- tools
8180
+ tools: effectiveTools
7094
8181
  });
7095
8182
  inlineSubagents.unshift(generalPurposeSpec);
7096
8183
  }
@@ -7106,7 +8193,7 @@ function createDeepAgent(params = {}) {
7106
8193
  }),
7107
8194
  createSubAgentMiddleware({
7108
8195
  defaultModel: model,
7109
- defaultTools: tools,
8196
+ defaultTools: effectiveTools,
7110
8197
  defaultInterruptOn: interruptOn,
7111
8198
  subagents: inlineSubagents,
7112
8199
  generalPurposeAgent: false
@@ -7131,6 +8218,31 @@ function createDeepAgent(params = {}) {
7131
8218
  })] : [],
7132
8219
  ...interruptOn ? [(0, langchain.humanInTheLoopMiddleware)({ interruptOn })] : []
7133
8220
  ];
8221
+ const profileMiddleware = resolveMiddleware(harnessProfile.extraMiddleware);
8222
+ if (profileMiddleware.length > 0) {
8223
+ const cacheIdx = middleware.findIndex((m) => m.name === "AnthropicPromptCachingMiddleware");
8224
+ if (cacheIdx !== -1) middleware.splice(cacheIdx, 0, ...profileMiddleware);
8225
+ else middleware.push(...profileMiddleware);
8226
+ }
8227
+ if (harnessProfile.excludedMiddleware.size > 0) {
8228
+ const excluded = harnessProfile.excludedMiddleware;
8229
+ const filtered = middleware.filter((m) => !excluded.has(m.name));
8230
+ middleware.length = 0;
8231
+ middleware.push(...filtered);
8232
+ }
8233
+ if (harnessProfile.excludedTools.size > 0) {
8234
+ const excludedTools = harnessProfile.excludedTools;
8235
+ middleware.push((0, langchain.createMiddleware)({
8236
+ name: "_ToolExclusionMiddleware",
8237
+ wrapModelCall: async (request, handler) => {
8238
+ return handler({
8239
+ ...request,
8240
+ tools: request.tools?.filter((t) => !excludedTools.has(t.name))
8241
+ });
8242
+ }
8243
+ }));
8244
+ }
8245
+ const effectiveBasePrompt = applyProfilePrompt(harnessProfile, BASE_AGENT_PROMPT);
7134
8246
  /**
7135
8247
  * Return as DeepAgent with proper DeepAgentTypeConfig
7136
8248
  * - Response: InferStructuredResponse<TResponse> (unwraps ToolStrategy<T>/ProviderStrategy<T> → T)
@@ -7148,15 +8260,15 @@ function createDeepAgent(params = {}) {
7148
8260
  text: systemPrompt
7149
8261
  }, {
7150
8262
  type: "text",
7151
- text: BASE_AGENT_PROMPT
8263
+ text: effectiveBasePrompt
7152
8264
  }] }) : langchain.SystemMessage.isInstance(systemPrompt) ? new langchain.SystemMessage({ contentBlocks: [...systemPrompt.contentBlocks, {
7153
8265
  type: "text",
7154
- text: BASE_AGENT_PROMPT
8266
+ text: effectiveBasePrompt
7155
8267
  }] }) : new langchain.SystemMessage({ contentBlocks: [{
7156
8268
  type: "text",
7157
- text: BASE_AGENT_PROMPT
8269
+ text: effectiveBasePrompt
7158
8270
  }] }),
7159
- tools,
8271
+ tools: effectiveTools,
7160
8272
  middleware,
7161
8273
  ...responseFormat !== null && { responseFormat },
7162
8274
  contextSchema,
@@ -7688,8 +8800,10 @@ function listSkills(options) {
7688
8800
  exports.BaseSandbox = BaseSandbox;
7689
8801
  exports.CompositeBackend = CompositeBackend;
7690
8802
  exports.ConfigurationError = ConfigurationError;
8803
+ exports.ContextHubBackend = ContextHubBackend;
7691
8804
  exports.DEFAULT_GENERAL_PURPOSE_DESCRIPTION = DEFAULT_GENERAL_PURPOSE_DESCRIPTION;
7692
8805
  exports.DEFAULT_SUBAGENT_PROMPT = DEFAULT_SUBAGENT_PROMPT;
8806
+ exports.EMPTY_HARNESS_PROFILE = EMPTY_HARNESS_PROFILE;
7693
8807
  exports.FilesystemBackend = FilesystemBackend;
7694
8808
  exports.GENERAL_PURPOSE_SUBAGENT = GENERAL_PURPOSE_SUBAGENT;
7695
8809
  exports.LangSmithSandbox = LangSmithSandbox;
@@ -7697,6 +8811,7 @@ exports.LocalShellBackend = LocalShellBackend;
7697
8811
  exports.MAX_SKILL_DESCRIPTION_LENGTH = MAX_SKILL_DESCRIPTION_LENGTH;
7698
8812
  exports.MAX_SKILL_FILE_SIZE = MAX_SKILL_FILE_SIZE;
7699
8813
  exports.MAX_SKILL_NAME_LENGTH = MAX_SKILL_NAME_LENGTH;
8814
+ exports.REQUIRED_MIDDLEWARE_NAMES = REQUIRED_MIDDLEWARE_NAMES;
7700
8815
  exports.SandboxError = SandboxError;
7701
8816
  exports.StateBackend = StateBackend;
7702
8817
  exports.StoreBackend = StoreBackend;
@@ -7709,6 +8824,7 @@ exports.createAsyncSubAgentMiddleware = createAsyncSubAgentMiddleware;
7709
8824
  exports.createCompletionCallbackMiddleware = createCompletionCallbackMiddleware;
7710
8825
  exports.createDeepAgent = createDeepAgent;
7711
8826
  exports.createFilesystemMiddleware = createFilesystemMiddleware;
8827
+ exports.createHarnessProfile = createHarnessProfile;
7712
8828
  exports.createMemoryMiddleware = createMemoryMiddleware;
7713
8829
  exports.createPatchToolCallsMiddleware = createPatchToolCallsMiddleware;
7714
8830
  exports.createSettings = createSettings;
@@ -7718,11 +8834,17 @@ exports.createSubagentTransformer = createSubagentTransformer;
7718
8834
  exports.createSummarizationMiddleware = createSummarizationMiddleware;
7719
8835
  exports.filesValue = filesValue;
7720
8836
  exports.findProjectRoot = findProjectRoot;
8837
+ exports.generalPurposeSubagentConfigSchema = generalPurposeSubagentConfigSchema;
8838
+ exports.getHarnessProfile = getHarnessProfile;
8839
+ exports.harnessProfileConfigSchema = harnessProfileConfigSchema;
7721
8840
  exports.isAsyncSubAgent = isAsyncSubAgent;
7722
8841
  exports.isSandboxBackend = isSandboxBackend;
7723
8842
  exports.isSandboxProtocol = isSandboxProtocol;
7724
8843
  exports.listSkills = listSkills;
8844
+ exports.parseHarnessProfileConfig = parseHarnessProfileConfig;
7725
8845
  exports.parseSkillMetadata = parseSkillMetadata;
8846
+ exports.registerHarnessProfile = registerHarnessProfile;
7726
8847
  exports.resolveBackend = resolveBackend;
8848
+ exports.serializeProfile = serializeProfile;
7727
8849
 
7728
8850
  //# sourceMappingURL=index.cjs.map