agentfootprint 9.28.0 → 9.30.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.
- package/README.md +3 -2
- package/dist/adapters/google/aiPlatform.js +23 -1
- package/dist/adapters/google/aiPlatform.js.map +1 -1
- package/dist/adapters/hosting/googleAgentEngine.js +180 -61
- package/dist/adapters/hosting/googleAgentEngine.js.map +1 -1
- package/dist/adapters/identity/google.js +18 -4
- package/dist/adapters/identity/google.js.map +1 -1
- package/dist/adapters/llm/GeminiProvider.js +198 -36
- package/dist/adapters/llm/GeminiProvider.js.map +1 -1
- package/dist/adapters/llm/OpenAIProvider.js +66 -2
- package/dist/adapters/llm/OpenAIProvider.js.map +1 -1
- package/dist/adapters/llm/googleGenAI.js +155 -8
- package/dist/adapters/llm/googleGenAI.js.map +1 -1
- package/dist/adapters/memory/memoryBank.js +250 -6
- package/dist/adapters/memory/memoryBank.js.map +1 -1
- package/dist/adapters/types.js.map +1 -1
- package/dist/artifacts/gcsArtifacts.js +21 -5
- package/dist/artifacts/gcsArtifacts.js.map +1 -1
- package/dist/embedders/index.js +8 -6
- package/dist/embedders/index.js.map +1 -1
- package/dist/esm/adapters/google/aiPlatform.d.ts +44 -0
- package/dist/esm/adapters/google/aiPlatform.js +23 -1
- package/dist/esm/adapters/google/aiPlatform.js.map +1 -1
- package/dist/esm/adapters/hosting/googleAgentEngine.d.ts +104 -18
- package/dist/esm/adapters/hosting/googleAgentEngine.js +179 -60
- package/dist/esm/adapters/hosting/googleAgentEngine.js.map +1 -1
- package/dist/esm/adapters/identity/google.d.ts +18 -4
- package/dist/esm/adapters/identity/google.js +18 -4
- package/dist/esm/adapters/identity/google.js.map +1 -1
- package/dist/esm/adapters/llm/GeminiProvider.d.ts +96 -15
- package/dist/esm/adapters/llm/GeminiProvider.js +199 -37
- package/dist/esm/adapters/llm/GeminiProvider.js.map +1 -1
- package/dist/esm/adapters/llm/OpenAIProvider.d.ts +34 -2
- package/dist/esm/adapters/llm/OpenAIProvider.js +66 -2
- package/dist/esm/adapters/llm/OpenAIProvider.js.map +1 -1
- package/dist/esm/adapters/llm/googleGenAI.d.ts +105 -4
- package/dist/esm/adapters/llm/googleGenAI.js +152 -7
- package/dist/esm/adapters/llm/googleGenAI.js.map +1 -1
- package/dist/esm/adapters/memory/memoryBank.d.ts +42 -3
- package/dist/esm/adapters/memory/memoryBank.js +249 -5
- package/dist/esm/adapters/memory/memoryBank.js.map +1 -1
- package/dist/esm/adapters/types.d.ts +27 -0
- package/dist/esm/adapters/types.js.map +1 -1
- package/dist/esm/artifacts/gcsArtifacts.d.ts +21 -5
- package/dist/esm/artifacts/gcsArtifacts.js +21 -5
- package/dist/esm/artifacts/gcsArtifacts.js.map +1 -1
- package/dist/esm/core/agent/types.d.ts +1 -0
- package/dist/esm/embedders/index.js +9 -7
- package/dist/esm/embedders/index.js.map +1 -1
- package/dist/esm/events/payloads.d.ts +22 -0
- package/dist/esm/hosting-providers.d.ts +4 -2
- package/dist/esm/hosting-providers.js +4 -2
- package/dist/esm/hosting-providers.js.map +1 -1
- package/dist/esm/memory-providers.d.ts +1 -1
- package/dist/esm/memory-providers.js +4 -2
- package/dist/esm/memory-providers.js.map +1 -1
- package/dist/esm/providers.js.map +1 -1
- package/dist/esm/recorders/observability/AgentThinkingTraceRecorder.js +5 -1
- package/dist/esm/recorders/observability/AgentThinkingTraceRecorder.js.map +1 -1
- package/dist/esm/recorders/observability/commentary/commentaryTemplates.js +44 -3
- package/dist/esm/recorders/observability/commentary/commentaryTemplates.js.map +1 -1
- package/dist/hosting-providers.js +5 -2
- package/dist/hosting-providers.js.map +1 -1
- package/dist/memory-providers.js +5 -2
- package/dist/memory-providers.js.map +1 -1
- package/dist/providers.js.map +1 -1
- package/dist/recorders/observability/AgentThinkingTraceRecorder.js +5 -1
- package/dist/recorders/observability/AgentThinkingTraceRecorder.js.map +1 -1
- package/dist/recorders/observability/commentary/commentaryTemplates.js +44 -3
- package/dist/recorders/observability/commentary/commentaryTemplates.js.map +1 -1
- package/dist/types/adapters/google/aiPlatform.d.ts +44 -0
- package/dist/types/adapters/google/aiPlatform.d.ts.map +1 -1
- package/dist/types/adapters/hosting/googleAgentEngine.d.ts +104 -18
- package/dist/types/adapters/hosting/googleAgentEngine.d.ts.map +1 -1
- package/dist/types/adapters/identity/google.d.ts +18 -4
- package/dist/types/adapters/identity/google.d.ts.map +1 -1
- package/dist/types/adapters/llm/GeminiProvider.d.ts +96 -15
- package/dist/types/adapters/llm/GeminiProvider.d.ts.map +1 -1
- package/dist/types/adapters/llm/OpenAIProvider.d.ts +34 -2
- package/dist/types/adapters/llm/OpenAIProvider.d.ts.map +1 -1
- package/dist/types/adapters/llm/googleGenAI.d.ts +105 -4
- package/dist/types/adapters/llm/googleGenAI.d.ts.map +1 -1
- package/dist/types/adapters/memory/memoryBank.d.ts +42 -3
- package/dist/types/adapters/memory/memoryBank.d.ts.map +1 -1
- package/dist/types/adapters/types.d.ts +27 -0
- package/dist/types/adapters/types.d.ts.map +1 -1
- package/dist/types/artifacts/gcsArtifacts.d.ts +21 -5
- package/dist/types/artifacts/gcsArtifacts.d.ts.map +1 -1
- package/dist/types/core/agent/types.d.ts +1 -0
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/embedders/index.d.ts.map +1 -1
- package/dist/types/events/payloads.d.ts +22 -0
- package/dist/types/events/payloads.d.ts.map +1 -1
- package/dist/types/hosting-providers.d.ts +4 -2
- package/dist/types/hosting-providers.d.ts.map +1 -1
- package/dist/types/memory-providers.d.ts +1 -1
- package/dist/types/memory-providers.d.ts.map +1 -1
- package/dist/types/providers.d.ts.map +1 -1
- package/dist/types/recorders/observability/AgentThinkingTraceRecorder.d.ts.map +1 -1
- package/dist/types/recorders/observability/commentary/commentaryTemplates.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -28,10 +28,63 @@
|
|
|
28
28
|
* `test/adapters/google/google-surface-pin.test.ts`. It is not `v1`. Callers
|
|
29
29
|
* who need a specific version pass `apiVersion`; callers who do not at least
|
|
30
30
|
* find the truth stated instead of assuming a GA path.
|
|
31
|
+
*
|
|
32
|
+
* ─── The key can be a CALLBACK, and why (9.29.0) ────────────────────
|
|
33
|
+
*
|
|
34
|
+
* `GoogleGenAIOptions.apiKey` is `string` on the installed SDK (@google/genai
|
|
35
|
+
* 2.16.0 `genai.d.ts`) — one value, fixed at construction. That is fine for an
|
|
36
|
+
* AI Studio key, which does not expire, and wrong for every credential that
|
|
37
|
+
* does: a Vertex OAuth access token lives about an hour, and a key pulled from
|
|
38
|
+
* a secret manager is rotated on somebody else's schedule.
|
|
39
|
+
*
|
|
40
|
+
* An independent field trial on live GCP measured exactly that boundary: an
|
|
41
|
+
* OAuth token that worked returned HTTP 401 once expired, with no place in the
|
|
42
|
+
* options to put a fresh one ("Part 2B — ADC refresh versus OpenAI-compatible
|
|
43
|
+
* OAuth"). So `apiKey` widens to `string | (() => string | Promise<string>)`,
|
|
44
|
+
* and the refresh boundary is stated rather than implied:
|
|
45
|
+
*
|
|
46
|
+
* • The callback runs ONCE PER REQUEST, before the request is built.
|
|
47
|
+
* • The SDK client is REBUILT only when the returned string differs from the
|
|
48
|
+
* one already in hand — a callback that returns a cached token costs one
|
|
49
|
+
* function call and nothing else.
|
|
50
|
+
* • A stream keeps the key it STARTED with. The boundary is the call, not
|
|
51
|
+
* the chunk; nothing here can re-authenticate a socket that is already
|
|
52
|
+
* open.
|
|
53
|
+
* • Vertex needs none of this. ADC refreshes itself, which the same trial
|
|
54
|
+
* verified by forcing an in-memory expiry and completing the next call.
|
|
31
55
|
*/
|
|
32
56
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
-
exports.resolveGoogleGenAIClient = void 0;
|
|
57
|
+
exports.createGoogleGenAIClientResolver = exports.resolveGoogleGenAIClient = exports.resolveGoogleDoor = void 0;
|
|
34
58
|
const lazyRequire_js_1 = require("../../lib/lazyRequire.js");
|
|
59
|
+
/** An empty environment variable is not a setting — see `resolveGoogleGenAIClient`. */
|
|
60
|
+
const set = (value) => value !== undefined && value.trim().length > 0 ? value : undefined;
|
|
61
|
+
/** The env this module reads, or `{}` off-Node. */
|
|
62
|
+
const environment = () => (typeof process !== 'undefined' ? process.env : {});
|
|
63
|
+
/**
|
|
64
|
+
* Which door these options open.
|
|
65
|
+
*
|
|
66
|
+
* The order mirrors `resolveGoogleGenAIClient` exactly, because a door that
|
|
67
|
+
* disagreed with the client that gets built is worse than no door at all.
|
|
68
|
+
*
|
|
69
|
+
* @param readEnv `false` when the caller injected a `_client`. A double talks
|
|
70
|
+
* to no service, so the ambient `GEMINI_API_KEY` of whoever is running
|
|
71
|
+
* the tests describes a door that will never be dialled — reading it
|
|
72
|
+
* would make an offline suite depend on a developer's shell.
|
|
73
|
+
*/
|
|
74
|
+
function resolveGoogleDoor(options, readEnv = true) {
|
|
75
|
+
if (options.vertexai === true)
|
|
76
|
+
return 'vertex';
|
|
77
|
+
if (options.vertexai === false)
|
|
78
|
+
return 'gemini-api';
|
|
79
|
+
const env = readEnv ? environment() : {};
|
|
80
|
+
if ((set(options.project) ?? set(env.GOOGLE_CLOUD_PROJECT)) !== undefined)
|
|
81
|
+
return 'vertex';
|
|
82
|
+
const key = (typeof options.apiKey === 'function' ? 'callback' : set(options.apiKey)) ??
|
|
83
|
+
set(env.GEMINI_API_KEY) ??
|
|
84
|
+
set(env.GOOGLE_API_KEY);
|
|
85
|
+
return key !== undefined ? 'gemini-api' : 'vertex';
|
|
86
|
+
}
|
|
87
|
+
exports.resolveGoogleDoor = resolveGoogleDoor;
|
|
35
88
|
/**
|
|
36
89
|
* Build (or accept) a `GoogleGenAI` client.
|
|
37
90
|
*
|
|
@@ -43,14 +96,18 @@ const lazyRequire_js_1 = require("../../lib/lazyRequire.js");
|
|
|
43
96
|
* @param factory the public name to put in refusals — `'gemini'`,
|
|
44
97
|
* `'geminiEmbedder'`. A message that names the wrong factory
|
|
45
98
|
* sends the reader to the wrong line of their own code.
|
|
99
|
+
* @param apiKey the key STRING for this build. Callers holding a callback
|
|
100
|
+
* resolve it first (see {@link createGoogleGenAIClientResolver});
|
|
101
|
+
* a function reaching the SDK's `apiKey` field would be sent
|
|
102
|
+
* as `[object Function]` and fail as an auth error.
|
|
46
103
|
* @throws when neither a project nor an API key is resolvable; when
|
|
47
104
|
* `vertexai: true` is asked for without a project; and when
|
|
48
105
|
* `@google/genai` is not installed.
|
|
49
106
|
*/
|
|
50
|
-
function resolveGoogleGenAIClient(options, factory, injected) {
|
|
107
|
+
function resolveGoogleGenAIClient(options, factory, injected, apiKey) {
|
|
51
108
|
if (injected)
|
|
52
109
|
return injected;
|
|
53
|
-
const env = (
|
|
110
|
+
const env = environment();
|
|
54
111
|
// Read HERE only to decide whether there is enough configuration to build
|
|
55
112
|
// anything at all. The values are never copied into the constructor, so the
|
|
56
113
|
// SDK stays the single resolver of its own environment variables — one place
|
|
@@ -58,11 +115,13 @@ function resolveGoogleGenAIClient(options, factory, injected) {
|
|
|
58
115
|
// An EMPTY variable is not a setting. `GOOGLE_CLOUD_PROJECT=` in a shell or a
|
|
59
116
|
// CI matrix is how a project comes to be "present" and unusable — the guards
|
|
60
117
|
// below would pass and the SDK would fail on the first call instead.
|
|
61
|
-
const set = (value) => value !== undefined && value.trim().length > 0 ? value : undefined;
|
|
62
118
|
const project = set(options.project) ?? set(env.GOOGLE_CLOUD_PROJECT);
|
|
63
|
-
const
|
|
119
|
+
const resolvedKey = set(apiKey) ??
|
|
120
|
+
(typeof options.apiKey === 'string' ? set(options.apiKey) : undefined) ??
|
|
121
|
+
set(env.GEMINI_API_KEY) ??
|
|
122
|
+
set(env.GOOGLE_API_KEY);
|
|
64
123
|
const vertexai = options.vertexai ?? (project !== undefined ? true : undefined);
|
|
65
|
-
if (vertexai !== false && project === undefined &&
|
|
124
|
+
if (vertexai !== false && project === undefined && resolvedKey === undefined) {
|
|
66
125
|
throw new Error(`${factory}: no Google project and no API key — this factory cannot tell which service ` +
|
|
67
126
|
'you meant, and the SDK would warn on stderr, construct anyway, and fail on the first ' +
|
|
68
127
|
'call with something that reads like a network problem.\n' +
|
|
@@ -93,12 +152,15 @@ function resolveGoogleGenAIClient(options, factory, injected) {
|
|
|
93
152
|
'are built against @google/genai 2.x — update the package.');
|
|
94
153
|
}
|
|
95
154
|
// Only what was ASKED for goes on the wire; everything omitted is left to the
|
|
96
|
-
// SDK's own environment resolution.
|
|
155
|
+
// SDK's own environment resolution. The key is the one exception: the
|
|
156
|
+
// CALLBACK form has already been called by the time we get here, so what
|
|
157
|
+
// travels is the string it answered with — never the function.
|
|
158
|
+
const key = apiKey ?? (typeof options.apiKey === 'string' ? options.apiKey : undefined);
|
|
97
159
|
return new GoogleGenAI({
|
|
98
160
|
...(vertexai !== undefined && { vertexai }),
|
|
99
161
|
...(options.project !== undefined && { project: options.project }),
|
|
100
162
|
...(options.location !== undefined && { location: options.location }),
|
|
101
|
-
...(
|
|
163
|
+
...(key !== undefined && { apiKey: key }),
|
|
102
164
|
...(options.apiVersion !== undefined && { apiVersion: options.apiVersion }),
|
|
103
165
|
...(options.googleAuthOptions !== undefined && {
|
|
104
166
|
googleAuthOptions: options.googleAuthOptions,
|
|
@@ -106,4 +168,89 @@ function resolveGoogleGenAIClient(options, factory, injected) {
|
|
|
106
168
|
});
|
|
107
169
|
}
|
|
108
170
|
exports.resolveGoogleGenAIClient = resolveGoogleGenAIClient;
|
|
171
|
+
/**
|
|
172
|
+
* The client seam every Google adapter calls through — one per request.
|
|
173
|
+
*
|
|
174
|
+
* Three paths, and only the third one is new:
|
|
175
|
+
*
|
|
176
|
+
* 1. **`_client` injected** — the double, always, no SDK, no environment.
|
|
177
|
+
* Any `apiKey` still rides along on the lease so redaction tests can
|
|
178
|
+
* prove a secret never reaches an error message.
|
|
179
|
+
* 2. **`apiKey` a string (or absent)** — the client is built ONCE. With
|
|
180
|
+
* `eager`, it is built at factory time, so a missing peer dependency and
|
|
181
|
+
* an unanswerable door are still refused where the consumer typed the
|
|
182
|
+
* call rather than on their first request.
|
|
183
|
+
* 3. **`apiKey` a callback** — called before every request. The client is
|
|
184
|
+
* rebuilt only when the answer CHANGED, so a cached token costs one
|
|
185
|
+
* function call. Construction cannot be eager here: calling a consumer's
|
|
186
|
+
* credential provider from a factory would fetch a token nobody asked
|
|
187
|
+
* for yet, and possibly before their own setup ran.
|
|
188
|
+
*
|
|
189
|
+
* @param eager build now (path 2 only), preserving construction-time refusals.
|
|
190
|
+
*/
|
|
191
|
+
function createGoogleGenAIClientResolver(options, factory, injected, eager = false) {
|
|
192
|
+
const source = options.apiKey;
|
|
193
|
+
if (injected) {
|
|
194
|
+
// A double stands in for the SDK CLIENT, not for the credential. So a
|
|
195
|
+
// callback is still called per request here: it is the only way a test can
|
|
196
|
+
// watch a key rotate, and the only way redaction can be proved against the
|
|
197
|
+
// key that is actually in force rather than the one at construction.
|
|
198
|
+
if (typeof source !== 'function') {
|
|
199
|
+
const lease = {
|
|
200
|
+
client: injected,
|
|
201
|
+
...(source !== undefined && { apiKey: source }),
|
|
202
|
+
};
|
|
203
|
+
return async () => lease;
|
|
204
|
+
}
|
|
205
|
+
return async () => ({ client: injected, apiKey: await resolveKey(source, factory) });
|
|
206
|
+
}
|
|
207
|
+
if (typeof source !== 'function') {
|
|
208
|
+
let built = eager
|
|
209
|
+
? resolveGoogleGenAIClient(options, factory, undefined)
|
|
210
|
+
: undefined;
|
|
211
|
+
return async () => {
|
|
212
|
+
built ??= resolveGoogleGenAIClient(options, factory, undefined);
|
|
213
|
+
return { client: built, ...(source !== undefined && { apiKey: source }) };
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
// The callback path. `lastKey` is held so the SDK client survives a callback
|
|
217
|
+
// that keeps answering the same thing — which is what a well-behaved token
|
|
218
|
+
// cache does on all but one call in a thousand.
|
|
219
|
+
let lastKey;
|
|
220
|
+
let built;
|
|
221
|
+
return async () => {
|
|
222
|
+
const answer = await resolveKey(source, factory);
|
|
223
|
+
if (built === undefined || answer !== lastKey) {
|
|
224
|
+
built = resolveGoogleGenAIClient(options, factory, undefined, answer);
|
|
225
|
+
lastKey = answer;
|
|
226
|
+
}
|
|
227
|
+
return { client: built, apiKey: answer };
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
exports.createGoogleGenAIClientResolver = createGoogleGenAIClientResolver;
|
|
231
|
+
/** Call the credential callback and insist on something usable. */
|
|
232
|
+
async function resolveKey(source, factory) {
|
|
233
|
+
const answer = await source();
|
|
234
|
+
if (typeof answer === 'string' && answer.trim().length > 0)
|
|
235
|
+
return answer;
|
|
236
|
+
// The value is NOT quoted back. It is a credential when it is right, and it
|
|
237
|
+
// is whatever the consumer's provider returned when it is wrong; a message
|
|
238
|
+
// that printed it would be the one place this library leaks one.
|
|
239
|
+
throw new Error(`${factory}: the \`apiKey\` callback returned ${describeKey(answer)}, and a key has to be a ` +
|
|
240
|
+
'non-empty string.\n' +
|
|
241
|
+
' The callback is called before every request, so this is a live credential failure, ' +
|
|
242
|
+
'not a configuration one — a token fetch that failed usually throws rather than returning ' +
|
|
243
|
+
'nothing.\n' +
|
|
244
|
+
` Fix: return the token, or throw from the callback so ${factory} can report why.`);
|
|
245
|
+
}
|
|
246
|
+
/** What came back, said without saying it. */
|
|
247
|
+
function describeKey(value) {
|
|
248
|
+
if (typeof value === 'string')
|
|
249
|
+
return 'an empty string';
|
|
250
|
+
if (value === null)
|
|
251
|
+
return 'null';
|
|
252
|
+
if (value === undefined)
|
|
253
|
+
return 'undefined';
|
|
254
|
+
return `a ${typeof value}`;
|
|
255
|
+
}
|
|
109
256
|
//# sourceMappingURL=googleGenAI.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"googleGenAI.js","sourceRoot":"","sources":["../../../src/adapters/llm/googleGenAI.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"googleGenAI.js","sourceRoot":"","sources":["../../../src/adapters/llm/googleGenAI.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;;;AAEH,6DAAuD;AAgDvD,uFAAuF;AACvF,MAAM,GAAG,GAAG,CAAC,KAAyB,EAAsB,EAAE,CAC5D,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAErE,mDAAmD;AACnD,MAAM,WAAW,GAAG,GAAuC,EAAE,CAC3D,CAAC,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAuC,CAAC;AAe5F;;;;;;;;;;GAUG;AACH,SAAgB,iBAAiB,CAC/B,OAAqC,EACrC,OAAO,GAAG,IAAI;IAEd,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI;QAAE,OAAO,QAAQ,CAAC;IAC/C,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK;QAAE,OAAO,YAAY,CAAC;IACpD,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACzC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IAC3F,MAAM,GAAG,GACP,CAAC,OAAO,OAAO,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACzE,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC;QACvB,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC1B,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC;AACrD,CAAC;AAbD,8CAaC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,wBAAwB,CACtC,OAAqC,EACrC,OAAe,EACf,QAAY,EACZ,MAAe;IAEf,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9B,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC;IAC1B,0EAA0E;IAC1E,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,8EAA8E;IAC9E,6EAA6E;IAC7E,qEAAqE;IACrE,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACtE,MAAM,WAAW,GACf,GAAG,CAAC,MAAM,CAAC;QACX,CAAC,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACtE,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC;QACvB,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC1B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAEhF,IAAI,QAAQ,KAAK,KAAK,IAAI,OAAO,KAAK,SAAS,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC7E,MAAM,IAAI,KAAK,CACb,GAAG,OAAO,8EAA8E;YACtF,uFAAuF;YACvF,0DAA0D;YAC1D,kBAAkB,OAAO,gEAAgE;YACzF,gFAAgF;YAChF,kBAAkB,OAAO,oDAAoD;YAC7E,mBAAmB;YACnB,kBAAkB,OAAO,6DAA6D;YACtF,0BAA0B,CAC7B,CAAC;IACJ,CAAC;IACD,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CACb,GAAG,OAAO,gFAAgF;YACxF,qFAAqF;YACrF,6CAA6C,CAChD,CAAC;IACJ,CAAC;IAED,IAAI,WAAkE,CAAC;IACvE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAA,4BAAW,EACrB,eAAe,CAChB,CAAC;QACF,WAAW,GAAG,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,OAAO,EAAE,WAAW,CAE7C,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,GAAG,OAAO,4CAA4C;YACpD,yCAAyC;YACzC,yCAAyC,CAC5C,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,GAAG,OAAO,kFAAkF;YAC1F,2DAA2D,CAC9D,CAAC;IACJ,CAAC;IAED,8EAA8E;IAC9E,sEAAsE;IACtE,yEAAyE;IACzE,+DAA+D;IAC/D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACxF,OAAO,IAAI,WAAW,CAAC;QACrB,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC3C,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;QAClE,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrE,GAAG,CAAC,GAAG,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;QACzC,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;QAC3E,GAAG,CAAC,OAAO,CAAC,iBAAiB,KAAK,SAAS,IAAI;YAC7C,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;SAC7C,CAAC;KACH,CAAC,CAAC;AACL,CAAC;AAlFD,4DAkFC;AAmBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAgB,+BAA+B,CAC7C,OAAqC,EACrC,OAAe,EACf,QAAY,EACZ,KAAK,GAAG,KAAK;IAEb,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAE9B,IAAI,QAAQ,EAAE,CAAC;QACb,sEAAsE;QACtE,2EAA2E;QAC3E,2EAA2E;QAC3E,qEAAqE;QACrE,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;YACjC,MAAM,KAAK,GAA8B;gBACvC,MAAM,EAAE,QAAQ;gBAChB,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;aAChD,CAAC;YACF,OAAO,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC;QAC3B,CAAC;QACD,OAAO,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACvF,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;QACjC,IAAI,KAAK,GAAkB,KAAK;YAC9B,CAAC,CAAC,wBAAwB,CAAI,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC;YAC1D,CAAC,CAAC,SAAS,CAAC;QACd,OAAO,KAAK,IAAI,EAAE;YAChB,KAAK,KAAK,wBAAwB,CAAI,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;YACnE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAC5E,CAAC,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,2EAA2E;IAC3E,gDAAgD;IAChD,IAAI,OAA2B,CAAC;IAChC,IAAI,KAAoB,CAAC;IACzB,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjD,IAAI,KAAK,KAAK,SAAS,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YAC9C,KAAK,GAAG,wBAAwB,CAAI,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;YACzE,OAAO,GAAG,MAAM,CAAC;QACnB,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC3C,CAAC,CAAC;AACJ,CAAC;AA9CD,0EA8CC;AAED,mEAAmE;AACnE,KAAK,UAAU,UAAU,CACvB,MAAsC,EACtC,OAAe;IAEf,MAAM,MAAM,GAAG,MAAM,MAAM,EAAE,CAAC;IAC9B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IAC1E,4EAA4E;IAC5E,2EAA2E;IAC3E,iEAAiE;IACjE,MAAM,IAAI,KAAK,CACb,GAAG,OAAO,sCAAsC,WAAW,CAAC,MAAM,CAAC,0BAA0B;QAC3F,qBAAqB;QACrB,uFAAuF;QACvF,2FAA2F;QAC3F,YAAY;QACZ,2DAA2D,OAAO,kBAAkB,CACvF,CAAC;AACJ,CAAC;AAED,8CAA8C;AAC9C,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,iBAAiB,CAAC;IACxD,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC;IAC5C,OAAO,KAAK,OAAO,KAAK,EAAE,CAAC;AAC7B,CAAC"}
|
|
@@ -65,6 +65,22 @@
|
|
|
65
65
|
* before every overwrite — so even an address collision is refused rather than
|
|
66
66
|
* written through.
|
|
67
67
|
*
|
|
68
|
+
* ── The retrieved NAME is not the name you wrote ────────────────────────────
|
|
69
|
+
* A live field trial on the raw service found this and it is worth stating,
|
|
70
|
+
* because it is exactly the assumption an adapter is tempted to make: `create`
|
|
71
|
+
* and `list` came back with the caller-chosen memory ids, while SIMILARITY
|
|
72
|
+
* RETRIEVAL answered with generated numeric resource names for the very same
|
|
73
|
+
* facts (FINDINGS "Agent Runtime Memory Bank"). Anything reading an entry id
|
|
74
|
+
* out of `memory.name` would therefore work perfectly on `list()` and hand back
|
|
75
|
+
* unusable ids from `search()` — ids that no `get()` or `delete()` could find.
|
|
76
|
+
*
|
|
77
|
+
* This adapter never does that: `toEntry` reads the id from the metadata this
|
|
78
|
+
* library wrote, and the resource name is carried only as
|
|
79
|
+
* `entry.metadata.resourceName`, for looking at. The same trial also confirmed
|
|
80
|
+
* exact-match scope semantics (a partial `{tenant}` scope retrieved nothing)
|
|
81
|
+
* and the distance-not-similarity ranking that {@link MemoryBankStore.search}
|
|
82
|
+
* converts.
|
|
83
|
+
*
|
|
68
84
|
* ── Writes are long-running operations ──────────────────────────────────────
|
|
69
85
|
* `create`, `patch` and `delete` all answer with an Operation rather than the
|
|
70
86
|
* resource — verified against the installed SDK's own return types. Every
|
|
@@ -88,7 +104,7 @@
|
|
|
88
104
|
* through the shared REST client in `adapters/google/aiPlatform.ts`.
|
|
89
105
|
*/
|
|
90
106
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
91
|
-
exports.scoreFromDistance = exports.memoryBankStore = exports.MemoryBankStore = exports.MAX_PAGE_SIZE = void 0;
|
|
107
|
+
exports.scoreFromDistance = exports.memoryBankStore = exports.MemoryBankStore = exports.MAX_PAGE_SIZE = exports.MAX_CARRIED_JSON = void 0;
|
|
92
108
|
const aiPlatform_js_1 = require("../google/aiPlatform.js");
|
|
93
109
|
const ADAPTER = 'memoryBankStore';
|
|
94
110
|
/**
|
|
@@ -107,7 +123,44 @@ const META = {
|
|
|
107
123
|
updatedAt: 'agentfootprint_updated_at',
|
|
108
124
|
tier: 'agentfootprint_tier',
|
|
109
125
|
json: 'agentfootprint_json',
|
|
126
|
+
/** {@link MemoryEntry.source}, verbatim, as JSON. See {@link MAX_CARRIED_JSON}. */
|
|
127
|
+
source: 'agentfootprint_source',
|
|
128
|
+
/** {@link MemoryEntry.metadata} the CALLER wrote, verbatim, as JSON. */
|
|
129
|
+
meta: 'agentfootprint_metadata',
|
|
130
|
+
/** {@link MemoryEntry.decayPolicy}, as JSON — retrieval ranking depends on it. */
|
|
131
|
+
decay: 'agentfootprint_decay',
|
|
110
132
|
};
|
|
133
|
+
/**
|
|
134
|
+
* The metadata keys this adapter GENERATES on the way out, which therefore
|
|
135
|
+
* cannot also be caller metadata.
|
|
136
|
+
*
|
|
137
|
+
* They are read-side facts about the row, not stored data: `source` names the
|
|
138
|
+
* backend, `resourceName` is the Vertex name, `distance` is the raw retrieval
|
|
139
|
+
* distance. Round-tripping an entry this store handed back must not fail, and
|
|
140
|
+
* a caller's own value under one of these names must not be silently replaced
|
|
141
|
+
* — see {@link splitMetadata} for how those two are told apart.
|
|
142
|
+
*/
|
|
143
|
+
const GENERATED_METADATA = ['source', 'resourceName', 'distance'];
|
|
144
|
+
/**
|
|
145
|
+
* What a Vertex resource name for a memory looks like:
|
|
146
|
+
* `projects/<p>/locations/<l>/reasoningEngines/<e>/memories/<resource id>`.
|
|
147
|
+
*
|
|
148
|
+
* Used as the SHAPE half of recognising this adapter's own `resourceName` on the
|
|
149
|
+
* way back in — never as a parser. Nothing reads an id out of a resource name
|
|
150
|
+
* here (the module header says why); this only answers "could this string be one
|
|
151
|
+
* of ours?".
|
|
152
|
+
*/
|
|
153
|
+
const RESOURCE_NAME = /^projects\/.+\/memories\/[^/]+$/;
|
|
154
|
+
/**
|
|
155
|
+
* The most JSON one carried field may be, in characters.
|
|
156
|
+
*
|
|
157
|
+
* Memory Bank metadata values are typed scalars and this column has NOT
|
|
158
|
+
* measured the service's own ceiling on a string one, so the bound is this
|
|
159
|
+
* adapter's own and says so where it refuses. It is a refusal rather than a
|
|
160
|
+
* truncation on purpose: provenance that came back shortened would be
|
|
161
|
+
* provenance nobody could tell was shortened.
|
|
162
|
+
*/
|
|
163
|
+
exports.MAX_CARRIED_JSON = 8_192;
|
|
111
164
|
/** The service's own ceiling on a page or a top-k. Larger values are coerced. */
|
|
112
165
|
exports.MAX_PAGE_SIZE = 100;
|
|
113
166
|
/** What a `list()` page carries when nothing was asked for. */
|
|
@@ -115,9 +168,22 @@ const DEFAULT_PAGE_SIZE = 20;
|
|
|
115
168
|
/**
|
|
116
169
|
* A `MemoryStore` over Vertex AI Memory Bank.
|
|
117
170
|
*
|
|
118
|
-
* **Status:
|
|
119
|
-
*
|
|
120
|
-
*
|
|
171
|
+
* **Status: field-validated on the data plane (2026-08-14).** An independent
|
|
172
|
+
* trial ran THIS class against a real Memory Bank and every one of these
|
|
173
|
+
* answered a live request: the honest `supportsVectorSearch: false` /
|
|
174
|
+
* `ranksBy: 'server-text'` declarations; cross-conversation recall under a
|
|
175
|
+
* widened `scopeFor`; two identities using the SAME entry id without collision
|
|
176
|
+
* or disclosure; string and structured values; opaque pagination cursors; tier
|
|
177
|
+
* filtering; text similarity that found the right marker, excluded the other
|
|
178
|
+
* identity, returned finite distances and converted them to correctly ORDERED
|
|
179
|
+
* scores; overwrite advancing value and version; a scoped delete leaving the
|
|
180
|
+
* other identity intact; `forget()` erasing across the widened scope; and the
|
|
181
|
+
* five unsupported operations refusing by name rather than pretending.
|
|
182
|
+
*
|
|
183
|
+
* The same trial found the one thing that was NOT faithful — `source` and
|
|
184
|
+
* caller `metadata` were accepted and silently dropped — which 9.30.0 fixes by
|
|
185
|
+
* carrying them (see `toMemory`). That fix is tested here and has not itself
|
|
186
|
+
* been re-run in a live project.
|
|
121
187
|
*/
|
|
122
188
|
class MemoryBankStore {
|
|
123
189
|
/**
|
|
@@ -732,10 +798,29 @@ const num = (value) => ({ doubleValue: value });
|
|
|
732
798
|
* retrieval over structured values is close to meaningless. Store a sentence
|
|
733
799
|
* when you want it found.
|
|
734
800
|
*
|
|
735
|
-
*
|
|
801
|
+
* ── What is CARRIED, and why it took a field trial ──────────────────────────
|
|
802
|
+
* `source`, the caller's own `metadata` and `decayPolicy` are stored as JSON
|
|
803
|
+
* under prefixed keys of ours and restored verbatim on the way back.
|
|
804
|
+
*
|
|
805
|
+
* They were dropped until 9.30.0, and an independent trial (2026-08-14) is what
|
|
806
|
+
* made that visible: an entry written with `source.turn`, `source.messageId`
|
|
807
|
+
* and `metadata.classification` came back carrying only this adapter's own
|
|
808
|
+
* metadata. That is a contract failure and not merely a gap —
|
|
809
|
+
* `MemorySource.identity` is documented as a field "storage adapters MUST
|
|
810
|
+
* preserve verbatim on every read/write", and the fields feed audit, causal
|
|
811
|
+
* chains, decay and retrieval policy. A store that accepts them, reports
|
|
812
|
+
* success and silently forgets them is the exact shape of wrongness this
|
|
813
|
+
* library refuses everywhere else.
|
|
814
|
+
*
|
|
815
|
+
* `embedding` and `embeddingModel` are the ONE pair still dropped, and that is
|
|
816
|
+
* a refusal rather than an oversight: there is nowhere to put a vector here and
|
|
817
|
+
* nothing that would rank it (see `supportsVectorSearch`), and a stored
|
|
818
|
+
* `embeddingModel` with no vector beside it would claim a compatibility that
|
|
819
|
+
* does not exist.
|
|
736
820
|
*/
|
|
737
821
|
function toMemory(entry, scope, defaultTtl) {
|
|
738
822
|
const isString = typeof entry.value === 'string';
|
|
823
|
+
const carried = splitMetadata(entry);
|
|
739
824
|
const metadata = {
|
|
740
825
|
[META.id]: str(entry.id),
|
|
741
826
|
[META.version]: num(entry.version),
|
|
@@ -743,6 +828,15 @@ function toMemory(entry, scope, defaultTtl) {
|
|
|
743
828
|
[META.updatedAt]: num(entry.updatedAt),
|
|
744
829
|
[META.json]: { boolValue: !isString },
|
|
745
830
|
...(entry.tier !== undefined && { [META.tier]: str(entry.tier) }),
|
|
831
|
+
...(entry.source !== undefined && {
|
|
832
|
+
[META.source]: str(carriedJson(entry.source, 'source', entry.id)),
|
|
833
|
+
}),
|
|
834
|
+
...(carried !== undefined && {
|
|
835
|
+
[META.meta]: str(carriedJson(carried, 'metadata', entry.id)),
|
|
836
|
+
}),
|
|
837
|
+
...(entry.decayPolicy !== undefined && {
|
|
838
|
+
[META.decay]: str(carriedJson(entry.decayPolicy, 'decayPolicy', entry.id)),
|
|
839
|
+
}),
|
|
746
840
|
};
|
|
747
841
|
return {
|
|
748
842
|
fact: isString ? entry.value : JSON.stringify(entry.value ?? null),
|
|
@@ -756,6 +850,128 @@ function toMemory(entry, scope, defaultTtl) {
|
|
|
756
850
|
: defaultTtl !== undefined && { ttl: defaultTtl }),
|
|
757
851
|
};
|
|
758
852
|
}
|
|
853
|
+
/**
|
|
854
|
+
* The caller's own metadata, with this adapter's generated keys separated out
|
|
855
|
+
* — or `undefined` when there is none to carry.
|
|
856
|
+
*
|
|
857
|
+
* The three keys in {@link GENERATED_METADATA} are produced on every read, so
|
|
858
|
+
* an entry that came out of this store carries them and a `get`-then-`put`
|
|
859
|
+
* round trip hands them straight back. Two cases, told apart rather than
|
|
860
|
+
* conflated:
|
|
861
|
+
*
|
|
862
|
+
* • **It is one of ours coming home** — recognised by IDENTITY, not by shape.
|
|
863
|
+
* See {@link isGeneratedValue}.
|
|
864
|
+
* • **It is the caller's own value** under one of those names — refused by
|
|
865
|
+
* name. Storing it would mean the next read silently answered with our
|
|
866
|
+
* value instead of theirs, which is the failure mode this whole function
|
|
867
|
+
* exists to end.
|
|
868
|
+
*/
|
|
869
|
+
function splitMetadata(entry) {
|
|
870
|
+
const metadata = entry.metadata;
|
|
871
|
+
if (metadata === undefined || metadata === null)
|
|
872
|
+
return undefined;
|
|
873
|
+
// `toEntry` stamps `source` on EVERY read, so its presence — with exactly the
|
|
874
|
+
// backend name this store writes — is what distinguishes an entry coming home
|
|
875
|
+
// from a caller who happened to pick one of our key names. Computed once for
|
|
876
|
+
// the whole bag, because it is a fact about the ENTRY, not about a key.
|
|
877
|
+
const ours = metadata['source'] === BACKEND_NAME;
|
|
878
|
+
const kept = {};
|
|
879
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
880
|
+
if (!GENERATED_METADATA.includes(key)) {
|
|
881
|
+
kept[key] = value;
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
if (isGeneratedValue(key, value, ours))
|
|
885
|
+
continue;
|
|
886
|
+
throw metadataKeyConflict(entry.id, key, value, ours);
|
|
887
|
+
}
|
|
888
|
+
return Object.keys(kept).length === 0 ? undefined : kept;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Is this the value THIS adapter put under that key on a read — or the caller's
|
|
892
|
+
* own, wearing one of our names?
|
|
893
|
+
*
|
|
894
|
+
* **Recognition is by identity, not by shape**, and the difference is a bug this
|
|
895
|
+
* adapter shipped once. A shape test asks "is `resourceName` a string, is
|
|
896
|
+
* `distance` a number?" — which every string and every number passes, so a
|
|
897
|
+
* caller's `metadata.resourceName = 'sku-42'` or `metadata.distance = 12` was
|
|
898
|
+
* accepted as ours, dropped, and answered on the next read with THIS adapter's
|
|
899
|
+
* resourceName sitting where their value had been. That is precisely the
|
|
900
|
+
* accepted-and-silently-wrong failure {@link metadataKeyConflict} exists to
|
|
901
|
+
* refuse, and a shape test could not tell the two apart even in principle
|
|
902
|
+
* (finding 29, 2026-08-14 field trial).
|
|
903
|
+
*
|
|
904
|
+
* So the test is: `source` must be exactly {@link BACKEND_NAME} — the stamp
|
|
905
|
+
* `toEntry` writes on every read and nothing else does — and `resourceName` /
|
|
906
|
+
* `distance` count as ours only when that stamp RIDES WITH THEM on the same
|
|
907
|
+
* entry, and the value itself still has the right shape (a Vertex resource name;
|
|
908
|
+
* a number). Both halves are needed: the stamp says "this entry came from here",
|
|
909
|
+
* the shape says "and this field was not overwritten since".
|
|
910
|
+
*
|
|
911
|
+
* The one ambiguity left is honest and narrow: a caller who takes an entry out
|
|
912
|
+
* of this store and writes their OWN number into `metadata.distance` before
|
|
913
|
+
* putting it back is indistinguishable from the retrieval distance that came
|
|
914
|
+
* with it, and is dropped. Keep such a value under a name of your own.
|
|
915
|
+
*/
|
|
916
|
+
function isGeneratedValue(key, value, ours) {
|
|
917
|
+
if (key === 'source')
|
|
918
|
+
return value === BACKEND_NAME;
|
|
919
|
+
if (!ours)
|
|
920
|
+
return false;
|
|
921
|
+
if (key === 'resourceName')
|
|
922
|
+
return typeof value === 'string' && RESOURCE_NAME.test(value);
|
|
923
|
+
return typeof value === 'number';
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* A caller's metadata key collides with one this adapter generates on every read.
|
|
927
|
+
*
|
|
928
|
+
* `ours` says whether the entry carried this store's own `source` stamp, which
|
|
929
|
+
* decides WHICH of the two things went wrong — a name collision, or a value
|
|
930
|
+
* edited into a round-tripped entry — and therefore what the reader should do.
|
|
931
|
+
*/
|
|
932
|
+
function metadataKeyConflict(id, key, value, ours) {
|
|
933
|
+
const err = new Error(
|
|
934
|
+
// The caller's own value, echoed back short: enough to recognise which
|
|
935
|
+
// field this is, never enough to paste a whole payload into a log line
|
|
936
|
+
// that an error reaches more sinks than the writer expects.
|
|
937
|
+
`${ADAPTER}: entry '${id}' carries metadata.${key} = ${shortly(value)}, and this ` +
|
|
938
|
+
`adapter GENERATES metadata.${key} on every read.\n` +
|
|
939
|
+
` Storing it would mean your value went in and this adapter's came back — a read that ` +
|
|
940
|
+
`looks right and is not. The three generated keys are: ` +
|
|
941
|
+
`${GENERATED_METADATA.join(', ')} (the backend name, the Vertex resource name, and the ` +
|
|
942
|
+
`raw retrieval distance).\n` +
|
|
943
|
+
(ours
|
|
944
|
+
? ` This entry does carry this store's own metadata.source = '${BACKEND_NAME}', so it ` +
|
|
945
|
+
`came from here — but this particular value is not the one this adapter wrote ` +
|
|
946
|
+
`(a resourceName must look like 'projects/…/memories/<id>'; a distance must be a ` +
|
|
947
|
+
`number). It is refused rather than dropped, because dropping a value somebody ` +
|
|
948
|
+
`deliberately set is the same silent wrongness in the other direction.\n`
|
|
949
|
+
: '') +
|
|
950
|
+
` Fix: rename the key on your entry. Values this store itself produced are recognised ` +
|
|
951
|
+
`and dropped instead, so reading an entry and writing it back is always safe.`);
|
|
952
|
+
err.name = 'MemoryMetadataConflictError';
|
|
953
|
+
return err;
|
|
954
|
+
}
|
|
955
|
+
/** A value as at most 80 characters of JSON, for a refusal to point with. */
|
|
956
|
+
function shortly(value) {
|
|
957
|
+
const json = JSON.stringify(value) ?? String(value);
|
|
958
|
+
return json.length <= 80 ? json : `${json.slice(0, 77)}…`;
|
|
959
|
+
}
|
|
960
|
+
/** One carried field as JSON, or a refusal that says which field and how big. */
|
|
961
|
+
function carriedJson(value, field, id) {
|
|
962
|
+
const json = JSON.stringify(value ?? null);
|
|
963
|
+
if (json.length <= exports.MAX_CARRIED_JSON)
|
|
964
|
+
return json;
|
|
965
|
+
throw new RangeError(`${ADAPTER}: entry '${id}' has a '${field}' of ${json.length} JSON characters, over this ` +
|
|
966
|
+
`adapter's ${exports.MAX_CARRIED_JSON}-character bound for a carried field.\n` +
|
|
967
|
+
` Memory Bank stores these as metadata STRINGS, and the service's own ceiling on one is ` +
|
|
968
|
+
`not measured by this library — so the bound is ours and deliberately conservative.\n` +
|
|
969
|
+
` It refuses rather than truncating: provenance that came back shortened would be ` +
|
|
970
|
+
`provenance nothing could tell was shortened. Keep large payloads in the entry VALUE, ` +
|
|
971
|
+
`which becomes the memory's fact.`);
|
|
972
|
+
}
|
|
973
|
+
/** What this adapter names itself as the backend, in a returned entry's metadata. */
|
|
974
|
+
const BACKEND_NAME = 'vertex-memory-bank';
|
|
759
975
|
/**
|
|
760
976
|
* `Memory` → `MemoryEntry`, or `null` for a row this store did not write.
|
|
761
977
|
*
|
|
@@ -788,6 +1004,9 @@ function toEntry(memory, retrieved) {
|
|
|
788
1004
|
const updatedAt = metadata[META.updatedAt]?.doubleValue ?? toMillis(memory.updateTime);
|
|
789
1005
|
const tier = metadata[META.tier]?.stringValue;
|
|
790
1006
|
const expireTime = toMillis(memory.expireTime);
|
|
1007
|
+
const source = carriedBack(metadata[META.source]?.stringValue);
|
|
1008
|
+
const callerMetadata = carriedBack(metadata[META.meta]?.stringValue);
|
|
1009
|
+
const decayPolicy = carriedBack(metadata[META.decay]?.stringValue);
|
|
791
1010
|
return {
|
|
792
1011
|
id,
|
|
793
1012
|
value: value,
|
|
@@ -798,8 +1017,14 @@ function toEntry(memory, retrieved) {
|
|
|
798
1017
|
accessCount: 0,
|
|
799
1018
|
...(expireTime > 0 && { ttl: expireTime }),
|
|
800
1019
|
...(tier === 'hot' || tier === 'warm' || tier === 'cold' ? { tier } : {}),
|
|
1020
|
+
...(source !== undefined && { source }),
|
|
1021
|
+
...(decayPolicy !== undefined && { decayPolicy }),
|
|
801
1022
|
metadata: {
|
|
802
|
-
|
|
1023
|
+
// The caller's own metadata first, then this adapter's generated facts.
|
|
1024
|
+
// They cannot collide: a caller value under a generated key is refused at
|
|
1025
|
+
// write time rather than overwritten here (see splitMetadata).
|
|
1026
|
+
...callerMetadata,
|
|
1027
|
+
source: BACKEND_NAME,
|
|
803
1028
|
...(memory.name !== null && memory.name !== undefined && { resourceName: memory.name }),
|
|
804
1029
|
// The raw distance, carried through unmodified — the honest number
|
|
805
1030
|
// beside the converted one, for a caller who knows the metric.
|
|
@@ -807,6 +1032,25 @@ function toEntry(memory, retrieved) {
|
|
|
807
1032
|
},
|
|
808
1033
|
};
|
|
809
1034
|
}
|
|
1035
|
+
/**
|
|
1036
|
+
* One carried JSON field on the way back, or `undefined`.
|
|
1037
|
+
*
|
|
1038
|
+
* Unparseable JSON answers `undefined` rather than raising: the bytes were
|
|
1039
|
+
* written by an older release of this adapter or edited in the console, and a
|
|
1040
|
+
* memory that EXISTS must not read as one that was never written over a
|
|
1041
|
+
* damaged provenance field. The entry's own value is unaffected.
|
|
1042
|
+
*/
|
|
1043
|
+
function carriedBack(json) {
|
|
1044
|
+
if (typeof json !== 'string' || json === '')
|
|
1045
|
+
return undefined;
|
|
1046
|
+
try {
|
|
1047
|
+
const parsed = JSON.parse(json);
|
|
1048
|
+
return parsed === null ? undefined : parsed;
|
|
1049
|
+
}
|
|
1050
|
+
catch {
|
|
1051
|
+
return undefined;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
810
1054
|
/** An RFC 3339 timestamp as unix milliseconds, or 0 when it is not one. */
|
|
811
1055
|
function toMillis(value) {
|
|
812
1056
|
if (typeof value !== 'string')
|