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.
Files changed (77) hide show
  1. package/dist/adapters/hosting/agentcore.js +36 -1
  2. package/dist/adapters/hosting/agentcore.js.map +1 -1
  3. package/dist/adapters/identity/agentcore.js +173 -33
  4. package/dist/adapters/identity/agentcore.js.map +1 -1
  5. package/dist/adapters/llm/GeminiProvider.js +519 -0
  6. package/dist/adapters/llm/GeminiProvider.js.map +1 -0
  7. package/dist/adapters/llm/contextWindow.js +20 -0
  8. package/dist/adapters/llm/contextWindow.js.map +1 -1
  9. package/dist/adapters/llm/createProvider.js +3 -0
  10. package/dist/adapters/llm/createProvider.js.map +1 -1
  11. package/dist/adapters/llm/googleGenAI.js +109 -0
  12. package/dist/adapters/llm/googleGenAI.js.map +1 -0
  13. package/dist/embedders/index.js +233 -1
  14. package/dist/embedders/index.js.map +1 -1
  15. package/dist/esm/adapters/hosting/agentcore.d.ts +4 -1
  16. package/dist/esm/adapters/hosting/agentcore.js +36 -1
  17. package/dist/esm/adapters/hosting/agentcore.js.map +1 -1
  18. package/dist/esm/adapters/identity/agentcore.d.ts +74 -15
  19. package/dist/esm/adapters/identity/agentcore.js +173 -33
  20. package/dist/esm/adapters/identity/agentcore.js.map +1 -1
  21. package/dist/esm/adapters/llm/GeminiProvider.d.ts +204 -0
  22. package/dist/esm/adapters/llm/GeminiProvider.js +514 -0
  23. package/dist/esm/adapters/llm/GeminiProvider.js.map +1 -0
  24. package/dist/esm/adapters/llm/contextWindow.js +20 -0
  25. package/dist/esm/adapters/llm/contextWindow.js.map +1 -1
  26. package/dist/esm/adapters/llm/createProvider.d.ts +4 -1
  27. package/dist/esm/adapters/llm/createProvider.js +3 -0
  28. package/dist/esm/adapters/llm/createProvider.js.map +1 -1
  29. package/dist/esm/adapters/llm/googleGenAI.d.ts +67 -0
  30. package/dist/esm/adapters/llm/googleGenAI.js +105 -0
  31. package/dist/esm/adapters/llm/googleGenAI.js.map +1 -0
  32. package/dist/esm/embedders/index.d.ts +188 -1
  33. package/dist/esm/embedders/index.js +231 -0
  34. package/dist/esm/embedders/index.js.map +1 -1
  35. package/dist/esm/hosting/httpHost.d.ts +10 -0
  36. package/dist/esm/hosting/httpHost.js +2 -1
  37. package/dist/esm/hosting/httpHost.js.map +1 -1
  38. package/dist/esm/hosting/standingAgent.js +63 -1
  39. package/dist/esm/hosting/standingAgent.js.map +1 -1
  40. package/dist/esm/hosting/types.d.ts +24 -0
  41. package/dist/esm/hosting/types.js.map +1 -1
  42. package/dist/esm/identity/types.d.ts +30 -0
  43. package/dist/esm/identity/types.js.map +1 -1
  44. package/dist/esm/providers.d.ts +3 -0
  45. package/dist/esm/providers.js +2 -0
  46. package/dist/esm/providers.js.map +1 -1
  47. package/dist/hosting/httpHost.js +2 -1
  48. package/dist/hosting/httpHost.js.map +1 -1
  49. package/dist/hosting/standingAgent.js +63 -1
  50. package/dist/hosting/standingAgent.js.map +1 -1
  51. package/dist/hosting/types.js.map +1 -1
  52. package/dist/identity/types.js.map +1 -1
  53. package/dist/providers.js +5 -1
  54. package/dist/providers.js.map +1 -1
  55. package/dist/types/adapters/hosting/agentcore.d.ts +4 -1
  56. package/dist/types/adapters/hosting/agentcore.d.ts.map +1 -1
  57. package/dist/types/adapters/identity/agentcore.d.ts +74 -15
  58. package/dist/types/adapters/identity/agentcore.d.ts.map +1 -1
  59. package/dist/types/adapters/llm/GeminiProvider.d.ts +205 -0
  60. package/dist/types/adapters/llm/GeminiProvider.d.ts.map +1 -0
  61. package/dist/types/adapters/llm/contextWindow.d.ts.map +1 -1
  62. package/dist/types/adapters/llm/createProvider.d.ts +4 -1
  63. package/dist/types/adapters/llm/createProvider.d.ts.map +1 -1
  64. package/dist/types/adapters/llm/googleGenAI.d.ts +68 -0
  65. package/dist/types/adapters/llm/googleGenAI.d.ts.map +1 -0
  66. package/dist/types/embedders/index.d.ts +188 -1
  67. package/dist/types/embedders/index.d.ts.map +1 -1
  68. package/dist/types/hosting/httpHost.d.ts +10 -0
  69. package/dist/types/hosting/httpHost.d.ts.map +1 -1
  70. package/dist/types/hosting/standingAgent.d.ts.map +1 -1
  71. package/dist/types/hosting/types.d.ts +24 -0
  72. package/dist/types/hosting/types.d.ts.map +1 -1
  73. package/dist/types/identity/types.d.ts +30 -0
  74. package/dist/types/identity/types.d.ts.map +1 -1
  75. package/dist/types/providers.d.ts +3 -0
  76. package/dist/types/providers.d.ts.map +1 -1
  77. 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
