deepagents 1.12.3 → 1.12.4

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.
@@ -1992,7 +1992,7 @@ function createGrepTool(backend, options) {
1992
1992
  pattern: z.string().describe("Literal text pattern to search for (not regex)"),
1993
1993
  path: z.string().optional().default("/").describe("Base path to search from (default: /)"),
1994
1994
  glob: z.string().optional().nullable().default(null).describe("Optional glob pattern to filter files (e.g., '*.py')"),
1995
- max_count: z.number().int().positive().optional().nullable().default(null).describe("Optional cap on the total number of matches returned across all files. Leave unset to use the configured default. When the cap is hit, results are truncated and a note says so; narrow the pattern or path to see the rest."),
1995
+ max_count: z.coerce.number().int().positive().optional().nullable().default(null).describe("Optional cap on the total number of matches returned across all files. Leave unset to use the configured default. When the cap is hit, results are truncated and a note says so; narrow the pattern or path to see the rest."),
1996
1996
  output_mode: z.enum([
1997
1997
  "files_with_matches",
1998
1998
  "content",
@@ -2254,23 +2254,18 @@ const SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY = "__deepagents_subagent_response_form
2254
2254
  */
2255
2255
  const DEFAULT_SUBAGENT_PROMPT = "In order to complete the objective that the user asks of you, you have access to a number of standard tools.";
2256
2256
  /**
2257
- * State keys that are excluded when passing state to subagents and when returning
2258
- * updates from subagents.
2259
- *
2260
- * When returning updates:
2261
- * 1. The messages key is handled explicitly to ensure only the final message is included
2262
- * 2. The todos and structuredResponse keys are excluded as they do not have a defined reducer
2263
- * and no clear meaning for returning them from a subagent to the main agent.
2264
- * 3. The skillsMetadata and memoryContents keys are automatically excluded from subagent output
2265
- * to prevent parent state from leaking to child agents. Each agent loads its own skills/memory
2266
- * independently based on its middleware configuration.
2257
+ * State keys excluded when passing state to subagents and when returning
2258
+ * updates from subagents. Summarization keys are excluded because their
2259
+ * cutoffIndex is only valid against the message list it was computed from.
2267
2260
  */
2268
2261
  const EXCLUDED_STATE_KEYS = [
2269
2262
  "messages",
2270
2263
  "todos",
2271
2264
  "structuredResponse",
2272
2265
  "skillsMetadata",
2273
- "memoryContents"
2266
+ "memoryContents",
2267
+ "_summarizationEvent",
2268
+ "_summarizationSessionId"
2274
2269
  ];
2275
2270
  /**
2276
2271
  * Default description for the general-purpose subagent.
@@ -2492,6 +2487,7 @@ function createTaskTool(options) {
2492
2487
  const subagent = selectSubagent(subagent_type, config);
2493
2488
  const subagentState = filterStateForSubagent(getCurrentTaskInput());
2494
2489
  subagentState.messages = [new HumanMessage$1({ content: description })];
2490
+ subagentState._summarizationSessionId = `session_${crypto.randomUUID().substring(0, 8)}`;
2495
2491
  const subagentConfig = {
2496
2492
  ...config,
2497
2493
  metadata: {
@@ -2732,6 +2728,43 @@ function isAnthropicModel(model) {
2732
2728
  return model.getName() === "ChatAnthropic";
2733
2729
  }
2734
2730
  /**
2731
+ * A one-shot promise whose settlement is controlled externally.
2732
+ *
2733
+ * Use this when one part of a workflow must wait for an event that is owned
2734
+ * elsewhere—for example, a queued mutation waiting for the worker that will
2735
+ * push it. `Deferred` is awaitable because it implements `PromiseLike`, and
2736
+ * `.promise` is available when a concrete `Promise` is required.
2737
+ *
2738
+ * The first call to `resolve` or `reject` wins; later calls are ignored. This
2739
+ * class deliberately does not provide cancellation, reset, or notification
2740
+ * semantics. It models exactly one eventual outcome.
2741
+ */
2742
+ var Deferred = class {
2743
+ promise;
2744
+ settled = false;
2745
+ resolvePromise;
2746
+ rejectPromise;
2747
+ constructor() {
2748
+ this.promise = new Promise((resolve, reject) => {
2749
+ this.resolvePromise = resolve;
2750
+ this.rejectPromise = reject;
2751
+ });
2752
+ }
2753
+ resolve(value) {
2754
+ if (this.settled) return;
2755
+ this.settled = true;
2756
+ this.resolvePromise(value);
2757
+ }
2758
+ reject(reason) {
2759
+ if (this.settled) return;
2760
+ this.settled = true;
2761
+ this.rejectPromise(reason);
2762
+ }
2763
+ then(onfulfilled, onrejected) {
2764
+ return this.promise.then(onfulfilled, onrejected);
2765
+ }
2766
+ };
2767
+ /**
2735
2768
  * Detect whether a model is an AWS Bedrock Converse model.
2736
2769
  *
2737
2770
  * Accepts the wider `RunnableInterface` shape (the type of `request.model`
@@ -6586,9 +6619,36 @@ var StoreBackend = class {
6586
6619
  /**
6587
6620
  * ContextHubBackend: Store files in a LangSmith Hub agent repo (persistent).
6588
6621
  */
6589
- const URL_COMMIT_SUFFIX_RE = /:([0-9a-f]{8,64})$/i;
6622
+ const CONTEXT_URL_COMMIT_PATH_RE = /^\/context\/([^/]+)\/([0-9a-f]{8})$/;
6623
+ const LEGACY_URL_COMMIT_PATH_RE = /^\/hub\/([^/]+)\/([^/:]+):([0-9a-f]{8})$/;
6624
+ const MUTATION_COALESCE_MS = 50;
6625
+ const MAX_CONFLICT_RETRIES = 3;
6590
6626
  const TEXT_MIME_TYPE = "text/plain";
6591
6627
  const FNMATCH_OPTIONS = { bash: true };
6628
+ function parseHubTargetIdentifier(identifier) {
6629
+ if (!identifier || identifier.split("/").length > 2 || identifier.startsWith("/") || identifier.endsWith("/") || identifier.split(":").length > 2) return null;
6630
+ const [ownerNamePart] = identifier.split(":");
6631
+ if (ownerNamePart.includes("/")) {
6632
+ const [owner, name] = ownerNamePart.split("/", 2);
6633
+ return owner && name ? [owner, name] : null;
6634
+ }
6635
+ return ownerNamePart ? ["-", ownerNamePart] : null;
6636
+ }
6637
+ function parseCommitHashFromUrl(url, identifier) {
6638
+ try {
6639
+ const pathname = decodeURIComponent(new URL(url).pathname);
6640
+ const target = parseHubTargetIdentifier(identifier);
6641
+ if (target === null) return null;
6642
+ const [targetOwner, targetName] = target;
6643
+ const contextMatch = CONTEXT_URL_COMMIT_PATH_RE.exec(pathname);
6644
+ if (contextMatch !== null && contextMatch[1] === targetName) return contextMatch[2];
6645
+ const legacyMatch = LEGACY_URL_COMMIT_PATH_RE.exec(pathname);
6646
+ if (legacyMatch !== null && legacyMatch[1] === targetOwner && legacyMatch[2] === targetName) return legacyMatch[3];
6647
+ return null;
6648
+ } catch {
6649
+ return null;
6650
+ }
6651
+ }
6592
6652
  function getErrorMessage(error) {
6593
6653
  if (typeof error === "string") return error;
6594
6654
  if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") return error.message;
@@ -6627,6 +6687,12 @@ function getLangSmithStatus(error) {
6627
6687
  const maybeError = error;
6628
6688
  if (typeof maybeError.status === "number") return maybeError.status;
6629
6689
  }
6690
+ function createLangSmithConflictError(message) {
6691
+ const error = new Error(message);
6692
+ error.name = "LangSmithConflictError";
6693
+ error.status = 409;
6694
+ return error;
6695
+ }
6630
6696
  function mapHubFileOperationError(error) {
6631
6697
  const status = getLangSmithStatus(error);
6632
6698
  if (status === 401 || status === 403) return "permission_denied";
@@ -6636,12 +6702,49 @@ function mapHubFileOperationError(error) {
6636
6702
  /**
6637
6703
  * Backend that stores files in a LangSmith Hub agent repo (persistent).
6638
6704
  */
6705
+ /**
6706
+ * Backend that stores files in a LangSmith Hub agent repository.
6707
+ *
6708
+ * ## Mutation model
6709
+ *
6710
+ * Mutations are accepted in call order, coalesced for a short window, and
6711
+ * pushed by one worker. Only one batch is in flight at a time; mutations that
6712
+ * arrive during a push form the next batch. This serializes one backend
6713
+ * instance's writes while still reducing the number of Hub commits.
6714
+ *
6715
+ * Reads use an optimistic view: the last durable cache overlaid with the
6716
+ * in-flight batch and then the pending batch. A read can therefore observe an
6717
+ * accepted mutation before it is durable; a failed push invalidates that view
6718
+ * and the next operation reloads from Hub.
6719
+ *
6720
+ * A `409` parent conflict triggers an authoritative pull and rematerializes
6721
+ * the in-flight batch over the fetched tree before retrying. Edits replay their
6722
+ * original replacement intent; absolute writes, deletes, and uploads replay as
6723
+ * absolute changes. Retries are bounded by `MAX_CONFLICT_RETRIES`.
6724
+ */
6639
6725
  var ContextHubBackend = class ContextHubBackend {
6640
6726
  identifier;
6641
6727
  client;
6728
+ /** Last durable Hub file state; `null` means the next access must load it. */
6642
6729
  cache = null;
6643
6730
  linkedEntries = {};
6731
+ /** Parent hash for the durable cache, used for optimistic-concurrency pushes. */
6644
6732
  commitHash = null;
6733
+ /** Shared cold-load promise so concurrent first operations perform one pull. */
6734
+ loadPromise = null;
6735
+ /** Promise chain serializing mutation acceptance and optimistic projections. */
6736
+ mutationOrder = Promise.resolve();
6737
+ /** Mutations accepted for the next coalesced push. */
6738
+ pendingBatch = null;
6739
+ /** The batch currently submitted to Hub and visible to optimistic reads. */
6740
+ inFlightBatch = null;
6741
+ /** The single queue-draining worker, when active. */
6742
+ workerPromise = null;
6743
+ /**
6744
+ * Blocks cache consumers while a successful push without a parseable commit
6745
+ * hash is being confirmed by an authoritative pull.
6746
+ */
6747
+ snapshotPublication = null;
6645
6748
  constructor(identifier, options = {}) {
6646
6749
  this.identifier = identifier;
6647
6750
  this.client = options.client ?? new Client$1();
@@ -6652,49 +6755,319 @@ var ContextHubBackend = class ContextHubBackend {
6652
6755
  static toHubUnavailableError(error) {
6653
6756
  return `Hub unavailable: ${getErrorMessage(error)}`;
6654
6757
  }
6655
- async loadTree() {
6758
+ async fetchTree() {
6656
6759
  let context;
6657
6760
  try {
6658
6761
  context = await this.client.pullAgent(this.identifier);
6659
6762
  } catch (error) {
6660
- if (isLangSmithNotFoundError(error)) {
6661
- this.cache = {};
6662
- this.linkedEntries = {};
6663
- this.commitHash = null;
6664
- return;
6665
- }
6763
+ if (isLangSmithNotFoundError(error)) return {
6764
+ cache: {},
6765
+ linkedEntries: {},
6766
+ commitHash: null
6767
+ };
6666
6768
  throw error;
6667
6769
  }
6668
- this.commitHash = context.commit_hash;
6669
- this.cache = {};
6670
- this.linkedEntries = {};
6671
- for (const [path, entry] of Object.entries(context.files)) if (entry.type === "file") this.cache[path] = entry.content;
6672
- else if ((entry.type === "agent" || entry.type === "skill") && typeof entry.repo_handle === "string") this.linkedEntries[path] = entry.repo_handle;
6770
+ const cache = {};
6771
+ const linkedEntries = {};
6772
+ for (const [path, entry] of Object.entries(context.files)) if (entry.type === "file") cache[path] = entry.content;
6773
+ else if ((entry.type === "agent" || entry.type === "skill") && typeof entry.repo_handle === "string") linkedEntries[path] = entry.repo_handle;
6774
+ return {
6775
+ cache,
6776
+ linkedEntries,
6777
+ commitHash: context.commit_hash
6778
+ };
6673
6779
  }
6674
- async ensureCache() {
6675
- if (this.cache === null) await this.loadTree();
6780
+ publishSnapshot(snapshot) {
6781
+ this.cache = snapshot.cache;
6782
+ this.linkedEntries = snapshot.linkedEntries;
6783
+ this.commitHash = snapshot.commitHash;
6784
+ }
6785
+ async loadTree() {
6786
+ this.publishSnapshot(await this.fetchTree());
6787
+ }
6788
+ beginSnapshotPublication() {
6789
+ if (this.snapshotPublication !== null) throw new Error("Context Hub snapshot publication is already pending");
6790
+ this.snapshotPublication = new Deferred();
6791
+ }
6792
+ finishSnapshotPublication() {
6793
+ const publication = this.snapshotPublication;
6794
+ this.snapshotPublication = null;
6795
+ publication?.resolve();
6796
+ }
6797
+ async ensureCacheLoaded() {
6798
+ while (this.snapshotPublication !== null) await this.snapshotPublication;
6799
+ if (this.cache === null) {
6800
+ let loadPromise = this.loadPromise;
6801
+ if (loadPromise === null) {
6802
+ loadPromise = this.loadTree();
6803
+ this.loadPromise = loadPromise;
6804
+ }
6805
+ try {
6806
+ await loadPromise;
6807
+ } finally {
6808
+ if (this.loadPromise === loadPromise) this.loadPromise = null;
6809
+ }
6810
+ }
6676
6811
  if (this.cache === null) throw new Error("Context Hub cache failed to initialize");
6677
- return this.cache;
6678
- }
6679
- async commit(changes) {
6680
- if (Object.keys(changes).length === 0) return;
6681
- const payload = {};
6682
- for (const [path, content] of Object.entries(changes)) payload[path] = content === null ? null : {
6683
- type: "file",
6684
- content
6812
+ }
6813
+ async ensureCache() {
6814
+ await this.ensureCacheLoaded();
6815
+ return this.visibleCache();
6816
+ }
6817
+ static applyChanges(cache, changes) {
6818
+ const next = { ...cache };
6819
+ for (const [path, content] of Object.entries(changes)) if (content === null) delete next[path];
6820
+ else next[path] = content;
6821
+ return next;
6822
+ }
6823
+ /**
6824
+ * Build the read-your-writes view without publishing speculative data as the
6825
+ * durable cache. Later batches overlay earlier ones, matching worker order.
6826
+ */
6827
+ visibleCache() {
6828
+ let visible = { ...this.cache ?? {} };
6829
+ if (this.inFlightBatch !== null) visible = ContextHubBackend.applyChanges(visible, this.inFlightBatch.changes);
6830
+ if (this.pendingBatch !== null) visible = ContextHubBackend.applyChanges(visible, this.pendingBatch.changes);
6831
+ return visible;
6832
+ }
6833
+ invalidateCache() {
6834
+ this.cache = null;
6835
+ this.linkedEntries = {};
6836
+ this.commitHash = null;
6837
+ this.loadPromise = null;
6838
+ }
6839
+ async acquireMutationTurn() {
6840
+ let release;
6841
+ const previous = this.mutationOrder;
6842
+ this.mutationOrder = new Promise((resolve) => {
6843
+ release = resolve;
6844
+ });
6845
+ await previous;
6846
+ return release;
6847
+ }
6848
+ /**
6849
+ * Serialize validation and enqueueing so each operation is evaluated against
6850
+ * a stable optimistic projection. Cache loading begins before acquiring the
6851
+ * turn, allowing concurrent cold-start callers to share the same pull.
6852
+ */
6853
+ async acceptMutation(operation) {
6854
+ const turn = this.acquireMutationTurn();
6855
+ const cacheOutcome = this.ensureCacheLoaded().then(() => ({ loaded: true }), (error) => ({
6856
+ loaded: false,
6857
+ error
6858
+ }));
6859
+ const release = await turn;
6860
+ try {
6861
+ const outcome = await cacheOutcome;
6862
+ if (!outcome.loaded) throw outcome.error;
6863
+ while (this.cache === null) await this.ensureCacheLoaded();
6864
+ return operation(this.visibleCache());
6865
+ } finally {
6866
+ release();
6867
+ }
6868
+ }
6869
+ /**
6870
+ * Start a batch's coalescing window. The worker waits for this signal before
6871
+ * detaching the batch; cancellation resolves it immediately so failures do
6872
+ * not leave the worker waiting on a timer.
6873
+ */
6874
+ createMutationBatch() {
6875
+ const batch = {
6876
+ changes: {},
6877
+ waiters: [],
6878
+ ready: new Deferred(),
6879
+ timer: null
6685
6880
  };
6686
- const url = await this.client.pushAgent(this.identifier, {
6687
- files: payload,
6688
- ...this.commitHash ? { parentCommit: this.commitHash } : {}
6881
+ batch.timer = setTimeout(() => {
6882
+ batch.timer = null;
6883
+ batch.ready.resolve();
6884
+ }, MUTATION_COALESCE_MS);
6885
+ return batch;
6886
+ }
6887
+ cancelBatchTimer(batch) {
6888
+ if (batch.timer !== null) {
6889
+ clearTimeout(batch.timer);
6890
+ batch.timer = null;
6891
+ }
6892
+ batch.ready.resolve();
6893
+ }
6894
+ enqueueCommit(changes, intent = {
6895
+ kind: "changes",
6896
+ changes: { ...changes }
6897
+ }) {
6898
+ if (Object.keys(changes).length === 0) return Promise.resolve();
6899
+ let batch = this.pendingBatch;
6900
+ if (batch === null) {
6901
+ batch = this.createMutationBatch();
6902
+ this.pendingBatch = batch;
6903
+ }
6904
+ Object.assign(batch.changes, changes);
6905
+ const completion = new Deferred();
6906
+ batch.waiters.push({
6907
+ intent,
6908
+ completion
6689
6909
  });
6690
- const match = URL_COMMIT_SUFFIX_RE.exec(url);
6691
- if (match) this.commitHash = match[1];
6692
- if (this.cache !== null) {
6693
- const deletions = new Set(Object.entries(changes).filter(([, content]) => content === null).map(([path]) => path));
6694
- const updates = Object.fromEntries(Object.entries(changes).filter((entry) => entry[1] !== null));
6695
- this.cache = {
6696
- ...Object.fromEntries(Object.entries(this.cache).filter(([path]) => !deletions.has(path))),
6697
- ...updates
6910
+ this.startWorker();
6911
+ return completion.promise;
6912
+ }
6913
+ /**
6914
+ * Replay ordered intents over an authoritative base after a conflict. This
6915
+ * rebuilds the push payload and optimistic overlay. An edit that no longer
6916
+ * applies throws the supplied conflict error; absolute changes are reapplied.
6917
+ */
6918
+ rematerializeBatch(batch, base, conflictError) {
6919
+ let cache = { ...base };
6920
+ const changes = {};
6921
+ for (const waiter of batch.waiters) {
6922
+ const { intent } = waiter;
6923
+ if (intent.kind === "changes") {
6924
+ Object.assign(changes, intent.changes);
6925
+ cache = ContextHubBackend.applyChanges(cache, intent.changes);
6926
+ continue;
6927
+ }
6928
+ const current = cache[intent.path];
6929
+ if (current === void 0) throw conflictError;
6930
+ const replacementResult = performStringReplacement(current, intent.oldString, intent.newString, intent.replaceAll);
6931
+ if (typeof replacementResult === "string") throw conflictError;
6932
+ const [newContent, occurrences] = replacementResult;
6933
+ const editChanges = { [intent.path]: newContent };
6934
+ Object.assign(changes, editChanges);
6935
+ cache = ContextHubBackend.applyChanges(cache, editChanges);
6936
+ intent.updateOccurrences(occurrences);
6937
+ }
6938
+ batch.changes = changes;
6939
+ return cache;
6940
+ }
6941
+ rematerializeAfterConflict(batch, snapshot, conflictError) {
6942
+ const cache = this.rematerializeBatch(batch, snapshot.cache, conflictError);
6943
+ let pendingReplayError = null;
6944
+ if (this.pendingBatch !== null) try {
6945
+ this.rematerializeBatch(this.pendingBatch, cache, conflictError);
6946
+ } catch (error) {
6947
+ if (error !== conflictError) throw error;
6948
+ pendingReplayError = error;
6949
+ }
6950
+ this.publishSnapshot(snapshot);
6951
+ if (pendingReplayError !== null) this.failPendingBatch(pendingReplayError);
6952
+ }
6953
+ rematerializePendingBatch(snapshot) {
6954
+ if (this.pendingBatch === null) return null;
6955
+ const conflictError = createLangSmithConflictError("Pending Context Hub mutation conflicts with authoritative state");
6956
+ try {
6957
+ this.rematerializeBatch(this.pendingBatch, snapshot.cache, conflictError);
6958
+ return null;
6959
+ } catch (error) {
6960
+ if (error !== conflictError) throw error;
6961
+ return conflictError;
6962
+ }
6963
+ }
6964
+ startWorker() {
6965
+ if (this.workerPromise !== null) return;
6966
+ const worker = this.drainMutationQueue().catch((error) => {
6967
+ this.failAllBatches(error);
6968
+ }).finally(() => {
6969
+ if (this.workerPromise === worker) {
6970
+ this.workerPromise = null;
6971
+ if (this.pendingBatch !== null) this.startWorker();
6972
+ }
6973
+ });
6974
+ this.workerPromise = worker;
6975
+ }
6976
+ /**
6977
+ * Drain coalesced batches sequentially. A completed batch publishes durable
6978
+ * state before settling its callers; a failed batch invalidates local state
6979
+ * and rejects both in-flight and queued callers so the next mutation reloads.
6980
+ */
6981
+ async drainMutationQueue() {
6982
+ while (this.pendingBatch !== null) {
6983
+ const batch = this.pendingBatch;
6984
+ await batch.ready;
6985
+ if (this.pendingBatch !== batch) continue;
6986
+ this.pendingBatch = null;
6987
+ this.inFlightBatch = batch;
6988
+ let pendingReplayError = null;
6989
+ try {
6990
+ const result = await this.pushBatch(batch);
6991
+ if (result.kind === "snapshot") {
6992
+ pendingReplayError = this.rematerializePendingBatch(result.snapshot);
6993
+ this.publishSnapshot(result.snapshot);
6994
+ } else {
6995
+ this.cache = ContextHubBackend.applyChanges(this.cache ?? {}, batch.changes);
6996
+ this.commitHash = result.commitHash;
6997
+ }
6998
+ } catch (error) {
6999
+ this.inFlightBatch = null;
7000
+ this.invalidateCache();
7001
+ this.finishSnapshotPublication();
7002
+ for (const waiter of batch.waiters) waiter.completion.reject(error);
7003
+ this.failPendingBatch(error);
7004
+ return;
7005
+ }
7006
+ this.inFlightBatch = null;
7007
+ this.finishSnapshotPublication();
7008
+ for (const waiter of batch.waiters) waiter.completion.resolve();
7009
+ if (pendingReplayError !== null) {
7010
+ this.failPendingBatch(pendingReplayError);
7011
+ return;
7012
+ }
7013
+ }
7014
+ }
7015
+ failPendingBatch(error) {
7016
+ const pending = this.pendingBatch;
7017
+ if (pending === null) return;
7018
+ this.pendingBatch = null;
7019
+ this.cancelBatchTimer(pending);
7020
+ for (const waiter of pending.waiters) waiter.completion.reject(error);
7021
+ }
7022
+ failAllBatches(error) {
7023
+ const inFlight = this.inFlightBatch;
7024
+ this.inFlightBatch = null;
7025
+ this.invalidateCache();
7026
+ this.finishSnapshotPublication();
7027
+ if (inFlight !== null) {
7028
+ this.cancelBatchTimer(inFlight);
7029
+ for (const waiter of inFlight.waiters) waiter.completion.reject(error);
7030
+ }
7031
+ this.failPendingBatch(error);
7032
+ }
7033
+ /**
7034
+ * Push a materialized batch with the durable commit as its parent. On a 409,
7035
+ * refresh Hub state, replay the batch, and retry with the new parent. A push
7036
+ * response without a trustworthy hash is confirmed by a pull before callers
7037
+ * are allowed to observe it as durable.
7038
+ */
7039
+ async pushBatch(batch) {
7040
+ for (let attempt = 0;; attempt += 1) {
7041
+ const payload = {};
7042
+ for (const [path, content] of Object.entries(batch.changes)) payload[path] = content === null ? null : {
7043
+ type: "file",
7044
+ content
7045
+ };
7046
+ let url;
7047
+ try {
7048
+ url = await this.client.pushAgent(this.identifier, {
7049
+ files: payload,
7050
+ ...this.commitHash ? { parentCommit: this.commitHash } : {}
7051
+ });
7052
+ } catch (error) {
7053
+ if (getLangSmithStatus(error) !== 409 || attempt >= MAX_CONFLICT_RETRIES) throw error;
7054
+ const snapshot = await this.fetchTree();
7055
+ this.rematerializeAfterConflict(batch, snapshot, error);
7056
+ continue;
7057
+ }
7058
+ const pushedCommitHash = parseCommitHashFromUrl(url, this.identifier);
7059
+ if (pushedCommitHash === null) {
7060
+ this.beginSnapshotPublication();
7061
+ const snapshot = await this.fetchTree();
7062
+ if (snapshot.commitHash === null) throw new Error("Context Hub commit succeeded but its hash could not be resolved");
7063
+ return {
7064
+ kind: "snapshot",
7065
+ snapshot
7066
+ };
7067
+ }
7068
+ return {
7069
+ kind: "commit",
7070
+ commitHash: pushedCommitHash
6698
7071
  };
6699
7072
  }
6700
7073
  }
@@ -6822,53 +7195,71 @@ var ContextHubBackend = class ContextHubBackend {
6822
7195
  async write(filePath, content) {
6823
7196
  const hubPath = ContextHubBackend.stripPrefix(filePath);
6824
7197
  try {
6825
- await this.ensureCache();
6826
- await this.commit({ [hubPath]: content });
7198
+ const accepted = await this.acceptMutation(() => {
7199
+ return {
7200
+ result: {
7201
+ path: filePath,
7202
+ filesUpdate: null
7203
+ },
7204
+ completion: this.enqueueCommit({ [hubPath]: content })
7205
+ };
7206
+ });
7207
+ await accepted.completion;
7208
+ return accepted.result;
6827
7209
  } catch (error) {
6828
- if (isLangSmithError(error)) {
6829
- this.cache = null;
6830
- return { error: ContextHubBackend.toHubUnavailableError(error) };
6831
- }
7210
+ if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
6832
7211
  throw error;
6833
7212
  }
6834
- return {
6835
- path: filePath,
6836
- filesUpdate: null
6837
- };
6838
7213
  }
6839
7214
  async edit(filePath, oldString, newString, replaceAll = false) {
6840
7215
  const hubPath = ContextHubBackend.stripPrefix(filePath);
6841
7216
  try {
6842
- const current = (await this.ensureCache())[hubPath];
6843
- if (current === void 0) return { error: `Error: File '${filePath}' not found` };
6844
- const replacementResult = performStringReplacement(current, oldString, newString, replaceAll);
6845
- if (typeof replacementResult === "string") return { error: replacementResult };
6846
- const [newContent, occurrences] = replacementResult;
6847
- await this.commit({ [hubPath]: newContent });
6848
- return {
6849
- path: filePath,
6850
- filesUpdate: null,
6851
- occurrences
6852
- };
7217
+ const accepted = await this.acceptMutation((cache) => {
7218
+ const current = cache[hubPath];
7219
+ if (current === void 0) return { result: { error: `Error: File '${filePath}' not found` } };
7220
+ const replacementResult = performStringReplacement(current, oldString, newString, replaceAll);
7221
+ if (typeof replacementResult === "string") return { result: { error: replacementResult } };
7222
+ const [newContent, occurrences] = replacementResult;
7223
+ const result = {
7224
+ path: filePath,
7225
+ filesUpdate: null,
7226
+ occurrences
7227
+ };
7228
+ return {
7229
+ result,
7230
+ completion: this.enqueueCommit({ [hubPath]: newContent }, {
7231
+ kind: "edit",
7232
+ path: hubPath,
7233
+ oldString,
7234
+ newString,
7235
+ replaceAll,
7236
+ updateOccurrences: (replayedOccurrences) => {
7237
+ result.occurrences = replayedOccurrences;
7238
+ }
7239
+ })
7240
+ };
7241
+ });
7242
+ await accepted.completion;
7243
+ return accepted.result;
6853
7244
  } catch (error) {
6854
- if (isLangSmithError(error)) {
6855
- this.cache = null;
6856
- return { error: ContextHubBackend.toHubUnavailableError(error) };
6857
- }
7245
+ if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
6858
7246
  throw error;
6859
7247
  }
6860
7248
  }
6861
7249
  async delete(filePath) {
6862
7250
  const hubPath = ContextHubBackend.stripPrefix(filePath);
6863
7251
  try {
6864
- if (!(hubPath in await this.ensureCache())) return { error: `Error: File '${filePath}' not found` };
6865
- await this.commit({ [hubPath]: null });
6866
- return { path: filePath };
7252
+ const accepted = await this.acceptMutation((cache) => {
7253
+ if (!(hubPath in cache)) return { result: { error: `Error: File '${filePath}' not found` } };
7254
+ return {
7255
+ result: { path: filePath },
7256
+ completion: this.enqueueCommit({ [hubPath]: null })
7257
+ };
7258
+ });
7259
+ await accepted.completion;
7260
+ return accepted.result;
6867
7261
  } catch (error) {
6868
- if (isLangSmithError(error)) {
6869
- this.cache = null;
6870
- return { error: ContextHubBackend.toHubUnavailableError(error) };
6871
- }
7262
+ if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
6872
7263
  throw error;
6873
7264
  }
6874
7265
  }
@@ -6885,13 +7276,15 @@ var ContextHubBackend = class ContextHubBackend {
6885
7276
  }
6886
7277
  let commitError = null;
6887
7278
  if (Object.keys(validFiles).length > 0) try {
6888
- await this.ensureCache();
6889
- await this.commit(validFiles);
7279
+ await (await this.acceptMutation(() => {
7280
+ return {
7281
+ result: null,
7282
+ completion: this.enqueueCommit(validFiles)
7283
+ };
7284
+ })).completion;
6890
7285
  } catch (error) {
6891
- if (isLangSmithError(error)) {
6892
- this.cache = null;
6893
- commitError = mapHubFileOperationError(error);
6894
- } else throw error;
7286
+ if (isLangSmithError(error)) commitError = mapHubFileOperationError(error);
7287
+ else throw error;
6895
7288
  }
6896
7289
  return decoded.map(([path, text]) => {
6897
7290
  if (text === null) return {
@@ -7576,4 +7969,4 @@ var LangSmithSandbox = class LangSmithSandbox extends BaseSandbox {
7576
7969
  //#endregion
7577
7970
  export { filesValue as A, StateBackend as B, createSummarizationMiddleware as C, MAX_SKILL_NAME_LENGTH as D, MAX_SKILL_FILE_SIZE as E, SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY as F, resolveBackend as G, applyGrepMaxCount as H, createSubAgent as I, checkEmptyContent as J, adaptBackendProtocol as K, createSubAgentMiddleware as L, DEFAULT_GENERAL_PURPOSE_DESCRIPTION as M, DEFAULT_SUBAGENT_PROMPT as N, createSkillsMiddleware as O, GENERAL_PURPOSE_SUBAGENT as P, createFilesystemMiddleware as R, computeSummarizationDefaults as S, MAX_SKILL_DESCRIPTION_LENGTH as T, isSandboxBackend as U, SandboxError as V, isSandboxProtocol as W, isTextMimeType as X, getMimeType as Y, performStringReplacement as Z, createHarnessProfile as _, ASYNC_TASK_SYSTEM_PROMPT as a, createAsyncSubAgentMiddleware as b, TASK_SYSTEM_PROMPT as c, registerHarnessProfile as d, generalPurposeSubagentConfigSchema as f, EMPTY_HARNESS_PROFILE as g, serializeProfile as h, StoreBackend as i, createPatchToolCallsMiddleware as j, createMemoryMiddleware as k, createDeepAgent as l, parseHarnessProfileConfig as m, BaseSandbox as n, BASE_AGENT_PROMPT as o, harnessProfileConfigSchema as p, adaptSandboxProtocol as q, ContextHubBackend as r, EXECUTION_SYSTEM_PROMPT as s, LangSmithSandbox as t, getHarnessProfile as u, REQUIRED_MIDDLEWARE_NAMES as v, createCompletionCallbackMiddleware as w, isAsyncSubAgent as x, ConfigurationError as y, CompositeBackend as z };
7578
7971
 
7579
- //# sourceMappingURL=langsmith-CUTUAjHo.js.map
7972
+ //# sourceMappingURL=langsmith-DRyafCNe.js.map