agentfootprint 9.26.0 → 9.28.0

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.
Files changed (115) hide show
  1. package/dist/adapters/google/aiPlatform.js +438 -0
  2. package/dist/adapters/google/aiPlatform.js.map +1 -0
  3. package/dist/adapters/hosting/googleAgentEngine.js +372 -0
  4. package/dist/adapters/hosting/googleAgentEngine.js.map +1 -0
  5. package/dist/adapters/identity/google.js +275 -0
  6. package/dist/adapters/identity/google.js.map +1 -0
  7. package/dist/adapters/memory/agentcore.js +19 -0
  8. package/dist/adapters/memory/agentcore.js.map +1 -1
  9. package/dist/adapters/memory/memoryBank.js +823 -0
  10. package/dist/adapters/memory/memoryBank.js.map +1 -0
  11. package/dist/core/agent/stages/routeTurn.js +13 -1
  12. package/dist/core/agent/stages/routeTurn.js.map +1 -1
  13. package/dist/esm/adapters/google/aiPlatform.d.ts +453 -0
  14. package/dist/esm/adapters/google/aiPlatform.js +424 -0
  15. package/dist/esm/adapters/google/aiPlatform.js.map +1 -0
  16. package/dist/esm/adapters/hosting/googleAgentEngine.d.ts +156 -0
  17. package/dist/esm/adapters/hosting/googleAgentEngine.js +368 -0
  18. package/dist/esm/adapters/hosting/googleAgentEngine.js.map +1 -0
  19. package/dist/esm/adapters/identity/google.d.ts +179 -0
  20. package/dist/esm/adapters/identity/google.js +271 -0
  21. package/dist/esm/adapters/identity/google.js.map +1 -0
  22. package/dist/esm/adapters/memory/agentcore.d.ts +19 -0
  23. package/dist/esm/adapters/memory/agentcore.js +19 -0
  24. package/dist/esm/adapters/memory/agentcore.js.map +1 -1
  25. package/dist/esm/adapters/memory/memoryBank.d.ts +390 -0
  26. package/dist/esm/adapters/memory/memoryBank.js +817 -0
  27. package/dist/esm/adapters/memory/memoryBank.js.map +1 -0
  28. package/dist/esm/core/agent/stages/routeTurn.js +13 -1
  29. package/dist/esm/core/agent/stages/routeTurn.js.map +1 -1
  30. package/dist/esm/events/payloads.d.ts +23 -0
  31. package/dist/esm/hosting-providers.d.ts +7 -0
  32. package/dist/esm/hosting-providers.js +6 -0
  33. package/dist/esm/hosting-providers.js.map +1 -1
  34. package/dist/esm/identity.d.ts +1 -0
  35. package/dist/esm/identity.js +5 -0
  36. package/dist/esm/identity.js.map +1 -1
  37. package/dist/esm/lib/injection-engine/buildInjectionEngineSubflow.js +9 -0
  38. package/dist/esm/lib/injection-engine/buildInjectionEngineSubflow.js.map +1 -1
  39. package/dist/esm/lib/injection-engine/routingPolicy.d.ts +8 -0
  40. package/dist/esm/lib/injection-engine/routingPolicy.js.map +1 -1
  41. package/dist/esm/lib/injection-engine/skillGraph.d.ts +11 -2
  42. package/dist/esm/lib/injection-engine/skillGraph.js +25 -1
  43. package/dist/esm/lib/injection-engine/skillGraph.js.map +1 -1
  44. package/dist/esm/lib/injection-engine/skillIntent.d.ts +13 -5
  45. package/dist/esm/lib/injection-engine/skillIntent.js +12 -2
  46. package/dist/esm/lib/injection-engine/skillIntent.js.map +1 -1
  47. package/dist/esm/lib/injection-engine/skillMatch.d.ts +40 -0
  48. package/dist/esm/lib/injection-engine/skillMatch.js +60 -0
  49. package/dist/esm/lib/injection-engine/skillMatch.js.map +1 -1
  50. package/dist/esm/memory-providers.d.ts +1 -0
  51. package/dist/esm/memory-providers.js +7 -0
  52. package/dist/esm/memory-providers.js.map +1 -1
  53. package/dist/esm/recorders/observability/AgentThinkingTraceRecorder.d.ts +4 -0
  54. package/dist/esm/recorders/observability/AgentThinkingTraceRecorder.js +4 -1
  55. package/dist/esm/recorders/observability/AgentThinkingTraceRecorder.js.map +1 -1
  56. package/dist/esm/recorders/observability/commentary/artifactPhrases.d.ts +50 -0
  57. package/dist/esm/recorders/observability/commentary/artifactPhrases.js +88 -0
  58. package/dist/esm/recorders/observability/commentary/artifactPhrases.js.map +1 -0
  59. package/dist/esm/recorders/observability/commentary/commentaryTemplates.js +233 -9
  60. package/dist/esm/recorders/observability/commentary/commentaryTemplates.js.map +1 -1
  61. package/dist/hosting-providers.js +10 -1
  62. package/dist/hosting-providers.js.map +1 -1
  63. package/dist/identity.js +8 -1
  64. package/dist/identity.js.map +1 -1
  65. package/dist/lib/injection-engine/buildInjectionEngineSubflow.js +9 -0
  66. package/dist/lib/injection-engine/buildInjectionEngineSubflow.js.map +1 -1
  67. package/dist/lib/injection-engine/routingPolicy.js.map +1 -1
  68. package/dist/lib/injection-engine/skillGraph.js +25 -1
  69. package/dist/lib/injection-engine/skillGraph.js.map +1 -1
  70. package/dist/lib/injection-engine/skillIntent.js +12 -2
  71. package/dist/lib/injection-engine/skillIntent.js.map +1 -1
  72. package/dist/lib/injection-engine/skillMatch.js +61 -1
  73. package/dist/lib/injection-engine/skillMatch.js.map +1 -1
  74. package/dist/memory-providers.js +12 -1
  75. package/dist/memory-providers.js.map +1 -1
  76. package/dist/recorders/observability/AgentThinkingTraceRecorder.js +4 -1
  77. package/dist/recorders/observability/AgentThinkingTraceRecorder.js.map +1 -1
  78. package/dist/recorders/observability/commentary/artifactPhrases.js +94 -0
  79. package/dist/recorders/observability/commentary/artifactPhrases.js.map +1 -0
  80. package/dist/recorders/observability/commentary/commentaryTemplates.js +233 -9
  81. package/dist/recorders/observability/commentary/commentaryTemplates.js.map +1 -1
  82. package/dist/types/adapters/google/aiPlatform.d.ts +454 -0
  83. package/dist/types/adapters/google/aiPlatform.d.ts.map +1 -0
  84. package/dist/types/adapters/hosting/googleAgentEngine.d.ts +157 -0
  85. package/dist/types/adapters/hosting/googleAgentEngine.d.ts.map +1 -0
  86. package/dist/types/adapters/identity/google.d.ts +180 -0
  87. package/dist/types/adapters/identity/google.d.ts.map +1 -0
  88. package/dist/types/adapters/memory/agentcore.d.ts +19 -0
  89. package/dist/types/adapters/memory/agentcore.d.ts.map +1 -1
  90. package/dist/types/adapters/memory/memoryBank.d.ts +391 -0
  91. package/dist/types/adapters/memory/memoryBank.d.ts.map +1 -0
  92. package/dist/types/core/agent/stages/routeTurn.d.ts.map +1 -1
  93. package/dist/types/events/payloads.d.ts +23 -0
  94. package/dist/types/events/payloads.d.ts.map +1 -1
  95. package/dist/types/hosting-providers.d.ts +7 -0
  96. package/dist/types/hosting-providers.d.ts.map +1 -1
  97. package/dist/types/identity.d.ts +1 -0
  98. package/dist/types/identity.d.ts.map +1 -1
  99. package/dist/types/lib/injection-engine/buildInjectionEngineSubflow.d.ts.map +1 -1
  100. package/dist/types/lib/injection-engine/routingPolicy.d.ts +8 -0
  101. package/dist/types/lib/injection-engine/routingPolicy.d.ts.map +1 -1
  102. package/dist/types/lib/injection-engine/skillGraph.d.ts +11 -2
  103. package/dist/types/lib/injection-engine/skillGraph.d.ts.map +1 -1
  104. package/dist/types/lib/injection-engine/skillIntent.d.ts +13 -5
  105. package/dist/types/lib/injection-engine/skillIntent.d.ts.map +1 -1
  106. package/dist/types/lib/injection-engine/skillMatch.d.ts +40 -0
  107. package/dist/types/lib/injection-engine/skillMatch.d.ts.map +1 -1
  108. package/dist/types/memory-providers.d.ts +1 -0
  109. package/dist/types/memory-providers.d.ts.map +1 -1
  110. package/dist/types/recorders/observability/AgentThinkingTraceRecorder.d.ts +4 -0
  111. package/dist/types/recorders/observability/AgentThinkingTraceRecorder.d.ts.map +1 -1
  112. package/dist/types/recorders/observability/commentary/artifactPhrases.d.ts +51 -0
  113. package/dist/types/recorders/observability/commentary/artifactPhrases.d.ts.map +1 -0
  114. package/dist/types/recorders/observability/commentary/commentaryTemplates.d.ts.map +1 -1
  115. package/package.json +24 -15
