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.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({ templateName: "deepagents-cli" });
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({ templateName: "deepagents" });
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 = "deepagents", apiKey = process.env.LANGSMITH_API_KEY, defaultTimeout, ...createSandboxOptions } = options;
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(templateName, createSandboxOptions),
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
- if (!inlineSubagents.some((item) => item.name === GENERAL_PURPOSE_SUBAGENT["name"])) {
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: tools,
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: BASE_AGENT_PROMPT
8224
+ text: effectiveBasePrompt
7113
8225
  }] }) : SystemMessage.isInstance(systemPrompt) ? new SystemMessage({ contentBlocks: [...systemPrompt.contentBlocks, {
7114
8226
  type: "text",
7115
- text: BASE_AGENT_PROMPT
8227
+ text: effectiveBasePrompt
7116
8228
  }] }) : new SystemMessage({ contentBlocks: [{
7117
8229
  type: "text",
7118
- text: BASE_AGENT_PROMPT
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