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.
- package/dist/adapters/google/aiPlatform.js +438 -0
- package/dist/adapters/google/aiPlatform.js.map +1 -0
- package/dist/adapters/hosting/googleAgentEngine.js +372 -0
- package/dist/adapters/hosting/googleAgentEngine.js.map +1 -0
- package/dist/adapters/identity/google.js +275 -0
- package/dist/adapters/identity/google.js.map +1 -0
- package/dist/adapters/memory/agentcore.js +19 -0
- package/dist/adapters/memory/agentcore.js.map +1 -1
- package/dist/adapters/memory/memoryBank.js +823 -0
- package/dist/adapters/memory/memoryBank.js.map +1 -0
- package/dist/core/agent/stages/routeTurn.js +13 -1
- package/dist/core/agent/stages/routeTurn.js.map +1 -1
- package/dist/esm/adapters/google/aiPlatform.d.ts +453 -0
- package/dist/esm/adapters/google/aiPlatform.js +424 -0
- package/dist/esm/adapters/google/aiPlatform.js.map +1 -0
- package/dist/esm/adapters/hosting/googleAgentEngine.d.ts +156 -0
- package/dist/esm/adapters/hosting/googleAgentEngine.js +368 -0
- package/dist/esm/adapters/hosting/googleAgentEngine.js.map +1 -0
- package/dist/esm/adapters/identity/google.d.ts +179 -0
- package/dist/esm/adapters/identity/google.js +271 -0
- package/dist/esm/adapters/identity/google.js.map +1 -0
- package/dist/esm/adapters/memory/agentcore.d.ts +19 -0
- package/dist/esm/adapters/memory/agentcore.js +19 -0
- package/dist/esm/adapters/memory/agentcore.js.map +1 -1
- package/dist/esm/adapters/memory/memoryBank.d.ts +390 -0
- package/dist/esm/adapters/memory/memoryBank.js +817 -0
- package/dist/esm/adapters/memory/memoryBank.js.map +1 -0
- package/dist/esm/core/agent/stages/routeTurn.js +13 -1
- package/dist/esm/core/agent/stages/routeTurn.js.map +1 -1
- package/dist/esm/events/payloads.d.ts +23 -0
- package/dist/esm/hosting-providers.d.ts +7 -0
- package/dist/esm/hosting-providers.js +6 -0
- package/dist/esm/hosting-providers.js.map +1 -1
- package/dist/esm/identity.d.ts +1 -0
- package/dist/esm/identity.js +5 -0
- package/dist/esm/identity.js.map +1 -1
- package/dist/esm/lib/injection-engine/buildInjectionEngineSubflow.js +9 -0
- package/dist/esm/lib/injection-engine/buildInjectionEngineSubflow.js.map +1 -1
- package/dist/esm/lib/injection-engine/routingPolicy.d.ts +8 -0
- package/dist/esm/lib/injection-engine/routingPolicy.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillGraph.d.ts +11 -2
- package/dist/esm/lib/injection-engine/skillGraph.js +25 -1
- package/dist/esm/lib/injection-engine/skillGraph.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillIntent.d.ts +13 -5
- package/dist/esm/lib/injection-engine/skillIntent.js +12 -2
- package/dist/esm/lib/injection-engine/skillIntent.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillMatch.d.ts +40 -0
- package/dist/esm/lib/injection-engine/skillMatch.js +60 -0
- package/dist/esm/lib/injection-engine/skillMatch.js.map +1 -1
- package/dist/esm/memory-providers.d.ts +1 -0
- package/dist/esm/memory-providers.js +7 -0
- package/dist/esm/memory-providers.js.map +1 -1
- package/dist/esm/recorders/observability/AgentThinkingTraceRecorder.d.ts +4 -0
- package/dist/esm/recorders/observability/AgentThinkingTraceRecorder.js +4 -1
- package/dist/esm/recorders/observability/AgentThinkingTraceRecorder.js.map +1 -1
- package/dist/esm/recorders/observability/commentary/artifactPhrases.d.ts +50 -0
- package/dist/esm/recorders/observability/commentary/artifactPhrases.js +88 -0
- package/dist/esm/recorders/observability/commentary/artifactPhrases.js.map +1 -0
- package/dist/esm/recorders/observability/commentary/commentaryTemplates.js +233 -9
- package/dist/esm/recorders/observability/commentary/commentaryTemplates.js.map +1 -1
- package/dist/hosting-providers.js +10 -1
- package/dist/hosting-providers.js.map +1 -1
- package/dist/identity.js +8 -1
- package/dist/identity.js.map +1 -1
- package/dist/lib/injection-engine/buildInjectionEngineSubflow.js +9 -0
- package/dist/lib/injection-engine/buildInjectionEngineSubflow.js.map +1 -1
- package/dist/lib/injection-engine/routingPolicy.js.map +1 -1
- package/dist/lib/injection-engine/skillGraph.js +25 -1
- package/dist/lib/injection-engine/skillGraph.js.map +1 -1
- package/dist/lib/injection-engine/skillIntent.js +12 -2
- package/dist/lib/injection-engine/skillIntent.js.map +1 -1
- package/dist/lib/injection-engine/skillMatch.js +61 -1
- package/dist/lib/injection-engine/skillMatch.js.map +1 -1
- package/dist/memory-providers.js +12 -1
- package/dist/memory-providers.js.map +1 -1
- package/dist/recorders/observability/AgentThinkingTraceRecorder.js +4 -1
- package/dist/recorders/observability/AgentThinkingTraceRecorder.js.map +1 -1
- package/dist/recorders/observability/commentary/artifactPhrases.js +94 -0
- package/dist/recorders/observability/commentary/artifactPhrases.js.map +1 -0
- package/dist/recorders/observability/commentary/commentaryTemplates.js +233 -9
- package/dist/recorders/observability/commentary/commentaryTemplates.js.map +1 -1
- package/dist/types/adapters/google/aiPlatform.d.ts +454 -0
- package/dist/types/adapters/google/aiPlatform.d.ts.map +1 -0
- package/dist/types/adapters/hosting/googleAgentEngine.d.ts +157 -0
- package/dist/types/adapters/hosting/googleAgentEngine.d.ts.map +1 -0
- package/dist/types/adapters/identity/google.d.ts +180 -0
- package/dist/types/adapters/identity/google.d.ts.map +1 -0
- package/dist/types/adapters/memory/agentcore.d.ts +19 -0
- package/dist/types/adapters/memory/agentcore.d.ts.map +1 -1
- package/dist/types/adapters/memory/memoryBank.d.ts +391 -0
- package/dist/types/adapters/memory/memoryBank.d.ts.map +1 -0
- package/dist/types/core/agent/stages/routeTurn.d.ts.map +1 -1
- package/dist/types/events/payloads.d.ts +23 -0
- package/dist/types/events/payloads.d.ts.map +1 -1
- package/dist/types/hosting-providers.d.ts +7 -0
- package/dist/types/hosting-providers.d.ts.map +1 -1
- package/dist/types/identity.d.ts +1 -0
- package/dist/types/identity.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/buildInjectionEngineSubflow.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/routingPolicy.d.ts +8 -0
- package/dist/types/lib/injection-engine/routingPolicy.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillGraph.d.ts +11 -2
- package/dist/types/lib/injection-engine/skillGraph.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillIntent.d.ts +13 -5
- package/dist/types/lib/injection-engine/skillIntent.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillMatch.d.ts +40 -0
- package/dist/types/lib/injection-engine/skillMatch.d.ts.map +1 -1
- package/dist/types/memory-providers.d.ts +1 -0
- package/dist/types/memory-providers.d.ts.map +1 -1
- package/dist/types/recorders/observability/AgentThinkingTraceRecorder.d.ts +4 -0
- package/dist/types/recorders/observability/AgentThinkingTraceRecorder.d.ts.map +1 -1
- package/dist/types/recorders/observability/commentary/artifactPhrases.d.ts +51 -0
- package/dist/types/recorders/observability/commentary/artifactPhrases.d.ts.map +1 -0
- package/dist/types/recorders/observability/commentary/commentaryTemplates.d.ts.map +1 -1
- package/package.json +24 -15
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agentEngineSessions — conversations in Vertex AI's own session service, so a
|
|
3
|
+
* fleet shares them.
|
|
4
|
+
*
|
|
5
|
+
* `memorySessions()` loses everything on restart and says so. `sqliteSessions()`
|
|
6
|
+
* survives a restart on ONE machine and says so. The row above both — *many
|
|
7
|
+
* containers, one conversation* — is where a managed session service belongs,
|
|
8
|
+
* and on this column that service is the `sessions` collection under a
|
|
9
|
+
* reasoning engine.
|
|
10
|
+
*
|
|
11
|
+
* ── The name, said once ─────────────────────────────────────────────────────
|
|
12
|
+
* The product was **Agent Engine**, is now **Agent Runtime**, and the API
|
|
13
|
+
* resource is still spelled `reasoningEngines`. This factory keeps the name it
|
|
14
|
+
* was designed under; where the product name and the API disagree, the API is
|
|
15
|
+
* the one that has not moved.
|
|
16
|
+
*
|
|
17
|
+
* ── The fit, and it is a good one ───────────────────────────────────────────
|
|
18
|
+
* `Session.sessionState` is an arbitrary JSON `Struct`. A `CheckpointEnvelope`
|
|
19
|
+
* is arbitrary JSON. So the envelope goes in whole, under one key, and comes
|
|
20
|
+
* back whole — no event log to fold, no blob encoding to get wrong, no
|
|
21
|
+
* per-turn append. That is a materially better fit than the other column's
|
|
22
|
+
* session store, which had to learn the hard way that an object handed to an
|
|
23
|
+
* event blob comes back as somebody else's `toString()`.
|
|
24
|
+
*
|
|
25
|
+
* ── The four facts that shaped the code, all read off the installed SDK ─────
|
|
26
|
+
* 1. **`sessions.create` takes a caller-supplied `sessionId`.** So our session
|
|
27
|
+
* id IS the resource id and `hydrate` is one `get` by name. No mapping
|
|
28
|
+
* table, no listing to find a conversation.
|
|
29
|
+
* 2. **`create` and `delete` answer a long-running Operation; `get` and
|
|
30
|
+
* `patch` answer the Session.** Every write here therefore waits for the
|
|
31
|
+
* operation to report `done` before it returns — a `persist` that returned
|
|
32
|
+
* early would make the very next `hydrate` a race whose failure mode is
|
|
33
|
+
* "no conversation", which nobody can tell from a new user.
|
|
34
|
+
* 3. **`Session.userId` is required and immutable.** Our port's
|
|
35
|
+
* `persist(sessionId, envelope)` carries no user, so one has to be
|
|
36
|
+
* resolved — see {@link AgentEngineSessionsOptions.userId}. Immutable
|
|
37
|
+
* means the first write decides forever, which is exactly the ownership
|
|
38
|
+
* rule this library already enforces in its own stores; here the service
|
|
39
|
+
* enforces it for us.
|
|
40
|
+
* 4. **`ttl` is input-only with a 24-hour floor**, and `expireTime` always
|
|
41
|
+
* comes back. Sliding expiry is free; an hour-long TTL is not available at
|
|
42
|
+
* any price.
|
|
43
|
+
*
|
|
44
|
+
* ── The laws it inherits rather than re-implements ──────────────────────────
|
|
45
|
+
* `checkEnvelope` runs on the way OUT and on the way IN, so an envelope whose
|
|
46
|
+
* `format` this runtime does not know is refused by name, and a session that
|
|
47
|
+
* is PRESENT but unreadable is refused by name too. Only a session that was
|
|
48
|
+
* never written hydrates as `undefined`. A conversation that exists and cannot
|
|
49
|
+
* be read must never be answered with a fresh start — that failure is
|
|
50
|
+
* indistinguishable, from the outside, from a brand-new user.
|
|
51
|
+
*/
|
|
52
|
+
import { checkEnvelope, envelopeOwner, envelopeTranscript } from '../../hosting/envelope.js';
|
|
53
|
+
import { UnreadableEnvelopeError } from '../../hosting/errors.js';
|
|
54
|
+
import { awaitOperation, buildAiPlatformClient, DEFAULT_OPERATION_TIMEOUT_MS, googleSdkFailure, isAlreadyExists, isNotFound, resolveEngine, safeResourceId, } from '../google/aiPlatform.js';
|
|
55
|
+
const ADAPTER = 'agentEngineSessions';
|
|
56
|
+
/**
|
|
57
|
+
* The `sessionState` key the envelope lives under.
|
|
58
|
+
*
|
|
59
|
+
* One key, namespaced, rather than spreading the envelope's own fields across
|
|
60
|
+
* `sessionState`: the struct belongs to whoever owns the reasoning engine, an
|
|
61
|
+
* agent framework is a guest in it, and a guest that scatters `format` and
|
|
62
|
+
* `data` at the top level collides with the next guest. Namespacing also makes
|
|
63
|
+
* the console readable — one entry that says whose it is.
|
|
64
|
+
*/
|
|
65
|
+
export const SESSION_STATE_KEY = 'agentfootprint.envelope';
|
|
66
|
+
/** What a conversation that named nobody is stored under. */
|
|
67
|
+
export const DEFAULT_USER_ID = 'agentfootprint-anonymous';
|
|
68
|
+
/**
|
|
69
|
+
* Conversations in Vertex AI's session service — the store that survives a
|
|
70
|
+
* fleet, not just a restart.
|
|
71
|
+
*
|
|
72
|
+
* **Status: contract-shaped and tested; awaiting field use.** Every call is
|
|
73
|
+
* exercised through an injected client and pinned against the really-installed
|
|
74
|
+
* SDK. None of it has yet answered a request from Google in a real project.
|
|
75
|
+
*
|
|
76
|
+
* @example A standing agent whose conversations are shared across instances
|
|
77
|
+
* import { standingAgent, nodeHost } from 'agentfootprint/hosting';
|
|
78
|
+
* import { agentEngineSessions } from 'agentfootprint/hosting';
|
|
79
|
+
*
|
|
80
|
+
* const handle = await standingAgent({
|
|
81
|
+
* agentFactory: () => buildAgent(),
|
|
82
|
+
* host: nodeHost({ port: 8080 }),
|
|
83
|
+
* sessions: agentEngineSessions({
|
|
84
|
+
* project: 'my-project',
|
|
85
|
+
* location: 'us-central1',
|
|
86
|
+
* reasoningEngine: '1234567890',
|
|
87
|
+
* }),
|
|
88
|
+
* });
|
|
89
|
+
*/
|
|
90
|
+
export function agentEngineSessions(options) {
|
|
91
|
+
const scope = resolveEngine(ADAPTER, options);
|
|
92
|
+
const client = buildAiPlatformClient(ADAPTER, options, scope);
|
|
93
|
+
const sessions = client.projects.locations.reasoningEngines.sessions;
|
|
94
|
+
const operationTimeoutMs = options.operationTimeoutMs ?? DEFAULT_OPERATION_TIMEOUT_MS;
|
|
95
|
+
const resolveUserId = userIdResolver(options.userId);
|
|
96
|
+
let closed = false;
|
|
97
|
+
const open = (verb) => {
|
|
98
|
+
if (!closed)
|
|
99
|
+
return;
|
|
100
|
+
throw new Error(`[hosting] the ${ADAPTER} store for '${scope.parent}' is closed, so it cannot ${verb}. ` +
|
|
101
|
+
`close() is final by design — reconnecting behind you would hide a shutdown-ordering ` +
|
|
102
|
+
`bug rather than surface it. Build a new store if you need one after closing this.`);
|
|
103
|
+
};
|
|
104
|
+
const nameOf = (sessionId) => `${scope.parent}/sessions/${safeResourceId(sessionId)}`;
|
|
105
|
+
return {
|
|
106
|
+
parent: scope.parent,
|
|
107
|
+
async hydrate(sessionId) {
|
|
108
|
+
open('hydrate a session');
|
|
109
|
+
let session;
|
|
110
|
+
try {
|
|
111
|
+
session = (await sessions.get({ name: nameOf(sessionId) }))?.data;
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
// The ONE failure that means "no conversation". Everything else is a
|
|
115
|
+
// failure to READ, which is a different fact and never answered with a
|
|
116
|
+
// fresh start.
|
|
117
|
+
if (isNotFound(err))
|
|
118
|
+
return undefined;
|
|
119
|
+
throw googleSdkFailure(ADAPTER, 'sessions.get', err);
|
|
120
|
+
}
|
|
121
|
+
if (session === undefined)
|
|
122
|
+
return undefined;
|
|
123
|
+
const state = session.sessionState;
|
|
124
|
+
// A session with no state at all was created by something that is not
|
|
125
|
+
// this library — or by us, and never written to. Nothing here ever
|
|
126
|
+
// claimed to be a conversation, so it is an absence.
|
|
127
|
+
if (state === null || state === undefined)
|
|
128
|
+
return undefined;
|
|
129
|
+
const stored = state[SESSION_STATE_KEY];
|
|
130
|
+
if (stored === undefined)
|
|
131
|
+
return undefined;
|
|
132
|
+
// Present and not an object: a conversation EXISTS here and this runtime
|
|
133
|
+
// cannot read it. Refused by name, never as `undefined`.
|
|
134
|
+
if (stored === null || typeof stored !== 'object') {
|
|
135
|
+
throw new UnreadableEnvelopeError(stored, sessionId);
|
|
136
|
+
}
|
|
137
|
+
// Validated HERE as well as in the composer, so a refusal points at the
|
|
138
|
+
// store that produced the bytes rather than at whoever read them next.
|
|
139
|
+
return checkEnvelope(stored, sessionId);
|
|
140
|
+
},
|
|
141
|
+
async persist(sessionId, envelope) {
|
|
142
|
+
open('persist a session');
|
|
143
|
+
// Checked on the way IN as well as out: a session this store could not
|
|
144
|
+
// read back is one it has no business writing.
|
|
145
|
+
const checked = checkEnvelope(envelope, sessionId);
|
|
146
|
+
const name = nameOf(sessionId);
|
|
147
|
+
const body = {
|
|
148
|
+
sessionState: { [SESSION_STATE_KEY]: checked },
|
|
149
|
+
...(options.ttl !== undefined && { ttl: options.ttl }),
|
|
150
|
+
};
|
|
151
|
+
// PATCH first, CREATE on 404 — rather than the other way round.
|
|
152
|
+
//
|
|
153
|
+
// The steady state of a conversation is "it already exists": a session
|
|
154
|
+
// is created once and written on every turn after that. Trying create
|
|
155
|
+
// first would mean one guaranteed-to-fail call per turn for the life of
|
|
156
|
+
// every conversation, and would burn a long-running operation to learn
|
|
157
|
+
// something a patch answers directly.
|
|
158
|
+
try {
|
|
159
|
+
await sessions.patch({
|
|
160
|
+
name,
|
|
161
|
+
// Only the fields we own. Without a mask a patch is a REPLACE, and a
|
|
162
|
+
// replace would drop `userId` — which is immutable, so the service
|
|
163
|
+
// would refuse the write and a conversation would stop persisting.
|
|
164
|
+
updateMask: maskFor(body),
|
|
165
|
+
requestBody: body,
|
|
166
|
+
});
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
if (!isNotFound(err))
|
|
171
|
+
throw googleSdkFailure(ADAPTER, 'sessions.patch', err);
|
|
172
|
+
}
|
|
173
|
+
const userId = resolveUserId(sessionId, checked);
|
|
174
|
+
let created;
|
|
175
|
+
try {
|
|
176
|
+
created = await sessions.create({
|
|
177
|
+
parent: scope.parent,
|
|
178
|
+
sessionId: safeResourceId(sessionId),
|
|
179
|
+
requestBody: { ...body, userId },
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
// Two writers opened the same conversation at once and the other one
|
|
184
|
+
// won. That is not a failure: the session exists now, which is all
|
|
185
|
+
// this call wanted, so the patch below writes our state onto it.
|
|
186
|
+
if (!isAlreadyExists(err))
|
|
187
|
+
throw googleSdkFailure(ADAPTER, 'sessions.create', err);
|
|
188
|
+
try {
|
|
189
|
+
await sessions.patch({ name, updateMask: maskFor(body), requestBody: body });
|
|
190
|
+
}
|
|
191
|
+
catch (patchErr) {
|
|
192
|
+
throw googleSdkFailure(ADAPTER, 'sessions.patch', patchErr);
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
// OUTSIDE the try on purpose: this refusal is already sanitized and
|
|
197
|
+
// already says what to do, and re-wrapping it would replace a precise
|
|
198
|
+
// diagnosis with a generic one.
|
|
199
|
+
await awaitOperation(ADAPTER, sessions.operations, created?.data, `creating session '${sessionId}'`, operationTimeoutMs);
|
|
200
|
+
},
|
|
201
|
+
async listByUser(userId, listOptions) {
|
|
202
|
+
open('list a user’s sessions');
|
|
203
|
+
const limit = Math.max(1, Math.floor(listOptions?.limit ?? DEFAULT_PAGE));
|
|
204
|
+
let page;
|
|
205
|
+
try {
|
|
206
|
+
page = (await sessions.list({
|
|
207
|
+
parent: scope.parent,
|
|
208
|
+
// The service's own filter over its own immutable field, with the
|
|
209
|
+
// caller's id as a quoted literal — see quoteFilterValue.
|
|
210
|
+
filter: `user_id=${quoteFilterValue(userId)}`,
|
|
211
|
+
orderBy: 'update_time desc',
|
|
212
|
+
pageSize: limit,
|
|
213
|
+
...(listOptions?.cursor !== undefined && { pageToken: listOptions.cursor }),
|
|
214
|
+
}))?.data;
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
throw googleSdkFailure(ADAPTER, 'sessions.list', err);
|
|
218
|
+
}
|
|
219
|
+
const summaries = (page?.sessions ?? []).map((session) => {
|
|
220
|
+
const sessionId = lastSegment(session.name);
|
|
221
|
+
const stored = session.sessionState?.[SESSION_STATE_KEY];
|
|
222
|
+
// A listing must not fail because ONE row is unreadable — a sidebar
|
|
223
|
+
// that 500s over a corrupt conversation is worse than one that shows
|
|
224
|
+
// it with an honest zero. The transcript op reads the envelope itself
|
|
225
|
+
// and refuses there, which is where a reader can act on it.
|
|
226
|
+
const readable = stored !== null && typeof stored === 'object' ? stored : undefined;
|
|
227
|
+
return {
|
|
228
|
+
sessionId,
|
|
229
|
+
savedAt: toMillis(session.updateTime ?? session.createTime),
|
|
230
|
+
format: formatOf(readable),
|
|
231
|
+
messageCount: readable === undefined ? 0 : envelopeTranscript(readable).length,
|
|
232
|
+
};
|
|
233
|
+
});
|
|
234
|
+
const cursor = page?.nextPageToken;
|
|
235
|
+
return {
|
|
236
|
+
sessions: summaries,
|
|
237
|
+
...(typeof cursor === 'string' && cursor !== '' && { cursor }),
|
|
238
|
+
};
|
|
239
|
+
},
|
|
240
|
+
async ownerOf(sessionId) {
|
|
241
|
+
open('read a session’s owner');
|
|
242
|
+
try {
|
|
243
|
+
const session = (await sessions.get({ name: nameOf(sessionId) }))?.data;
|
|
244
|
+
const userId = session?.userId;
|
|
245
|
+
// `undefined` for "no such session" AND for a session stored under the
|
|
246
|
+
// anonymous placeholder — the deliberate ambiguity the composer's one
|
|
247
|
+
// not-found rests on. A store that answered those differently would
|
|
248
|
+
// hand a caller an oracle for which session ids are real.
|
|
249
|
+
return typeof userId === 'string' && userId !== '' && userId !== DEFAULT_USER_ID
|
|
250
|
+
? userId
|
|
251
|
+
: undefined;
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
if (isNotFound(err))
|
|
255
|
+
return undefined;
|
|
256
|
+
throw googleSdkFailure(ADAPTER, 'sessions.get', err);
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
async forget(sessionId) {
|
|
260
|
+
open('forget a session');
|
|
261
|
+
let deleted;
|
|
262
|
+
try {
|
|
263
|
+
deleted = await sessions.delete({ name: nameOf(sessionId) });
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
// Already gone is the outcome this asked for.
|
|
267
|
+
if (isNotFound(err))
|
|
268
|
+
return;
|
|
269
|
+
throw googleSdkFailure(ADAPTER, 'sessions.delete', err);
|
|
270
|
+
}
|
|
271
|
+
await awaitOperation(ADAPTER, sessions.operations, deleted?.data, `deleting session '${sessionId}'`, operationTimeoutMs);
|
|
272
|
+
},
|
|
273
|
+
close() {
|
|
274
|
+
closed = true;
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// ─── Internals ───────────────────────────────────────────────────────
|
|
279
|
+
/** How many rows one `listByUser` page carries when the caller names no limit. */
|
|
280
|
+
const DEFAULT_PAGE = 50;
|
|
281
|
+
/**
|
|
282
|
+
* A user id as an AIP-160 string literal — **backslash first, then the quote.**
|
|
283
|
+
*
|
|
284
|
+
* The order is the whole point. This grammar honours backslash escapes, so
|
|
285
|
+
* escaping only the quote leaves the escape character itself free to escape our
|
|
286
|
+
* escape: a user id of `\" OR user_id!=` renders as `user_id="\\" OR user_id!=""`,
|
|
287
|
+
* where `\\` is a literal backslash, the quote after it CLOSES the literal, and
|
|
288
|
+
* the rest of the id is filter syntax the service evaluates. The listing then
|
|
289
|
+
* matches every session with a non-empty user id and hands back other people's
|
|
290
|
+
* conversation ids, timestamps and message counts.
|
|
291
|
+
*
|
|
292
|
+
* The benign case matters too: any id merely ENDING in a backslash swallows the
|
|
293
|
+
* closing quote and the call fails with a malformed-filter 400 whose text
|
|
294
|
+
* {@link googleSdkFailure} withholds — a local error delivered as a censored
|
|
295
|
+
* remote one. Escaping the backslash first fixes both.
|
|
296
|
+
*
|
|
297
|
+
* A user id is caller data on every column of this library. It is never
|
|
298
|
+
* concatenated into a query language without passing through here.
|
|
299
|
+
*/
|
|
300
|
+
function quoteFilterValue(value) {
|
|
301
|
+
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Resolve the immutable `userId` a session is created under.
|
|
305
|
+
*
|
|
306
|
+
* See {@link AgentEngineSessionsOptions.userId} for why the default reads the
|
|
307
|
+
* envelope rather than inventing anything.
|
|
308
|
+
*/
|
|
309
|
+
function userIdResolver(option) {
|
|
310
|
+
if (typeof option === 'function') {
|
|
311
|
+
return (sessionId, envelope) => {
|
|
312
|
+
const resolved = option(sessionId, envelope);
|
|
313
|
+
if (typeof resolved !== 'string' || resolved.trim() === '') {
|
|
314
|
+
throw new TypeError(`${ADAPTER}: the 'userId' resolver returned ${JSON.stringify(resolved)} for session ` +
|
|
315
|
+
`'${sessionId}'. The service requires a non-empty user id on create and will not ` +
|
|
316
|
+
`let it be changed afterwards, so this refuses rather than storing the ` +
|
|
317
|
+
`conversation under a name nobody chose.\n` +
|
|
318
|
+
` Return '${DEFAULT_USER_ID}' explicitly if an anonymous conversation is what ` +
|
|
319
|
+
`you mean.`);
|
|
320
|
+
}
|
|
321
|
+
return resolved;
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
if (typeof option === 'string') {
|
|
325
|
+
if (option.trim() === '') {
|
|
326
|
+
throw new TypeError(`${ADAPTER}: 'userId' was an empty string. The service requires one on create and ` +
|
|
327
|
+
`treats it as immutable. Pass a real id, a resolver function, or leave it unset to ` +
|
|
328
|
+
`derive the owner from the conversation itself.`);
|
|
329
|
+
}
|
|
330
|
+
return () => option;
|
|
331
|
+
}
|
|
332
|
+
// The default: the principal the conversation was signed with, so the
|
|
333
|
+
// service's owner and this library's own ownership index agree.
|
|
334
|
+
return (_sessionId, envelope) => envelopeOwner(envelope) ?? DEFAULT_USER_ID;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* The update mask for a patch.
|
|
338
|
+
*
|
|
339
|
+
* A patch with no mask REPLACES the resource, which would clear `userId` — and
|
|
340
|
+
* `userId` is immutable, so the service refuses the write and the conversation
|
|
341
|
+
* silently stops persisting. Naming the fields we own is the whole fix.
|
|
342
|
+
*/
|
|
343
|
+
function maskFor(body) {
|
|
344
|
+
const fields = ['sessionState'];
|
|
345
|
+
if (body.ttl !== undefined)
|
|
346
|
+
fields.push('ttl');
|
|
347
|
+
return fields.join(',');
|
|
348
|
+
}
|
|
349
|
+
/** The id out of a resource name, which is everything after the last `/`. */
|
|
350
|
+
function lastSegment(name) {
|
|
351
|
+
if (typeof name !== 'string')
|
|
352
|
+
return '';
|
|
353
|
+
const at = name.lastIndexOf('/');
|
|
354
|
+
return at === -1 ? name : name.slice(at + 1);
|
|
355
|
+
}
|
|
356
|
+
/** An RFC 3339 timestamp as unix milliseconds, or 0 when it is not one. */
|
|
357
|
+
function toMillis(value) {
|
|
358
|
+
if (typeof value !== 'string')
|
|
359
|
+
return 0;
|
|
360
|
+
const parsed = Date.parse(value);
|
|
361
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
362
|
+
}
|
|
363
|
+
/** The stored envelope's `format`, without trusting it to be one of ours. */
|
|
364
|
+
function formatOf(stored) {
|
|
365
|
+
const format = stored?.format;
|
|
366
|
+
return typeof format === 'string' ? format : 'unknown';
|
|
367
|
+
}
|
|
368
|
+
//# sourceMappingURL=googleAgentEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"googleAgentEngine.js","sourceRoot":"","sources":["../../../../src/adapters/hosting/googleAgentEngine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* googleIdentity — the {@link CredentialProvider} port over Google's own
|
|
3
|
+
* credential machinery (peer-dep `google-auth-library`).
|
|
4
|
+
*
|
|
5
|
+
* import { googleIdentity } from 'agentfootprint/security';
|
|
6
|
+
* const credentials = googleIdentity();
|
|
7
|
+
*
|
|
8
|
+
* ── What it is, and what it deliberately is not ─────────────────────────────
|
|
9
|
+
* This is the **narrow** adapter: it vends *Google* access tokens for *Google*
|
|
10
|
+
* APIs, from whatever credential the environment already has — Application
|
|
11
|
+
* Default Credentials on Cloud Run or GKE, a workload-identity federation
|
|
12
|
+
* config, a service account, optionally impersonating another service account.
|
|
13
|
+
* That is one job and it is done completely.
|
|
14
|
+
*
|
|
15
|
+
* It is **not** a token vault and does not pretend to be one. The other
|
|
16
|
+
* column's identity adapter can vend a *GitHub* token for a *user* because
|
|
17
|
+
* that service runs a vault with per-user OAuth grants behind it. Google's
|
|
18
|
+
* equivalent — the Agent Identity auth manager — is Preview with no Node
|
|
19
|
+
* surface, so `mode: 'user'` here is **refused by name** rather than quietly
|
|
20
|
+
* served with a machine token. A machine token returned where a user token was
|
|
21
|
+
* asked for is the exact silent downgrade the port exists to prevent: the call
|
|
22
|
+
* succeeds, the data comes back, and it was the agent's access rather than the
|
|
23
|
+
* person's. See {@link googleIdentity} for the refusal's wording.
|
|
24
|
+
*
|
|
25
|
+
* ── The one-hour fact, and where it bites ───────────────────────────────────
|
|
26
|
+
* A Google OAuth access token lives about an hour. That is fine wherever the
|
|
27
|
+
* credential is fetched per use — which is how `ctx.credential` works, so the
|
|
28
|
+
* ordinary path is unaffected. It bites in exactly one place, and it is worth
|
|
29
|
+
* naming because it looks like it should work:
|
|
30
|
+
*
|
|
31
|
+
* > **The OpenAI-compatible endpoint trap.** Google publishes an
|
|
32
|
+
* > OpenAI-compatible Gemini endpoint, and `openai({ baseURL, apiKey })` does
|
|
33
|
+
* > reach it. If you fill that `apiKey` with a token from here, it works for
|
|
34
|
+
* > an hour and then every call fails with a 401 — because `apiKey` is a
|
|
35
|
+
* > STRING captured when the provider is constructed, and a long-lived agent
|
|
36
|
+
* > process outlives it. There is no refresh home in the OpenAI provider's
|
|
37
|
+
* > options for a credential that expires. Use the native `gemini()` provider,
|
|
38
|
+
* > which reads ADC through the SDK and refreshes underneath you.
|
|
39
|
+
*
|
|
40
|
+
* `expiresAt` is reported on every issued credential so a caller that caches
|
|
41
|
+
* one can tell. This adapter never caches: {@link CachedGoogleClient} keeps
|
|
42
|
+
* the *client*, and the client's own refresh logic keeps the token fresh.
|
|
43
|
+
*
|
|
44
|
+
* ── Secrets ─────────────────────────────────────────────────────────────────
|
|
45
|
+
* The `sdkFailure` law, same as every other credential-touching adapter here:
|
|
46
|
+
* the library's own message never comes through, because auth libraries echo
|
|
47
|
+
* request detail into failure text and a message thrown from a
|
|
48
|
+
* `CredentialProvider` reaches the LLM as a tool result AND rides
|
|
49
|
+
* `agentfootprint.credential.failed` to every sink. What comes through is the
|
|
50
|
+
* operation that failed and the error's NAME. The original is not attached as
|
|
51
|
+
* `cause` — a cause travels into every serializer that walks own properties,
|
|
52
|
+
* which would undo all of it in one `JSON.stringify`.
|
|
53
|
+
*
|
|
54
|
+
* Pattern: Adapter (GoF) + lazy peer-dep load — `google-auth-library` is
|
|
55
|
+
* required the first time `getCredential` runs, or never if you inject one.
|
|
56
|
+
*/
|
|
57
|
+
import type { CredentialProvider } from '../../identity/types.js';
|
|
58
|
+
/** The scope every Google Cloud data-plane API accepts. */
|
|
59
|
+
export declare const CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
|
|
60
|
+
/**
|
|
61
|
+
* The slice of an auth client this adapter calls — a `GoogleAuth`, an
|
|
62
|
+
* `OAuth2Client` and an `Impersonated` all satisfy it, which is why nothing
|
|
63
|
+
* here switches on which one it has.
|
|
64
|
+
*/
|
|
65
|
+
export interface GoogleAuthClientLike {
|
|
66
|
+
/** The current access token, refreshed if the library thinks it is stale. */
|
|
67
|
+
getAccessToken(): Promise<{
|
|
68
|
+
token?: string | null;
|
|
69
|
+
} | string | null | undefined>;
|
|
70
|
+
/** Where the library records the expiry it knows about, in unix ms. */
|
|
71
|
+
readonly credentials?: {
|
|
72
|
+
readonly expiry_date?: number | null;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** The slice of a `GoogleAuth` this adapter calls. */
|
|
76
|
+
export interface GoogleAuthLike {
|
|
77
|
+
getClient(): Promise<GoogleAuthClientLike>;
|
|
78
|
+
}
|
|
79
|
+
/** The slice of `google-auth-library` this adapter loads. */
|
|
80
|
+
export interface GoogleAuthSdkModule {
|
|
81
|
+
readonly GoogleAuth?: new (options: {
|
|
82
|
+
scopes?: readonly string[];
|
|
83
|
+
}) => GoogleAuthLike;
|
|
84
|
+
readonly Impersonated?: new (options: {
|
|
85
|
+
sourceClient: GoogleAuthClientLike;
|
|
86
|
+
targetPrincipal: string;
|
|
87
|
+
targetScopes: string[];
|
|
88
|
+
delegates?: string[];
|
|
89
|
+
lifetime?: number;
|
|
90
|
+
}) => GoogleAuthClientLike;
|
|
91
|
+
}
|
|
92
|
+
/** Impersonate another service account for the tokens this provider vends. */
|
|
93
|
+
export interface GoogleImpersonation {
|
|
94
|
+
/**
|
|
95
|
+
* The service account to act as, e.g.
|
|
96
|
+
* `'agent-runner@my-project.iam.gserviceaccount.com'`. The credential the
|
|
97
|
+
* environment already has must hold `roles/iam.serviceAccountTokenCreator`
|
|
98
|
+
* on it; without that the exchange fails with a 403 whose text this adapter
|
|
99
|
+
* withholds, so the permission is worth checking before you wonder why.
|
|
100
|
+
*/
|
|
101
|
+
readonly targetPrincipal: string;
|
|
102
|
+
/** A delegation chain, when one account cannot impersonate the target directly. */
|
|
103
|
+
readonly delegates?: readonly string[];
|
|
104
|
+
/**
|
|
105
|
+
* How long the impersonated token lives, in seconds. The service's own
|
|
106
|
+
* ceiling is 3600 (one hour) unless the org policy raises it, and asking for
|
|
107
|
+
* more than it allows is refused by Google rather than clamped.
|
|
108
|
+
*/
|
|
109
|
+
readonly lifetimeSeconds?: number;
|
|
110
|
+
}
|
|
111
|
+
/** Options for {@link googleIdentity}. */
|
|
112
|
+
export interface GoogleIdentityOptions {
|
|
113
|
+
/**
|
|
114
|
+
* The OAuth scopes to request. Default `[cloud-platform]`, which is what
|
|
115
|
+
* every Google Cloud data-plane API accepts.
|
|
116
|
+
*
|
|
117
|
+
* A request's own `scopes` win when it names any — a tool that knows it only
|
|
118
|
+
* needs read access should say so, and this is where that is honoured.
|
|
119
|
+
*/
|
|
120
|
+
readonly scopes?: readonly string[];
|
|
121
|
+
/**
|
|
122
|
+
* Act as another service account. Off by default; see
|
|
123
|
+
* {@link GoogleImpersonation}.
|
|
124
|
+
*/
|
|
125
|
+
readonly impersonate?: GoogleImpersonation;
|
|
126
|
+
/**
|
|
127
|
+
* Which downstream services this provider will answer for.
|
|
128
|
+
*
|
|
129
|
+
* Unset — the default — it answers for ANY `service`, because the token it
|
|
130
|
+
* vends is a Google credential and the caller knows better than this adapter
|
|
131
|
+
* which Google API they are about to call.
|
|
132
|
+
*
|
|
133
|
+
* Set it and a request for a service outside the list is refused BY NAME
|
|
134
|
+
* rather than served. That is the useful setting in a deployment where tools
|
|
135
|
+
* declare `needs: [{ credential: 'github' }]` alongside Google ones: without
|
|
136
|
+
* it, this provider would happily hand a Google access token to the tool
|
|
137
|
+
* that wanted a GitHub one, and the failure would surface as a puzzling 401
|
|
138
|
+
* from GitHub rather than as a wiring error here.
|
|
139
|
+
*/
|
|
140
|
+
readonly services?: readonly string[];
|
|
141
|
+
/** Stable provider id (default `'google-identity'`). */
|
|
142
|
+
readonly id?: string;
|
|
143
|
+
/**
|
|
144
|
+
* @internal Test seam — a pre-built auth client. Bypasses the SDK entirely,
|
|
145
|
+
* so the suite runs with no package and no credential.
|
|
146
|
+
*/
|
|
147
|
+
readonly _client?: GoogleAuthClientLike;
|
|
148
|
+
/** @internal Test seam — the SDK module, to exercise the real construction. */
|
|
149
|
+
readonly _sdk?: GoogleAuthSdkModule;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Vend Google access tokens from whatever credential this environment has.
|
|
153
|
+
*
|
|
154
|
+
* **Status: contract-shaped and tested; awaiting field use.** Every path is
|
|
155
|
+
* exercised through an injected client and the loaded surface is pinned
|
|
156
|
+
* against the really-installed package. None of it has yet answered a real
|
|
157
|
+
* Google token request in a live project.
|
|
158
|
+
*
|
|
159
|
+
* @throws when `mode: 'user'` is requested — Google's per-user token vault has
|
|
160
|
+
* no Node surface, and a machine token returned in its place would be a
|
|
161
|
+
* silent downgrade.
|
|
162
|
+
* @throws when `services` is configured and the request names another one.
|
|
163
|
+
*
|
|
164
|
+
* @example A tool that calls a Google API with the deployment's own identity
|
|
165
|
+
* const agent = Agent.create({ provider, credentials: googleIdentity() })
|
|
166
|
+
* .tool(defineTool({
|
|
167
|
+
* name: 'read_sheet',
|
|
168
|
+
* needs: [{ credential: 'sheets', scopes: ['https://www.googleapis.com/auth/spreadsheets.readonly'] }],
|
|
169
|
+
* execute: async (args, ctx) =>
|
|
170
|
+
* fetch(url, { headers: ctx.credential!.toHeaders() }).then((r) => r.text()),
|
|
171
|
+
* }))
|
|
172
|
+
* .build();
|
|
173
|
+
*
|
|
174
|
+
* @example Acting as a dedicated service account
|
|
175
|
+
* googleIdentity({
|
|
176
|
+
* impersonate: { targetPrincipal: 'agent-runner@my-project.iam.gserviceaccount.com' },
|
|
177
|
+
* });
|
|
178
|
+
*/
|
|
179
|
+
export declare function googleIdentity(options?: GoogleIdentityOptions): CredentialProvider;
|