@anuma/sdk 9.1.5 → 9.1.6

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.
@@ -19,7 +19,7 @@ import {
19
19
  taskTypeHeader,
20
20
  validateEndpointOverride,
21
21
  withInternalFlowMarker
22
- } from "./chunk-7QX4J7KV.mjs";
22
+ } from "./chunk-RSA3S6OA.mjs";
23
23
  import {
24
24
  BASE_URL,
25
25
  MCP_R2_DOMAIN,
@@ -14957,6 +14957,16 @@ async function updateVaultMemoryOp(ctx, id2, opts) {
14957
14957
  const probe = await ctx.vaultMemoryCollection.find(id2);
14958
14958
  if (probe.isDeleted || probe.supersededBy || !isOwnedByCtxUser(ctx, probe)) return null;
14959
14959
  if (opts.freeFormOnly && isKinded(probe)) return null;
14960
+ const readStoredContent = probe._getRaw("content");
14961
+ if (opts.expectedContent !== void 0) {
14962
+ const current = await vaultMemoryToStored(
14963
+ probe,
14964
+ ctx.walletAddress,
14965
+ ctx.signMessage,
14966
+ ctx.embeddedWalletSigner
14967
+ );
14968
+ if (current.content !== opts.expectedContent) return null;
14969
+ }
14960
14970
  const levelAfter = (current) => {
14961
14971
  if (opts.level !== void 0) return opts.level;
14962
14972
  if (opts.scope === void 0) return resolveMemoryLevel(current.level, current.scope);
@@ -14964,7 +14974,7 @@ async function updateVaultMemoryOp(ctx, id2, opts) {
14964
14974
  };
14965
14975
  assertValidKindLevel(opts.kind !== void 0 ? opts.kind : probe.kind, levelAfter(probe));
14966
14976
  const encryptedKindValue = await encryptKindValue(ctx, opts.kindValue);
14967
- const encryptedContent = ctx.walletAddress && ctx.signMessage ? await encryptVaultMemoryContent(
14977
+ const encryptedContent = opts.content !== void 0 && ctx.walletAddress && ctx.signMessage ? await encryptVaultMemoryContent(
14968
14978
  opts.content,
14969
14979
  ctx.walletAddress,
14970
14980
  ctx.signMessage,
@@ -14972,13 +14982,13 @@ async function updateVaultMemoryOp(ctx, id2, opts) {
14972
14982
  ) : opts.content;
14973
14983
  let stale = false;
14974
14984
  const record2 = probe;
14975
- const originalUpdatedAt = record2.updatedAt.getTime();
14976
14985
  await ctx.database.write(async () => {
14977
14986
  await assertCanWrite2(ctx);
14978
- if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2) || opts.freeFormOnly && isKinded(record2)) {
14987
+ if (record2.isDeleted || record2.supersededBy || !isOwnedByCtxUser(ctx, record2) || opts.freeFormOnly && isKinded(record2) || opts.expectedContent !== void 0 && record2._getRaw("content") !== readStoredContent) {
14979
14988
  stale = true;
14980
14989
  return;
14981
14990
  }
14991
+ const originalUpdatedAt = record2.updatedAt.getTime();
14982
14992
  const nextKind = opts.kind !== void 0 ? opts.kind : record2.kind;
14983
14993
  const nextLevel = levelAfter(record2);
14984
14994
  assertValidKindLevel(nextKind, nextLevel);
@@ -14994,7 +15004,7 @@ async function updateVaultMemoryOp(ctx, id2, opts) {
14994
15004
  replayedObservation = opts.observationSourceIds.every((id3) => observedSources.includes(id3));
14995
15005
  }
14996
15006
  await record2.update((r) => {
14997
- r._setRaw("content", encryptedContent);
15007
+ if (encryptedContent !== void 0) r._setRaw("content", encryptedContent);
14998
15008
  if (opts.level !== void 0) {
14999
15009
  r._setRaw("level", nextLevel);
15000
15010
  r._setRaw("scope", scopeForLevel(nextLevel));
@@ -15761,7 +15771,7 @@ function classifyDecay(m, now, policy) {
15761
15771
  if (m.source === "manual" || m.source === SOURCE_PHOTO) return "keep";
15762
15772
  if (isTimeSensitiveType(m.factType) && m.eventTimeEnd !== null) {
15763
15773
  if (m.eventTimeEnd < now - resolved.pastEventGraceMs) return "archive";
15764
- if (m.eventTimeEnd >= now) return "keep";
15774
+ return "keep";
15765
15775
  }
15766
15776
  if (now - lastActivityAt(m) > ttlForType(m.factType, resolved)) return "archive";
15767
15777
  return "keep";
@@ -19261,7 +19271,6 @@ async function retainWithPersistence(content, ctx, options = {}) {
19261
19271
  const factTypeUpdate = pickFactTypeUpdate(existing, options.factType);
19262
19272
  const resurrect = resurrectFields(existing);
19263
19273
  const updated = await ctx.persistence.update(targetId, {
19264
- content: existing.content,
19265
19274
  freeFormOnly: true,
19266
19275
  proofCountIncrement: 1,
19267
19276
  observationSourceIds: options.sourceChunkIds,
@@ -19465,7 +19474,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
19465
19474
  content: m.content,
19466
19475
  similarity: m.similarity
19467
19476
  }));
19468
- const { consolidateMemory: doConsolidate } = await import("./consolidate-WLDYJF6J.mjs");
19477
+ const { consolidateMemory: doConsolidate } = await import("./consolidate-WFTPBHDV.mjs");
19469
19478
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
19470
19479
  if (decision.action === "create") return decision.fallbackReason ? null : { create: true };
19471
19480
  if (decision.action === "supersede" && decision.content) {
@@ -19500,7 +19509,6 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
19500
19509
  const factTypeUpdate = pickFactTypeUpdate(existing, options.factType);
19501
19510
  const resurrect = resurrectFields(existing);
19502
19511
  const updated = await ctx.persistence.update(decision.targetId, {
19503
- content: existing.content,
19504
19512
  freeFormOnly: true,
19505
19513
  proofCountIncrement: 1,
19506
19514
  observationSourceIds: options.sourceChunkIds,
@@ -19528,6 +19536,10 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
19528
19536
  };
19529
19537
  }
19530
19538
  if (decision.action === "update" && decision.targetId && decision.content) {
19539
+ const expectedContent = candidates.find(
19540
+ (candidate) => candidate.id === decision.targetId
19541
+ )?.content;
19542
+ if (expectedContent === void 0) return null;
19531
19543
  const existing = await ctx.persistence.get(decision.targetId);
19532
19544
  if (!existing || existing.supersededBy) {
19533
19545
  return abandonToRace(
@@ -19535,6 +19547,9 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
19535
19547
  `update: target ${decision.targetId} ${existing ? "superseded" : "deleted"} before the write`
19536
19548
  );
19537
19549
  }
19550
+ if (existing.content !== expectedContent) {
19551
+ return abandonToRace(options, `update: target ${decision.targetId} edited before the write`);
19552
+ }
19538
19553
  const mergedSourceIds = unionStrings(
19539
19554
  existing.sourceChunkIds ?? [],
19540
19555
  options.sourceChunkIds ?? []
@@ -19547,6 +19562,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
19547
19562
  const resurrect = resurrectFields(existing);
19548
19563
  const updated = await ctx.persistence.update(decision.targetId, {
19549
19564
  content: decision.content,
19565
+ expectedContent,
19550
19566
  freeFormOnly: true,
19551
19567
  proofCountIncrement: 1,
19552
19568
  observationSourceIds: options.sourceChunkIds,
@@ -19559,10 +19575,10 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
19559
19575
  ...factTypeUpdate !== void 0 && { factType: factTypeUpdate }
19560
19576
  });
19561
19577
  if (!updated) {
19562
- await assertMergeTargetGoneOrThrow(ctx, decision.targetId);
19578
+ await assertMergeTargetGoneOrThrow(ctx, decision.targetId, expectedContent);
19563
19579
  return abandonToRace(
19564
19580
  options,
19565
- `update: target ${decision.targetId} deleted mid-write (consolidated rewrite lost)`
19581
+ `update: target ${decision.targetId} changed mid-write (consolidated rewrite lost)`
19566
19582
  );
19567
19583
  }
19568
19584
  cacheRowVector(
@@ -19589,9 +19605,9 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
19589
19605
  );
19590
19606
  return null;
19591
19607
  }
19592
- async function assertMergeTargetGoneOrThrow(ctx, targetId) {
19608
+ async function assertMergeTargetGoneOrThrow(ctx, targetId, expectedContent) {
19593
19609
  const stillExists = await (ctx.persistence.getFresh ? ctx.persistence.getFresh(targetId) : ctx.persistence.get(targetId));
19594
- if (stillExists && (stillExists.kind === null || stillExists.kind === void 0)) {
19610
+ if (stillExists && (stillExists.kind === null || stillExists.kind === void 0) && (expectedContent === void 0 || stillExists.content === expectedContent)) {
19595
19611
  throw new RetainError(
19596
19612
  "merge-not-persisted",
19597
19613
  `retain: merge into memory ${targetId} failed to persist`
@@ -23004,16 +23020,16 @@ function createRemoteMemoryPipeline(options) {
23004
23020
  create: (input) => write(createRow(input), 0),
23005
23021
  update: async (id2, patch) => {
23006
23022
  const existing = await get(id2);
23007
- if (!existing || patch.freeFormOnly && existing.kind !== null && existing.kind !== void 0)
23023
+ if (!existing || patch.expectedContent !== void 0 && existing.content !== patch.expectedContent || patch.freeFormOnly && existing.kind !== null && existing.kind !== void 0)
23008
23024
  return null;
23009
23025
  const snapshot = snapshots.get(id2);
23010
23026
  const memory = {
23011
23027
  ...snapshot.memory,
23012
- content: patch.content,
23028
+ content: patch.content ?? snapshot.memory.content,
23013
23029
  updated_at: patch.preserveUpdatedAt ? snapshot.memory.updated_at : Math.max(Date.now(), (snapshot.memory.topics_extracted_at ?? 0) + 1)
23014
23030
  };
23015
23031
  if (patch.scope !== void 0) memory.scope = patch.scope;
23016
- if (patch.embedding === void 0 && patch.content !== snapshot.memory.content) {
23032
+ if (patch.embedding === void 0 && patch.content !== void 0 && patch.content !== snapshot.memory.content) {
23017
23033
  delete memory.embedding;
23018
23034
  delete memory.embedding_model;
23019
23035
  }
@@ -36750,20 +36766,27 @@ function useSubscription(options = {}) {
36750
36766
  }
36751
36767
 
36752
36768
  // src/react/useTools.ts
36753
- import { useCallback as useCallback18, useEffect as useEffect12, useRef as useRef9, useState as useState16 } from "react";
36769
+ import { useCallback as useCallback18, useEffect as useEffect12, useMemo as useMemo10, useRef as useRef9, useState as useState16 } from "react";
36754
36770
  function useTools(options) {
36755
36771
  const { getToken, baseUrl = BASE_URL, includeTools, autoFetch = true } = options;
36756
- const [tools, setTools] = useState16([]);
36772
+ const [allTools, setAllTools] = useState16([]);
36757
36773
  const [checksum, setChecksum] = useState16(getToolsChecksum2);
36758
36774
  const [isLoading, setIsLoading] = useState16(false);
36759
36775
  const [error, setError] = useState16(null);
36776
+ const includeToolsRef = useRef9(includeTools);
36777
+ if (includeTools !== includeToolsRef.current && (includeTools === void 0 || includeToolsRef.current === void 0 || includeTools.length !== includeToolsRef.current.length || includeTools.some((name, i) => name !== includeToolsRef.current?.[i]))) {
36778
+ includeToolsRef.current = includeTools;
36779
+ }
36780
+ const stableIncludeTools = includeToolsRef.current;
36781
+ const tools = useMemo10(
36782
+ () => filterServerTools(allTools, stableIncludeTools),
36783
+ [allTools, stableIncludeTools]
36784
+ );
36760
36785
  const getTokenRef = useRef9(getToken);
36761
36786
  const baseUrlRef = useRef9(baseUrl);
36762
- const includeToolsRef = useRef9(includeTools);
36763
36787
  useEffect12(() => {
36764
36788
  getTokenRef.current = getToken;
36765
36789
  baseUrlRef.current = baseUrl;
36766
- includeToolsRef.current = includeTools;
36767
36790
  });
36768
36791
  const fetchTools = useCallback18(async (forceRefresh = false) => {
36769
36792
  setIsLoading(true);
@@ -36774,8 +36797,7 @@ function useTools(options) {
36774
36797
  baseUrl: baseUrlRef.current,
36775
36798
  forceRefresh
36776
36799
  });
36777
- const filteredTools = filterServerTools(fetchedTools, includeToolsRef.current);
36778
- setTools(filteredTools);
36800
+ setAllTools(fetchedTools);
36779
36801
  setChecksum(getToolsChecksum2());
36780
36802
  } catch (err) {
36781
36803
  setError(err instanceof Error ? err : new Error(String(err)));
@@ -36799,11 +36821,6 @@ function useTools(options) {
36799
36821
  },
36800
36822
  [refresh]
36801
36823
  );
36802
- useEffect12(() => {
36803
- if (tools.length > 0 && includeTools !== void 0) {
36804
- setTools((currentTools) => filterServerTools(currentTools, includeTools));
36805
- }
36806
- }, [includeTools, tools.length]);
36807
36824
  const hasFetchedRef = useRef9(false);
36808
36825
  useEffect12(() => {
36809
36826
  if (autoFetch && !hasFetchedRef.current) {
@@ -36830,7 +36847,7 @@ import {
36830
36847
  createElement as createElement4,
36831
36848
  useCallback as useCallback19,
36832
36849
  useContext as useContext5,
36833
- useMemo as useMemo10,
36850
+ useMemo as useMemo11,
36834
36851
  useRef as useRef10,
36835
36852
  useState as useState17
36836
36853
  } from "react";
@@ -36967,7 +36984,7 @@ function UIInteractionProvider({
36967
36984
  },
36968
36985
  [pendingInteractions]
36969
36986
  );
36970
- const contextValue = useMemo10(
36987
+ const contextValue = useMemo11(
36971
36988
  () => ({
36972
36989
  pendingInteractions,
36973
36990
  createInteraction,
@@ -36991,7 +37008,7 @@ function UIInteractionProvider({
36991
37008
  }
36992
37009
 
36993
37010
  // src/react/useVoice.ts
36994
- import { useCallback as useCallback20, useMemo as useMemo11, useRef as useRef11, useState as useState18 } from "react";
37011
+ import { useCallback as useCallback20, useMemo as useMemo12, useRef as useRef11, useState as useState18 } from "react";
36995
37012
  function getOnDeviceSpeechRecognition() {
36996
37013
  if (typeof window === "undefined") return null;
36997
37014
  if (!/iPhone|iPad|iPod/.test(navigator.userAgent)) return null;
@@ -37195,7 +37212,7 @@ function useVoice(options) {
37195
37212
  },
37196
37213
  [recording, loadPipeline, options?.language]
37197
37214
  );
37198
- const nativeSpeechAvailable = useMemo11(() => getOnDeviceSpeechRecognition() !== null, []);
37215
+ const nativeSpeechAvailable = useMemo12(() => getOnDeviceSpeechRecognition() !== null, []);
37199
37216
  const [isNativeListening, setIsNativeListening] = useState18(false);
37200
37217
  const nativeRecognitionRef = useRef11(null);
37201
37218
  const nativeTextRef = useRef11("");
@@ -710,6 +710,12 @@ function assertTransportMatchesEndpoint(transport, endpoint) {
710
710
  `endpointOverride "${endpoint}" is the other transport's endpoint, but transport is "${transport}". The request body is built for the transport, so this sends the wrong shape and 400s without retry.`
711
711
  );
712
712
  }
713
+ var TIME_BUDGET_EXHAUSTED = {
714
+ kind: "terminal",
715
+ code: "time-budget-exhausted",
716
+ reason: "time budget exhausted"
717
+ };
718
+ var MAX_TIMER_DELAY_MS = 2147483647;
713
719
  var RETRYABLE_HTTP = /* @__PURE__ */ new Set([408, 409, 425, 429]);
714
720
  function isRetryableStatus(status) {
715
721
  return status >= 500 || RETRYABLE_HTTP.has(status);
@@ -765,7 +771,8 @@ async function callPortalJsonCompletion(req) {
765
771
  }
766
772
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
767
773
  const startedAt = Date.now();
768
- const overBudget = () => req.totalTimeoutMs !== void 0 && Date.now() - startedAt >= req.totalTimeoutMs;
774
+ const deadlineAt = req.totalTimeoutMs === void 0 ? void 0 : startedAt + req.totalTimeoutMs;
775
+ const overBudget = () => deadlineAt !== void 0 && Date.now() >= deadlineAt;
769
776
  let lastFailure;
770
777
  let reinforce = false;
771
778
  const giveUp = () => {
@@ -778,17 +785,11 @@ async function callPortalJsonCompletion(req) {
778
785
  lastFailure = { reason: "time-budget-exhausted", attempts: attempt - 1 };
779
786
  break;
780
787
  }
781
- const attemptReq = req.totalTimeoutMs !== void 0 ? {
782
- ...req,
783
- timeoutMs: Math.max(
784
- 1,
785
- Math.min(req.timeoutMs ?? 6e4, req.totalTimeoutMs - (Date.now() - startedAt))
786
- ),
787
- ...reinforce && { reinforceJsonContract: true }
788
- } : reinforce ? { ...req, reinforceJsonContract: true } : req;
788
+ const attemptReq = reinforce ? { ...req, reinforceJsonContract: true } : req;
789
789
  const outcome = await attemptPortalJson(
790
790
  { ...attemptReq, transport },
791
- endpoint
791
+ endpoint,
792
+ deadlineAt
792
793
  );
793
794
  if (outcome.kind === "ok") {
794
795
  req.onAttempt?.({ attempt, ok: true });
@@ -828,13 +829,38 @@ async function callPortalJsonCompletion(req) {
828
829
  }
829
830
  return giveUp();
830
831
  }
831
- async function attemptPortalJson(req, endpoint) {
832
+ async function attemptPortalJson(req, endpoint, deadlineAt) {
833
+ const controller = new AbortController();
834
+ if (deadlineAt === void 0 || deadlineAt - Date.now() > MAX_TIMER_DELAY_MS) {
835
+ return requestPortalJson(req, endpoint, controller);
836
+ }
837
+ const remainingMs = deadlineAt - Date.now();
838
+ if (remainingMs <= 0) return TIME_BUDGET_EXHAUSTED;
839
+ let timer;
840
+ try {
841
+ const outcome = await Promise.race([
842
+ new Promise((resolve) => {
843
+ timer = setTimeout(() => {
844
+ resolve(TIME_BUDGET_EXHAUSTED);
845
+ controller.abort();
846
+ }, remainingMs);
847
+ }),
848
+ requestPortalJson(req, endpoint, controller, deadlineAt)
849
+ ]);
850
+ return Date.now() >= deadlineAt ? TIME_BUDGET_EXHAUSTED : outcome;
851
+ } finally {
852
+ clearTimeout(timer);
853
+ }
854
+ }
855
+ async function requestPortalJson(req, endpoint, controller, deadlineAt) {
832
856
  const baseUrl = req.baseUrl ?? defaultBaseUrl();
833
857
  const fetchImpl = req.fetchFn ?? fetch;
834
- const timeoutMs = req.timeoutMs ?? 6e4;
835
858
  const authHeaders = await resolvePortalAuthHeaders(req, req.tag);
859
+ const remainingMs = deadlineAt === void 0 ? Infinity : deadlineAt - Date.now();
860
+ if (controller.signal.aborted || remainingMs <= 0) return TIME_BUDGET_EXHAUSTED;
836
861
  if (authHeaders === null)
837
862
  return { kind: "terminal", code: "auth-unavailable", reason: "auth unavailable (no token)" };
863
+ const timeoutMs = Math.min(req.timeoutMs ?? 6e4, remainingMs);
838
864
  const usesPrefill = req.transport !== "responses" && req.model.startsWith("anthropic/");
839
865
  const prefill = usesPrefill ? "{" : "";
840
866
  const messages = [
@@ -844,7 +870,6 @@ async function attemptPortalJson(req, endpoint) {
844
870
  ];
845
871
  if (prefill) messages.push({ role: "assistant", content: prefill });
846
872
  const supportsJsonObjectFormat = supportsResponseFormat(req.model);
847
- const controller = new AbortController();
848
873
  const timer = setTimeout(() => controller.abort(), timeoutMs);
849
874
  const requestBody = req.transport === "responses" ? {
850
875
  model: req.model,
@@ -4,7 +4,7 @@ import {
4
4
  notifyConsolidationFallback,
5
5
  requiresResponsesTransport,
6
6
  resolvePiiRedactor
7
- } from "./chunk-AU3Z7NBC.mjs";
7
+ } from "./chunk-QEROZ4VH.mjs";
8
8
  import "./chunk-3RPGXXPZ.mjs";
9
9
 
10
10
  // src/lib/memory/consolidate.ts
@@ -15742,7 +15742,8 @@ async function callPortalJsonCompletion(req) {
15742
15742
  }
15743
15743
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
15744
15744
  const startedAt = Date.now();
15745
- const overBudget = () => req.totalTimeoutMs !== void 0 && Date.now() - startedAt >= req.totalTimeoutMs;
15745
+ const deadlineAt = req.totalTimeoutMs === void 0 ? void 0 : startedAt + req.totalTimeoutMs;
15746
+ const overBudget = () => deadlineAt !== void 0 && Date.now() >= deadlineAt;
15746
15747
  let lastFailure;
15747
15748
  let reinforce = false;
15748
15749
  const giveUp = () => {
@@ -15755,17 +15756,11 @@ async function callPortalJsonCompletion(req) {
15755
15756
  lastFailure = { reason: "time-budget-exhausted", attempts: attempt - 1 };
15756
15757
  break;
15757
15758
  }
15758
- const attemptReq = req.totalTimeoutMs !== void 0 ? {
15759
- ...req,
15760
- timeoutMs: Math.max(
15761
- 1,
15762
- Math.min(req.timeoutMs ?? 6e4, req.totalTimeoutMs - (Date.now() - startedAt))
15763
- ),
15764
- ...reinforce && { reinforceJsonContract: true }
15765
- } : reinforce ? { ...req, reinforceJsonContract: true } : req;
15759
+ const attemptReq = reinforce ? { ...req, reinforceJsonContract: true } : req;
15766
15760
  const outcome = await attemptPortalJson(
15767
15761
  { ...attemptReq, transport },
15768
- endpoint
15762
+ endpoint,
15763
+ deadlineAt
15769
15764
  );
15770
15765
  if (outcome.kind === "ok") {
15771
15766
  req.onAttempt?.({ attempt, ok: true });
@@ -15805,13 +15800,38 @@ async function callPortalJsonCompletion(req) {
15805
15800
  }
15806
15801
  return giveUp();
15807
15802
  }
15808
- async function attemptPortalJson(req, endpoint) {
15803
+ async function attemptPortalJson(req, endpoint, deadlineAt) {
15804
+ const controller = new AbortController();
15805
+ if (deadlineAt === void 0 || deadlineAt - Date.now() > MAX_TIMER_DELAY_MS) {
15806
+ return requestPortalJson(req, endpoint, controller);
15807
+ }
15808
+ const remainingMs = deadlineAt - Date.now();
15809
+ if (remainingMs <= 0) return TIME_BUDGET_EXHAUSTED;
15810
+ let timer;
15811
+ try {
15812
+ const outcome = await Promise.race([
15813
+ new Promise((resolve) => {
15814
+ timer = setTimeout(() => {
15815
+ resolve(TIME_BUDGET_EXHAUSTED);
15816
+ controller.abort();
15817
+ }, remainingMs);
15818
+ }),
15819
+ requestPortalJson(req, endpoint, controller, deadlineAt)
15820
+ ]);
15821
+ return Date.now() >= deadlineAt ? TIME_BUDGET_EXHAUSTED : outcome;
15822
+ } finally {
15823
+ clearTimeout(timer);
15824
+ }
15825
+ }
15826
+ async function requestPortalJson(req, endpoint, controller, deadlineAt) {
15809
15827
  const baseUrl = req.baseUrl ?? defaultBaseUrl();
15810
15828
  const fetchImpl = req.fetchFn ?? fetch;
15811
- const timeoutMs = req.timeoutMs ?? 6e4;
15812
15829
  const authHeaders = await resolvePortalAuthHeaders(req, req.tag);
15830
+ const remainingMs = deadlineAt === void 0 ? Infinity : deadlineAt - Date.now();
15831
+ if (controller.signal.aborted || remainingMs <= 0) return TIME_BUDGET_EXHAUSTED;
15813
15832
  if (authHeaders === null)
15814
15833
  return { kind: "terminal", code: "auth-unavailable", reason: "auth unavailable (no token)" };
15834
+ const timeoutMs = Math.min(req.timeoutMs ?? 6e4, remainingMs);
15815
15835
  const usesPrefill = req.transport !== "responses" && req.model.startsWith("anthropic/");
15816
15836
  const prefill = usesPrefill ? "{" : "";
15817
15837
  const messages = [
@@ -15821,7 +15841,6 @@ async function attemptPortalJson(req, endpoint) {
15821
15841
  ];
15822
15842
  if (prefill) messages.push({ role: "assistant", content: prefill });
15823
15843
  const supportsJsonObjectFormat = supportsResponseFormat(req.model);
15824
- const controller = new AbortController();
15825
15844
  const timer = setTimeout(() => controller.abort(), timeoutMs);
15826
15845
  const requestBody = req.transport === "responses" ? {
15827
15846
  model: req.model,
@@ -16006,7 +16025,7 @@ function extractResponsesContent(body) {
16006
16025
  const text12 = root.output.filter((item) => asRecord(item)?.type === "message").flatMap((item) => Array.isArray(item.content) ? item.content : []).map((part) => asRecord(part)?.text).filter((t) => typeof t === "string").join("");
16007
16026
  return text12;
16008
16027
  }
16009
- var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, JSON_CONTRACT_REMINDER, RESPONSES_ONLY_MODEL_PREFIXES, CHAT_TO_RESPONSES_FIELDS, FOREIGN_ENDPOINT_SUFFIX, RETRYABLE_HTTP;
16028
+ var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, JSON_CONTRACT_REMINDER, RESPONSES_ONLY_MODEL_PREFIXES, CHAT_TO_RESPONSES_FIELDS, FOREIGN_ENDPOINT_SUFFIX, TIME_BUDGET_EXHAUSTED, MAX_TIMER_DELAY_MS, RETRYABLE_HTTP;
16010
16029
  var init_portalLlm = __esm({
16011
16030
  "src/lib/memory/portalLlm.ts"() {
16012
16031
  "use strict";
@@ -16026,6 +16045,12 @@ var init_portalLlm = __esm({
16026
16045
  chat: "/responses",
16027
16046
  responses: "/chat/completions"
16028
16047
  };
16048
+ TIME_BUDGET_EXHAUSTED = {
16049
+ kind: "terminal",
16050
+ code: "time-budget-exhausted",
16051
+ reason: "time budget exhausted"
16052
+ };
16053
+ MAX_TIMER_DELAY_MS = 2147483647;
16029
16054
  RETRYABLE_HTTP = /* @__PURE__ */ new Set([408, 409, 425, 429]);
16030
16055
  }
16031
16056
  });
@@ -29072,6 +29097,16 @@ async function updateVaultMemoryOp(ctx, id, opts) {
29072
29097
  const probe = await ctx.vaultMemoryCollection.find(id);
29073
29098
  if (probe.isDeleted || probe.supersededBy || !isOwnedByCtxUser(ctx, probe)) return null;
29074
29099
  if (opts.freeFormOnly && isKinded(probe)) return null;
29100
+ const readStoredContent = probe._getRaw("content");
29101
+ if (opts.expectedContent !== void 0) {
29102
+ const current = await vaultMemoryToStored(
29103
+ probe,
29104
+ ctx.walletAddress,
29105
+ ctx.signMessage,
29106
+ ctx.embeddedWalletSigner
29107
+ );
29108
+ if (current.content !== opts.expectedContent) return null;
29109
+ }
29075
29110
  const levelAfter = (current) => {
29076
29111
  if (opts.level !== void 0) return opts.level;
29077
29112
  if (opts.scope === void 0) return resolveMemoryLevel(current.level, current.scope);
@@ -29079,7 +29114,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
29079
29114
  };
29080
29115
  assertValidKindLevel(opts.kind !== void 0 ? opts.kind : probe.kind, levelAfter(probe));
29081
29116
  const encryptedKindValue = await encryptKindValue(ctx, opts.kindValue);
29082
- const encryptedContent = ctx.walletAddress && ctx.signMessage ? await encryptVaultMemoryContent(
29117
+ const encryptedContent = opts.content !== void 0 && ctx.walletAddress && ctx.signMessage ? await encryptVaultMemoryContent(
29083
29118
  opts.content,
29084
29119
  ctx.walletAddress,
29085
29120
  ctx.signMessage,
@@ -29087,13 +29122,13 @@ async function updateVaultMemoryOp(ctx, id, opts) {
29087
29122
  ) : opts.content;
29088
29123
  let stale = false;
29089
29124
  const record = probe;
29090
- const originalUpdatedAt = record.updatedAt.getTime();
29091
29125
  await ctx.database.write(async () => {
29092
29126
  await assertCanWrite2(ctx);
29093
- if (record.isDeleted || record.supersededBy || !isOwnedByCtxUser(ctx, record) || opts.freeFormOnly && isKinded(record)) {
29127
+ if (record.isDeleted || record.supersededBy || !isOwnedByCtxUser(ctx, record) || opts.freeFormOnly && isKinded(record) || opts.expectedContent !== void 0 && record._getRaw("content") !== readStoredContent) {
29094
29128
  stale = true;
29095
29129
  return;
29096
29130
  }
29131
+ const originalUpdatedAt = record.updatedAt.getTime();
29097
29132
  const nextKind = opts.kind !== void 0 ? opts.kind : record.kind;
29098
29133
  const nextLevel = levelAfter(record);
29099
29134
  assertValidKindLevel(nextKind, nextLevel);
@@ -29109,7 +29144,7 @@ async function updateVaultMemoryOp(ctx, id, opts) {
29109
29144
  replayedObservation = opts.observationSourceIds.every((id2) => observedSources.includes(id2));
29110
29145
  }
29111
29146
  await record.update((r) => {
29112
- r._setRaw("content", encryptedContent);
29147
+ if (encryptedContent !== void 0) r._setRaw("content", encryptedContent);
29113
29148
  if (opts.level !== void 0) {
29114
29149
  r._setRaw("level", nextLevel);
29115
29150
  r._setRaw("scope", scopeForLevel(nextLevel));
@@ -29877,7 +29912,7 @@ function classifyDecay(m, now, policy) {
29877
29912
  if (m.source === "manual" || m.source === SOURCE_PHOTO) return "keep";
29878
29913
  if (isTimeSensitiveType(m.factType) && m.eventTimeEnd !== null) {
29879
29914
  if (m.eventTimeEnd < now - resolved.pastEventGraceMs) return "archive";
29880
- if (m.eventTimeEnd >= now) return "keep";
29915
+ return "keep";
29881
29916
  }
29882
29917
  if (now - lastActivityAt(m) > ttlForType(m.factType, resolved)) return "archive";
29883
29918
  return "keep";
@@ -33098,7 +33133,6 @@ async function retainWithPersistence(content, ctx, options = {}) {
33098
33133
  const factTypeUpdate = pickFactTypeUpdate(existing, options.factType);
33099
33134
  const resurrect = resurrectFields(existing);
33100
33135
  const updated = await ctx.persistence.update(targetId, {
33101
- content: existing.content,
33102
33136
  freeFormOnly: true,
33103
33137
  proofCountIncrement: 1,
33104
33138
  observationSourceIds: options.sourceChunkIds,
@@ -33337,7 +33371,6 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
33337
33371
  const factTypeUpdate = pickFactTypeUpdate(existing, options.factType);
33338
33372
  const resurrect = resurrectFields(existing);
33339
33373
  const updated = await ctx.persistence.update(decision.targetId, {
33340
- content: existing.content,
33341
33374
  freeFormOnly: true,
33342
33375
  proofCountIncrement: 1,
33343
33376
  observationSourceIds: options.sourceChunkIds,
@@ -33365,6 +33398,10 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
33365
33398
  };
33366
33399
  }
33367
33400
  if (decision.action === "update" && decision.targetId && decision.content) {
33401
+ const expectedContent = candidates.find(
33402
+ (candidate) => candidate.id === decision.targetId
33403
+ )?.content;
33404
+ if (expectedContent === void 0) return null;
33368
33405
  const existing = await ctx.persistence.get(decision.targetId);
33369
33406
  if (!existing || existing.supersededBy) {
33370
33407
  return abandonToRace(
@@ -33372,6 +33409,9 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
33372
33409
  `update: target ${decision.targetId} ${existing ? "superseded" : "deleted"} before the write`
33373
33410
  );
33374
33411
  }
33412
+ if (existing.content !== expectedContent) {
33413
+ return abandonToRace(options, `update: target ${decision.targetId} edited before the write`);
33414
+ }
33375
33415
  const mergedSourceIds = unionStrings(
33376
33416
  existing.sourceChunkIds ?? [],
33377
33417
  options.sourceChunkIds ?? []
@@ -33384,6 +33424,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
33384
33424
  const resurrect = resurrectFields(existing);
33385
33425
  const updated = await ctx.persistence.update(decision.targetId, {
33386
33426
  content: decision.content,
33427
+ expectedContent,
33387
33428
  freeFormOnly: true,
33388
33429
  proofCountIncrement: 1,
33389
33430
  observationSourceIds: options.sourceChunkIds,
@@ -33396,10 +33437,10 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
33396
33437
  ...factTypeUpdate !== void 0 && { factType: factTypeUpdate }
33397
33438
  });
33398
33439
  if (!updated) {
33399
- await assertMergeTargetGoneOrThrow(ctx, decision.targetId);
33440
+ await assertMergeTargetGoneOrThrow(ctx, decision.targetId, expectedContent);
33400
33441
  return abandonToRace(
33401
33442
  options,
33402
- `update: target ${decision.targetId} deleted mid-write (consolidated rewrite lost)`
33443
+ `update: target ${decision.targetId} changed mid-write (consolidated rewrite lost)`
33403
33444
  );
33404
33445
  }
33405
33446
  cacheRowVector(
@@ -33426,9 +33467,9 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
33426
33467
  );
33427
33468
  return null;
33428
33469
  }
33429
- async function assertMergeTargetGoneOrThrow(ctx, targetId) {
33470
+ async function assertMergeTargetGoneOrThrow(ctx, targetId, expectedContent) {
33430
33471
  const stillExists = await (ctx.persistence.getFresh ? ctx.persistence.getFresh(targetId) : ctx.persistence.get(targetId));
33431
- if (stillExists && (stillExists.kind === null || stillExists.kind === void 0)) {
33472
+ if (stillExists && (stillExists.kind === null || stillExists.kind === void 0) && (expectedContent === void 0 || stillExists.content === expectedContent)) {
33432
33473
  throw new RetainError(
33433
33474
  "merge-not-persisted",
33434
33475
  `retain: merge into memory ${targetId} failed to persist`
@@ -5287,7 +5287,8 @@ interface CreateVaultMemoryOptions {
5287
5287
  geohash?: string;
5288
5288
  }
5289
5289
  interface UpdateVaultMemoryOptions {
5290
- content: string;
5290
+ content?: string;
5291
+ expectedContent?: string;
5291
5292
  /** When true, the write is skipped (returns null) if the row carries a profile kind. */
5292
5293
  freeFormOnly?: boolean;
5293
5294
  /**
@@ -5849,9 +5850,10 @@ declare function ttlForType(factType: string | null, policy?: Partial<DecayPolic
5849
5850
  * null `eventTimeEnd` is still ongoing, so this rule does not fire for it.
5850
5851
  * 4. Age fallback — `now - updatedAt > TTL(factType)` → archive. Durable types
5851
5852
  * have Infinity TTL, so this never fires for them; null/unknown use medium.
5852
- * EXCEPTION: a `plan`/`ongoing_context` with a still-upcoming event
5853
- * (`eventTimeEnd >= now`) is kept — it must not age-archive before the event
5854
- * happens; only rule 3 (after it passes) may archive it.
5853
+ * EXCEPTION: a `plan` / `ongoing_context` with an upcoming event or an event
5854
+ * within the grace period (`eventTimeEnd >= now - pastEventGraceMs`) stays
5855
+ * active, even after its activity TTL expires. Rule 3 archives it after the
5856
+ * grace period ends.
5855
5857
  * 5. Otherwise → keep.
5856
5858
  *
5857
5859
  * @param m Plaintext decay inputs for one row.
@@ -6182,7 +6184,9 @@ type MemoryCreate = Pick<CreateVaultMemoryOptions, "content" | "scope" | "factTy
6182
6184
  * `embedding` (with `embeddingModel`) only when you already have the new one.
6183
6185
  * @public
6184
6186
  */
6185
- type MemoryUpdate = Pick<UpdateVaultMemoryOptions, "content" | "scope" | "factType" | "eventTime" | "embedding" | "embeddingModel" | "kind" | "kindValue" | "level">;
6187
+ type MemoryUpdate = Pick<UpdateVaultMemoryOptions, "scope" | "factType" | "eventTime" | "embedding" | "embeddingModel" | "kind" | "kindValue" | "level"> & {
6188
+ content: string;
6189
+ };
6186
6190
  /**
6187
6191
  * Fact-only recall. Conversation chunks are message storage, not memory, so
6188
6192
  * the chunk-lane knobs are not part of this surface; nor is `folderId` (see
@@ -5287,7 +5287,8 @@ interface CreateVaultMemoryOptions {
5287
5287
  geohash?: string;
5288
5288
  }
5289
5289
  interface UpdateVaultMemoryOptions {
5290
- content: string;
5290
+ content?: string;
5291
+ expectedContent?: string;
5291
5292
  /** When true, the write is skipped (returns null) if the row carries a profile kind. */
5292
5293
  freeFormOnly?: boolean;
5293
5294
  /**
@@ -5849,9 +5850,10 @@ declare function ttlForType(factType: string | null, policy?: Partial<DecayPolic
5849
5850
  * null `eventTimeEnd` is still ongoing, so this rule does not fire for it.
5850
5851
  * 4. Age fallback — `now - updatedAt > TTL(factType)` → archive. Durable types
5851
5852
  * have Infinity TTL, so this never fires for them; null/unknown use medium.
5852
- * EXCEPTION: a `plan`/`ongoing_context` with a still-upcoming event
5853
- * (`eventTimeEnd >= now`) is kept — it must not age-archive before the event
5854
- * happens; only rule 3 (after it passes) may archive it.
5853
+ * EXCEPTION: a `plan` / `ongoing_context` with an upcoming event or an event
5854
+ * within the grace period (`eventTimeEnd >= now - pastEventGraceMs`) stays
5855
+ * active, even after its activity TTL expires. Rule 3 archives it after the
5856
+ * grace period ends.
5855
5857
  * 5. Otherwise → keep.
5856
5858
  *
5857
5859
  * @param m Plaintext decay inputs for one row.
@@ -6182,7 +6184,9 @@ type MemoryCreate = Pick<CreateVaultMemoryOptions, "content" | "scope" | "factTy
6182
6184
  * `embedding` (with `embeddingModel`) only when you already have the new one.
6183
6185
  * @public
6184
6186
  */
6185
- type MemoryUpdate = Pick<UpdateVaultMemoryOptions, "content" | "scope" | "factType" | "eventTime" | "embedding" | "embeddingModel" | "kind" | "kindValue" | "level">;
6187
+ type MemoryUpdate = Pick<UpdateVaultMemoryOptions, "scope" | "factType" | "eventTime" | "embedding" | "embeddingModel" | "kind" | "kindValue" | "level"> & {
6188
+ content: string;
6189
+ };
6186
6190
  /**
6187
6191
  * Fact-only recall. Conversation chunks are message storage, not memory, so
6188
6192
  * the chunk-lane knobs are not part of this surface; nor is `folderId` (see