@@ -0,0 +1,823 @@
1
+ "use strict";
2
+ /**
3
+ * memoryBankStore — the `MemoryStore` port over Vertex AI **Memory Bank**.
4
+ *
5
+ * import { memoryBankStore } from 'agentfootprint/memory';
6
+ *
7
+ * const store = memoryBankStore({
8
+ * project: 'my-project',
9
+ * location: 'us-central1',
10
+ * reasoningEngine: '1234567890',
11
+ * });
12
+ *
13
+ * ── Read this before you write anything into it ─────────────────────────────
14
+ * Memory Bank is a **natural-language** memory service, not a key-value store
15
+ * and not a vector database. A `Memory` is a `fact` string plus an immutable
16
+ * `scope`; retrieval takes a QUESTION IN WORDS, embeds it on Google's side and
17
+ * ranks there. Three consequences, each of which has a silent-failure mode
18
+ * that this adapter turns into something you can see:
19
+ *
20
+ * 1. **It never ranks the vectors you wrote.** `supportsVectorSearch` is
21
+ * `false` and `ranksBy` is `'server-text'`, so the corpus builders
22
+ * (`indexCorpus`, `indexFolder`, `indexDocuments`) refuse this store by
23
+ * name instead of embedding a whole corpus, reporting success, and leaving
24
+ * it unreachable forever. An `embedding` on an entry handed to `put()` is
25
+ * **not stored** — there is nowhere to put it and nothing that would read
26
+ * it — and the two declarations above are how this adapter says so before
27
+ * you spend anything.
28
+ *
29
+ * 2. **The retrieval score is a DISTANCE, and smaller is closer.** The port's
30
+ * `ScoredEntry.score` is a cosine similarity, where HIGHER is closer.
31
+ * Passed through unconverted, `search()` would return the LEAST relevant
32
+ * memories first with a confident-looking number in the right range —
33
+ * which no threshold and no eyeball can separate from a working search.
34
+ * See {@link MemoryBankStore.search} for exactly what this adapter does
35
+ * instead, and why it refuses `minScore` rather than reinterpreting it.
36
+ *
37
+ * 3. **`scope` is an exact match and immutable once written.** A retrieval
38
+ * whose scope is a subset of a memory's scope returns NOTHING — not a
39
+ * superset, not a partial match, nothing. So the scope convention has to
40
+ * be right before the first write, because it cannot be changed after it
41
+ * and a bank written under the wrong one is poisoned permanently. See
42
+ * {@link MemoryBankStoreOptions.scopeFor}.
43
+ *
44
+ * ── How a memory is ADDRESSED, and why it is not just the entry id ──────────
45
+ * A memory's resource name is `<engine>/memories/<resource id>`, and that
46
+ * resource id is composed from **the resolved scope and the entry id
47
+ * together** — never the entry id alone.
48
+ *
49
+ * The reason is that entry ids in this library are deliberately deterministic
50
+ * and identity-free: `msg-<turn>-<index>`, `fact:<key>`, `snap-<turn>`. Two
51
+ * people talking to the same agent produce the SAME entry ids, so an address
52
+ * built from the id alone is one row for both of them — and since a resource
53
+ * name addresses a row directly, the second writer's `fact` lands on the first
54
+ * writer's row while the immutable `scope` stays the first writer's. What comes
55
+ * back is one tenant reading another tenant's private fact, one tenant's own
56
+ * write invisible to them, and `forget()` finding nothing to erase. Every
57
+ * sibling store in this library namespaces by identity (`s3Vectors` keys on
58
+ * `<namespace>#<id>`, `pgVector` on `(namespace, id)`, `InMemoryStore` on a map
59
+ * per namespace); this one does the same thing, keyed on the SCOPE rather than
60
+ * the raw identity so that a widened `scopeFor` widens sharing exactly as much
61
+ * as it widens retrieval, and not one row more.
62
+ *
63
+ * The composed address is a partition, not the boundary itself. The boundary is
64
+ * the stored `scope`, which is re-checked on the way back from every read AND
65
+ * before every overwrite — so even an address collision is refused rather than
66
+ * written through.
67
+ *
68
+ * ── Writes are long-running operations ──────────────────────────────────────
69
+ * `create`, `patch` and `delete` all answer with an Operation rather than the
70
+ * resource — verified against the installed SDK's own return types. Every
71
+ * write here waits for `done` before returning, because a `put` that came back
72
+ * early followed by a `get` is a race whose failure mode is "no data", and
73
+ * nobody can tell that from a memory that was never written.
74
+ *
75
+ * ── What has no primitive here, and is therefore refused ────────────────────
76
+ * `putIfVersion`, `seen`, `recordSignature`, `feedback` and `getFeedback` have
77
+ * no counterpart in this service: a `Memory` carries no etag and there is no
78
+ * dedup or feedback surface. The sibling AgentCore adapter emulates them
79
+ * in-process; this one refuses them by name, and the difference is deliberate.
80
+ * A store you reach for BECAUSE it is shared across a fleet is the worst place
81
+ * for a per-process shadow: `seen()` would answer `false` in the second
82
+ * container for a signature the first one recorded, and an emulated
83
+ * `putIfVersion` across two writers is a lost-update generator that reports
84
+ * `{ applied: true }` to both. A refusal you read once beats a correctness bug
85
+ * you never find.
86
+ *
87
+ * Pattern: Adapter (GoF) — `MemoryStore` onto `reasoningEngines.memories`,
88
+ * through the shared REST client in `adapters/google/aiPlatform.ts`.
89
+ */
90
+ Object.defineProperty(exports, "__esModule", { value: true });
91
+ exports.scoreFromDistance = exports.memoryBankStore = exports.MemoryBankStore = exports.MAX_PAGE_SIZE = void 0;
92
+ const aiPlatform_js_1 = require("../google/aiPlatform.js");
93
+ const ADAPTER = 'memoryBankStore';
94
+ /**
95
+ * The metadata keys this adapter owns on a `Memory`.
96
+ *
97
+ * Prefixed, because the metadata map belongs to whoever owns the bank and this
98
+ * library is a guest in it: an unprefixed `id` or `version` collides with the
99
+ * next writer, and Memory Bank's metadata filters are exact-key matches, so a
100
+ * collision is not a merge — it is one party's filter quietly matching the
101
+ * other party's rows.
102
+ */
103
+ const META = {
104
+ id: 'agentfootprint_id',
105
+ version: 'agentfootprint_version',
106
+ createdAt: 'agentfootprint_created_at',
107
+ updatedAt: 'agentfootprint_updated_at',
108
+ tier: 'agentfootprint_tier',
109
+ json: 'agentfootprint_json',
110
+ };
111
+ /** The service's own ceiling on a page or a top-k. Larger values are coerced. */
112
+ exports.MAX_PAGE_SIZE = 100;
113
+ /** What a `list()` page carries when nothing was asked for. */
114
+ const DEFAULT_PAGE_SIZE = 20;
115
+ /**
116
+ * A `MemoryStore` over Vertex AI Memory Bank.
117
+ *
118
+ * **Status: contract-shaped and tested; awaiting field use.** Every call is
119
+ * exercised through an injected client and pinned against the really-installed
120
+ * SDK. None of it has yet answered a request from Google in a real project.
121
+ */
122
+ class MemoryBankStore {
123
+ /**
124
+ * **No.** `search()` exists here, but it is Memory Bank's own retrieval:
125
+ * Google embeds and ranks on its side, over the `fact` strings this store
126
+ * wrote, and never over an `embedding` handed to `put()`. Embeddings are not
127
+ * stored at all.
128
+ *
129
+ * Declared because a method's presence could not say that — and because the
130
+ * sibling column already paid for the lesson once, with a corpus that
131
+ * indexed, billed, reported success, and was unreachable forever.
132
+ */
133
+ supportsVectorSearch = false;
134
+ /**
135
+ * The query form this store takes: **words, not a vector.** `search()` reads
136
+ * {@link SearchOptions.text} and refuses by name without it. A retriever
137
+ * built over this store therefore needs no `Embedder`, and wiring one would
138
+ * be spend on a vector discarded on arrival.
139
+ */
140
+ ranksBy = 'server-text';
141
+ memories;
142
+ scope;
143
+ scopeFor;
144
+ operationTimeoutMs;
145
+ pageSize;
146
+ defaultTtl;
147
+ closed = false;
148
+ constructor(options) {
149
+ this.scope = (0, aiPlatform_js_1.resolveEngine)(ADAPTER, options);
150
+ this.memories = (0, aiPlatform_js_1.buildAiPlatformClient)(ADAPTER, options, this.scope).projects.locations.reasoningEngines.memories;
151
+ this.scopeFor = options.scopeFor ?? defaultScopeFor;
152
+ this.operationTimeoutMs = options.operationTimeoutMs ?? aiPlatform_js_1.DEFAULT_OPERATION_TIMEOUT_MS;
153
+ this.pageSize = clampPage(options.pageSize ?? DEFAULT_PAGE_SIZE);
154
+ this.defaultTtl = options.ttl;
155
+ }
156
+ // ── Reads ───────────────────────────────────────────────────────
157
+ /**
158
+ * One memory by id.
159
+ *
160
+ * Two independent things keep this from reading somebody else's memory, and
161
+ * both are deliberate. The **address** carries the scope, so another
162
+ * identity's row for the same entry id is a different resource name that
163
+ * simply is not there. And the **stored scope is re-checked on the way
164
+ * back**, because a resource name addresses a memory directly and a `get`
165
+ * alone would happily read another tenant's row for anyone who could guess a
166
+ * name. A memory whose scope is not this identity's answers `null` — the same
167
+ * `null` a missing one answers, because "exists but not yours" is an oracle
168
+ * for which ids are real.
169
+ */
170
+ async get(identity, id) {
171
+ this.ensureOpen('get');
172
+ const wanted = this.resolveScope(identity);
173
+ const memory = await this.fetch(this.nameOf(wanted, id));
174
+ if (memory === undefined)
175
+ return null;
176
+ if (!sameScope(memory.scope, wanted))
177
+ return null;
178
+ const entry = toEntry(memory);
179
+ if (entry === null)
180
+ return null;
181
+ if (entry.ttl !== undefined && entry.ttl <= Date.now())
182
+ return null;
183
+ return entry;
184
+ }
185
+ /**
186
+ * A page of this identity's memories, in no particular order.
187
+ *
188
+ * It rides `retrieve` with `simpleRetrievalParams` rather than `memories.list`
189
+ * — deliberately. `list` filters with AIP-160 over the resource's own fields,
190
+ * and whether that filter language can express an exact scope match is not
191
+ * something this adapter is willing to guess at for the call that decides
192
+ * which memories a caller can see. `retrieve` takes the scope as a structured
193
+ * field with semantics the SDK states outright, so the isolation is the
194
+ * service's rather than a filter string's.
195
+ *
196
+ * `tiers` filtering is applied to what comes back, since a tier is this
197
+ * library's own metadata and not something the service ranks on.
198
+ */
199
+ async list(identity, options = {}) {
200
+ this.ensureOpen('list');
201
+ const scope = this.resolveScope(identity);
202
+ const pageSize = clampPage(options.limit ?? this.pageSize);
203
+ let page;
204
+ try {
205
+ page = (await this.memories.retrieve({
206
+ parent: this.scope.parent,
207
+ requestBody: {
208
+ scope,
209
+ simpleRetrievalParams: {
210
+ pageSize,
211
+ ...(options.cursor !== undefined && { pageToken: options.cursor }),
212
+ },
213
+ },
214
+ }))?.data;
215
+ }
216
+ catch (err) {
217
+ throw (0, aiPlatform_js_1.googleSdkFailure)(ADAPTER, 'memories.retrieve', err);
218
+ }
219
+ const now = Date.now();
220
+ const entries = [];
221
+ for (const row of page?.retrievedMemories ?? []) {
222
+ const entry = row.memory === undefined ? null : toEntry(row.memory);
223
+ if (entry === null)
224
+ continue;
225
+ if (entry.ttl !== undefined && entry.ttl <= now)
226
+ continue;
227
+ if (options.tiers && (entry.tier === undefined || !options.tiers.includes(entry.tier))) {
228
+ continue;
229
+ }
230
+ entries.push(entry);
231
+ }
232
+ const cursor = page?.nextPageToken;
233
+ return typeof cursor === 'string' && cursor !== '' ? { entries, cursor } : { entries };
234
+ }
235
+ /**
236
+ * Memory Bank's own semantic retrieval — **text in, and the ranking trap
237
+ * handled rather than passed on.**
238
+ *
239
+ * ── It takes WORDS, not the vector ───────────────────────────────────────
240
+ * Google embeds and ranks server-side, so the `query` vector this method is
241
+ * handed cannot be sent anywhere. The query it needs travels in
242
+ * {@link SearchOptions.text}, and omitting it is refused by name — returning
243
+ * `[]` would read as "no matches" when it means "wrong query form".
244
+ *
245
+ * ── The score, and what this adapter refuses to pretend ──────────────────
246
+ * The service reports a **distance**, in its own words "smaller values
247
+ * indicate more similar memories". The port's score is a cosine similarity,
248
+ * where higher is closer. Two things follow, and both are decisions:
249
+ *
250
+ * • The distance is **converted**, never forwarded: `score = 1 / (1 + d)`,
251
+ * which is strictly decreasing in `d`, so **the ordering is right** —
252
+ * the closest memory has the highest score, which is the whole point.
253
+ * The raw distance is carried on `entry.metadata.distance` so nothing is
254
+ * hidden and a caller who knows the metric can do better.
255
+ *
256
+ * • **`minScore` is REFUSED by name**, because that number is calibrated
257
+ * for a cosine similarity and this scale is not one. Silently applying a
258
+ * cosine threshold to a converted distance is precisely the failure this
259
+ * library refuses elsewhere: a number that READS like a similarity, in
260
+ * the right range, that no threshold and no eyeball can separate from a
261
+ * real one. The sibling S3 Vectors adapter refuses a non-cosine index
262
+ * for the same reason; here the metric is Google's and cannot be
263
+ * changed, so the threshold is what goes rather than the store.
264
+ *
265
+ * ── One more thing the service requires ──────────────────────────────────
266
+ * Similarity search only works if the reasoning engine was configured with a
267
+ * similarity-search config. Without it the service refuses the call, and
268
+ * that refusal is passed through with its status — it is a setup fact, not a
269
+ * bug in the query.
270
+ *
271
+ * @throws when `options.text` is absent, or `options.minScore` is present.
272
+ */
273
+ async search(identity, query, options = {}) {
274
+ this.ensureOpen('search');
275
+ const text = options.text?.trim();
276
+ if (!text) {
277
+ throw new Error(`${ADAPTER}.search() needs the query as TEXT, in \`options.text\`.\n` +
278
+ ` Memory Bank embeds and ranks on Google's side, so the ${query.length}-dimension ` +
279
+ `vector this method was handed cannot be sent anywhere — and returning [] would look ` +
280
+ `like "no matches" rather than "wrong query form".\n` +
281
+ ` Fix: store.search(identity, vector, { text: theUserQuestion })\n` +
282
+ ` Backends that rank locally ignore \`text\`, so passing both is always safe.`);
283
+ }
284
+ if (options.minScore !== undefined) {
285
+ throw new Error(`${ADAPTER}.search() does not accept \`minScore\`.\n` +
286
+ ` This service reports a DISTANCE (smaller is closer), not a cosine similarity ` +
287
+ `(higher is closer). This adapter converts it to 1/(1+distance) so the ORDERING is ` +
288
+ `right, but that scale is not a cosine one and your threshold was calibrated for a ` +
289
+ `cosine — applying it here would silently keep or drop the wrong memories, with a ` +
290
+ `number in the right range that nothing can distinguish from a real score.\n` +
291
+ ` Fix: drop minScore and use \`k\` to bound the result, or filter yourself on ` +
292
+ `\`entry.metadata.distance\`, which is carried through unmodified.`);
293
+ }
294
+ const scope = this.resolveScope(identity);
295
+ const topK = clampPage(options.k ?? 10);
296
+ let page;
297
+ try {
298
+ page = (await this.memories.retrieve({
299
+ parent: this.scope.parent,
300
+ requestBody: { scope, similaritySearchParams: { searchQuery: text, topK } },
301
+ }))?.data;
302
+ }
303
+ catch (err) {
304
+ throw (0, aiPlatform_js_1.googleSdkFailure)(ADAPTER, 'memories.retrieve', err);
305
+ }
306
+ const now = Date.now();
307
+ const scored = [];
308
+ for (const row of page?.retrievedMemories ?? []) {
309
+ const entry = row.memory === undefined ? null : toEntry(row.memory, row);
310
+ if (entry === null)
311
+ continue;
312
+ if (entry.ttl !== undefined && entry.ttl <= now)
313
+ continue;
314
+ // A tier is this library's metadata, not something the service ranks on,
315
+ // so a tier filter is applied to what came back rather than ignored.
316
+ if (options.tiers && (entry.tier === undefined || !options.tiers.includes(entry.tier))) {
317
+ continue;
318
+ }
319
+ scored.push({ entry, score: scoreFromDistance(row.distance) });
320
+ }
321
+ // The service returns its own ranking; re-sort anyway so the port's
322
+ // "descending by score" holds even if a future API version reorders.
323
+ scored.sort((a, b) => b.score !== a.score ? b.score - a.score : a.entry.id < b.entry.id ? -1 : 1);
324
+ return scored.slice(0, topK);
325
+ }
326
+ // ── Writes ──────────────────────────────────────────────────────
327
+ /**
328
+ * Write one memory, waiting for the service to say it landed.
329
+ *
330
+ * ── Read-then-write, and what the read is FOR ────────────────────────────
331
+ * This costs one `get` before the write, and that read is not an
332
+ * optimisation — it is the tenant check. A `patch` names a resource
333
+ * directly and the service does not ask whose it is, so a patch sent
334
+ * without looking is a write this adapter cannot promise landed on its own
335
+ * row. The address already carries the scope (see the module header), so
336
+ * the row under this name is ours in every ordinary run; the read is what
337
+ * turns "ordinary" into "checked", and what makes the one case where it is
338
+ * NOT ours a refusal you can read instead of a fact one tenant wrote into
339
+ * another tenant's memory.
340
+ *
341
+ * What the read finds decides the rest: an existing row of ours is patched,
342
+ * an absent one is created, and a row that is somebody else's is refused by
343
+ * name. The `create` race — two writers, neither of whom saw a row — is
344
+ * caught as `ALREADY EXISTS` and folded back into the same checked patch.
345
+ */
346
+ async put(identity, entry) {
347
+ this.ensureOpen('put');
348
+ if (entry.ttl !== undefined && entry.ttl <= Date.now())
349
+ return;
350
+ const scope = this.resolveScope(identity);
351
+ const body = toMemory(entry, scope, this.defaultTtl);
352
+ const resourceId = resourceIdFor(scope, entry.id);
353
+ const name = `${this.scope.parent}/memories/${resourceId}`;
354
+ if (await this.overwrite(name, scope, entry.id, body))
355
+ return;
356
+ let created;
357
+ try {
358
+ created = await this.memories.create({
359
+ parent: this.scope.parent,
360
+ memoryId: resourceId,
361
+ requestBody: body,
362
+ });
363
+ }
364
+ catch (err) {
365
+ if (!(0, aiPlatform_js_1.isAlreadyExists)(err))
366
+ throw this.asFailure(err, 'memories.create');
367
+ // Another writer created it between our read and our create. The row
368
+ // exists now, which is all this wanted — so take the same checked patch
369
+ // path rather than reporting a failure for a race that resolved.
370
+ if (await this.overwrite(name, scope, entry.id, body))
371
+ return;
372
+ // Created by someone and gone again before we could read it. Nothing
373
+ // this adapter can say landed, so it says so.
374
+ throw this.asFailure(err, 'memories.create');
375
+ }
376
+ await this.wait(created?.data, `creating memory '${entry.id}'`);
377
+ }
378
+ /**
379
+ * Sequential, not batched: the service has no batch-write operation, and
380
+ * each write is a long-running operation that has to be waited on
381
+ * individually. An empty batch is a no-op and costs no round trip, which
382
+ * callers rely on.
383
+ */
384
+ async putMany(identity, entries) {
385
+ this.ensureOpen('putMany');
386
+ for (const entry of entries)
387
+ await this.put(identity, entry);
388
+ }
389
+ /**
390
+ * Remove one memory.
391
+ *
392
+ * Scope-checked first, for the reason {@link get} spells out: a resource
393
+ * name addresses a row directly, and a delete that skipped the check would
394
+ * let anyone who could guess an id remove another tenant's memory. A memory
395
+ * that is not this identity's is left alone and reported as nothing to do —
396
+ * the same answer a missing one gets.
397
+ */
398
+ async delete(identity, id) {
399
+ this.ensureOpen('delete');
400
+ const scope = this.resolveScope(identity);
401
+ const name = this.nameOf(scope, id);
402
+ const existing = await this.fetch(name);
403
+ if (existing === undefined || !sameScope(existing.scope, scope))
404
+ return;
405
+ let deleted;
406
+ try {
407
+ deleted = await this.memories.delete({ name });
408
+ }
409
+ catch (err) {
410
+ if ((0, aiPlatform_js_1.isNotFound)(err))
411
+ return;
412
+ throw this.asFailure(err, 'memories.delete');
413
+ }
414
+ await this.wait(deleted?.data, `deleting memory '${id}'`);
415
+ }
416
+ /**
417
+ * GDPR — every memory for this identity, gone.
418
+ *
419
+ * Paginated retrieve-then-delete, and **deliberately not `memories.purge`**,
420
+ * for two reasons that are both about not being silently wrong on the one
421
+ * operation where that matters most:
422
+ *
423
+ * 1. `PurgeMemoriesRequest.force` defaults to **false**, which the service
424
+ * documents as "the purge request will be validated but not executed".
425
+ * A forget built on it and written without that flag would report
426
+ * success and delete nothing — a compliance failure that looks exactly
427
+ * like a working erasure.
428
+ * 2. Purge selects rows with an AIP-160 filter STRING, and whether that
429
+ * language can express an exact scope match is not verified. A filter
430
+ * that under-matches leaves data behind; one that over-matches deletes
431
+ * somebody else's. Neither is a guess worth making here.
432
+ *
433
+ * The scoped retrieve has semantics the SDK states outright, so that is what
434
+ * this uses. It costs one delete per memory, which is the right price.
435
+ */
436
+ async forget(identity) {
437
+ this.ensureOpen('forget');
438
+ const scope = this.resolveScope(identity);
439
+ let cursor;
440
+ do {
441
+ let page;
442
+ try {
443
+ page = (await this.memories.retrieve({
444
+ parent: this.scope.parent,
445
+ requestBody: {
446
+ scope,
447
+ simpleRetrievalParams: {
448
+ pageSize: exports.MAX_PAGE_SIZE,
449
+ ...(cursor !== undefined && { pageToken: cursor }),
450
+ },
451
+ },
452
+ }))?.data;
453
+ }
454
+ catch (err) {
455
+ throw (0, aiPlatform_js_1.googleSdkFailure)(ADAPTER, 'memories.retrieve', err);
456
+ }
457
+ const names = (page?.retrievedMemories ?? [])
458
+ .map((row) => row.memory?.name)
459
+ .filter((name) => typeof name === 'string' && name !== '');
460
+ for (const name of names) {
461
+ let deleted;
462
+ try {
463
+ deleted = await this.memories.delete({ name });
464
+ }
465
+ catch (err) {
466
+ if ((0, aiPlatform_js_1.isNotFound)(err))
467
+ continue;
468
+ throw this.asFailure(err, 'memories.delete');
469
+ }
470
+ await this.wait(deleted?.data, `deleting memory '${name}'`);
471
+ }
472
+ const next = page?.nextPageToken;
473
+ // A page that deleted everything it listed and hands back the same
474
+ // cursor would loop forever. Erasure walks forward or it stops.
475
+ cursor = typeof next === 'string' && next !== '' && next !== cursor ? next : undefined;
476
+ } while (cursor !== undefined);
477
+ }
478
+ // ── The five with no primitive behind them ──────────────────────
479
+ /**
480
+ * @throws always — this service has no compare-and-set.
481
+ * @see the module header for why this refuses where the sibling adapter
482
+ * emulates.
483
+ */
484
+ putIfVersion(_identity, entry, expectedVersion) {
485
+ return Promise.reject(unsupported('putIfVersion', `A \`Memory\` carries no etag or generation number, so there is nothing to compare ` +
486
+ `against — the write for '${entry.id}' at version ${expectedVersion} cannot be made ` +
487
+ `conditional.\n` +
488
+ ` Emulating it with a read-then-write would report \`{ applied: true }\` to two ` +
489
+ `writers at once, which is a lost update that no log would show. ` +
490
+ `Use put() when you know you are the only writer, or keep memories that need ` +
491
+ `optimistic concurrency in a store that has it (pgVectorStore, RedisStore, ` +
492
+ `sqliteVectorStore).`));
493
+ }
494
+ /** @throws always — this service has no recognition set. */
495
+ seen(_identity, signature) {
496
+ return Promise.reject(unsupported('seen', `There is no dedup primitive here, and answering '${signature}' from a per-process Map ` +
497
+ `would be worse than refusing: this store is one you reach for BECAUSE it is shared ` +
498
+ `across a fleet, and a second container would answer "never seen" for a signature ` +
499
+ `the first one recorded.\n` +
500
+ ` Keep the recognition set in a store that is really shared (RedisStore), or ` +
501
+ `dedup on a deterministic entry id and let put() overwrite.`));
502
+ }
503
+ /** @throws always — the write side of a recognition set this service does not have. */
504
+ recordSignature(_identity, signature) {
505
+ return Promise.reject(unsupported('recordSignature', `There is nowhere to record '${signature}'. See seen() — the two are one feature and ` +
506
+ `neither is emulated in-process.`));
507
+ }
508
+ /** @throws always — this service has no feedback primitive. */
509
+ feedback(_identity, id, _usefulness) {
510
+ return Promise.reject(unsupported('feedback', `There is no usefulness signal on a \`Memory\`, so feedback for '${id}' has nowhere ` +
511
+ `durable to go. A per-process tally would be lost on the next deploy and invisible ` +
512
+ `to every other container, which is the shape of a metric that quietly stops ` +
513
+ `meaning anything.`));
514
+ }
515
+ /** @throws always — the read side of feedback this service does not record. */
516
+ getFeedback(_identity, id) {
517
+ return Promise.reject(unsupported('getFeedback', `Nothing records feedback here, so there is none to read for '${id}'. Answering ` +
518
+ `\`null\` would be indistinguishable from "recorded, but neutral", which callers ` +
519
+ `are documented to treat differently.`));
520
+ }
521
+ // ── Lifecycle ───────────────────────────────────────────────────
522
+ /**
523
+ * Stop using this store. Idempotent and final. Nothing is torn down on
524
+ * Google's side — the memories outlive this process, which is the point.
525
+ */
526
+ close() {
527
+ this.closed = true;
528
+ return Promise.resolve();
529
+ }
530
+ // ── Internals ───────────────────────────────────────────────────
531
+ /** Where this identity's copy of `id` lives. See the module header. */
532
+ nameOf(scope, id) {
533
+ return `${this.scope.parent}/memories/${resourceIdFor(scope, id)}`;
534
+ }
535
+ /** One memory by resource name, or `undefined` when there is none. */
536
+ async fetch(name) {
537
+ try {
538
+ return (await this.memories.get({ name }))?.data;
539
+ }
540
+ catch (err) {
541
+ if ((0, aiPlatform_js_1.isNotFound)(err))
542
+ return undefined;
543
+ throw this.asFailure(err, 'memories.get');
544
+ }
545
+ }
546
+ /**
547
+ * Patch the row at `name` if it exists AND is this scope's; answer `false`
548
+ * when there is nothing there to patch.
549
+ *
550
+ * A row that exists under somebody else's scope is REFUSED rather than
551
+ * written. It should be unreachable — the address carries the scope — so
552
+ * reaching it means the fingerprint collided or the bank was written by
553
+ * another tool under a name of ours, and both of those are facts an operator
554
+ * has to be told rather than have resolved in favour of the last writer.
555
+ */
556
+ async overwrite(name, scope, id, body) {
557
+ const existing = await this.fetch(name);
558
+ if (existing === undefined)
559
+ return false;
560
+ if (!sameScope(existing.scope, scope))
561
+ throw foreignMemory(id, name);
562
+ let patched;
563
+ try {
564
+ patched = await this.memories.patch({
565
+ name,
566
+ // `scope` is immutable and deliberately NOT in the mask: including it
567
+ // would make every update a request to change something the service
568
+ // refuses to change.
569
+ updateMask: 'fact,metadata',
570
+ requestBody: { fact: body.fact, metadata: body.metadata },
571
+ });
572
+ }
573
+ catch (err) {
574
+ // Deleted between the read and the patch. Nothing was overwritten, so
575
+ // the caller falls through to a create — the outcome they asked for.
576
+ if ((0, aiPlatform_js_1.isNotFound)(err))
577
+ return false;
578
+ throw this.asFailure(err, 'memories.patch');
579
+ }
580
+ // Awaited OUTSIDE the try: an operation refusal is already sanitized and
581
+ // already says what to do, and re-wrapping would replace a precise
582
+ // diagnosis with a generic one.
583
+ await this.wait(patched?.data, `updating memory '${id}'`);
584
+ return true;
585
+ }
586
+ ensureOpen(op) {
587
+ if (this.closed)
588
+ throw new Error(`${ADAPTER}.${op}() called after close().`);
589
+ }
590
+ wait(operation, what) {
591
+ return (0, aiPlatform_js_1.awaitOperation)(ADAPTER, this.memories.operations, operation, what, this.operationTimeoutMs);
592
+ }
593
+ /** Keep an already-sanitized refusal; sanitize anything else. */
594
+ asFailure(err, operation) {
595
+ if ((0, aiPlatform_js_1.isSanitizedGoogleError)(err))
596
+ return err;
597
+ return (0, aiPlatform_js_1.googleSdkFailure)(ADAPTER, operation, err);
598
+ }
599
+ /** The identity's scope, checked for the two values that would break isolation. */
600
+ resolveScope(identity) {
601
+ const raw = this.scopeFor(identity);
602
+ const entries = Object.entries(raw ?? {}).filter(([key, value]) => typeof value === 'string' && key !== '');
603
+ if (entries.length === 0) {
604
+ throw new TypeError(`${ADAPTER}: 'scopeFor' produced an empty scope for conversation ` +
605
+ `'${identity.conversationId}'.\n` +
606
+ ` An empty scope is not "no scoping" — it is a real scope that matches every other ` +
607
+ `empty-scoped memory in the bank, whoever wrote it. Return at least one key.`);
608
+ }
609
+ // The service rejects `*` in a scope value. Replaced rather than passed on,
610
+ // so a tenant named with one is a NAME and not a wildcard.
611
+ return Object.fromEntries(entries.map(([key, value]) => [key, value.replace(/\*/g, '_')]));
612
+ }
613
+ }
614
+ exports.MemoryBankStore = MemoryBankStore;
615
+ /**
616
+ * A `MemoryStore` over Vertex AI Memory Bank.
617
+ *
618
+ * @example Per-person memory that survives a conversation ending
619
+ * const store = memoryBankStore({
620
+ * project: 'my-project',
621
+ * location: 'us-central1',
622
+ * reasoningEngine: '1234567890',
623
+ * scopeFor: (id) => ({ tenant: id.tenant ?? '_', principal: id.principal ?? '_' }),
624
+ * });
625
+ *
626
+ * const hits = await store.search(identity, [], { text: 'what does she prefer?', k: 5 });
627
+ */
628
+ function memoryBankStore(options) {
629
+ return new MemoryBankStore(options);
630
+ }
631
+ exports.memoryBankStore = memoryBankStore;
632
+ // ─── Mapping ─────────────────────────────────────────────────────────
633
+ /**
634
+ * The resource id one memory is addressed by: **the scope and the entry id,
635
+ * together.**
636
+ *
637
+ * Keyed on the resolved SCOPE rather than on the raw identity, so the two
638
+ * decisions stay one decision: whatever `scopeFor` says two callers share, they
639
+ * share here too, and whatever it keeps apart is kept apart here. A `scopeFor`
640
+ * widened to `{ tenant, principal }` gives one person one row for `fact:tone`
641
+ * across all their conversations — which is the point of widening it — while
642
+ * the default tuple gives them one per conversation.
643
+ *
644
+ * The scope is folded to a fingerprint rather than spelled out: real tenant,
645
+ * principal and conversation ids do not fit in 63 characters together, and the
646
+ * entry id is the half worth being able to read in the console. The `m` prefix
647
+ * is what keeps the composed id starting with a letter, which is what lets an
648
+ * ordinary lowercase entry id survive {@link safeResourceId} unchanged and stay
649
+ * legible.
650
+ */
651
+ function resourceIdFor(scope, id) {
652
+ return (0, aiPlatform_js_1.safeResourceId)(`m${(0, aiPlatform_js_1.fingerprint)(canonicalScope(scope))}-${id}`);
653
+ }
654
+ /**
655
+ * A scope as one unambiguous string.
656
+ *
657
+ * Keys sorted, so the order `scopeFor` happened to return them in cannot change
658
+ * an address. Every key and value length-prefixed, so no two different scopes
659
+ * can spell themselves the same way — `{ 'a=1,b': '2' }` and
660
+ * `{ a: '1', b: '2' }` would otherwise be the same string, and two different
661
+ * scopes that share an address are the very bug this composer exists to close.
662
+ */
663
+ function canonicalScope(scope) {
664
+ return Object.keys(scope)
665
+ .sort()
666
+ .map((key) => `${key.length}:${key}=${scope[key].length}:${scope[key]}`)
667
+ .join(',');
668
+ }
669
+ /** Somebody else's memory is sitting where this identity's would be. */
670
+ function foreignMemory(id, name) {
671
+ const err = new Error(`${ADAPTER}: refusing to overwrite '${id}' — the memory at '${name}' carries a different ` +
672
+ `scope, so it belongs to another identity.\n` +
673
+ ` A memory's address is composed from the scope AND the entry id, so this should be ` +
674
+ `unreachable. Reaching it means either the scope fingerprint collided, or something ` +
675
+ `other than this library wrote a memory under a name of ours.\n` +
676
+ ` The write is refused rather than applied: the row's scope is immutable, so writing ` +
677
+ `would leave one identity's fact under another identity's scope — readable by them, ` +
678
+ `invisible to you, and missed by forget(). Report the memory id; do not retry.`);
679
+ err.name = 'MemoryScopeConflictError';
680
+ return err;
681
+ }
682
+ /** The default scope: the full identity tuple, matching every other store. */
683
+ function defaultScopeFor(identity) {
684
+ return {
685
+ tenant: identity.tenant || '_',
686
+ principal: identity.principal || '_',
687
+ conversation: identity.conversationId,
688
+ };
689
+ }
690
+ /**
691
+ * Distance → a score whose ORDER is right.
692
+ *
693
+ * `1 / (1 + d)` is strictly decreasing on `d >= 0`, lands in `(0, 1]`, and is
694
+ * exactly 1 at distance 0. It is **not** a cosine similarity and this adapter
695
+ * never says it is — see {@link MemoryBankStore.search} for why `minScore` is
696
+ * refused rather than measured against it.
697
+ *
698
+ * A row with no distance is a simple retrieval, which does no ranking at all;
699
+ * `0` is the honest score for "this was not ranked", and it sorts last.
700
+ */
701
+ function scoreFromDistance(distance) {
702
+ if (typeof distance !== 'number' || !Number.isFinite(distance))
703
+ return 0;
704
+ return 1 / (1 + Math.max(0, distance));
705
+ }
706
+ exports.scoreFromDistance = scoreFromDistance;
707
+ /** Do two scopes match the way the service matches them — same keys, same values? */
708
+ function sameScope(stored, wanted) {
709
+ if (stored === null || stored === undefined)
710
+ return false;
711
+ const storedKeys = Object.keys(stored);
712
+ const wantedKeys = Object.keys(wanted);
713
+ if (storedKeys.length !== wantedKeys.length)
714
+ return false;
715
+ return wantedKeys.every((key) => stored[key] === wanted[key]);
716
+ }
717
+ /** Clamp a page size or top-k into what the service will actually honour. */
718
+ function clampPage(value) {
719
+ if (!Number.isFinite(value))
720
+ return DEFAULT_PAGE_SIZE;
721
+ return Math.max(1, Math.min(exports.MAX_PAGE_SIZE, Math.floor(value)));
722
+ }
723
+ const str = (value) => ({ stringValue: value });
724
+ const num = (value) => ({ doubleValue: value });
725
+ /**
726
+ * `MemoryEntry` → `Memory`.
727
+ *
728
+ * The value becomes the `fact`. A string goes through as itself — this is a
729
+ * natural-language store and a sentence is what it ranks well. Anything else
730
+ * is JSON, flagged so the read side restores the original type, and worth
731
+ * knowing about: a JSON blob IS what gets embedded and ranked, so semantic
732
+ * retrieval over structured values is close to meaningless. Store a sentence
733
+ * when you want it found.
734
+ *
735
+ * `embedding` is deliberately dropped — see `supportsVectorSearch`.
736
+ */
737
+ function toMemory(entry, scope, defaultTtl) {
738
+ const isString = typeof entry.value === 'string';
739
+ const metadata = {
740
+ [META.id]: str(entry.id),
741
+ [META.version]: num(entry.version),
742
+ [META.createdAt]: num(entry.createdAt),
743
+ [META.updatedAt]: num(entry.updatedAt),
744
+ [META.json]: { boolValue: !isString },
745
+ ...(entry.tier !== undefined && { [META.tier]: str(entry.tier) }),
746
+ };
747
+ return {
748
+ fact: isString ? entry.value : JSON.stringify(entry.value ?? null),
749
+ scope: { ...scope },
750
+ metadata,
751
+ // An entry's own `ttl` is a unix TIMESTAMP; the service takes a DURATION.
752
+ // Converted here rather than at the call site so both spellings of "how
753
+ // long does this live" resolve in one place.
754
+ ...(entry.ttl !== undefined
755
+ ? { ttl: `${Math.max(1, Math.round((entry.ttl - Date.now()) / 1000))}s` }
756
+ : defaultTtl !== undefined && { ttl: defaultTtl }),
757
+ };
758
+ }
759
+ /**
760
+ * `Memory` → `MemoryEntry`, or `null` for a row this store did not write.
761
+ *
762
+ * Memory Bank generates memories of its own (that is a headline feature), and
763
+ * those carry a `fact` and no metadata of ours. They are not entries and are
764
+ * skipped rather than dressed up as ones with invented ids and versions — the
765
+ * ids would belong to Google, `store.get()` on them would work by accident,
766
+ * and a caller could not tell which of their "memories" they had ever written.
767
+ */
768
+ function toEntry(memory, retrieved) {
769
+ const metadata = memory.metadata ?? {};
770
+ const id = metadata[META.id]?.stringValue;
771
+ if (typeof id !== 'string' || id === '')
772
+ return null;
773
+ const fact = typeof memory.fact === 'string' ? memory.fact : '';
774
+ const isJson = metadata[META.json]?.boolValue === true;
775
+ let value = fact;
776
+ if (isJson) {
777
+ try {
778
+ value = JSON.parse(fact);
779
+ }
780
+ catch {
781
+ // Written as JSON, came back as something else. The bytes are handed
782
+ // through as text rather than dropped: a memory that exists must not
783
+ // read as one that was never written.
784
+ value = fact;
785
+ }
786
+ }
787
+ const createdAt = metadata[META.createdAt]?.doubleValue ?? toMillis(memory.createTime);
788
+ const updatedAt = metadata[META.updatedAt]?.doubleValue ?? toMillis(memory.updateTime);
789
+ const tier = metadata[META.tier]?.stringValue;
790
+ const expireTime = toMillis(memory.expireTime);
791
+ return {
792
+ id,
793
+ value: value,
794
+ version: metadata[META.version]?.doubleValue ?? 1,
795
+ createdAt,
796
+ updatedAt,
797
+ lastAccessedAt: Date.now(),
798
+ accessCount: 0,
799
+ ...(expireTime > 0 && { ttl: expireTime }),
800
+ ...(tier === 'hot' || tier === 'warm' || tier === 'cold' ? { tier } : {}),
801
+ metadata: {
802
+ source: 'vertex-memory-bank',
803
+ ...(memory.name !== null && memory.name !== undefined && { resourceName: memory.name }),
804
+ // The raw distance, carried through unmodified — the honest number
805
+ // beside the converted one, for a caller who knows the metric.
806
+ ...(typeof retrieved?.distance === 'number' && { distance: retrieved.distance }),
807
+ },
808
+ };
809
+ }
810
+ /** An RFC 3339 timestamp as unix milliseconds, or 0 when it is not one. */
811
+ function toMillis(value) {
812
+ if (typeof value !== 'string')
813
+ return 0;
814
+ const parsed = Date.parse(value);
815
+ return Number.isFinite(parsed) ? parsed : 0;
816
+ }
817
+ /** One refusal shape for the five operations this service has no primitive for. */
818
+ function unsupported(operation, why) {
819
+ const err = new Error(`${ADAPTER}.${operation}() is not supported by Vertex AI Memory Bank.\n ${why}`);
820
+ err.name = 'MemoryOperationUnsupportedError';
821
+ return err;
822
+ }
823
+ //# sourceMappingURL=memoryBank.js.map