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.
@@ -2016,7 +2016,7 @@ function createGrepTool(backend, options) {
2016
2016
  pattern: zod_v4.z.string().describe("Literal text pattern to search for (not regex)"),
2017
2017
  path: zod_v4.z.string().optional().default("/").describe("Base path to search from (default: /)"),
2018
2018
  glob: zod_v4.z.string().optional().nullable().default(null).describe("Optional glob pattern to filter files (e.g., '*.py')"),
2019
- max_count: zod_v4.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."),
2019
+ max_count: zod_v4.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."),
2020
2020
  output_mode: zod_v4.z.enum([
2021
2021
  "files_with_matches",
2022
2022
  "content",
@@ -2278,23 +2278,18 @@ const SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY = "__deepagents_subagent_response_form
2278
2278
  */
2279
2279
  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.";
2280
2280
  /**
2281
- * State keys that are excluded when passing state to subagents and when returning
2282
- * updates from subagents.
2283
- *
2284
- * When returning updates:
2285
- * 1. The messages key is handled explicitly to ensure only the final message is included
2286
- * 2. The todos and structuredResponse keys are excluded as they do not have a defined reducer
2287
- * and no clear meaning for returning them from a subagent to the main agent.
2288
- * 3. The skillsMetadata and memoryContents keys are automatically excluded from subagent output
2289
- * to prevent parent state from leaking to child agents. Each agent loads its own skills/memory
2290
- * independently based on its middleware configuration.
2281
+ * State keys excluded when passing state to subagents and when returning
2282
+ * updates from subagents. Summarization keys are excluded because their
2283
+ * cutoffIndex is only valid against the message list it was computed from.
2291
2284
  */
2292
2285
  const EXCLUDED_STATE_KEYS = [
2293
2286
  "messages",
2294
2287
  "todos",
2295
2288
  "structuredResponse",
2296
2289
  "skillsMetadata",
2297
- "memoryContents"
2290
+ "memoryContents",
2291
+ "_summarizationEvent",
2292
+ "_summarizationSessionId"
2298
2293
  ];
2299
2294
  /**
2300
2295
  * Default description for the general-purpose subagent.
@@ -2516,6 +2511,7 @@ function createTaskTool(options) {
2516
2511
  const subagent = selectSubagent(subagent_type, config);
2517
2512
  const subagentState = filterStateForSubagent((0, _langchain_langgraph.getCurrentTaskInput)());
2518
2513
  subagentState.messages = [new _langchain_core_messages.HumanMessage({ content: description })];
2514
+ subagentState._summarizationSessionId = `session_${crypto.randomUUID().substring(0, 8)}`;
2519
2515
  const subagentConfig = {
2520
2516
  ...config,
2521
2517
  metadata: {
@@ -2756,6 +2752,43 @@ function isAnthropicModel(model) {
2756
2752
  return model.getName() === "ChatAnthropic";
2757
2753
  }
2758
2754
  /**
2755
+ * A one-shot promise whose settlement is controlled externally.
2756
+ *
2757
+ * Use this when one part of a workflow must wait for an event that is owned
2758
+ * elsewhere—for example, a queued mutation waiting for the worker that will
2759
+ * push it. `Deferred` is awaitable because it implements `PromiseLike`, and
2760
+ * `.promise` is available when a concrete `Promise` is required.
2761
+ *
2762
+ * The first call to `resolve` or `reject` wins; later calls are ignored. This
2763
+ * class deliberately does not provide cancellation, reset, or notification
2764
+ * semantics. It models exactly one eventual outcome.
2765
+ */
2766
+ var Deferred = class {
2767
+ promise;
2768
+ settled = false;
2769
+ resolvePromise;
2770
+ rejectPromise;
2771
+ constructor() {
2772
+ this.promise = new Promise((resolve, reject) => {
2773
+ this.resolvePromise = resolve;
2774
+ this.rejectPromise = reject;
2775
+ });
2776
+ }
2777
+ resolve(value) {
2778
+ if (this.settled) return;
2779
+ this.settled = true;
2780
+ this.resolvePromise(value);
2781
+ }
2782
+ reject(reason) {
2783
+ if (this.settled) return;
2784
+ this.settled = true;
2785
+ this.rejectPromise(reason);
2786
+ }
2787
+ then(onfulfilled, onrejected) {
2788
+ return this.promise.then(onfulfilled, onrejected);
2789
+ }
2790
+ };
2791
+ /**
2759
2792
  * Detect whether a model is an AWS Bedrock Converse model.
2760
2793
  *
2761
2794
  * Accepts the wider `RunnableInterface` shape (the type of `request.model`
@@ -6615,9 +6648,36 @@ var StoreBackend = class {
6615
6648
  /**
6616
6649
  * ContextHubBackend: Store files in a LangSmith Hub agent repo (persistent).
6617
6650
  */
6618
- const URL_COMMIT_SUFFIX_RE = /:([0-9a-f]{8,64})$/i;
6651
+ const CONTEXT_URL_COMMIT_PATH_RE = /^\/context\/([^/]+)\/([0-9a-f]{8})$/;
6652
+ const LEGACY_URL_COMMIT_PATH_RE = /^\/hub\/([^/]+)\/([^/:]+):([0-9a-f]{8})$/;
6653
+ const MUTATION_COALESCE_MS = 50;
6654
+ const MAX_CONFLICT_RETRIES = 3;
6619
6655
  const TEXT_MIME_TYPE = "text/plain";
6620
6656
  const FNMATCH_OPTIONS = { bash: true };
6657
+ function parseHubTargetIdentifier(identifier) {
6658
+ if (!identifier || identifier.split("/").length > 2 || identifier.startsWith("/") || identifier.endsWith("/") || identifier.split(":").length > 2) return null;
6659
+ const [ownerNamePart] = identifier.split(":");
6660
+ if (ownerNamePart.includes("/")) {
6661
+ const [owner, name] = ownerNamePart.split("/", 2);
6662
+ return owner && name ? [owner, name] : null;
6663
+ }
6664
+ return ownerNamePart ? ["-", ownerNamePart] : null;
6665
+ }
6666
+ function parseCommitHashFromUrl(url, identifier) {
6667
+ try {
6668
+ const pathname = decodeURIComponent(new URL(url).pathname);
6669
+ const target = parseHubTargetIdentifier(identifier);
6670
+ if (target === null) return null;
6671
+ const [targetOwner, targetName] = target;
6672
+ const contextMatch = CONTEXT_URL_COMMIT_PATH_RE.exec(pathname);
6673
+ if (contextMatch !== null && contextMatch[1] === targetName) return contextMatch[2];
6674
+ const legacyMatch = LEGACY_URL_COMMIT_PATH_RE.exec(pathname);
6675
+ if (legacyMatch !== null && legacyMatch[1] === targetOwner && legacyMatch[2] === targetName) return legacyMatch[3];
6676
+ return null;
6677
+ } catch {
6678
+ return null;
6679
+ }
6680
+ }
6621
6681
  function getErrorMessage(error) {
6622
6682
  if (typeof error === "string") return error;
6623
6683
  if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") return error.message;
@@ -6656,6 +6716,12 @@ function getLangSmithStatus(error) {
6656
6716
  const maybeError = error;
6657
6717
  if (typeof maybeError.status === "number") return maybeError.status;
6658
6718
  }
6719
+ function createLangSmithConflictError(message) {
6720
+ const error = new Error(message);
6721
+ error.name = "LangSmithConflictError";
6722
+ error.status = 409;
6723
+ return error;
6724
+ }
6659
6725
  function mapHubFileOperationError(error) {
6660
6726
  const status = getLangSmithStatus(error);
6661
6727
  if (status === 401 || status === 403) return "permission_denied";
@@ -6665,12 +6731,49 @@ function mapHubFileOperationError(error) {
6665
6731
  /**
6666
6732
  * Backend that stores files in a LangSmith Hub agent repo (persistent).
6667
6733
  */
6734
+ /**
6735
+ * Backend that stores files in a LangSmith Hub agent repository.
6736
+ *
6737
+ * ## Mutation model
6738
+ *
6739
+ * Mutations are accepted in call order, coalesced for a short window, and
6740
+ * pushed by one worker. Only one batch is in flight at a time; mutations that
6741
+ * arrive during a push form the next batch. This serializes one backend
6742
+ * instance's writes while still reducing the number of Hub commits.
6743
+ *
6744
+ * Reads use an optimistic view: the last durable cache overlaid with the
6745
+ * in-flight batch and then the pending batch. A read can therefore observe an
6746
+ * accepted mutation before it is durable; a failed push invalidates that view
6747
+ * and the next operation reloads from Hub.
6748
+ *
6749
+ * A `409` parent conflict triggers an authoritative pull and rematerializes
6750
+ * the in-flight batch over the fetched tree before retrying. Edits replay their
6751
+ * original replacement intent; absolute writes, deletes, and uploads replay as
6752
+ * absolute changes. Retries are bounded by `MAX_CONFLICT_RETRIES`.
6753
+ */
6668
6754
  var ContextHubBackend = class ContextHubBackend {
6669
6755
  identifier;
6670
6756
  client;
6757
+ /** Last durable Hub file state; `null` means the next access must load it. */
6671
6758
  cache = null;
6672
6759
  linkedEntries = {};
6760
+ /** Parent hash for the durable cache, used for optimistic-concurrency pushes. */
6673
6761
  commitHash = null;
6762
+ /** Shared cold-load promise so concurrent first operations perform one pull. */
6763
+ loadPromise = null;
6764
+ /** Promise chain serializing mutation acceptance and optimistic projections. */
6765
+ mutationOrder = Promise.resolve();
6766
+ /** Mutations accepted for the next coalesced push. */
6767
+ pendingBatch = null;
6768
+ /** The batch currently submitted to Hub and visible to optimistic reads. */
6769
+ inFlightBatch = null;
6770
+ /** The single queue-draining worker, when active. */
6771
+ workerPromise = null;
6772
+ /**
6773
+ * Blocks cache consumers while a successful push without a parseable commit
6774
+ * hash is being confirmed by an authoritative pull.
6775
+ */
6776
+ snapshotPublication = null;
6674
6777
  constructor(identifier, options = {}) {
6675
6778
  this.identifier = identifier;
6676
6779
  this.client = options.client ?? new langsmith.Client();
@@ -6681,49 +6784,319 @@ var ContextHubBackend = class ContextHubBackend {
6681
6784
  static toHubUnavailableError(error) {
6682
6785
  return `Hub unavailable: ${getErrorMessage(error)}`;
6683
6786
  }
6684
- async loadTree() {
6787
+ async fetchTree() {
6685
6788
  let context;
6686
6789
  try {
6687
6790
  context = await this.client.pullAgent(this.identifier);
6688
6791
  } catch (error) {
6689
- if (isLangSmithNotFoundError(error)) {
6690
- this.cache = {};
6691
- this.linkedEntries = {};
6692
- this.commitHash = null;
6693
- return;
6694
- }
6792
+ if (isLangSmithNotFoundError(error)) return {
6793
+ cache: {},
6794
+ linkedEntries: {},
6795
+ commitHash: null
6796
+ };
6695
6797
  throw error;
6696
6798
  }
6697
- this.commitHash = context.commit_hash;
6698
- this.cache = {};
6699
- this.linkedEntries = {};
6700
- for (const [path, entry] of Object.entries(context.files)) if (entry.type === "file") this.cache[path] = entry.content;
6701
- else if ((entry.type === "agent" || entry.type === "skill") && typeof entry.repo_handle === "string") this.linkedEntries[path] = entry.repo_handle;
6799
+ const cache = {};
6800
+ const linkedEntries = {};
6801
+ for (const [path, entry] of Object.entries(context.files)) if (entry.type === "file") cache[path] = entry.content;
6802
+ else if ((entry.type === "agent" || entry.type === "skill") && typeof entry.repo_handle === "string") linkedEntries[path] = entry.repo_handle;
6803
+ return {
6804
+ cache,
6805
+ linkedEntries,
6806
+ commitHash: context.commit_hash
6807
+ };
6702
6808
  }
6703
- async ensureCache() {
6704
- if (this.cache === null) await this.loadTree();
6809
+ publishSnapshot(snapshot) {
6810
+ this.cache = snapshot.cache;
6811
+ this.linkedEntries = snapshot.linkedEntries;
6812
+ this.commitHash = snapshot.commitHash;
6813
+ }
6814
+ async loadTree() {
6815
+ this.publishSnapshot(await this.fetchTree());
6816
+ }
6817
+ beginSnapshotPublication() {
6818
+ if (this.snapshotPublication !== null) throw new Error("Context Hub snapshot publication is already pending");
6819
+ this.snapshotPublication = new Deferred();
6820
+ }
6821
+ finishSnapshotPublication() {
6822
+ const publication = this.snapshotPublication;
6823
+ this.snapshotPublication = null;
6824
+ publication?.resolve();
6825
+ }
6826
+ async ensureCacheLoaded() {
6827
+ while (this.snapshotPublication !== null) await this.snapshotPublication;
6828
+ if (this.cache === null) {
6829
+ let loadPromise = this.loadPromise;
6830
+ if (loadPromise === null) {
6831
+ loadPromise = this.loadTree();
6832
+ this.loadPromise = loadPromise;
6833
+ }
6834
+ try {
6835
+ await loadPromise;
6836
+ } finally {
6837
+ if (this.loadPromise === loadPromise) this.loadPromise = null;
6838
+ }
6839
+ }
6705
6840
  if (this.cache === null) throw new Error("Context Hub cache failed to initialize");
6706
- return this.cache;
6707
- }
6708
- async commit(changes) {
6709
- if (Object.keys(changes).length === 0) return;
6710
- const payload = {};
6711
- for (const [path, content] of Object.entries(changes)) payload[path] = content === null ? null : {
6712
- type: "file",
6713
- content
6841
+ }
6842
+ async ensureCache() {
6843
+ await this.ensureCacheLoaded();
6844
+ return this.visibleCache();
6845
+ }
6846
+ static applyChanges(cache, changes) {
6847
+ const next = { ...cache };
6848
+ for (const [path, content] of Object.entries(changes)) if (content === null) delete next[path];
6849
+ else next[path] = content;
6850
+ return next;
6851
+ }
6852
+ /**
6853
+ * Build the read-your-writes view without publishing speculative data as the
6854
+ * durable cache. Later batches overlay earlier ones, matching worker order.
6855
+ */
6856
+ visibleCache() {
6857
+ let visible = { ...this.cache ?? {} };
6858
+ if (this.inFlightBatch !== null) visible = ContextHubBackend.applyChanges(visible, this.inFlightBatch.changes);
6859
+ if (this.pendingBatch !== null) visible = ContextHubBackend.applyChanges(visible, this.pendingBatch.changes);
6860
+ return visible;
6861
+ }
6862
+ invalidateCache() {
6863
+ this.cache = null;
6864
+ this.linkedEntries = {};
6865
+ this.commitHash = null;
6866
+ this.loadPromise = null;
6867
+ }
6868
+ async acquireMutationTurn() {
6869
+ let release;
6870
+ const previous = this.mutationOrder;
6871
+ this.mutationOrder = new Promise((resolve) => {
6872
+ release = resolve;
6873
+ });
6874
+ await previous;
6875
+ return release;
6876
+ }
6877
+ /**
6878
+ * Serialize validation and enqueueing so each operation is evaluated against
6879
+ * a stable optimistic projection. Cache loading begins before acquiring the
6880
+ * turn, allowing concurrent cold-start callers to share the same pull.
6881
+ */
6882
+ async acceptMutation(operation) {
6883
+ const turn = this.acquireMutationTurn();
6884
+ const cacheOutcome = this.ensureCacheLoaded().then(() => ({ loaded: true }), (error) => ({
6885
+ loaded: false,
6886
+ error
6887
+ }));
6888
+ const release = await turn;
6889
+ try {
6890
+ const outcome = await cacheOutcome;
6891
+ if (!outcome.loaded) throw outcome.error;
6892
+ while (this.cache === null) await this.ensureCacheLoaded();
6893
+ return operation(this.visibleCache());
6894
+ } finally {
6895
+ release();
6896
+ }
6897
+ }
6898
+ /**
6899
+ * Start a batch's coalescing window. The worker waits for this signal before
6900
+ * detaching the batch; cancellation resolves it immediately so failures do
6901
+ * not leave the worker waiting on a timer.
6902
+ */
6903
+ createMutationBatch() {
6904
+ const batch = {
6905
+ changes: {},
6906
+ waiters: [],
6907
+ ready: new Deferred(),
6908
+ timer: null
6714
6909
  };
6715
- const url = await this.client.pushAgent(this.identifier, {
6716
- files: payload,
6717
- ...this.commitHash ? { parentCommit: this.commitHash } : {}
6910
+ batch.timer = setTimeout(() => {
6911
+ batch.timer = null;
6912
+ batch.ready.resolve();
6913
+ }, MUTATION_COALESCE_MS);
6914
+ return batch;
6915
+ }
6916
+ cancelBatchTimer(batch) {
6917
+ if (batch.timer !== null) {
6918
+ clearTimeout(batch.timer);
6919
+ batch.timer = null;
6920
+ }
6921
+ batch.ready.resolve();
6922
+ }
6923
+ enqueueCommit(changes, intent = {
6924
+ kind: "changes",
6925
+ changes: { ...changes }
6926
+ }) {
6927
+ if (Object.keys(changes).length === 0) return Promise.resolve();
6928
+ let batch = this.pendingBatch;
6929
+ if (batch === null) {
6930
+ batch = this.createMutationBatch();
6931
+ this.pendingBatch = batch;
6932
+ }
6933
+ Object.assign(batch.changes, changes);
6934
+ const completion = new Deferred();
6935
+ batch.waiters.push({
6936
+ intent,
6937
+ completion
6718
6938
  });
6719
- const match = URL_COMMIT_SUFFIX_RE.exec(url);
6720
- if (match) this.commitHash = match[1];
6721
- if (this.cache !== null) {
6722
- const deletions = new Set(Object.entries(changes).filter(([, content]) => content === null).map(([path]) => path));
6723
- const updates = Object.fromEntries(Object.entries(changes).filter((entry) => entry[1] !== null));
6724
- this.cache = {
6725
- ...Object.fromEntries(Object.entries(this.cache).filter(([path]) => !deletions.has(path))),
6726
- ...updates
6939
+ this.startWorker();
6940
+ return completion.promise;
6941
+ }
6942
+ /**
6943
+ * Replay ordered intents over an authoritative base after a conflict. This
6944
+ * rebuilds the push payload and optimistic overlay. An edit that no longer
6945
+ * applies throws the supplied conflict error; absolute changes are reapplied.
6946
+ */
6947
+ rematerializeBatch(batch, base, conflictError) {
6948
+ let cache = { ...base };
6949
+ const changes = {};
6950
+ for (const waiter of batch.waiters) {
6951
+ const { intent } = waiter;
6952
+ if (intent.kind === "changes") {
6953
+ Object.assign(changes, intent.changes);
6954
+ cache = ContextHubBackend.applyChanges(cache, intent.changes);
6955
+ continue;
6956
+ }
6957
+ const current = cache[intent.path];
6958
+ if (current === void 0) throw conflictError;
6959
+ const replacementResult = performStringReplacement(current, intent.oldString, intent.newString, intent.replaceAll);
6960
+ if (typeof replacementResult === "string") throw conflictError;
6961
+ const [newContent, occurrences] = replacementResult;
6962
+ const editChanges = { [intent.path]: newContent };
6963
+ Object.assign(changes, editChanges);
6964
+ cache = ContextHubBackend.applyChanges(cache, editChanges);
6965
+ intent.updateOccurrences(occurrences);
6966
+ }
6967
+ batch.changes = changes;
6968
+ return cache;
6969
+ }
6970
+ rematerializeAfterConflict(batch, snapshot, conflictError) {
6971
+ const cache = this.rematerializeBatch(batch, snapshot.cache, conflictError);
6972
+ let pendingReplayError = null;
6973
+ if (this.pendingBatch !== null) try {
6974
+ this.rematerializeBatch(this.pendingBatch, cache, conflictError);
6975
+ } catch (error) {
6976
+ if (error !== conflictError) throw error;
6977
+ pendingReplayError = error;
6978
+ }
6979
+ this.publishSnapshot(snapshot);
6980
+ if (pendingReplayError !== null) this.failPendingBatch(pendingReplayError);
6981
+ }
6982
+ rematerializePendingBatch(snapshot) {
6983
+ if (this.pendingBatch === null) return null;
6984
+ const conflictError = createLangSmithConflictError("Pending Context Hub mutation conflicts with authoritative state");
6985
+ try {
6986
+ this.rematerializeBatch(this.pendingBatch, snapshot.cache, conflictError);
6987
+ return null;
6988
+ } catch (error) {
6989
+ if (error !== conflictError) throw error;
6990
+ return conflictError;
6991
+ }
6992
+ }
6993
+ startWorker() {
6994
+ if (this.workerPromise !== null) return;
6995
+ const worker = this.drainMutationQueue().catch((error) => {
6996
+ this.failAllBatches(error);
6997
+ }).finally(() => {
6998
+ if (this.workerPromise === worker) {
6999
+ this.workerPromise = null;
7000
+ if (this.pendingBatch !== null) this.startWorker();
7001
+ }
7002
+ });
7003
+ this.workerPromise = worker;
7004
+ }
7005
+ /**
7006
+ * Drain coalesced batches sequentially. A completed batch publishes durable
7007
+ * state before settling its callers; a failed batch invalidates local state
7008
+ * and rejects both in-flight and queued callers so the next mutation reloads.
7009
+ */
7010
+ async drainMutationQueue() {
7011
+ while (this.pendingBatch !== null) {
7012
+ const batch = this.pendingBatch;
7013
+ await batch.ready;
7014
+ if (this.pendingBatch !== batch) continue;
7015
+ this.pendingBatch = null;
7016
+ this.inFlightBatch = batch;
7017
+ let pendingReplayError = null;
7018
+ try {
7019
+ const result = await this.pushBatch(batch);
7020
+ if (result.kind === "snapshot") {
7021
+ pendingReplayError = this.rematerializePendingBatch(result.snapshot);
7022
+ this.publishSnapshot(result.snapshot);
7023
+ } else {
7024
+ this.cache = ContextHubBackend.applyChanges(this.cache ?? {}, batch.changes);
7025
+ this.commitHash = result.commitHash;
7026
+ }
7027
+ } catch (error) {
7028
+ this.inFlightBatch = null;
7029
+ this.invalidateCache();
7030
+ this.finishSnapshotPublication();
7031
+ for (const waiter of batch.waiters) waiter.completion.reject(error);
7032
+ this.failPendingBatch(error);
7033
+ return;
7034
+ }
7035
+ this.inFlightBatch = null;
7036
+ this.finishSnapshotPublication();
7037
+ for (const waiter of batch.waiters) waiter.completion.resolve();
7038
+ if (pendingReplayError !== null) {
7039
+ this.failPendingBatch(pendingReplayError);
7040
+ return;
7041
+ }
7042
+ }
7043
+ }
7044
+ failPendingBatch(error) {
7045
+ const pending = this.pendingBatch;
7046
+ if (pending === null) return;
7047
+ this.pendingBatch = null;
7048
+ this.cancelBatchTimer(pending);
7049
+ for (const waiter of pending.waiters) waiter.completion.reject(error);
7050
+ }
7051
+ failAllBatches(error) {
7052
+ const inFlight = this.inFlightBatch;
7053
+ this.inFlightBatch = null;
7054
+ this.invalidateCache();
7055
+ this.finishSnapshotPublication();
7056
+ if (inFlight !== null) {
7057
+ this.cancelBatchTimer(inFlight);
7058
+ for (const waiter of inFlight.waiters) waiter.completion.reject(error);
7059
+ }
7060
+ this.failPendingBatch(error);
7061
+ }
7062
+ /**
7063
+ * Push a materialized batch with the durable commit as its parent. On a 409,
7064
+ * refresh Hub state, replay the batch, and retry with the new parent. A push
7065
+ * response without a trustworthy hash is confirmed by a pull before callers
7066
+ * are allowed to observe it as durable.
7067
+ */
7068
+ async pushBatch(batch) {
7069
+ for (let attempt = 0;; attempt += 1) {
7070
+ const payload = {};
7071
+ for (const [path, content] of Object.entries(batch.changes)) payload[path] = content === null ? null : {
7072
+ type: "file",
7073
+ content
7074
+ };
7075
+ let url;
7076
+ try {
7077
+ url = await this.client.pushAgent(this.identifier, {
7078
+ files: payload,
7079
+ ...this.commitHash ? { parentCommit: this.commitHash } : {}
7080
+ });
7081
+ } catch (error) {
7082
+ if (getLangSmithStatus(error) !== 409 || attempt >= MAX_CONFLICT_RETRIES) throw error;
7083
+ const snapshot = await this.fetchTree();
7084
+ this.rematerializeAfterConflict(batch, snapshot, error);
7085
+ continue;
7086
+ }
7087
+ const pushedCommitHash = parseCommitHashFromUrl(url, this.identifier);
7088
+ if (pushedCommitHash === null) {
7089
+ this.beginSnapshotPublication();
7090
+ const snapshot = await this.fetchTree();
7091
+ if (snapshot.commitHash === null) throw new Error("Context Hub commit succeeded but its hash could not be resolved");
7092
+ return {
7093
+ kind: "snapshot",
7094
+ snapshot
7095
+ };
7096
+ }
7097
+ return {
7098
+ kind: "commit",
7099
+ commitHash: pushedCommitHash
6727
7100
  };
6728
7101
  }
6729
7102
  }
@@ -6851,53 +7224,71 @@ var ContextHubBackend = class ContextHubBackend {
6851
7224
  async write(filePath, content) {
6852
7225
  const hubPath = ContextHubBackend.stripPrefix(filePath);
6853
7226
  try {
6854
- await this.ensureCache();
6855
- await this.commit({ [hubPath]: content });
7227
+ const accepted = await this.acceptMutation(() => {
7228
+ return {
7229
+ result: {
7230
+ path: filePath,
7231
+ filesUpdate: null
7232
+ },
7233
+ completion: this.enqueueCommit({ [hubPath]: content })
7234
+ };
7235
+ });
7236
+ await accepted.completion;
7237
+ return accepted.result;
6856
7238
  } catch (error) {
6857
- if (isLangSmithError(error)) {
6858
- this.cache = null;
6859
- return { error: ContextHubBackend.toHubUnavailableError(error) };
6860
- }
7239
+ if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
6861
7240
  throw error;
6862
7241
  }
6863
- return {
6864
- path: filePath,
6865
- filesUpdate: null
6866
- };
6867
7242
  }
6868
7243
  async edit(filePath, oldString, newString, replaceAll = false) {
6869
7244
  const hubPath = ContextHubBackend.stripPrefix(filePath);
6870
7245
  try {
6871
- const current = (await this.ensureCache())[hubPath];
6872
- if (current === void 0) return { error: `Error: File '${filePath}' not found` };
6873
- const replacementResult = performStringReplacement(current, oldString, newString, replaceAll);
6874
- if (typeof replacementResult === "string") return { error: replacementResult };
6875
- const [newContent, occurrences] = replacementResult;
6876
- await this.commit({ [hubPath]: newContent });
6877
- return {
6878
- path: filePath,
6879
- filesUpdate: null,
6880
- occurrences
6881
- };
7246
+ const accepted = await this.acceptMutation((cache) => {
7247
+ const current = cache[hubPath];
7248
+ if (current === void 0) return { result: { error: `Error: File '${filePath}' not found` } };
7249
+ const replacementResult = performStringReplacement(current, oldString, newString, replaceAll);
7250
+ if (typeof replacementResult === "string") return { result: { error: replacementResult } };
7251
+ const [newContent, occurrences] = replacementResult;
7252
+ const result = {
7253
+ path: filePath,
7254
+ filesUpdate: null,
7255
+ occurrences
7256
+ };
7257
+ return {
7258
+ result,
7259
+ completion: this.enqueueCommit({ [hubPath]: newContent }, {
7260
+ kind: "edit",
7261
+ path: hubPath,
7262
+ oldString,
7263
+ newString,
7264
+ replaceAll,
7265
+ updateOccurrences: (replayedOccurrences) => {
7266
+ result.occurrences = replayedOccurrences;
7267
+ }
7268
+ })
7269
+ };
7270
+ });
7271
+ await accepted.completion;
7272
+ return accepted.result;
6882
7273
  } catch (error) {
6883
- if (isLangSmithError(error)) {
6884
- this.cache = null;
6885
- return { error: ContextHubBackend.toHubUnavailableError(error) };
6886
- }
7274
+ if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
6887
7275
  throw error;
6888
7276
  }
6889
7277
  }
6890
7278
  async delete(filePath) {
6891
7279
  const hubPath = ContextHubBackend.stripPrefix(filePath);
6892
7280
  try {
6893
- if (!(hubPath in await this.ensureCache())) return { error: `Error: File '${filePath}' not found` };
6894
- await this.commit({ [hubPath]: null });
6895
- return { path: filePath };
7281
+ const accepted = await this.acceptMutation((cache) => {
7282
+ if (!(hubPath in cache)) return { result: { error: `Error: File '${filePath}' not found` } };
7283
+ return {
7284
+ result: { path: filePath },
7285
+ completion: this.enqueueCommit({ [hubPath]: null })
7286
+ };
7287
+ });
7288
+ await accepted.completion;
7289
+ return accepted.result;
6896
7290
  } catch (error) {
6897
- if (isLangSmithError(error)) {
6898
- this.cache = null;
6899
- return { error: ContextHubBackend.toHubUnavailableError(error) };
6900
- }
7291
+ if (isLangSmithError(error)) return { error: ContextHubBackend.toHubUnavailableError(error) };
6901
7292
  throw error;
6902
7293
  }
6903
7294
  }
@@ -6914,13 +7305,15 @@ var ContextHubBackend = class ContextHubBackend {
6914
7305
  }
6915
7306
  let commitError = null;
6916
7307
  if (Object.keys(validFiles).length > 0) try {
6917
- await this.ensureCache();
6918
- await this.commit(validFiles);
7308
+ await (await this.acceptMutation(() => {
7309
+ return {
7310
+ result: null,
7311
+ completion: this.enqueueCommit(validFiles)
7312
+ };
7313
+ })).completion;
6919
7314
  } catch (error) {
6920
- if (isLangSmithError(error)) {
6921
- this.cache = null;
6922
- commitError = mapHubFileOperationError(error);
6923
- } else throw error;
7315
+ if (isLangSmithError(error)) commitError = mapHubFileOperationError(error);
7316
+ else throw error;
6924
7317
  }
6925
7318
  return decoded.map(([path, text]) => {
6926
7319
  if (text === null) return {
@@ -7916,4 +8309,4 @@ Object.defineProperty(exports, "serializeProfile", {
7916
8309
  }
7917
8310
  });
7918
8311
 
7919
- //# sourceMappingURL=langsmith-Bjhs2iT_.cjs.map
8312
+ //# sourceMappingURL=langsmith-BJ2PdYqB.cjs.map