agentfootprint 9.72.0 → 9.74.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 (66) hide show
  1. package/CHANGELOG.md +160 -0
  2. package/dist/adapters/identity/azure.js +306 -0
  3. package/dist/adapters/identity/azure.js.map +1 -0
  4. package/dist/adapters/llm/FoundryLocalProvider.js +992 -0
  5. package/dist/adapters/llm/FoundryLocalProvider.js.map +1 -0
  6. package/dist/adapters/llm/FoundryProvider.js +273 -0
  7. package/dist/adapters/llm/FoundryProvider.js.map +1 -0
  8. package/dist/adapters/llm/OllamaProvider.js +171 -12
  9. package/dist/adapters/llm/OllamaProvider.js.map +1 -1
  10. package/dist/adapters/llm/OpenAIProvider.js +117 -5
  11. package/dist/adapters/llm/OpenAIProvider.js.map +1 -1
  12. package/dist/adapters/llm/createProvider.js +143 -10
  13. package/dist/adapters/llm/createProvider.js.map +1 -1
  14. package/dist/adapters/types.js.map +1 -1
  15. package/dist/esm/adapters/identity/azure.d.ts +188 -0
  16. package/dist/esm/adapters/identity/azure.js +302 -0
  17. package/dist/esm/adapters/identity/azure.js.map +1 -0
  18. package/dist/esm/adapters/llm/FoundryLocalProvider.d.ts +215 -0
  19. package/dist/esm/adapters/llm/FoundryLocalProvider.js +986 -0
  20. package/dist/esm/adapters/llm/FoundryLocalProvider.js.map +1 -0
  21. package/dist/esm/adapters/llm/FoundryProvider.d.ts +178 -0
  22. package/dist/esm/adapters/llm/FoundryProvider.js +268 -0
  23. package/dist/esm/adapters/llm/FoundryProvider.js.map +1 -0
  24. package/dist/esm/adapters/llm/OllamaProvider.js +171 -12
  25. package/dist/esm/adapters/llm/OllamaProvider.js.map +1 -1
  26. package/dist/esm/adapters/llm/OpenAIProvider.d.ts +111 -0
  27. package/dist/esm/adapters/llm/OpenAIProvider.js +115 -4
  28. package/dist/esm/adapters/llm/OpenAIProvider.js.map +1 -1
  29. package/dist/esm/adapters/llm/createProvider.d.ts +48 -12
  30. package/dist/esm/adapters/llm/createProvider.js +143 -10
  31. package/dist/esm/adapters/llm/createProvider.js.map +1 -1
  32. package/dist/esm/adapters/types.d.ts +4 -2
  33. package/dist/esm/adapters/types.js.map +1 -1
  34. package/dist/esm/identity.d.ts +1 -0
  35. package/dist/esm/identity.js +9 -0
  36. package/dist/esm/identity.js.map +1 -1
  37. package/dist/esm/index.d.ts +1 -1
  38. package/dist/esm/index.js.map +1 -1
  39. package/dist/esm/providers.d.ts +5 -0
  40. package/dist/esm/providers.js +14 -0
  41. package/dist/esm/providers.js.map +1 -1
  42. package/dist/identity.js +14 -1
  43. package/dist/identity.js.map +1 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/providers.js +20 -1
  46. package/dist/providers.js.map +1 -1
  47. package/dist/types/adapters/identity/azure.d.ts +189 -0
  48. package/dist/types/adapters/identity/azure.d.ts.map +1 -0
  49. package/dist/types/adapters/llm/FoundryLocalProvider.d.ts +216 -0
  50. package/dist/types/adapters/llm/FoundryLocalProvider.d.ts.map +1 -0
  51. package/dist/types/adapters/llm/FoundryProvider.d.ts +179 -0
  52. package/dist/types/adapters/llm/FoundryProvider.d.ts.map +1 -0
  53. package/dist/types/adapters/llm/OllamaProvider.d.ts.map +1 -1
  54. package/dist/types/adapters/llm/OpenAIProvider.d.ts +111 -0
  55. package/dist/types/adapters/llm/OpenAIProvider.d.ts.map +1 -1
  56. package/dist/types/adapters/llm/createProvider.d.ts +48 -12
  57. package/dist/types/adapters/llm/createProvider.d.ts.map +1 -1
  58. package/dist/types/adapters/types.d.ts +4 -2
  59. package/dist/types/adapters/types.d.ts.map +1 -1
  60. package/dist/types/identity.d.ts +1 -0
  61. package/dist/types/identity.d.ts.map +1 -1
  62. package/dist/types/index.d.ts +1 -1
  63. package/dist/types/index.d.ts.map +1 -1
  64. package/dist/types/providers.d.ts +5 -0
  65. package/dist/types/providers.d.ts.map +1 -1
  66. package/package.json +5 -1
