agentfootprint 9.11.0 → 9.13.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/hosting/agentcore.js +36 -1
- package/dist/adapters/hosting/agentcore.js.map +1 -1
- package/dist/adapters/identity/agentcore.js +173 -33
- package/dist/adapters/identity/agentcore.js.map +1 -1
- package/dist/adapters/llm/GeminiProvider.js +519 -0
- package/dist/adapters/llm/GeminiProvider.js.map +1 -0
- package/dist/adapters/llm/contextWindow.js +20 -0
- package/dist/adapters/llm/contextWindow.js.map +1 -1
- package/dist/adapters/llm/createProvider.js +3 -0
- package/dist/adapters/llm/createProvider.js.map +1 -1
- package/dist/adapters/llm/googleGenAI.js +109 -0
- package/dist/adapters/llm/googleGenAI.js.map +1 -0
- package/dist/embedders/index.js +233 -1
- package/dist/embedders/index.js.map +1 -1
- package/dist/esm/adapters/hosting/agentcore.d.ts +4 -1
- package/dist/esm/adapters/hosting/agentcore.js +36 -1
- package/dist/esm/adapters/hosting/agentcore.js.map +1 -1
- package/dist/esm/adapters/identity/agentcore.d.ts +74 -15
- package/dist/esm/adapters/identity/agentcore.js +173 -33
- package/dist/esm/adapters/identity/agentcore.js.map +1 -1
- package/dist/esm/adapters/llm/GeminiProvider.d.ts +204 -0
- package/dist/esm/adapters/llm/GeminiProvider.js +514 -0
- package/dist/esm/adapters/llm/GeminiProvider.js.map +1 -0
- package/dist/esm/adapters/llm/contextWindow.js +20 -0
- package/dist/esm/adapters/llm/contextWindow.js.map +1 -1
- package/dist/esm/adapters/llm/createProvider.d.ts +4 -1
- package/dist/esm/adapters/llm/createProvider.js +3 -0
- package/dist/esm/adapters/llm/createProvider.js.map +1 -1
- package/dist/esm/adapters/llm/googleGenAI.d.ts +67 -0
- package/dist/esm/adapters/llm/googleGenAI.js +105 -0
- package/dist/esm/adapters/llm/googleGenAI.js.map +1 -0
- package/dist/esm/embedders/index.d.ts +188 -1
- package/dist/esm/embedders/index.js +231 -0
- package/dist/esm/embedders/index.js.map +1 -1
- package/dist/esm/hosting/httpHost.d.ts +10 -0
- package/dist/esm/hosting/httpHost.js +2 -1
- package/dist/esm/hosting/httpHost.js.map +1 -1
- package/dist/esm/hosting/standingAgent.js +63 -1
- package/dist/esm/hosting/standingAgent.js.map +1 -1
- package/dist/esm/hosting/types.d.ts +24 -0
- package/dist/esm/hosting/types.js.map +1 -1
- package/dist/esm/identity/types.d.ts +30 -0
- package/dist/esm/identity/types.js.map +1 -1
- package/dist/esm/providers.d.ts +3 -0
- package/dist/esm/providers.js +2 -0
- package/dist/esm/providers.js.map +1 -1
- package/dist/hosting/httpHost.js +2 -1
- package/dist/hosting/httpHost.js.map +1 -1
- package/dist/hosting/standingAgent.js +63 -1
- package/dist/hosting/standingAgent.js.map +1 -1
- package/dist/hosting/types.js.map +1 -1
- package/dist/identity/types.js.map +1 -1
- package/dist/providers.js +5 -1
- package/dist/providers.js.map +1 -1
- package/dist/types/adapters/hosting/agentcore.d.ts +4 -1
- package/dist/types/adapters/hosting/agentcore.d.ts.map +1 -1
- package/dist/types/adapters/identity/agentcore.d.ts +74 -15
- package/dist/types/adapters/identity/agentcore.d.ts.map +1 -1
- package/dist/types/adapters/llm/GeminiProvider.d.ts +205 -0
- package/dist/types/adapters/llm/GeminiProvider.d.ts.map +1 -0
- package/dist/types/adapters/llm/contextWindow.d.ts.map +1 -1
- package/dist/types/adapters/llm/createProvider.d.ts +4 -1
- package/dist/types/adapters/llm/createProvider.d.ts.map +1 -1
- package/dist/types/adapters/llm/googleGenAI.d.ts +68 -0
- package/dist/types/adapters/llm/googleGenAI.d.ts.map +1 -0
- package/dist/types/embedders/index.d.ts +188 -1
- package/dist/types/embedders/index.d.ts.map +1 -1
- package/dist/types/hosting/httpHost.d.ts +10 -0
- package/dist/types/hosting/httpHost.d.ts.map +1 -1
- package/dist/types/hosting/standingAgent.d.ts.map +1 -1
- package/dist/types/hosting/types.d.ts +24 -0
- package/dist/types/hosting/types.d.ts.map +1 -1
- package/dist/types/identity/types.d.ts +30 -0
- package/dist/types/identity/types.d.ts.map +1 -1
- package/dist/types/providers.d.ts +3 -0
- package/dist/types/providers.d.ts.map +1 -1
- package/package.json +7 -1
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* - request.mode 'machine' → `M2M`; 'user' → `USER_FEDERATION`
|
|
11
11
|
* - request.service → the configured OAuth2 credential-provider name
|
|
12
12
|
* - request.identity → (per-request workload identity scoping; see below)
|
|
13
|
+
* - request.userToken → the user's own JWT, exchanged for a user-scoped
|
|
14
|
+
* workload token before vending (9.12.0; see below)
|
|
13
15
|
* - a returned access token → `{ status: 'issued', credential: bearer(token) }`
|
|
14
16
|
* - a returned auth URL → `{ status: 'authorization-required' }` (3LO consent)
|
|
15
17
|
*
|
|
@@ -18,21 +20,50 @@
|
|
|
18
20
|
*
|
|
19
21
|
* **Per-request identity forwarding (workload identity scoping).**
|
|
20
22
|
* `GetResourceOauth2Token` carries NO user/tenant field — in AgentCore the
|
|
21
|
-
* user identity is bound EARLIER, at workload-token acquisition
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
23
|
+
* user identity is bound EARLIER, at workload-token acquisition. There are two
|
|
24
|
+
* ways to bind it, and this adapter dispatches both:
|
|
25
|
+
*
|
|
26
|
+
* `GetWorkloadAccessTokenForUserId(workloadName, userId)` — the ASSERTION.
|
|
27
|
+
* The agent says who the user is; AWS takes its word for it and returns a
|
|
28
|
+
* workload token keyed to that (workload, user).
|
|
29
|
+
* `GetWorkloadAccessTokenForJWT(workloadName, userToken)` — the PROOF (9.12.0).
|
|
30
|
+
* The agent hands over the token the user's own identity provider signed,
|
|
31
|
+
* and gets back "an opaque token representing the identity of both the
|
|
32
|
+
* workload and the user" (the service's own words for the response).
|
|
33
|
+
*
|
|
34
|
+
* Either way the answer is a `workloadAccessToken`, and it flows into
|
|
35
|
+
* `GetResourceOauth2Token`'s `workloadIdentityToken` unchanged — which is why
|
|
36
|
+
* the second one needed no new credential path, no new result branch and no
|
|
37
|
+
* change to `toHeaders()`. AgentCore keys its token vault + 3LO grants per
|
|
38
|
+
* (workload, user), so the whole point of resolving either is that the vault
|
|
39
|
+
* entry belongs to the person rather than to the agent.
|
|
40
|
+
*
|
|
41
|
+
* **The JWT rides the REQUEST.** `req.userToken` is per call, because the
|
|
42
|
+
* person calling is per call; a JWT in this provider's construction options
|
|
43
|
+
* would be one user's session serving everybody. Presence is what selects the
|
|
44
|
+
* exchange — a proof that arrived is never downgraded to an assertion — and
|
|
45
|
+
* `requireUserToken` turns "no JWT on a `mode: 'user'` request" from a quiet
|
|
46
|
+
* fallback into a refusal, for a deployment where every user IS authenticated.
|
|
47
|
+
*
|
|
48
|
+
* The ladder, in order, for one `getCredential(req)`:
|
|
49
|
+
* 1. `req.userToken` present → exchange it (needs `workloadName`; `mode: 'user'`).
|
|
50
|
+
* 2. else a userId derives from `req.identity` (default `identity.principal`;
|
|
51
|
+
* override via `userIdFor`) on a `mode: 'user'` request, and
|
|
52
|
+
* `options.workloadName` is configured → the by-userId exchange.
|
|
53
|
+
* 3. else the static `options.workloadIdentityToken` flows exactly as before.
|
|
54
|
+
*
|
|
32
55
|
* `tenant` has no native AgentCore field and is NOT forwarded by default —
|
|
33
56
|
* tenant isolation derives from the workload identity itself (per-tenant
|
|
34
57
|
* workloads), or encode it via `userIdFor` (e.g. `${tenant}:${principal}`).
|
|
35
58
|
*
|
|
59
|
+
* **Secrecy.** The inbound JWT and the exchanged workload token are secrets of
|
|
60
|
+
* equal weight, and neither appears in anything this file throws. A failed
|
|
61
|
+
* exchange is described by the SHAPE of the response — how many fields it
|
|
62
|
+
* carried and what they are called — never by its content, because every field
|
|
63
|
+
* of a token-exchange response is a token. A thrown message reaches the LLM as
|
|
64
|
+
* a tool result AND rides `agentfootprint.credential.failed`, so an error that
|
|
65
|
+
* quotes the response has published the user's session to both at once.
|
|
66
|
+
*
|
|
36
67
|
* ── How this talks to the SDK (9.4.0 — read this before editing) ────────────
|
|
37
68
|
* Through **`client.send(new SomeCommand(input))`**, never through a method on
|
|
38
69
|
* the client. A bare `@aws-sdk/client-*` **Client** is command-based: its
|
|
@@ -85,6 +116,16 @@ export interface AgentCoreIdentityClientLike {
|
|
|
85
116
|
}): Promise<{
|
|
86
117
|
readonly workloadAccessToken?: string;
|
|
87
118
|
}>;
|
|
119
|
+
/** Optional — required only when a request carries `userToken`. Exchanges the
|
|
120
|
+
* user's OWN IdP-issued JWT for a workload access token representing both the
|
|
121
|
+
* workload and the user. Same answer shape as the by-userId exchange, which
|
|
122
|
+
* is why both feed the same `workloadIdentityToken`. */
|
|
123
|
+
getWorkloadAccessTokenForJWT?(input: {
|
|
124
|
+
readonly workloadName: string;
|
|
125
|
+
readonly userToken: string;
|
|
126
|
+
}): Promise<{
|
|
127
|
+
readonly workloadAccessToken?: string;
|
|
128
|
+
}>;
|
|
88
129
|
}
|
|
89
130
|
export interface AgentCoreIdentityOptions {
|
|
90
131
|
readonly region?: string;
|
|
@@ -93,11 +134,28 @@ export interface AgentCoreIdentityOptions {
|
|
|
93
134
|
* workload token is resolved (see `workloadName`). */
|
|
94
135
|
readonly workloadIdentityToken?: string;
|
|
95
136
|
/** The AgentCore workload identity name — the OPT-IN for per-request identity
|
|
96
|
-
* scoping. When set, `mode: 'user'`
|
|
97
|
-
* per-user workload access token
|
|
98
|
-
*
|
|
99
|
-
*
|
|
137
|
+
* scoping, and the one option BOTH exchanges need. When set, a `mode: 'user'`
|
|
138
|
+
* request resolves a per-user workload access token before vending — from
|
|
139
|
+
* `req.userToken` via `GetWorkloadAccessTokenForJWT` when the user's own JWT
|
|
140
|
+
* arrived, otherwise from `req.identity` via `GetWorkloadAccessTokenForUserId`
|
|
141
|
+
* — so AgentCore's token vault + 3LO grants are keyed per (workload, user)
|
|
142
|
+
* instead of per workload. Omit → today's static-token behavior. */
|
|
100
143
|
readonly workloadName?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Refuse a `mode: 'user'` request that carries no `req.userToken` (9.12.0),
|
|
146
|
+
* instead of falling back to the by-userId exchange or the static token.
|
|
147
|
+
*
|
|
148
|
+
* The opt-in for a deployment where the front door really does authenticate
|
|
149
|
+
* every person — an AgentCore Runtime behind JWT inbound auth, an API gateway
|
|
150
|
+
* that validates a bearer token. There, a delegated request with no proof
|
|
151
|
+
* attached is a wiring bug, and the failure it causes without this flag is the
|
|
152
|
+
* quiet kind: a token gets vended, the call succeeds, and it was scoped to the
|
|
153
|
+
* agent (or to a user id the agent asserted) rather than to the person.
|
|
154
|
+
*
|
|
155
|
+
* `mode: 'machine'` is never affected — M2M is the workload's own identity and
|
|
156
|
+
* has no user to prove.
|
|
157
|
+
*/
|
|
158
|
+
readonly requireUserToken?: boolean;
|
|
101
159
|
/** Map `req.identity` → the AgentCore `userId`. Default: `identity.principal`.
|
|
102
160
|
* `tenant` has no native AgentCore field — encode it here if you need
|
|
103
161
|
* tenant-scoped vault entries (e.g. ``({ tenant, principal }) =>
|
|
@@ -125,6 +183,7 @@ export interface BedrockAgentCoreIdentitySdkModule {
|
|
|
125
183
|
};
|
|
126
184
|
readonly GetResourceOauth2TokenCommand?: new (input: unknown) => unknown;
|
|
127
185
|
readonly GetWorkloadAccessTokenForUserIdCommand?: new (input: unknown) => unknown;
|
|
186
|
+
readonly GetWorkloadAccessTokenForJWTCommand?: new (input: unknown) => unknown;
|
|
128
187
|
}
|
|
129
188
|
/** Build a {@link CredentialProvider} backed by AWS Bedrock AgentCore Identity. */
|
|
130
189
|
export declare function agentCoreIdentity(options?: AgentCoreIdentityOptions): CredentialProvider;
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* - request.mode 'machine' → `M2M`; 'user' → `USER_FEDERATION`
|
|
11
11
|
* - request.service → the configured OAuth2 credential-provider name
|
|
12
12
|
* - request.identity → (per-request workload identity scoping; see below)
|
|
13
|
+
* - request.userToken → the user's own JWT, exchanged for a user-scoped
|
|
14
|
+
* workload token before vending (9.12.0; see below)
|
|
13
15
|
* - a returned access token → `{ status: 'issued', credential: bearer(token) }`
|
|
14
16
|
* - a returned auth URL → `{ status: 'authorization-required' }` (3LO consent)
|
|
15
17
|
*
|
|
@@ -18,21 +20,50 @@
|
|
|
18
20
|
*
|
|
19
21
|
* **Per-request identity forwarding (workload identity scoping).**
|
|
20
22
|
* `GetResourceOauth2Token` carries NO user/tenant field — in AgentCore the
|
|
21
|
-
* user identity is bound EARLIER, at workload-token acquisition
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
23
|
+
* user identity is bound EARLIER, at workload-token acquisition. There are two
|
|
24
|
+
* ways to bind it, and this adapter dispatches both:
|
|
25
|
+
*
|
|
26
|
+
* `GetWorkloadAccessTokenForUserId(workloadName, userId)` — the ASSERTION.
|
|
27
|
+
* The agent says who the user is; AWS takes its word for it and returns a
|
|
28
|
+
* workload token keyed to that (workload, user).
|
|
29
|
+
* `GetWorkloadAccessTokenForJWT(workloadName, userToken)` — the PROOF (9.12.0).
|
|
30
|
+
* The agent hands over the token the user's own identity provider signed,
|
|
31
|
+
* and gets back "an opaque token representing the identity of both the
|
|
32
|
+
* workload and the user" (the service's own words for the response).
|
|
33
|
+
*
|
|
34
|
+
* Either way the answer is a `workloadAccessToken`, and it flows into
|
|
35
|
+
* `GetResourceOauth2Token`'s `workloadIdentityToken` unchanged — which is why
|
|
36
|
+
* the second one needed no new credential path, no new result branch and no
|
|
37
|
+
* change to `toHeaders()`. AgentCore keys its token vault + 3LO grants per
|
|
38
|
+
* (workload, user), so the whole point of resolving either is that the vault
|
|
39
|
+
* entry belongs to the person rather than to the agent.
|
|
40
|
+
*
|
|
41
|
+
* **The JWT rides the REQUEST.** `req.userToken` is per call, because the
|
|
42
|
+
* person calling is per call; a JWT in this provider's construction options
|
|
43
|
+
* would be one user's session serving everybody. Presence is what selects the
|
|
44
|
+
* exchange — a proof that arrived is never downgraded to an assertion — and
|
|
45
|
+
* `requireUserToken` turns "no JWT on a `mode: 'user'` request" from a quiet
|
|
46
|
+
* fallback into a refusal, for a deployment where every user IS authenticated.
|
|
47
|
+
*
|
|
48
|
+
* The ladder, in order, for one `getCredential(req)`:
|
|
49
|
+
* 1. `req.userToken` present → exchange it (needs `workloadName`; `mode: 'user'`).
|
|
50
|
+
* 2. else a userId derives from `req.identity` (default `identity.principal`;
|
|
51
|
+
* override via `userIdFor`) on a `mode: 'user'` request, and
|
|
52
|
+
* `options.workloadName` is configured → the by-userId exchange.
|
|
53
|
+
* 3. else the static `options.workloadIdentityToken` flows exactly as before.
|
|
54
|
+
*
|
|
32
55
|
* `tenant` has no native AgentCore field and is NOT forwarded by default —
|
|
33
56
|
* tenant isolation derives from the workload identity itself (per-tenant
|
|
34
57
|
* workloads), or encode it via `userIdFor` (e.g. `${tenant}:${principal}`).
|
|
35
58
|
*
|
|
59
|
+
* **Secrecy.** The inbound JWT and the exchanged workload token are secrets of
|
|
60
|
+
* equal weight, and neither appears in anything this file throws. A failed
|
|
61
|
+
* exchange is described by the SHAPE of the response — how many fields it
|
|
62
|
+
* carried and what they are called — never by its content, because every field
|
|
63
|
+
* of a token-exchange response is a token. A thrown message reaches the LLM as
|
|
64
|
+
* a tool result AND rides `agentfootprint.credential.failed`, so an error that
|
|
65
|
+
* quotes the response has published the user's session to both at once.
|
|
66
|
+
*
|
|
36
67
|
* ── How this talks to the SDK (9.4.0 — read this before editing) ────────────
|
|
37
68
|
* Through **`client.send(new SomeCommand(input))`**, never through a method on
|
|
38
69
|
* the client. A bare `@aws-sdk/client-*` **Client** is command-based: its
|
|
@@ -53,6 +84,62 @@
|
|
|
53
84
|
*/
|
|
54
85
|
import { lazyRequire } from '../../lib/lazyRequire.js';
|
|
55
86
|
import { bearer } from '../../identity/kinds.js';
|
|
87
|
+
/**
|
|
88
|
+
* Re-raise a failed SDK call **without its text** (9.12.0).
|
|
89
|
+
*
|
|
90
|
+
* Every input this adapter sends is a secret: the user's JWT goes into
|
|
91
|
+
* `GetWorkloadAccessTokenForJWT`, a workload access token goes into
|
|
92
|
+
* `GetResourceOauth2Token`. AWS clients report transport and validation
|
|
93
|
+
* failures by echoing request detail into the message — which is how a live
|
|
94
|
+
* user session ends up in a string that this library then hands to the LLM as
|
|
95
|
+
* a tool result and emits on `agentfootprint.credential.failed`. One echo, and
|
|
96
|
+
* it is in the conversation, the commit log, the narrative and every sink.
|
|
97
|
+
*
|
|
98
|
+
* So the text does not come through. What does is the part that is both safe
|
|
99
|
+
* and actionable: which operation failed, the exception's NAME (AWS models
|
|
100
|
+
* them — `AccessDeniedException`, `ResourceNotFoundException`,
|
|
101
|
+
* `ThrottlingException`, `UnauthorizedException`, `ValidationException`) and
|
|
102
|
+
* the HTTP status. That is the same trade the Vault adapter makes, and it is
|
|
103
|
+
* the same reason.
|
|
104
|
+
*
|
|
105
|
+
* **The original is deliberately not attached as `cause`.** A cause travels
|
|
106
|
+
* with the error into every serializer that walks own properties, which would
|
|
107
|
+
* undo the whole of this in one `JSON.stringify`.
|
|
108
|
+
*
|
|
109
|
+
* An injected `_client` never reaches here: it owns its own mapping, and its
|
|
110
|
+
* own secrecy with it — the same law this file already states for field names.
|
|
111
|
+
*/
|
|
112
|
+
function sdkFailure(command, err) {
|
|
113
|
+
const e = err;
|
|
114
|
+
const name = typeof e?.name === 'string' && e.name.length > 0 ? e.name : 'an unnamed failure';
|
|
115
|
+
const status = e?.$metadata?.httpStatusCode;
|
|
116
|
+
return new Error(`agentCoreIdentity: ${command} failed — ${name}` +
|
|
117
|
+
(typeof status === 'number' ? ` (HTTP ${status})` : '') +
|
|
118
|
+
".\n The SDK's own message is withheld: this request carried a token, and AWS clients " +
|
|
119
|
+
'echo request detail into failure text. Check CloudWatch for the full exception.');
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Read the exchanged workload token, or refuse **by shape**.
|
|
123
|
+
*
|
|
124
|
+
* Shared by both exchanges so there is one refusal to read and one rule about
|
|
125
|
+
* what may be said: field NAMES and a count, never a value. Every field of a
|
|
126
|
+
* token-exchange response is a token, and this message travels to the model, to
|
|
127
|
+
* `agentfootprint.credential.failed` and to every log sink attached to it. An
|
|
128
|
+
* operator debugging a malformed answer needs to know which fields came back,
|
|
129
|
+
* which is exactly the part that is safe to print.
|
|
130
|
+
*/
|
|
131
|
+
function requireWorkloadAccessToken(response, operation) {
|
|
132
|
+
const token = response?.workloadAccessToken;
|
|
133
|
+
if (typeof token === 'string' && token.length > 0)
|
|
134
|
+
return token;
|
|
135
|
+
const fields = response && typeof response === 'object' ? Object.keys(response) : [];
|
|
136
|
+
throw new Error(`agentCoreIdentity: ${operation} returned no usable workloadAccessToken, so there is no ` +
|
|
137
|
+
'user-scoped workload token to vend with — refusing rather than falling back to a ' +
|
|
138
|
+
'workload-level one.\n' +
|
|
139
|
+
` The response carried ${fields.length} field(s)` +
|
|
140
|
+
(fields.length > 0 ? `: ${fields.join(', ')}.` : '.') +
|
|
141
|
+
'\n Values are withheld on purpose: every field of a token-exchange response is a secret.');
|
|
142
|
+
}
|
|
56
143
|
/**
|
|
57
144
|
* Map {@link AgentCoreIdentityClientLike} onto the real SDK commands.
|
|
58
145
|
*
|
|
@@ -92,7 +179,12 @@ function createIdentityClient(options) {
|
|
|
92
179
|
throw new Error(`agentCoreIdentity: \`@aws-sdk/client-bedrock-agentcore\` is missing ${name}. ` +
|
|
93
180
|
'Upgrade the SDK, or pass `_client` with your own mapping.');
|
|
94
181
|
}
|
|
95
|
-
|
|
182
|
+
try {
|
|
183
|
+
return await sdk.send(new Ctor(input));
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
throw sdkFailure(name, err);
|
|
187
|
+
}
|
|
96
188
|
};
|
|
97
189
|
return {
|
|
98
190
|
async getResourceOauth2Token(input) {
|
|
@@ -115,13 +207,27 @@ function createIdentityClient(options) {
|
|
|
115
207
|
...(r?.sessionUri !== undefined && { sessionId: r.sessionUri }),
|
|
116
208
|
};
|
|
117
209
|
},
|
|
210
|
+
// ── The two workload-token exchanges ──────────────────────────────
|
|
211
|
+
//
|
|
212
|
+
// Both answer `{ workloadAccessToken }` and nothing else, so unlike
|
|
213
|
+
// `getResourceOauth2Token` above there is no field to rename — and both
|
|
214
|
+
// hand the response back AS RECEIVED rather than narrowed to that one
|
|
215
|
+
// field. That is deliberate: when the field is missing,
|
|
216
|
+
// `requireWorkloadAccessToken` refuses by describing the response's SHAPE,
|
|
217
|
+
// and a shim that had already thrown the other field names away would leave
|
|
218
|
+
// it describing an empty object — the least useful message available, on
|
|
219
|
+
// the one path where the operator has nothing else to go on.
|
|
118
220
|
async getWorkloadAccessTokenForUserId(input) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
221
|
+
return ((await send(mod.GetWorkloadAccessTokenForUserIdCommand, 'GetWorkloadAccessTokenForUserIdCommand', input)) ?? {});
|
|
222
|
+
},
|
|
223
|
+
async getWorkloadAccessTokenForJWT(input) {
|
|
224
|
+
// `GetWorkloadAccessTokenForJWTRequest` is `{ workloadName, userToken }`,
|
|
225
|
+
// both required, and the response is `{ workloadAccessToken }` — verified
|
|
226
|
+
// against a real install of @aws-sdk/client-bedrock-agentcore before this
|
|
227
|
+
// shipped, the same way the code-interpreter shapes were. `userToken` is
|
|
228
|
+
// the SDK's own name for the user's IdP-issued OAuth2 token, so the
|
|
229
|
+
// adapter and the wire agree without a rename.
|
|
230
|
+
return ((await send(mod.GetWorkloadAccessTokenForJWTCommand, 'GetWorkloadAccessTokenForJWTCommand', input)) ?? {});
|
|
125
231
|
},
|
|
126
232
|
};
|
|
127
233
|
}
|
|
@@ -139,13 +245,55 @@ export function agentCoreIdentity(options = {}) {
|
|
|
139
245
|
const getClient = () => (client ??= resolveClient(options));
|
|
140
246
|
const userIdFor = options.userIdFor ?? defaultUserIdFor;
|
|
141
247
|
// Per-request identity forwarding (workload identity scoping) — see module
|
|
142
|
-
// header
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
// and vends with that.
|
|
146
|
-
//
|
|
248
|
+
// header for the ladder and why both exchanges answer the same question.
|
|
249
|
+
// `GetResourceOauth2Token` has no user field; the user is bound at
|
|
250
|
+
// workload-token acquisition, so a `mode: 'user'` request resolves a
|
|
251
|
+
// USER-SCOPED workload token first and vends with that. Both exchanges
|
|
252
|
+
// require `workloadName` (the opt-in); with neither engaged the static
|
|
253
|
+
// `workloadIdentityToken` flows unchanged (pre-forwarding behavior).
|
|
147
254
|
const resolveWorkloadToken = async (req) => {
|
|
148
|
-
const
|
|
255
|
+
const delegated = req.mode === 'user';
|
|
256
|
+
if (req.userToken !== undefined) {
|
|
257
|
+
if (!delegated) {
|
|
258
|
+
// A user's signed token on a request that asks for the WORKLOAD's own
|
|
259
|
+
// identity. Vending M2M while holding somebody's proof is the exact
|
|
260
|
+
// silent downgrade this whole path exists to close, so it is named
|
|
261
|
+
// rather than resolved one way or the other.
|
|
262
|
+
throw new Error("agentCoreIdentity: a `userToken` arrived on a `mode: 'machine'` request. M2M is the " +
|
|
263
|
+
"workload's own identity — there is no user to act on behalf of, and the token " +
|
|
264
|
+
"would be ignored.\n Declare `mode: 'user'` to exchange it, or drop the token.");
|
|
265
|
+
}
|
|
266
|
+
if (!options.workloadName) {
|
|
267
|
+
throw new Error('agentCoreIdentity: a `userToken` arrived but no `workloadName` is configured, so ' +
|
|
268
|
+
'there is nothing to exchange it against — and vending at workload level would ' +
|
|
269
|
+
"hand back a token scoped to the AGENT while holding the user's proof.\n" +
|
|
270
|
+
' Configure `workloadName` (your AgentCore workload identity), or stop sending ' +
|
|
271
|
+
'`userToken`.');
|
|
272
|
+
}
|
|
273
|
+
const c = getClient();
|
|
274
|
+
if (typeof c.getWorkloadAccessTokenForJWT !== 'function') {
|
|
275
|
+
// The SDK-backed client always implements this, so reaching here means
|
|
276
|
+
// an injected `_client` that does not.
|
|
277
|
+
throw new Error('agentCoreIdentity: a `userToken` arrived, but the injected `_client` has no ' +
|
|
278
|
+
'getWorkloadAccessTokenForJWT. Implement it, or stop sending `userToken`.');
|
|
279
|
+
}
|
|
280
|
+
return requireWorkloadAccessToken(await c.getWorkloadAccessTokenForJWT({
|
|
281
|
+
workloadName: options.workloadName,
|
|
282
|
+
userToken: req.userToken,
|
|
283
|
+
}), 'GetWorkloadAccessTokenForJWT');
|
|
284
|
+
}
|
|
285
|
+
if (options.requireUserToken === true && delegated) {
|
|
286
|
+
// The deployment said every delegated request is authenticated. One that
|
|
287
|
+
// is not is a wiring bug, and the only alternative to saying so is to
|
|
288
|
+
// vend something weaker and let it succeed.
|
|
289
|
+
throw new Error("agentCoreIdentity: `requireUserToken` is set, but this `mode: 'user'` request for " +
|
|
290
|
+
`'${req.service}' carried no \`userToken\`.\n` +
|
|
291
|
+
" Pass the caller's own IdP token as `getCredential({ …, userToken })` — inside " +
|
|
292
|
+
'AgentCore Runtime behind JWT inbound auth it is the bearer token the front door ' +
|
|
293
|
+
'validated.\n' +
|
|
294
|
+
' Or drop `requireUserToken` to fall back to the by-userId exchange.');
|
|
295
|
+
}
|
|
296
|
+
const userId = delegated && req.identity !== undefined ? userIdFor(req.identity) : undefined;
|
|
149
297
|
if (userId === undefined || !options.workloadName)
|
|
150
298
|
return options.workloadIdentityToken;
|
|
151
299
|
const c = getClient();
|
|
@@ -157,15 +305,7 @@ export function agentCoreIdentity(options = {}) {
|
|
|
157
305
|
'but the injected `_client` has no getWorkloadAccessTokenForUserId. Implement it, ' +
|
|
158
306
|
'or drop `workloadName` to vend with the static `workloadIdentityToken`.');
|
|
159
307
|
}
|
|
160
|
-
|
|
161
|
-
workloadName: options.workloadName,
|
|
162
|
-
userId,
|
|
163
|
-
});
|
|
164
|
-
if (!res.workloadAccessToken) {
|
|
165
|
-
throw new Error('agentCoreIdentity: GetWorkloadAccessTokenForUserId returned no workloadAccessToken ' +
|
|
166
|
-
'for per-user scoped vending.');
|
|
167
|
-
}
|
|
168
|
-
return res.workloadAccessToken;
|
|
308
|
+
return requireWorkloadAccessToken(await c.getWorkloadAccessTokenForUserId({ workloadName: options.workloadName, userId }), 'GetWorkloadAccessTokenForUserId');
|
|
169
309
|
};
|
|
170
310
|
return {
|
|
171
311
|
id: options.id ?? 'agentcore-identity',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agentcore.js","sourceRoot":"","sources":["../../../../src/adapters/identity/agentcore.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"agentcore.js","sourceRoot":"","sources":["../../../../src/adapters/identity/agentcore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeminiProvider — wraps `@google/genai` as an `LLMProvider` (9.13.0).
|
|
3
|
+
*
|
|
4
|
+
* Pattern: Adapter (GoF) + Ports-and-Adapters (Cockburn 2005).
|
|
5
|
+
* Role: Outer ring — translates `LLMRequest`/`LLMResponse` to/from Google's
|
|
6
|
+
* `generateContent` API. Knows nothing about agents, recorders, or
|
|
7
|
+
* compositions.
|
|
8
|
+
* Emits: N/A — providers don't emit; recorders observe via Agent.
|
|
9
|
+
*
|
|
10
|
+
* ─── Two doors, one adapter ─────────────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* The same SDK reaches two different services, and which one you get is a
|
|
13
|
+
* property of the OPTIONS, not of a separate factory:
|
|
14
|
+
*
|
|
15
|
+
* • **Vertex** — `gemini({ project, location })` builds
|
|
16
|
+
* `new GoogleGenAI({ vertexai: true, project, location, googleAuthOptions })`.
|
|
17
|
+
* Auth is Application Default Credentials (env key file, gcloud user
|
|
18
|
+
* credentials, or the metadata server on GCE / Cloud Run / Agent Runtime).
|
|
19
|
+
* No API key exists on this path.
|
|
20
|
+
* • **Gemini API (AI Studio)** — `gemini({ apiKey })` builds
|
|
21
|
+
* `new GoogleGenAI({ apiKey })`. One key, no cloud project.
|
|
22
|
+
*
|
|
23
|
+
* Neither is guessed. The factory refuses at construction when it can resolve
|
|
24
|
+
* neither a project nor a key, naming both doors — because the SDK's own
|
|
25
|
+
* behaviour in that case is to WARN on stderr, construct anyway, and fail on
|
|
26
|
+
* the first call, which reads like a network problem rather than a missing
|
|
27
|
+
* setting.
|
|
28
|
+
*
|
|
29
|
+
* ─── Why native, and not `openai({ baseURL })` ──────────────────────
|
|
30
|
+
*
|
|
31
|
+
* Google publishes an OpenAI-compatible endpoint, and it does work — for about
|
|
32
|
+
* an hour. It authenticates with an OAuth access token that expires and has no
|
|
33
|
+
* refresh home in `OpenAIProviderOptions`; its `tools.function.parameters` is
|
|
34
|
+
* **OpenAPI**, not JSON Schema, so `$ref` / `oneOf` / `additionalProperties`
|
|
35
|
+
* diverge silently; unsupported parameters are ignored rather than refused; and
|
|
36
|
+
* its documented response carries no cached- or reasoning-token counts. This
|
|
37
|
+
* adapter exists because every one of those is answerable on the native SDK:
|
|
38
|
+
* ADC refreshes itself, tools go over as JSON Schema untranslated
|
|
39
|
+
* (`parametersJsonSchema`), a forced single tool is expressible, and
|
|
40
|
+
* `usageMetadata` reports cached and thinking tokens as separate numbers.
|
|
41
|
+
*
|
|
42
|
+
* ─── Limitations (stated, not discovered) ───────────────────────────
|
|
43
|
+
*
|
|
44
|
+
* • Multi-modal input/output NOT supported — `LLMMessage.content` is `string`,
|
|
45
|
+
* so this adapter sends and reads text parts only. Inline data, files and
|
|
46
|
+
* generated images are out of scope for the port as it stands.
|
|
47
|
+
* • **Thought summaries are not requested.** `usage.thinking` is reported
|
|
48
|
+
* honestly from `thoughtsTokenCount`, and `req.thinking.budget` is threaded
|
|
49
|
+
* to `thinkingConfig.thinkingBudget` — but `includeThoughts` is deliberately
|
|
50
|
+
* left unset, so no `rawThinking` is produced. Gemini's thought parts carry a
|
|
51
|
+
* `thoughtSignature` that has to be echoed byte-exact on the next turn, and
|
|
52
|
+
* there is no Gemini `ThinkingHandler` in this release to normalize and
|
|
53
|
+
* round-trip them. Asking for content nothing can carry would be a leak, not
|
|
54
|
+
* a feature. `thinkingConfig.thinkingLevel` has no field on `LLMRequest` and
|
|
55
|
+
* is likewise not sent.
|
|
56
|
+
* • `responseJsonSchema` (native structured output) is NOT exposed; use
|
|
57
|
+
* `.outputSchema(...)`, which goes over as a forced tool — a shape this
|
|
58
|
+
* adapter DOES carry.
|
|
59
|
+
* • Grounding tools (Google Search, code execution, URL context, MCP servers)
|
|
60
|
+
* are not exposed. `tools` carries function declarations only.
|
|
61
|
+
*/
|
|
62
|
+
import type { LLMCallHooks, LLMChunk, LLMProvider, LLMRequest, LLMResponse, WireRole } from '../types.js';
|
|
63
|
+
import { type GoogleGenAIConnectionOptions } from './googleGenAI.js';
|
|
64
|
+
/** One `parts[]` entry. Only the members this adapter reads or writes. */
|
|
65
|
+
export interface GeminiPart {
|
|
66
|
+
readonly text?: string;
|
|
67
|
+
/** `true` on a thought-summary part. Never joined into visible content. */
|
|
68
|
+
readonly thought?: boolean;
|
|
69
|
+
readonly functionCall?: {
|
|
70
|
+
readonly id?: string;
|
|
71
|
+
readonly name?: string;
|
|
72
|
+
readonly args?: Record<string, unknown>;
|
|
73
|
+
};
|
|
74
|
+
readonly functionResponse?: {
|
|
75
|
+
readonly id?: string;
|
|
76
|
+
readonly name?: string;
|
|
77
|
+
readonly response?: Record<string, unknown>;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** One conversation turn. Gemini's assistant role is spelled `'model'`. */
|
|
81
|
+
export interface GeminiContent {
|
|
82
|
+
readonly role: 'user' | 'model';
|
|
83
|
+
readonly parts: readonly GeminiPart[];
|
|
84
|
+
}
|
|
85
|
+
/** A `FunctionDeclaration`, JSON-Schema flavour. */
|
|
86
|
+
export interface GeminiFunctionDeclaration {
|
|
87
|
+
readonly name: string;
|
|
88
|
+
readonly description: string;
|
|
89
|
+
/**
|
|
90
|
+
* JSON Schema, passed through UNCHANGED. The sibling `parameters` field
|
|
91
|
+
* takes OpenAPI instead and is never set here — sending both is what makes
|
|
92
|
+
* an adapter's tool schemas quietly disagree with the ones it was given.
|
|
93
|
+
*/
|
|
94
|
+
readonly parametersJsonSchema: Record<string, unknown>;
|
|
95
|
+
}
|
|
96
|
+
export interface GeminiGenerateConfig {
|
|
97
|
+
systemInstruction?: string;
|
|
98
|
+
temperature?: number;
|
|
99
|
+
maxOutputTokens?: number;
|
|
100
|
+
stopSequences?: string[];
|
|
101
|
+
abortSignal?: AbortSignal;
|
|
102
|
+
tools?: {
|
|
103
|
+
functionDeclarations: GeminiFunctionDeclaration[];
|
|
104
|
+
}[];
|
|
105
|
+
toolConfig?: {
|
|
106
|
+
functionCallingConfig: {
|
|
107
|
+
mode: 'AUTO' | 'ANY' | 'NONE';
|
|
108
|
+
allowedFunctionNames?: string[];
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
thinkingConfig?: {
|
|
112
|
+
thinkingBudget: number;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export interface GeminiGenerateParams {
|
|
116
|
+
readonly model: string;
|
|
117
|
+
readonly contents: readonly GeminiContent[];
|
|
118
|
+
readonly config?: GeminiGenerateConfig;
|
|
119
|
+
}
|
|
120
|
+
export interface GeminiUsageMetadata {
|
|
121
|
+
readonly promptTokenCount?: number;
|
|
122
|
+
readonly candidatesTokenCount?: number;
|
|
123
|
+
readonly cachedContentTokenCount?: number;
|
|
124
|
+
readonly thoughtsTokenCount?: number;
|
|
125
|
+
readonly totalTokenCount?: number;
|
|
126
|
+
}
|
|
127
|
+
export interface GeminiCandidate {
|
|
128
|
+
readonly content?: {
|
|
129
|
+
readonly parts?: readonly GeminiPart[];
|
|
130
|
+
};
|
|
131
|
+
readonly finishReason?: string;
|
|
132
|
+
}
|
|
133
|
+
export interface GeminiGenerateResponse {
|
|
134
|
+
readonly candidates?: readonly GeminiCandidate[];
|
|
135
|
+
readonly usageMetadata?: GeminiUsageMetadata;
|
|
136
|
+
readonly responseId?: string;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The `models` namespace, narrowed to the two operations this adapter
|
|
140
|
+
* dispatches.
|
|
141
|
+
*
|
|
142
|
+
* `countTokens` is deliberately ABSENT. It exists on the real namespace and it
|
|
143
|
+
* would be an easy way to fill in a usage number the wire did not report —
|
|
144
|
+
* which is precisely why it is not here. A count we asked for separately is not
|
|
145
|
+
* what the call was billed for; see the streaming note on `stream()`.
|
|
146
|
+
*/
|
|
147
|
+
export interface GeminiModelsLike {
|
|
148
|
+
generateContent(params: GeminiGenerateParams): Promise<GeminiGenerateResponse>;
|
|
149
|
+
/** Note the double await: the SDK returns a PROMISE of an async generator. */
|
|
150
|
+
generateContentStream(params: GeminiGenerateParams): Promise<AsyncIterable<GeminiGenerateResponse>>;
|
|
151
|
+
}
|
|
152
|
+
/** What `new GoogleGenAI(...)` gives us, narrowed to what we use. */
|
|
153
|
+
export interface GeminiClientLike {
|
|
154
|
+
readonly models: GeminiModelsLike;
|
|
155
|
+
}
|
|
156
|
+
export interface GeminiProviderOptions extends GoogleGenAIConnectionOptions {
|
|
157
|
+
/**
|
|
158
|
+
* Default model used when `LLMRequest.model` is `'gemini'` (the shorthand).
|
|
159
|
+
* A request naming a full model id wins.
|
|
160
|
+
*/
|
|
161
|
+
readonly defaultModel?: string;
|
|
162
|
+
/** Default `maxOutputTokens` when the request doesn't set it. Optional. */
|
|
163
|
+
readonly defaultMaxTokens?: number;
|
|
164
|
+
/** @internal Pre-built client for testing. Skips the SDK import entirely. */
|
|
165
|
+
readonly _client?: GeminiClientLike;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Build an `LLMProvider` backed by Gemini — on Vertex or on the Gemini API.
|
|
169
|
+
*
|
|
170
|
+
* @example Vertex (Application Default Credentials)
|
|
171
|
+
* ```ts
|
|
172
|
+
* import { Agent } from 'agentfootprint';
|
|
173
|
+
* import { gemini } from 'agentfootprint/providers';
|
|
174
|
+
*
|
|
175
|
+
* const agent = Agent.create({
|
|
176
|
+
* provider: gemini({ project: 'my-project', location: 'us-central1' }),
|
|
177
|
+
* model: 'gemini',
|
|
178
|
+
* })
|
|
179
|
+
* .tool(weatherTool)
|
|
180
|
+
* .build();
|
|
181
|
+
* ```
|
|
182
|
+
*
|
|
183
|
+
* @example Gemini API (one key, no cloud project)
|
|
184
|
+
* ```ts
|
|
185
|
+
* const provider = gemini({ apiKey: process.env.GEMINI_API_KEY });
|
|
186
|
+
* ```
|
|
187
|
+
*
|
|
188
|
+
* @throws at construction when neither a project nor an API key is resolvable,
|
|
189
|
+
* and when `@google/genai` is not installed.
|
|
190
|
+
*/
|
|
191
|
+
export declare function gemini(options?: GeminiProviderOptions): LLMProvider;
|
|
192
|
+
/**
|
|
193
|
+
* Class form for consumers who prefer `new GeminiProvider(...)` over the
|
|
194
|
+
* `gemini(...)` factory. Identical behavior; trivial wrapper.
|
|
195
|
+
*/
|
|
196
|
+
export declare class GeminiProvider implements LLMProvider {
|
|
197
|
+
readonly name = "gemini";
|
|
198
|
+
readonly carriesInMessages: readonly WireRole[];
|
|
199
|
+
readonly carriesForcedToolChoice = true;
|
|
200
|
+
private readonly inner;
|
|
201
|
+
constructor(options?: GeminiProviderOptions);
|
|
202
|
+
complete(req: LLMRequest, hooks?: LLMCallHooks): Promise<LLMResponse>;
|
|
203
|
+
stream(req: LLMRequest, hooks?: LLMCallHooks): AsyncIterable<LLMChunk>;
|
|
204
|
+
}
|