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,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":"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"}
|
|
@@ -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
|