@@ -0,0 +1,188 @@
1
+ /**
2
+ * entraIdentity — the {@link CredentialProvider} port over Microsoft Entra ID
3
+ * (peer-dep `@azure/identity`).
4
+ *
5
+ * import { entraIdentity } from 'agentfootprint/security';
6
+ * const credentials = entraIdentity();
7
+ *
8
+ * ── What it is, and what it deliberately is not ─────────────────────────────
9
+ * This is the **narrow** adapter: it vends *Entra* access tokens for *Azure*
10
+ * APIs, from whatever credential the environment already has — the
11
+ * DefaultAzureCredential chain walks environment service principal, workload
12
+ * identity, managed identity, VS Code, Azure CLI, Azure PowerShell and the
13
+ * Azure Developer CLI, in that order. That is one job and it is done
14
+ * completely.
15
+ *
16
+ * It is **not** a user-delegation surface. Entra's on-behalf-of flow (and any
17
+ * 3-legged consent dance) needs a confidential client app registration that
18
+ * this adapter does not hold, so `mode: 'user'` is **refused by name** rather
19
+ * than quietly served with a machine token. A machine token returned where a
20
+ * user token was asked for is the exact silent downgrade the port exists to
21
+ * prevent: the call succeeds, the data comes back, and it was the agent's
22
+ * access rather than the person's. OBO is a later train; when it lands it will
23
+ * be its own provider, not a flag here.
24
+ *
25
+ * ── The audience split, and where it bites ──────────────────────────────────
26
+ * Azure tokens are minted for ONE audience. {@link AZURE_AI_SCOPE}
27
+ * (`https://ai.azure.com/.default`) is the data plane — every Foundry and
28
+ * Azure OpenAI inference call takes it. {@link AZURE_MANAGEMENT_SCOPE}
29
+ * (`https://management.azure.com/.default`) is the ARM control plane —
30
+ * listing deployments, creating resources. A token for one audience is a 401
31
+ * on the other, which is why BOTH are exported by name instead of leaving the
32
+ * caller to guess a string. The default here is the data-plane scope, because
33
+ * vending inference credentials is what an agent runtime does all day.
34
+ *
35
+ * ── Caching: the credential, never a token ──────────────────────────────────
36
+ * One `DefaultAzureCredential` is constructed for the life of the provider and
37
+ * every `getToken` call goes through it. MSAL — the machinery underneath
38
+ * `@azure/identity` — caches and proactively refreshes tokens internally, so
39
+ * caching a token HERE would mean owning an expiry this adapter did not
40
+ * compute and cannot see revoked. Unlike the Google adapter (where scopes are
41
+ * fixed at client construction and a different scope set needs its own
42
+ * client), Azure scopes travel per `getToken` call, so the ONE cached
43
+ * credential serves every scope set.
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 401/403 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 — `@azure/identity` is required
56
+ * the first time `getCredential` runs, or never if you inject a credential.
57
+ */
58
+ import type { CredentialProvider } from '../../identity/types.js';
59
+ /**
60
+ * The data-plane scope for ALL Foundry / Azure OpenAI inference
61
+ * (`https://ai.azure.com/.default`). This is the default scope this provider
62
+ * requests.
63
+ *
64
+ * The audience split matters: a token minted for this scope does NOT work on
65
+ * the ARM control plane, and a {@link AZURE_MANAGEMENT_SCOPE} token does not
66
+ * work here — Azure validates the audience on every call. Both are exported by
67
+ * name so nobody has to remember which string is which.
68
+ */
69
+ export declare const AZURE_AI_SCOPE = "https://ai.azure.com/.default";
70
+ /**
71
+ * The ARM control-plane scope (`https://management.azure.com/.default`) —
72
+ * listing deployments, managing resources. A DIFFERENT audience from
73
+ * {@link AZURE_AI_SCOPE}: a token for one is a 401 on the other, which is why
74
+ * both are named rather than leaving the caller to guess.
75
+ */
76
+ export declare const AZURE_MANAGEMENT_SCOPE = "https://management.azure.com/.default";
77
+ /**
78
+ * The CLASSIC Azure OpenAI data-plane scope
79
+ * (`https://cognitiveservices.azure.com/.default`) — the audience Microsoft's
80
+ * own keyless guidance names for the older deployment-scoped route
81
+ * (`{endpoint}/openai/deployments/{d}/…`), which is the route `azureOpenai()`
82
+ * builds and therefore its default. Current resources widely accept
83
+ * {@link AZURE_AI_SCOPE} too, but an older `*.openai.azure.com` resource may
84
+ * not — and a door should default to the audience ITS route documents, not the
85
+ * one its sibling uses. (Azure Government spells this
86
+ * `https://cognitiveservices.azure.us/.default`.)
87
+ */
88
+ export declare const AZURE_COGNITIVE_SERVICES_SCOPE = "https://cognitiveservices.azure.com/.default";
89
+ /**
90
+ * The `@azure/core-auth` `TokenCredential` duck type — the slice this adapter
91
+ * calls. Anything `@azure/identity` exports (DefaultAzureCredential,
92
+ * ManagedIdentityCredential, ClientSecretCredential, …) satisfies it.
93
+ *
94
+ * These are THE shared Azure credential duck-types for the whole repo: sibling
95
+ * Azure adapters `import type` them from this file rather than re-declaring
96
+ * their own spelling of the same SDK surface.
97
+ */
98
+ export interface TokenCredentialLike {
99
+ /**
100
+ * Mint (or serve from MSAL's internal cache) an access token for the given
101
+ * scope(s). May resolve to `null` — the SDK's spelling of "no token
102
+ * available" — which this adapter refuses by name rather than passing along.
103
+ */
104
+ getToken(scopes: string | readonly string[], options?: unknown): Promise<AccessTokenLike | null>;
105
+ }
106
+ /**
107
+ * The `@azure/core-auth` `AccessToken` duck type. `expiresOnTimestamp` is unix
108
+ * MILLISECONDS — the port reports unix SECONDS, and the conversion lives in
109
+ * exactly one place ({@link entraIdentity}'s vend path).
110
+ *
111
+ * Shared repo-wide alongside {@link TokenCredentialLike} — sibling Azure
112
+ * adapters `import type` it from here.
113
+ */
114
+ export interface AccessTokenLike {
115
+ /** The bearer token itself. A SECRET — never echoed, never logged. */
116
+ readonly token: string;
117
+ /** Expiry in unix MILLISECONDS epoch (the SDK's unit, not the port's). */
118
+ readonly expiresOnTimestamp: number;
119
+ /** MSAL's proactive-refresh hint, when the SDK provides one. */
120
+ readonly refreshAfterTimestamp?: number;
121
+ /** 'Bearer' | 'pop'; absent on older SDK versions. */
122
+ readonly tokenType?: string;
123
+ }
124
+ /** The slice of `@azure/identity` this adapter loads. */
125
+ export interface AzureIdentitySdkModule {
126
+ readonly DefaultAzureCredential?: new () => TokenCredentialLike;
127
+ }
128
+ /** Options for {@link entraIdentity}. */
129
+ export interface EntraIdentityOptions {
130
+ /**
131
+ * The scopes to request. Default `[AZURE_AI_SCOPE]` — the data-plane
132
+ * audience every Foundry / Azure OpenAI inference call accepts.
133
+ *
134
+ * A request's own `scopes` win when it names any — a tool that knows it
135
+ * needs the control plane says so, and this is where that is honoured.
136
+ */
137
+ readonly scopes?: readonly string[];
138
+ /**
139
+ * Which downstream services this provider will answer for.
140
+ *
141
+ * Unset — the default — it answers for ANY `service`, because the token it
142
+ * vends is an Entra credential and the caller knows better than this
143
+ * adapter which Azure API they are about to call.
144
+ *
145
+ * Set it and a request for a service outside the list is refused BY NAME
146
+ * rather than served. That is the useful setting in a deployment where
147
+ * tools declare `needs: [{ credential: 'github' }]` alongside Azure ones:
148
+ * without it, this provider would happily hand an Entra access token to the
149
+ * tool that wanted a GitHub one, and the failure would surface as a
150
+ * puzzling 401 from GitHub rather than as a wiring error here.
151
+ */
152
+ readonly services?: readonly string[];
153
+ /** Stable provider id (default `'entra-identity'`). */
154
+ readonly id?: string;
155
+ /**
156
+ * @internal Test seam — a pre-built credential. Bypasses the SDK entirely,
157
+ * so the suite runs with no package and no Azure account.
158
+ */
159
+ readonly _credential?: TokenCredentialLike;
160
+ /** @internal Test seam — the SDK module, to exercise the real construction. */
161
+ readonly _sdk?: AzureIdentitySdkModule;
162
+ }
163
+ /**
164
+ * Vend Entra access tokens from whatever credential this environment has —
165
+ * the DefaultAzureCredential chain: environment service principal, workload
166
+ * identity, managed identity, VS Code, Azure CLI, Azure PowerShell, Azure
167
+ * Developer CLI. (`AZURE_TOKEN_CREDENTIALS` can restrict the chain; that is
168
+ * the SDK's own dial and this adapter does not second-guess it.)
169
+ *
170
+ * @throws when `mode: 'user'` is requested — no user-delegation surface is
171
+ * wired for Entra yet (on-behalf-of is a later train), and a machine token
172
+ * returned in its place would be a silent downgrade.
173
+ * @throws when `services` is configured and the request names another one.
174
+ *
175
+ * @example A tool that calls an Azure API with the deployment's own identity
176
+ * const agent = Agent.create({ provider, credentials: entraIdentity() })
177
+ * .tool(defineTool({
178
+ * name: 'ask_foundry',
179
+ * needs: [{ credential: 'azure-ai' }],
180
+ * execute: async (args, ctx) =>
181
+ * fetch(url, { headers: ctx.credential!.toHeaders() }).then((r) => r.text()),
182
+ * }))
183
+ * .build();
184
+ *
185
+ * @example A control-plane token, without touching the data-plane default
186
+ * entraIdentity({ scopes: [AZURE_MANAGEMENT_SCOPE] });
187
+ */
188
+ export declare function entraIdentity(options?: EntraIdentityOptions): CredentialProvider;
@@ -0,0 +1,302 @@
1
+ /**
2
+ * entraIdentity — the {@link CredentialProvider} port over Microsoft Entra ID
3
+ * (peer-dep `@azure/identity`).
4
+ *
5
+ * import { entraIdentity } from 'agentfootprint/security';
6
+ * const credentials = entraIdentity();
7
+ *
8
+ * ── What it is, and what it deliberately is not ─────────────────────────────
9
+ * This is the **narrow** adapter: it vends *Entra* access tokens for *Azure*
10
+ * APIs, from whatever credential the environment already has — the
11
+ * DefaultAzureCredential chain walks environment service principal, workload
12
+ * identity, managed identity, VS Code, Azure CLI, Azure PowerShell and the
13
+ * Azure Developer CLI, in that order. That is one job and it is done
14
+ * completely.
15
+ *
16
+ * It is **not** a user-delegation surface. Entra's on-behalf-of flow (and any
17
+ * 3-legged consent dance) needs a confidential client app registration that
18
+ * this adapter does not hold, so `mode: 'user'` is **refused by name** rather
19
+ * than quietly served with a machine token. A machine token returned where a
20
+ * user token was asked for is the exact silent downgrade the port exists to
21
+ * prevent: the call succeeds, the data comes back, and it was the agent's
22
+ * access rather than the person's. OBO is a later train; when it lands it will
23
+ * be its own provider, not a flag here.
24
+ *
25
+ * ── The audience split, and where it bites ──────────────────────────────────
26
+ * Azure tokens are minted for ONE audience. {@link AZURE_AI_SCOPE}
27
+ * (`https://ai.azure.com/.default`) is the data plane — every Foundry and
28
+ * Azure OpenAI inference call takes it. {@link AZURE_MANAGEMENT_SCOPE}
29
+ * (`https://management.azure.com/.default`) is the ARM control plane —
30
+ * listing deployments, creating resources. A token for one audience is a 401
31
+ * on the other, which is why BOTH are exported by name instead of leaving the
32
+ * caller to guess a string. The default here is the data-plane scope, because
33
+ * vending inference credentials is what an agent runtime does all day.
34
+ *
35
+ * ── Caching: the credential, never a token ──────────────────────────────────
36
+ * One `DefaultAzureCredential` is constructed for the life of the provider and
37
+ * every `getToken` call goes through it. MSAL — the machinery underneath
38
+ * `@azure/identity` — caches and proactively refreshes tokens internally, so
39
+ * caching a token HERE would mean owning an expiry this adapter did not
40
+ * compute and cannot see revoked. Unlike the Google adapter (where scopes are
41
+ * fixed at client construction and a different scope set needs its own
42
+ * client), Azure scopes travel per `getToken` call, so the ONE cached
43
+ * credential serves every scope set.
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 401/403 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 — `@azure/identity` is required
56
+ * the first time `getCredential` runs, or never if you inject a credential.
57
+ */
58
+ import { lazyRequire } from '../../lib/lazyRequire.js';
59
+ import { bearer } from '../../identity/kinds.js';
60
+ const ADAPTER = 'entraIdentity';
61
+ /**
62
+ * The data-plane scope for ALL Foundry / Azure OpenAI inference
63
+ * (`https://ai.azure.com/.default`). This is the default scope this provider
64
+ * requests.
65
+ *
66
+ * The audience split matters: a token minted for this scope does NOT work on
67
+ * the ARM control plane, and a {@link AZURE_MANAGEMENT_SCOPE} token does not
68
+ * work here — Azure validates the audience on every call. Both are exported by
69
+ * name so nobody has to remember which string is which.
70
+ */
71
+ export const AZURE_AI_SCOPE = 'https://ai.azure.com/.default';
72
+ /**
73
+ * The ARM control-plane scope (`https://management.azure.com/.default`) —
74
+ * listing deployments, managing resources. A DIFFERENT audience from
75
+ * {@link AZURE_AI_SCOPE}: a token for one is a 401 on the other, which is why
76
+ * both are named rather than leaving the caller to guess.
77
+ */
78
+ export const AZURE_MANAGEMENT_SCOPE = 'https://management.azure.com/.default';
79
+ /**
80
+ * The CLASSIC Azure OpenAI data-plane scope
81
+ * (`https://cognitiveservices.azure.com/.default`) — the audience Microsoft's
82
+ * own keyless guidance names for the older deployment-scoped route
83
+ * (`{endpoint}/openai/deployments/{d}/…`), which is the route `azureOpenai()`
84
+ * builds and therefore its default. Current resources widely accept
85
+ * {@link AZURE_AI_SCOPE} too, but an older `*.openai.azure.com` resource may
86
+ * not — and a door should default to the audience ITS route documents, not the
87
+ * one its sibling uses. (Azure Government spells this
88
+ * `https://cognitiveservices.azure.us/.default`.)
89
+ */
90
+ export const AZURE_COGNITIVE_SERVICES_SCOPE = 'https://cognitiveservices.azure.com/.default';
91
+ /**
92
+ * Vend Entra access tokens from whatever credential this environment has —
93
+ * the DefaultAzureCredential chain: environment service principal, workload
94
+ * identity, managed identity, VS Code, Azure CLI, Azure PowerShell, Azure
95
+ * Developer CLI. (`AZURE_TOKEN_CREDENTIALS` can restrict the chain; that is
96
+ * the SDK's own dial and this adapter does not second-guess it.)
97
+ *
98
+ * @throws when `mode: 'user'` is requested — no user-delegation surface is
99
+ * wired for Entra yet (on-behalf-of is a later train), and a machine token
100
+ * returned in its place would be a silent downgrade.
101
+ * @throws when `services` is configured and the request names another one.
102
+ *
103
+ * @example A tool that calls an Azure API with the deployment's own identity
104
+ * const agent = Agent.create({ provider, credentials: entraIdentity() })
105
+ * .tool(defineTool({
106
+ * name: 'ask_foundry',
107
+ * needs: [{ credential: 'azure-ai' }],
108
+ * execute: async (args, ctx) =>
109
+ * fetch(url, { headers: ctx.credential!.toHeaders() }).then((r) => r.text()),
110
+ * }))
111
+ * .build();
112
+ *
113
+ * @example A control-plane token, without touching the data-plane default
114
+ * entraIdentity({ scopes: [AZURE_MANAGEMENT_SCOPE] });
115
+ */
116
+ export function entraIdentity(options = {}) {
117
+ const defaultScopes = options.scopes ?? [AZURE_AI_SCOPE];
118
+ const allowed = options.services === undefined ? undefined : new Set(options.services);
119
+ const cache = {};
120
+ const resolveCredential = () => {
121
+ if (options._credential)
122
+ return options._credential;
123
+ // ONE credential for the life of the provider — scopes ride on getToken
124
+ // per call (unlike Google, where they are fixed at client construction),
125
+ // so no per-scope keying is needed. MSAL underneath caches and refreshes
126
+ // tokens on its own; we cache the CREDENTIAL, never a token.
127
+ if (cache.credential)
128
+ return cache.credential;
129
+ const mod = loadIdentitySdk(options._sdk);
130
+ if (typeof mod.DefaultAzureCredential !== 'function') {
131
+ throw new Error(`${ADAPTER}: \`@azure/identity\` is installed but exports no ` +
132
+ `\`DefaultAzureCredential\`. This adapter is built against the 4.x package — ` +
133
+ `update it, or pass \`_credential\`.`);
134
+ }
135
+ cache.credential = new mod.DefaultAzureCredential();
136
+ return cache.credential;
137
+ };
138
+ return {
139
+ id: options.id ?? 'entra-identity',
140
+ async getCredential(req) {
141
+ if (req.mode === 'user') {
142
+ throw new Error(`${ADAPTER}: a \`mode: 'user'\` request arrived for '${req.service}', and this ` +
143
+ `provider cannot serve one.\n` +
144
+ ` It vends the DEPLOYMENT's Entra credential (the DefaultAzureCredential ` +
145
+ `chain — environment service principal, workload identity, managed identity, ` +
146
+ `or a developer's \`az login\`). No user-delegation surface is wired for Entra ` +
147
+ `yet — the on-behalf-of flow is a later train.\n` +
148
+ ` Returning a machine token here would succeed and be wrong: the call would ` +
149
+ `run with the AGENT's access rather than the person's, and nothing downstream ` +
150
+ `could tell.\n` +
151
+ ` Fix: declare \`mode: 'machine'\` if the deployment's own identity is really ` +
152
+ `what you want, or vend the user's token from a provider that holds one.`);
153
+ }
154
+ if (req.userToken !== undefined) {
155
+ // A user's signed token handed to a provider that cannot exchange it.
156
+ // Named rather than ignored: without a confidential client app
157
+ // registration there is no on-behalf-of exchange to perform, and
158
+ // silently dropping somebody's proof and vending machine access is
159
+ // the same downgrade in a quieter costume.
160
+ throw new Error(`${ADAPTER}: a \`userToken\` arrived for '${req.service}', but this provider has ` +
161
+ `nothing to exchange it against — the on-behalf-of flow needs a confidential ` +
162
+ `client app registration, and this provider vends the deployment's own Entra ` +
163
+ `credential.\n` +
164
+ ` Ignoring it would hand back agent-scoped access while holding the user's ` +
165
+ `proof. Drop the token, or use a provider that can exchange one.`);
166
+ }
167
+ if (allowed !== undefined && !allowed.has(req.service)) {
168
+ throw new Error(`${ADAPTER}: this provider is configured for [${[...allowed].join(', ')}] and was ` +
169
+ `asked for '${req.service}'.\n` +
170
+ ` It vends ENTRA access tokens; handing one to a tool that wanted a different ` +
171
+ `service's credential would fail downstream as a puzzling 401 instead of here ` +
172
+ `as a wiring error.\n` +
173
+ ` Fix: add '${req.service}' to 'services', or attach a provider that serves it.`);
174
+ }
175
+ let credential;
176
+ try {
177
+ credential = resolveCredential();
178
+ }
179
+ catch (err) {
180
+ // A refusal this adapter authored (a missing peer dependency, a
181
+ // too-old SDK) is already the right diagnosis; rewriting it through
182
+ // sdkFailure would send the reader chasing Entra sign-in logs for a
183
+ // problem `npm install` fixes. Wrong diagnoses are their own kind of
184
+ // silently-wrong.
185
+ if (isOwnRefusal(err))
186
+ throw err;
187
+ throw sdkFailure('new DefaultAzureCredential', err);
188
+ }
189
+ // A request's own non-empty scopes win over the provider's default.
190
+ // The array goes to getToken as-is — Azure scopes are per-call, and
191
+ // `.default` scopes are single-element by convention anyway.
192
+ const scopes = req.scopes !== undefined && req.scopes.length > 0 ? req.scopes : defaultScopes;
193
+ let answer;
194
+ try {
195
+ answer = await credential.getToken(scopes);
196
+ }
197
+ catch (err) {
198
+ throw sdkFailure('getToken', err);
199
+ }
200
+ const token = answer?.token;
201
+ if (typeof token !== 'string' || token.trim() === '') {
202
+ // The SCOPES are quoted; nothing else is. An audience URI is public,
203
+ // and it is the datum most often wrong here — "could not mint for the
204
+ // requested scope" is useless when the reader cannot see WHICH scope
205
+ // was requested (a tool that declares `needs` without `scopes` gets
206
+ // this provider's default, which it never typed anywhere). The token
207
+ // response's own fields stay withheld: every one of them is a secret.
208
+ // Same conclusion `entraBearerToken` reached out loud in
209
+ // src/adapters/llm/OpenAIProvider.ts — the two siblings now agree.
210
+ throw new Error(`${ADAPTER}: the credential resolved but vended no access token for ` +
211
+ `'${req.service}' at scope${scopes.length === 1 ? '' : 's'} ` +
212
+ `[${scopes.join(', ')}] — ${answer === null
213
+ ? '`getToken` returned null, the SDK\'s spelling of "no token available"'
214
+ : "the response's `token` field was empty"}.\n` +
215
+ ` No token value is quoted here on purpose — every field of a token response is ` +
216
+ `a secret. This usually means the chain found a credential source that could not ` +
217
+ `actually mint for THAT audience.\n` +
218
+ ` Fix: check the audience first — inference is ${AZURE_AI_SCOPE} and the ARM ` +
219
+ `control plane is ${AZURE_MANAGEMENT_SCOPE}; name the right one in the request's ` +
220
+ `\`scopes\` or this provider's \`scopes\` option. If it is already right, sign in as ` +
221
+ `an identity that can mint for it (\`az login\`, or a managed identity with the role).`);
222
+ }
223
+ // The SDK records expiry in unix MILLISECONDS; the port reports unix
224
+ // SECONDS. Reported when known and omitted when not — an invented
225
+ // expiry is worse than none, because a caller would cache against it.
226
+ // (`answer` cannot be null past the token guard; the optional chain is
227
+ // for the compiler, which does not carry the narrowing across fields.)
228
+ const expiryMs = answer?.expiresOnTimestamp;
229
+ const expiresAt = typeof expiryMs === 'number' && Number.isFinite(expiryMs) && expiryMs > 0
230
+ ? Math.floor(expiryMs / 1000)
231
+ : undefined;
232
+ return {
233
+ status: 'issued',
234
+ credential: bearer(token),
235
+ ...(expiresAt !== undefined && { expiresAt }),
236
+ };
237
+ },
238
+ };
239
+ }
240
+ // ─── Internals ───────────────────────────────────────────────────────
241
+ function loadIdentitySdk(injected) {
242
+ if (injected)
243
+ return injected;
244
+ try {
245
+ return lazyRequire('@azure/identity');
246
+ }
247
+ catch {
248
+ throw new Error(`${ADAPTER} requires the \`@azure/identity\` peer dependency.\n` +
249
+ ` Install: npm install @azure/identity\n` +
250
+ ` It is optional and loaded only when this provider first vends, so nothing else ` +
251
+ `in this library pays for it.`);
252
+ }
253
+ }
254
+ /**
255
+ * "There is no usable credential here" — the most common real failure, given
256
+ * the fix instead of the library's own text. `@azure/identity` names it
257
+ * `CredentialUnavailableError` (one rung) or `AggregateAuthenticationError`
258
+ * (the whole chain came up empty); both mean the same thing to the reader.
259
+ */
260
+ function credentialsUnavailable(name) {
261
+ const failure = new Error(`${ADAPTER}: could not acquire an Entra token in this environment — ${name}.\n` +
262
+ ` The underlying message is withheld: auth libraries echo request detail into ` +
263
+ `failure text, and this message reaches the model as a tool result.\n` +
264
+ ` Every rung of the DefaultAzureCredential chain was tried: environment service ` +
265
+ `principal, workload identity, managed identity, VS Code, Azure CLI, Azure ` +
266
+ `PowerShell, Azure Developer CLI.\n` +
267
+ ` Fix: run \`az login\`, or set AZURE_CLIENT_ID / AZURE_TENANT_ID / ` +
268
+ `AZURE_CLIENT_SECRET, or run where a managed identity exists, or pass \`_credential\`.`);
269
+ failure.name = 'AzureCredentialsUnavailableError';
270
+ return failure;
271
+ }
272
+ /** Re-raise without the library's text. See the module header for why. */
273
+ function sdkFailure(operation, err) {
274
+ const name = errorName(err);
275
+ // The no-credential names get the fix, not just the diagnosis — but ONLY
276
+ // those names. Everything else is reported as what it is.
277
+ if (name === 'CredentialUnavailableError' || name === 'AggregateAuthenticationError') {
278
+ return credentialsUnavailable(name);
279
+ }
280
+ const failure = new Error(`${ADAPTER}: ${operation} failed — ${name}.\n` +
281
+ ` The underlying message is withheld: this call handles an access token, and auth ` +
282
+ `libraries echo request detail into failure text. Check the Entra sign-in logs for ` +
283
+ `the full error.`);
284
+ failure.name = 'AzureCredentialError';
285
+ return failure;
286
+ }
287
+ /**
288
+ * Did THIS adapter write this error?
289
+ *
290
+ * Every refusal authored here opens with the adapter's own name — both as the
291
+ * marker and because it is what makes the message readable ("entraIdentity:
292
+ * …", "entraIdentity requires …"). A library's own failure never does, so the
293
+ * prefix is a reliable discriminator without an error subclass per refusal.
294
+ */
295
+ function isOwnRefusal(err) {
296
+ return err instanceof Error && err.message.startsWith(ADAPTER);
297
+ }
298
+ function errorName(err) {
299
+ const name = err?.name;
300
+ return typeof name === 'string' && name.length > 0 ? name : 'an unnamed failure';
301
+ }
302
+ //# sourceMappingURL=azure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"azure.js","sourceRoot":"","sources":["../../../../src/adapters/identity/azure.ts"],"names":[],"mappings":"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"}