- * `GetWorkloadAccessTokenForUserId(workloadName, userId)` returns a workload
23
- * access token scoped to that user, and AgentCore keys its token vault + 3LO
24
- * grants per (workload, user). So this adapter forwards `req.identity` (the
25
- * `runIdentity` that the agent threads through `getCredential`) by resolving a
26
- * per-user workload token first, then vending with it. Engages only when ALL of:
27
- * - `req.mode === 'user'` (USER_FEDERATION — M2M is the workload's own identity),
28
- * - a userId derives from `req.identity` (default `identity.principal`;
29
- * override via `userIdFor`), and
30
- * - `options.workloadName` is configured (the opt-in).
31
- * Otherwise the static `options.workloadIdentityToken` flows exactly as before.
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'` requests carrying `req.identity` resolve a
97
- * per-user workload access token via `GetWorkloadAccessTokenForUserId(workloadName,
98
- * userId)` before vending, so AgentCore's token vault + 3LO grants are keyed per
99
- * (workload, user) instead of per workload. Omit → today's static-token behavior. */
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
- * `GetWorkloadAccessTokenForUserId(workloadName, userId)` returns a workload
23
- * access token scoped to that user, and AgentCore keys its token vault + 3LO
24
- * grants per (workload, user). So this adapter forwards `req.identity` (the
25
- * `runIdentity` that the agent threads through `getCredential`) by resolving a
26
- * per-user workload token first, then vending with it. Engages only when ALL of:
27
- * - `req.mode === 'user'` (USER_FEDERATION — M2M is the workload's own identity),
28
- * - a userId derives from `req.identity` (default `identity.principal`;
29
- * override via `userIdFor`), and
30
- * - `options.workloadName` is configured (the opt-in).
31
- * Otherwise the static `options.workloadIdentityToken` flows exactly as before.
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
- return sdk.send(new Ctor(input));
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
- const r = (await send(mod.GetWorkloadAccessTokenForUserIdCommand, 'GetWorkloadAccessTokenForUserIdCommand', input));
120
- return {
121
- ...(r?.workloadAccessToken !== undefined && {
122
- workloadAccessToken: r.workloadAccessToken,
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. `GetResourceOauth2Token` has no user field; the user is bound at
143
- // workload-token acquisition, so a `mode: 'user'` request carrying an
144
- // identity exchanges (workloadName, userId) for a USER-SCOPED workload token
145
- // and vends with that. Requires `workloadName` (the opt-in); without it the
146
- // static `workloadIdentityToken` flows unchanged (pre-forwarding behavior).
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 userId = req.mode === 'user' && req.identity !== undefined ? userIdFor(req.identity) : undefined;
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
- const res = await c.getWorkloadAccessTokenForUserId({
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":"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"}
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
+ }