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,372 @@
1
+ "use strict";
2
+ /**
3
+ * agentEngineSessions — conversations in Vertex AI's own session service, so a
4
+ * fleet shares them.
5
+ *
6
+ * `memorySessions()` loses everything on restart and says so. `sqliteSessions()`
7
+ * survives a restart on ONE machine and says so. The row above both — *many
8
+ * containers, one conversation* — is where a managed session service belongs,
9
+ * and on this column that service is the `sessions` collection under a
10
+ * reasoning engine.
11
+ *
12
+ * ── The name, said once ─────────────────────────────────────────────────────
13
+ * The product was **Agent Engine**, is now **Agent Runtime**, and the API
14
+ * resource is still spelled `reasoningEngines`. This factory keeps the name it
15
+ * was designed under; where the product name and the API disagree, the API is
16
+ * the one that has not moved.
17
+ *
18
+ * ── The fit, and it is a good one ───────────────────────────────────────────
19
+ * `Session.sessionState` is an arbitrary JSON `Struct`. A `CheckpointEnvelope`
20
+ * is arbitrary JSON. So the envelope goes in whole, under one key, and comes
21
+ * back whole — no event log to fold, no blob encoding to get wrong, no
22
+ * per-turn append. That is a materially better fit than the other column's
23
+ * session store, which had to learn the hard way that an object handed to an
24
+ * event blob comes back as somebody else's `toString()`.
25
+ *
26
+ * ── The four facts that shaped the code, all read off the installed SDK ─────
27
+ * 1. **`sessions.create` takes a caller-supplied `sessionId`.** So our session
28
+ * id IS the resource id and `hydrate` is one `get` by name. No mapping
29
+ * table, no listing to find a conversation.
30
+ * 2. **`create` and `delete` answer a long-running Operation; `get` and
31
+ * `patch` answer the Session.** Every write here therefore waits for the
32
+ * operation to report `done` before it returns — a `persist` that returned
33
+ * early would make the very next `hydrate` a race whose failure mode is
34
+ * "no conversation", which nobody can tell from a new user.
35
+ * 3. **`Session.userId` is required and immutable.** Our port's
36
+ * `persist(sessionId, envelope)` carries no user, so one has to be
37
+ * resolved — see {@link AgentEngineSessionsOptions.userId}. Immutable
38
+ * means the first write decides forever, which is exactly the ownership
39
+ * rule this library already enforces in its own stores; here the service
40
+ * enforces it for us.
41
+ * 4. **`ttl` is input-only with a 24-hour floor**, and `expireTime` always
42
+ * comes back. Sliding expiry is free; an hour-long TTL is not available at
43
+ * any price.
44
+ *
45
+ * ── The laws it inherits rather than re-implements ──────────────────────────
46
+ * `checkEnvelope` runs on the way OUT and on the way IN, so an envelope whose
47
+ * `format` this runtime does not know is refused by name, and a session that
48
+ * is PRESENT but unreadable is refused by name too. Only a session that was
49
+ * never written hydrates as `undefined`. A conversation that exists and cannot
50
+ * be read must never be answered with a fresh start — that failure is
51
+ * indistinguishable, from the outside, from a brand-new user.
52
+ */
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.agentEngineSessions = exports.DEFAULT_USER_ID = exports.SESSION_STATE_KEY = void 0;
55
+ const envelope_js_1 = require("../../hosting/envelope.js");
56
+ const errors_js_1 = require("../../hosting/errors.js");
57
+ const aiPlatform_js_1 = require("../google/aiPlatform.js");
58
+ const ADAPTER = 'agentEngineSessions';
59
+ /**
60
+ * The `sessionState` key the envelope lives under.
61
+ *
62
+ * One key, namespaced, rather than spreading the envelope's own fields across
63
+ * `sessionState`: the struct belongs to whoever owns the reasoning engine, an
64
+ * agent framework is a guest in it, and a guest that scatters `format` and
65
+ * `data` at the top level collides with the next guest. Namespacing also makes
66
+ * the console readable — one entry that says whose it is.
67
+ */
68
+ exports.SESSION_STATE_KEY = 'agentfootprint.envelope';
69
+ /** What a conversation that named nobody is stored under. */
70
+ exports.DEFAULT_USER_ID = 'agentfootprint-anonymous';
71
+ /**
72
+ * Conversations in Vertex AI's session service — the store that survives a
73
+ * fleet, not just a restart.
74
+ *
75
+ * **Status: contract-shaped and tested; awaiting field use.** Every call is
76
+ * exercised through an injected client and pinned against the really-installed
77
+ * SDK. None of it has yet answered a request from Google in a real project.
78
+ *
79
+ * @example A standing agent whose conversations are shared across instances
80
+ * import { standingAgent, nodeHost } from 'agentfootprint/hosting';
81
+ * import { agentEngineSessions } from 'agentfootprint/hosting';
82
+ *
83
+ * const handle = await standingAgent({
84
+ * agentFactory: () => buildAgent(),
85
+ * host: nodeHost({ port: 8080 }),
86
+ * sessions: agentEngineSessions({
87
+ * project: 'my-project',
88
+ * location: 'us-central1',
89
+ * reasoningEngine: '1234567890',
90
+ * }),
91
+ * });
92
+ */
93
+ function agentEngineSessions(options) {
94
+ const scope = (0, aiPlatform_js_1.resolveEngine)(ADAPTER, options);
95
+ const client = (0, aiPlatform_js_1.buildAiPlatformClient)(ADAPTER, options, scope);
96
+ const sessions = client.projects.locations.reasoningEngines.sessions;
97
+ const operationTimeoutMs = options.operationTimeoutMs ?? aiPlatform_js_1.DEFAULT_OPERATION_TIMEOUT_MS;
98
+ const resolveUserId = userIdResolver(options.userId);
99
+ let closed = false;
100
+ const open = (verb) => {
101
+ if (!closed)
102
+ return;
103
+ throw new Error(`[hosting] the ${ADAPTER} store for '${scope.parent}' is closed, so it cannot ${verb}. ` +
104
+ `close() is final by design — reconnecting behind you would hide a shutdown-ordering ` +
105
+ `bug rather than surface it. Build a new store if you need one after closing this.`);
106
+ };
107
+ const nameOf = (sessionId) => `${scope.parent}/sessions/${(0, aiPlatform_js_1.safeResourceId)(sessionId)}`;
108
+ return {
109
+ parent: scope.parent,
110
+ async hydrate(sessionId) {
111
+ open('hydrate a session');
112
+ let session;
113
+ try {
114
+ session = (await sessions.get({ name: nameOf(sessionId) }))?.data;
115
+ }
116
+ catch (err) {
117
+ // The ONE failure that means "no conversation". Everything else is a
118
+ // failure to READ, which is a different fact and never answered with a
119
+ // fresh start.
120
+ if ((0, aiPlatform_js_1.isNotFound)(err))
121
+ return undefined;
122
+ throw (0, aiPlatform_js_1.googleSdkFailure)(ADAPTER, 'sessions.get', err);
123
+ }
124
+ if (session === undefined)
125
+ return undefined;
126
+ const state = session.sessionState;
127
+ // A session with no state at all was created by something that is not
128
+ // this library — or by us, and never written to. Nothing here ever
129
+ // claimed to be a conversation, so it is an absence.
130
+ if (state === null || state === undefined)
131
+ return undefined;
132
+ const stored = state[exports.SESSION_STATE_KEY];
133
+ if (stored === undefined)
134
+ return undefined;
135
+ // Present and not an object: a conversation EXISTS here and this runtime
136
+ // cannot read it. Refused by name, never as `undefined`.
137
+ if (stored === null || typeof stored !== 'object') {
138
+ throw new errors_js_1.UnreadableEnvelopeError(stored, sessionId);
139
+ }
140
+ // Validated HERE as well as in the composer, so a refusal points at the
141
+ // store that produced the bytes rather than at whoever read them next.
142
+ return (0, envelope_js_1.checkEnvelope)(stored, sessionId);
143
+ },
144
+ async persist(sessionId, envelope) {
145
+ open('persist a session');
146
+ // Checked on the way IN as well as out: a session this store could not
147
+ // read back is one it has no business writing.
148
+ const checked = (0, envelope_js_1.checkEnvelope)(envelope, sessionId);
149
+ const name = nameOf(sessionId);
150
+ const body = {
151
+ sessionState: { [exports.SESSION_STATE_KEY]: checked },
152
+ ...(options.ttl !== undefined && { ttl: options.ttl }),
153
+ };
154
+ // PATCH first, CREATE on 404 — rather than the other way round.
155
+ //
156
+ // The steady state of a conversation is "it already exists": a session
157
+ // is created once and written on every turn after that. Trying create
158
+ // first would mean one guaranteed-to-fail call per turn for the life of
159
+ // every conversation, and would burn a long-running operation to learn
160
+ // something a patch answers directly.
161
+ try {
162
+ await sessions.patch({
163
+ name,
164
+ // Only the fields we own. Without a mask a patch is a REPLACE, and a
165
+ // replace would drop `userId` — which is immutable, so the service
166
+ // would refuse the write and a conversation would stop persisting.
167
+ updateMask: maskFor(body),
168
+ requestBody: body,
169
+ });
170
+ return;
171
+ }
172
+ catch (err) {
173
+ if (!(0, aiPlatform_js_1.isNotFound)(err))
174
+ throw (0, aiPlatform_js_1.googleSdkFailure)(ADAPTER, 'sessions.patch', err);
175
+ }
176
+ const userId = resolveUserId(sessionId, checked);
177
+ let created;
178
+ try {
179
+ created = await sessions.create({
180
+ parent: scope.parent,
181
+ sessionId: (0, aiPlatform_js_1.safeResourceId)(sessionId),
182
+ requestBody: { ...body, userId },
183
+ });
184
+ }
185
+ catch (err) {
186
+ // Two writers opened the same conversation at once and the other one
187
+ // won. That is not a failure: the session exists now, which is all
188
+ // this call wanted, so the patch below writes our state onto it.
189
+ if (!(0, aiPlatform_js_1.isAlreadyExists)(err))
190
+ throw (0, aiPlatform_js_1.googleSdkFailure)(ADAPTER, 'sessions.create', err);
191
+ try {
192
+ await sessions.patch({ name, updateMask: maskFor(body), requestBody: body });
193
+ }
194
+ catch (patchErr) {
195
+ throw (0, aiPlatform_js_1.googleSdkFailure)(ADAPTER, 'sessions.patch', patchErr);
196
+ }
197
+ return;
198
+ }
199
+ // OUTSIDE the try on purpose: this refusal is already sanitized and
200
+ // already says what to do, and re-wrapping it would replace a precise
201
+ // diagnosis with a generic one.
202
+ await (0, aiPlatform_js_1.awaitOperation)(ADAPTER, sessions.operations, created?.data, `creating session '${sessionId}'`, operationTimeoutMs);
203
+ },
204
+ async listByUser(userId, listOptions) {
205
+ open('list a user’s sessions');
206
+ const limit = Math.max(1, Math.floor(listOptions?.limit ?? DEFAULT_PAGE));
207
+ let page;
208
+ try {
209
+ page = (await sessions.list({
210
+ parent: scope.parent,
211
+ // The service's own filter over its own immutable field, with the
212
+ // caller's id as a quoted literal — see quoteFilterValue.
213
+ filter: `user_id=${quoteFilterValue(userId)}`,
214
+ orderBy: 'update_time desc',
215
+ pageSize: limit,
216
+ ...(listOptions?.cursor !== undefined && { pageToken: listOptions.cursor }),
217
+ }))?.data;
218
+ }
219
+ catch (err) {
220
+ throw (0, aiPlatform_js_1.googleSdkFailure)(ADAPTER, 'sessions.list', err);
221
+ }
222
+ const summaries = (page?.sessions ?? []).map((session) => {
223
+ const sessionId = lastSegment(session.name);
224
+ const stored = session.sessionState?.[exports.SESSION_STATE_KEY];
225
+ // A listing must not fail because ONE row is unreadable — a sidebar
226
+ // that 500s over a corrupt conversation is worse than one that shows
227
+ // it with an honest zero. The transcript op reads the envelope itself
228
+ // and refuses there, which is where a reader can act on it.
229
+ const readable = stored !== null && typeof stored === 'object' ? stored : undefined;
230
+ return {
231
+ sessionId,
232
+ savedAt: toMillis(session.updateTime ?? session.createTime),
233
+ format: formatOf(readable),
234
+ messageCount: readable === undefined ? 0 : (0, envelope_js_1.envelopeTranscript)(readable).length,
235
+ };
236
+ });
237
+ const cursor = page?.nextPageToken;
238
+ return {
239
+ sessions: summaries,
240
+ ...(typeof cursor === 'string' && cursor !== '' && { cursor }),
241
+ };
242
+ },
243
+ async ownerOf(sessionId) {
244
+ open('read a session’s owner');
245
+ try {
246
+ const session = (await sessions.get({ name: nameOf(sessionId) }))?.data;
247
+ const userId = session?.userId;
248
+ // `undefined` for "no such session" AND for a session stored under the
249
+ // anonymous placeholder — the deliberate ambiguity the composer's one
250
+ // not-found rests on. A store that answered those differently would
251
+ // hand a caller an oracle for which session ids are real.
252
+ return typeof userId === 'string' && userId !== '' && userId !== exports.DEFAULT_USER_ID
253
+ ? userId
254
+ : undefined;
255
+ }
256
+ catch (err) {
257
+ if ((0, aiPlatform_js_1.isNotFound)(err))
258
+ return undefined;
259
+ throw (0, aiPlatform_js_1.googleSdkFailure)(ADAPTER, 'sessions.get', err);
260
+ }
261
+ },
262
+ async forget(sessionId) {
263
+ open('forget a session');
264
+ let deleted;
265
+ try {
266
+ deleted = await sessions.delete({ name: nameOf(sessionId) });
267
+ }
268
+ catch (err) {
269
+ // Already gone is the outcome this asked for.
270
+ if ((0, aiPlatform_js_1.isNotFound)(err))
271
+ return;
272
+ throw (0, aiPlatform_js_1.googleSdkFailure)(ADAPTER, 'sessions.delete', err);
273
+ }
274
+ await (0, aiPlatform_js_1.awaitOperation)(ADAPTER, sessions.operations, deleted?.data, `deleting session '${sessionId}'`, operationTimeoutMs);
275
+ },
276
+ close() {
277
+ closed = true;
278
+ },
279
+ };
280
+ }
281
+ exports.agentEngineSessions = agentEngineSessions;
282
+ // ─── Internals ───────────────────────────────────────────────────────
283
+ /** How many rows one `listByUser` page carries when the caller names no limit. */
284
+ const DEFAULT_PAGE = 50;
285
+ /**
286
+ * A user id as an AIP-160 string literal — **backslash first, then the quote.**
287
+ *
288
+ * The order is the whole point. This grammar honours backslash escapes, so
289
+ * escaping only the quote leaves the escape character itself free to escape our
290
+ * escape: a user id of `\" OR user_id!=` renders as `user_id="\\" OR user_id!=""`,
291
+ * where `\\` is a literal backslash, the quote after it CLOSES the literal, and
292
+ * the rest of the id is filter syntax the service evaluates. The listing then
293
+ * matches every session with a non-empty user id and hands back other people's
294
+ * conversation ids, timestamps and message counts.
295
+ *
296
+ * The benign case matters too: any id merely ENDING in a backslash swallows the
297
+ * closing quote and the call fails with a malformed-filter 400 whose text
298
+ * {@link googleSdkFailure} withholds — a local error delivered as a censored
299
+ * remote one. Escaping the backslash first fixes both.
300
+ *
301
+ * A user id is caller data on every column of this library. It is never
302
+ * concatenated into a query language without passing through here.
303
+ */
304
+ function quoteFilterValue(value) {
305
+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
306
+ }
307
+ /**
308
+ * Resolve the immutable `userId` a session is created under.
309
+ *
310
+ * See {@link AgentEngineSessionsOptions.userId} for why the default reads the
311
+ * envelope rather than inventing anything.
312
+ */
313
+ function userIdResolver(option) {
314
+ if (typeof option === 'function') {
315
+ return (sessionId, envelope) => {
316
+ const resolved = option(sessionId, envelope);
317
+ if (typeof resolved !== 'string' || resolved.trim() === '') {
318
+ throw new TypeError(`${ADAPTER}: the 'userId' resolver returned ${JSON.stringify(resolved)} for session ` +
319
+ `'${sessionId}'. The service requires a non-empty user id on create and will not ` +
320
+ `let it be changed afterwards, so this refuses rather than storing the ` +
321
+ `conversation under a name nobody chose.\n` +
322
+ ` Return '${exports.DEFAULT_USER_ID}' explicitly if an anonymous conversation is what ` +
323
+ `you mean.`);
324
+ }
325
+ return resolved;
326
+ };
327
+ }
328
+ if (typeof option === 'string') {
329
+ if (option.trim() === '') {
330
+ throw new TypeError(`${ADAPTER}: 'userId' was an empty string. The service requires one on create and ` +
331
+ `treats it as immutable. Pass a real id, a resolver function, or leave it unset to ` +
332
+ `derive the owner from the conversation itself.`);
333
+ }
334
+ return () => option;
335
+ }
336
+ // The default: the principal the conversation was signed with, so the
337
+ // service's owner and this library's own ownership index agree.
338
+ return (_sessionId, envelope) => (0, envelope_js_1.envelopeOwner)(envelope) ?? exports.DEFAULT_USER_ID;
339
+ }
340
+ /**
341
+ * The update mask for a patch.
342
+ *
343
+ * A patch with no mask REPLACES the resource, which would clear `userId` — and
344
+ * `userId` is immutable, so the service refuses the write and the conversation
345
+ * silently stops persisting. Naming the fields we own is the whole fix.
346
+ */
347
+ function maskFor(body) {
348
+ const fields = ['sessionState'];
349
+ if (body.ttl !== undefined)
350
+ fields.push('ttl');
351
+ return fields.join(',');
352
+ }
353
+ /** The id out of a resource name, which is everything after the last `/`. */
354
+ function lastSegment(name) {
355
+ if (typeof name !== 'string')
356
+ return '';
357
+ const at = name.lastIndexOf('/');
358
+ return at === -1 ? name : name.slice(at + 1);
359
+ }
360
+ /** An RFC 3339 timestamp as unix milliseconds, or 0 when it is not one. */
361
+ function toMillis(value) {
362
+ if (typeof value !== 'string')
363
+ return 0;
364
+ const parsed = Date.parse(value);
365
+ return Number.isFinite(parsed) ? parsed : 0;
366
+ }
367
+ /** The stored envelope's `format`, without trusting it to be one of ours. */
368
+ function formatOf(stored) {
369
+ const format = stored?.format;
370
+ return typeof format === 'string' ? format : 'unknown';
371
+ }
372
+ //# sourceMappingURL=googleAgentEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"googleAgentEngine.js","sourceRoot":"","sources":["../../../src/adapters/hosting/googleAgentEngine.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;;;AAEH,2DAA6F;AAC7F,uDAAkE;AAOlE,2DAciC;AAEjC,MAAM,OAAO,GAAG,qBAAqB,CAAC;AAEtC;;;;;;;;GAQG;AACU,QAAA,iBAAiB,GAAG,yBAAyB,CAAC;AAgD3D,6DAA6D;AAChD,QAAA,eAAe,GAAG,0BAA0B,CAAC;AA0B1D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,SAAgB,mBAAmB,CAAC,OAAmC;IACrE,MAAM,KAAK,GAAgB,IAAA,6BAAa,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAyB,IAAA,qCAAqB,EAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IACpF,MAAM,QAAQ,GAAgB,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,gBAAgB,CAAC,QAAQ,CAAC;IAClF,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,IAAI,4CAA4B,CAAC;IACtF,MAAM,aAAa,GAAG,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAErD,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,MAAM,IAAI,GAAG,CAAC,IAAY,EAAQ,EAAE;QAClC,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,IAAI,KAAK,CACb,iBAAiB,OAAO,eAAe,KAAK,CAAC,MAAM,6BAA6B,IAAI,IAAI;YACtF,sFAAsF;YACtF,mFAAmF,CACtF,CAAC;IACJ,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,CAAC,SAAiB,EAAU,EAAE,CAC3C,GAAG,KAAK,CAAC,MAAM,aAAa,IAAA,8BAAc,EAAC,SAAS,CAAC,EAAE,CAAC;IAE1D,OAAO;QACL,MAAM,EAAE,KAAK,CAAC,MAAM;QAEpB,KAAK,CAAC,OAAO,CAAC,SAAiB;YAC7B,IAAI,CAAC,mBAAmB,CAAC,CAAC;YAC1B,IAAI,OAAkC,CAAC;YACvC,IAAI,CAAC;gBACH,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC;YACpE,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,qEAAqE;gBACrE,uEAAuE;gBACvE,eAAe;gBACf,IAAI,IAAA,0BAAU,EAAC,GAAG,CAAC;oBAAE,OAAO,SAAS,CAAC;gBACtC,MAAM,IAAA,gCAAgB,EAAC,OAAO,EAAE,cAAc,EAAE,GAAG,CAAC,CAAC;YACvD,CAAC;YACD,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAC;YAE5C,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC;YACnC,sEAAsE;YACtE,mEAAmE;YACnE,qDAAqD;YACrD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAC;YAC5D,MAAM,MAAM,GAAG,KAAK,CAAC,yBAAiB,CAAC,CAAC;YACxC,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAC;YAC3C,yEAAyE;YACzE,yDAAyD;YACzD,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAClD,MAAM,IAAI,mCAAuB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;YACvD,CAAC;YACD,wEAAwE;YACxE,uEAAuE;YACvE,OAAO,IAAA,2BAAa,EAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAC1C,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,SAAiB,EAAE,QAA4B;YAC3D,IAAI,CAAC,mBAAmB,CAAC,CAAC;YAC1B,uEAAuE;YACvE,+CAA+C;YAC/C,MAAM,OAAO,GAAG,IAAA,2BAAa,EAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;YACnD,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;YAC/B,MAAM,IAAI,GAAkB;gBAC1B,YAAY,EAAE,EAAE,CAAC,yBAAiB,CAAC,EAAE,OAA6C,EAAE;gBACpF,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;aACvD,CAAC;YAEF,gEAAgE;YAChE,EAAE;YACF,uEAAuE;YACvE,sEAAsE;YACtE,wEAAwE;YACxE,uEAAuE;YACvE,sCAAsC;YACtC,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,KAAK,CAAC;oBACnB,IAAI;oBACJ,qEAAqE;oBACrE,mEAAmE;oBACnE,mEAAmE;oBACnE,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC;oBACzB,WAAW,EAAE,IAAI;iBAClB,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,IAAA,0BAAU,EAAC,GAAG,CAAC;oBAAE,MAAM,IAAA,gCAAgB,EAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,CAAC,CAAC;YAC/E,CAAC;YAED,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YACjD,IAAI,OAAO,CAAC;YACZ,IAAI,CAAC;gBACH,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC;oBAC9B,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,SAAS,EAAE,IAAA,8BAAc,EAAC,SAAS,CAAC;oBACpC,WAAW,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE;iBACjC,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,qEAAqE;gBACrE,mEAAmE;gBACnE,iEAAiE;gBACjE,IAAI,CAAC,IAAA,+BAAe,EAAC,GAAG,CAAC;oBAAE,MAAM,IAAA,gCAAgB,EAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,CAAC,CAAC;gBACnF,IAAI,CAAC;oBACH,MAAM,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC/E,CAAC;gBAAC,OAAO,QAAQ,EAAE,CAAC;oBAClB,MAAM,IAAA,gCAAgB,EAAC,OAAO,EAAE,gBAAgB,EAAE,QAAQ,CAAC,CAAC;gBAC9D,CAAC;gBACD,OAAO;YACT,CAAC;YACD,oEAAoE;YACpE,sEAAsE;YACtE,gCAAgC;YAChC,MAAM,IAAA,8BAAc,EAClB,OAAO,EACP,QAAQ,CAAC,UAAU,EACnB,OAAO,EAAE,IAAI,EACb,qBAAqB,SAAS,GAAG,EACjC,kBAAkB,CACnB,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,MAAc,EAAE,WAAgC;YAC/D,IAAI,CAAC,wBAAwB,CAAC,CAAC;YAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,IAAI,YAAY,CAAC,CAAC,CAAC;YAC1E,IAAI,IAAI,CAAC;YACT,IAAI,CAAC;gBACH,IAAI,GAAG,CACL,MAAM,QAAQ,CAAC,IAAI,CAAC;oBAClB,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,kEAAkE;oBAClE,0DAA0D;oBAC1D,MAAM,EAAE,WAAW,gBAAgB,CAAC,MAAM,CAAC,EAAE;oBAC7C,OAAO,EAAE,kBAAkB;oBAC3B,QAAQ,EAAE,KAAK;oBACf,GAAG,CAAC,WAAW,EAAE,MAAM,KAAK,SAAS,IAAI,EAAE,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,CAAC;iBAC5E,CAAC,CACH,EAAE,IAAI,CAAC;YACV,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,IAAA,gCAAgB,EAAC,OAAO,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC;YACxD,CAAC;YAED,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;gBACvD,MAAM,SAAS,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,yBAAiB,CAAC,CAAC;gBACzD,oEAAoE;gBACpE,qEAAqE;gBACrE,sEAAsE;gBACtE,4DAA4D;gBAC5D,MAAM,QAAQ,GAAG,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;gBACpF,OAAO;oBACL,SAAS;oBACT,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC;oBAC3D,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC;oBAC1B,YAAY,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAA,gCAAkB,EAAC,QAAQ,CAAC,CAAC,MAAM;iBAC/E,CAAC;YACJ,CAAC,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,IAAI,EAAE,aAAa,CAAC;YACnC,OAAO;gBACL,QAAQ,EAAE,SAAS;gBACnB,GAAG,CAAC,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;aAC/D,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,SAAiB;YAC7B,IAAI,CAAC,wBAAwB,CAAC,CAAC;YAC/B,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC;gBACxE,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,CAAC;gBAC/B,uEAAuE;gBACvE,sEAAsE;gBACtE,oEAAoE;gBACpE,0DAA0D;gBAC1D,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,EAAE,IAAI,MAAM,KAAK,uBAAe;oBAC9E,CAAC,CAAC,MAAM;oBACR,CAAC,CAAC,SAAS,CAAC;YAChB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,IAAA,0BAAU,EAAC,GAAG,CAAC;oBAAE,OAAO,SAAS,CAAC;gBACtC,MAAM,IAAA,gCAAgB,EAAC,OAAO,EAAE,cAAc,EAAE,GAAG,CAAC,CAAC;YACvD,CAAC;QACH,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,SAAiB;YAC5B,IAAI,CAAC,kBAAkB,CAAC,CAAC;YACzB,IAAI,OAAO,CAAC;YACZ,IAAI,CAAC;gBACH,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YAC/D,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,8CAA8C;gBAC9C,IAAI,IAAA,0BAAU,EAAC,GAAG,CAAC;oBAAE,OAAO;gBAC5B,MAAM,IAAA,gCAAgB,EAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,CAAC,CAAC;YAC1D,CAAC;YACD,MAAM,IAAA,8BAAc,EAClB,OAAO,EACP,QAAQ,CAAC,UAAU,EACnB,OAAO,EAAE,IAAI,EACb,qBAAqB,SAAS,GAAG,EACjC,kBAAkB,CACnB,CAAC;QACJ,CAAC;QAED,KAAK;YACH,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC;AA1MD,kDA0MC;AAED,wEAAwE;AAExE,kFAAkF;AAClF,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAS,gBAAgB,CAAC,KAAa;IACrC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC;AAClE,CAAC;AAED;;;;;GAKG;AACH,SAAS,cAAc,CACrB,MAA4C;IAE5C,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;QACjC,OAAO,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE;YAC7B,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAC7C,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBAC3D,MAAM,IAAI,SAAS,CACjB,GAAG,OAAO,oCAAoC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,eAAe;oBACnF,IAAI,SAAS,qEAAqE;oBAClF,wEAAwE;oBACxE,2CAA2C;oBAC3C,aAAa,uBAAe,oDAAoD;oBAChF,WAAW,CACd,CAAC;YACJ,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACzB,MAAM,IAAI,SAAS,CACjB,GAAG,OAAO,yEAAyE;gBACjF,oFAAoF;gBACpF,gDAAgD,CACnD,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC;IACtB,CAAC;IACD,sEAAsE;IACtE,gEAAgE;IAChE,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,EAAE,CAAC,IAAA,2BAAa,EAAC,QAAQ,CAAC,IAAI,uBAAe,CAAC;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,SAAS,OAAO,CAAC,IAAmB;IAClC,MAAM,MAAM,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;QAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/C,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED,6EAA6E;AAC7E,SAAS,WAAW,CAAC,IAA+B;IAClD,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACxC,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACjC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,2EAA2E;AAC3E,SAAS,QAAQ,CAAC,KAAgC;IAChD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED,6EAA6E;AAC7E,SAAS,QAAQ,CAAC,MAAe;IAC/B,MAAM,MAAM,GAAI,MAA2C,EAAE,MAAM,CAAC;IACpE,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACzD,CAAC"}
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ /**
3
+ * googleIdentity — the {@link CredentialProvider} port over Google's own
4
+ * credential machinery (peer-dep `google-auth-library`).
5
+ *
6
+ * import { googleIdentity } from 'agentfootprint/security';
7
+ * const credentials = googleIdentity();
8
+ *
9
+ * ── What it is, and what it deliberately is not ─────────────────────────────
10
+ * This is the **narrow** adapter: it vends *Google* access tokens for *Google*
11
+ * APIs, from whatever credential the environment already has — Application
12
+ * Default Credentials on Cloud Run or GKE, a workload-identity federation
13
+ * config, a service account, optionally impersonating another service account.
14
+ * That is one job and it is done completely.
15
+ *
16
+ * It is **not** a token vault and does not pretend to be one. The other
17
+ * column's identity adapter can vend a *GitHub* token for a *user* because
18
+ * that service runs a vault with per-user OAuth grants behind it. Google's
19
+ * equivalent — the Agent Identity auth manager — is Preview with no Node
20
+ * surface, so `mode: 'user'` here is **refused by name** rather than quietly
21
+ * served with a machine token. A machine token returned where a user token was
22
+ * asked for is the exact silent downgrade the port exists to prevent: the call
23
+ * succeeds, the data comes back, and it was the agent's access rather than the
24
+ * person's. See {@link googleIdentity} for the refusal's wording.
25
+ *
26
+ * ── The one-hour fact, and where it bites ───────────────────────────────────
27
+ * A Google OAuth access token lives about an hour. That is fine wherever the
28
+ * credential is fetched per use — which is how `ctx.credential` works, so the
29
+ * ordinary path is unaffected. It bites in exactly one place, and it is worth
30
+ * naming because it looks like it should work:
31
+ *
32
+ * > **The OpenAI-compatible endpoint trap.** Google publishes an
33
+ * > OpenAI-compatible Gemini endpoint, and `openai({ baseURL, apiKey })` does
34
+ * > reach it. If you fill that `apiKey` with a token from here, it works for
35
+ * > an hour and then every call fails with a 401 — because `apiKey` is a
36
+ * > STRING captured when the provider is constructed, and a long-lived agent
37
+ * > process outlives it. There is no refresh home in the OpenAI provider's
38
+ * > options for a credential that expires. Use the native `gemini()` provider,
39
+ * > which reads ADC through the SDK and refreshes underneath you.
40
+ *
41
+ * `expiresAt` is reported on every issued credential so a caller that caches
42
+ * one can tell. This adapter never caches: {@link CachedGoogleClient} keeps
43
+ * the *client*, and the client's own refresh logic keeps the token fresh.
44
+ *
45
+ * ── Secrets ─────────────────────────────────────────────────────────────────
46
+ * The `sdkFailure` law, same as every other credential-touching adapter here:
47
+ * the library's own message never comes through, because auth libraries echo
48
+ * request detail into failure text and a message thrown from a
49
+ * `CredentialProvider` reaches the LLM as a tool result AND rides
50
+ * `agentfootprint.credential.failed` to every sink. What comes through is the
51
+ * operation that failed and the error's NAME. The original is not attached as
52
+ * `cause` — a cause travels into every serializer that walks own properties,
53
+ * which would undo all of it in one `JSON.stringify`.
54
+ *
55
+ * Pattern: Adapter (GoF) + lazy peer-dep load — `google-auth-library` is
56
+ * required the first time `getCredential` runs, or never if you inject one.
57
+ */
58
+ Object.defineProperty(exports, "__esModule", { value: true });
59
+ exports.googleIdentity = exports.CLOUD_PLATFORM_SCOPE = void 0;
60
+ const lazyRequire_js_1 = require("../../lib/lazyRequire.js");
61
+ const kinds_js_1 = require("../../identity/kinds.js");
62
+ const ADAPTER = 'googleIdentity';
63
+ /** The scope every Google Cloud data-plane API accepts. */
64
+ exports.CLOUD_PLATFORM_SCOPE = 'https://www.googleapis.com/auth/cloud-platform';
65
+ /**
66
+ * Vend Google access tokens from whatever credential this environment has.
67
+ *
68
+ * **Status: contract-shaped and tested; awaiting field use.** Every path is
69
+ * exercised through an injected client and the loaded surface is pinned
70
+ * against the really-installed package. None of it has yet answered a real
71
+ * Google token request in a live project.
72
+ *
73
+ * @throws when `mode: 'user'` is requested — Google's per-user token vault has
74
+ * no Node surface, and a machine token returned in its place would be a
75
+ * silent downgrade.
76
+ * @throws when `services` is configured and the request names another one.
77
+ *
78
+ * @example A tool that calls a Google API with the deployment's own identity
79
+ * const agent = Agent.create({ provider, credentials: googleIdentity() })
80
+ * .tool(defineTool({
81
+ * name: 'read_sheet',
82
+ * needs: [{ credential: 'sheets', scopes: ['https://www.googleapis.com/auth/spreadsheets.readonly'] }],
83
+ * execute: async (args, ctx) =>
84
+ * fetch(url, { headers: ctx.credential!.toHeaders() }).then((r) => r.text()),
85
+ * }))
86
+ * .build();
87
+ *
88
+ * @example Acting as a dedicated service account
89
+ * googleIdentity({
90
+ * impersonate: { targetPrincipal: 'agent-runner@my-project.iam.gserviceaccount.com' },
91
+ * });
92
+ */
93
+ function googleIdentity(options = {}) {
94
+ const defaultScopes = options.scopes ?? [exports.CLOUD_PLATFORM_SCOPE];
95
+ const allowed = options.services === undefined ? undefined : new Set(options.services);
96
+ const cache = {};
97
+ const clientFor = (scopes) => {
98
+ // One client for the life of the provider. Scopes are fixed at
99
+ // construction by the library, so a request that asks for DIFFERENT scopes
100
+ // gets its own client rather than a token minted for the wrong ones — the
101
+ // cache is keyed on the default set and skipped otherwise.
102
+ if (sameScopes(scopes, defaultScopes)) {
103
+ return (cache.client ??= buildClient(options, scopes));
104
+ }
105
+ return buildClient(options, scopes);
106
+ };
107
+ return {
108
+ id: options.id ?? 'google-identity',
109
+ async getCredential(req) {
110
+ if (req.mode === 'user') {
111
+ throw new Error(`${ADAPTER}: a \`mode: 'user'\` request arrived for '${req.service}', and this ` +
112
+ `provider cannot serve one.\n` +
113
+ ` It vends the DEPLOYMENT's Google credential (Application Default Credentials, ` +
114
+ `workload identity, or an impersonated service account). It has no per-user token ` +
115
+ `vault — Google's own (the Agent Identity auth manager) has no Node surface to ` +
116
+ `build on.\n` +
117
+ ` Returning a machine token here would succeed and be wrong: the call would run ` +
118
+ `with the AGENT's access rather than the person's, and nothing downstream could ` +
119
+ `tell.\n` +
120
+ ` Fix: declare \`mode: 'machine'\` if the deployment's own identity is really ` +
121
+ `what you want, or vend the user's token from a provider that holds one.`);
122
+ }
123
+ if (req.userToken !== undefined) {
124
+ // A user's signed token handed to a provider that cannot exchange it.
125
+ // Named rather than ignored: silently dropping somebody's proof and
126
+ // vending machine access is the same downgrade in a quieter costume.
127
+ throw new Error(`${ADAPTER}: a \`userToken\` arrived for '${req.service}', but this provider has ` +
128
+ `nothing to exchange it against — it vends the deployment's own Google credential ` +
129
+ `and cannot act on behalf of a person.\n` +
130
+ ` Ignoring it would hand back agent-scoped access while holding the user's proof. ` +
131
+ `Drop the token, or use a provider that can exchange one.`);
132
+ }
133
+ if (allowed !== undefined && !allowed.has(req.service)) {
134
+ throw new Error(`${ADAPTER}: this provider is configured for [${[...allowed].join(', ')}] and was ` +
135
+ `asked for '${req.service}'.\n` +
136
+ ` It vends GOOGLE access tokens; handing one to a tool that wanted a different ` +
137
+ `service's credential would fail downstream as a puzzling 401 instead of here as ` +
138
+ `a wiring error.\n` +
139
+ ` Fix: add '${req.service}' to 'services', or attach a provider that serves it.`);
140
+ }
141
+ const scopes = req.scopes !== undefined && req.scopes.length > 0 ? req.scopes : defaultScopes;
142
+ let client;
143
+ try {
144
+ client = await clientFor(scopes);
145
+ }
146
+ catch (err) {
147
+ // "No credential in this environment" is the most common real failure
148
+ // and deserves the fix rather than a library's stack trace — but ONLY
149
+ // when that is really what happened. A refusal this adapter authored
150
+ // (a missing peer dependency, an impersonation with no target) is
151
+ // already the right diagnosis, and rewriting it as "no credential"
152
+ // would send the reader to `gcloud auth` for a problem `npm install`
153
+ // fixes. Wrong diagnoses are their own kind of silently-wrong.
154
+ if (isOwnRefusal(err))
155
+ throw err;
156
+ throw noCredentials(err);
157
+ }
158
+ let answer;
159
+ try {
160
+ answer = await client.getAccessToken();
161
+ }
162
+ catch (err) {
163
+ throw sdkFailure('getAccessToken', err);
164
+ }
165
+ const token = typeof answer === 'string' ? answer : answer?.token;
166
+ if (typeof token !== 'string' || token === '') {
167
+ throw new Error(`${ADAPTER}: the auth client returned no access token for '${req.service}'.\n` +
168
+ ` No value is quoted here on purpose — every field of a token response is a ` +
169
+ `secret. A credential that resolved but vends nothing usually means the ` +
170
+ `environment has a config file with no usable key, or an impersonation the source ` +
171
+ `identity is not permitted to perform.`);
172
+ }
173
+ // The library records expiry in unix MILLISECONDS; the port reports unix
174
+ // SECONDS. Reported when known and omitted when not — an invented expiry
175
+ // is worse than none, because a caller would cache against it.
176
+ const expiryMs = client.credentials?.expiry_date;
177
+ const expiresAt = typeof expiryMs === 'number' && Number.isFinite(expiryMs) && expiryMs > 0
178
+ ? Math.floor(expiryMs / 1000)
179
+ : undefined;
180
+ return {
181
+ status: 'issued',
182
+ credential: (0, kinds_js_1.bearer)(token),
183
+ ...(expiresAt !== undefined && { expiresAt }),
184
+ };
185
+ },
186
+ };
187
+ }
188
+ exports.googleIdentity = googleIdentity;
189
+ // ─── Internals ───────────────────────────────────────────────────────
190
+ function sameScopes(a, b) {
191
+ return a.length === b.length && a.every((scope, index) => scope === b[index]);
192
+ }
193
+ /** Build the auth client: ADC, then impersonation on top when configured. */
194
+ async function buildClient(options, scopes) {
195
+ if (options._client)
196
+ return options._client;
197
+ const mod = loadAuthSdk(options._sdk);
198
+ if (typeof mod.GoogleAuth !== 'function') {
199
+ throw new Error(`${ADAPTER}: \`google-auth-library\` is installed but exports no \`GoogleAuth\`. ` +
200
+ `This adapter is built against the 11.x package — update it, or pass \`_client\`.`);
201
+ }
202
+ const source = await new mod.GoogleAuth({ scopes: [...scopes] }).getClient();
203
+ const impersonate = options.impersonate;
204
+ if (impersonate === undefined)
205
+ return source;
206
+ if (typeof mod.Impersonated !== 'function') {
207
+ throw new Error(`${ADAPTER}: 'impersonate' is configured but \`google-auth-library\` exports no ` +
208
+ `\`Impersonated\`. Update the package, or drop 'impersonate' to vend with the ` +
209
+ `environment's own identity.`);
210
+ }
211
+ if (typeof impersonate.targetPrincipal !== 'string' || impersonate.targetPrincipal === '') {
212
+ throw new TypeError(`${ADAPTER}: 'impersonate.targetPrincipal' is required — the service account to act as, ` +
213
+ `e.g. 'agent-runner@my-project.iam.gserviceaccount.com'.`);
214
+ }
215
+ return new mod.Impersonated({
216
+ sourceClient: source,
217
+ targetPrincipal: impersonate.targetPrincipal,
218
+ targetScopes: [...scopes],
219
+ ...(impersonate.delegates !== undefined && { delegates: [...impersonate.delegates] }),
220
+ ...(impersonate.lifetimeSeconds !== undefined && { lifetime: impersonate.lifetimeSeconds }),
221
+ });
222
+ }
223
+ function loadAuthSdk(injected) {
224
+ if (injected)
225
+ return injected;
226
+ try {
227
+ return (0, lazyRequire_js_1.lazyRequire)('google-auth-library');
228
+ }
229
+ catch {
230
+ throw new Error(`${ADAPTER} requires the \`google-auth-library\` peer dependency.\n` +
231
+ ` Install: npm install google-auth-library\n` +
232
+ ` It is optional and loaded only when this provider first vends, so nothing else in ` +
233
+ `this library pays for it.`);
234
+ }
235
+ }
236
+ /**
237
+ * "There is no usable credential here" — the most common real failure, given
238
+ * the fix instead of the library's own text.
239
+ */
240
+ function noCredentials(err) {
241
+ const name = errorName(err);
242
+ const failure = new Error(`${ADAPTER}: could not resolve a Google credential in this environment — ${name}.\n` +
243
+ ` The underlying message is withheld: auth libraries echo file paths and request ` +
244
+ `detail into failure text, and this message reaches the model as a tool result.\n` +
245
+ ` On Cloud Run / GKE / GCE the runtime service account is used automatically. ` +
246
+ `Elsewhere, run \`gcloud auth application-default login\`, or point ` +
247
+ `GOOGLE_APPLICATION_CREDENTIALS at a service-account key or a workload-identity ` +
248
+ `config file.`);
249
+ failure.name = 'GoogleCredentialsUnavailableError';
250
+ return failure;
251
+ }
252
+ /** Re-raise without the library's text. See the module header for why. */
253
+ function sdkFailure(operation, err) {
254
+ const failure = new Error(`${ADAPTER}: ${operation} failed — ${errorName(err)}.\n` +
255
+ ` The underlying message is withheld: this call handles an access token, and auth ` +
256
+ `libraries echo request detail into failure text. Check Cloud Logging for the full error.`);
257
+ failure.name = 'GoogleCredentialError';
258
+ return failure;
259
+ }
260
+ /**
261
+ * Did THIS adapter write this error?
262
+ *
263
+ * Every refusal authored here opens with the adapter's own name — both as the
264
+ * marker and because it is what makes the message readable ("googleIdentity:
265
+ * …", "googleIdentity requires …"). A library's own failure never does, so the
266
+ * prefix is a reliable discriminator without an error subclass per refusal.
267
+ */
268
+ function isOwnRefusal(err) {
269
+ return err instanceof Error && err.message.startsWith(ADAPTER);
270
+ }
271
+ function errorName(err) {
272
+ const name = err?.name;
273
+ return typeof name === 'string' && name.length > 0 ? name : 'an unnamed failure';
274
+ }
275
+ //# sourceMappingURL=google.js.map