agentfootprint 9.73.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 +139 -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 +105 -2
  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 +92 -1
  27. package/dist/esm/adapters/llm/OpenAIProvider.js +103 -1
  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 +92 -1
  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
package/CHANGELOG.md CHANGED
@@ -7,6 +7,145 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [9.74.0] - 2026-08-27
11
+
12
+ ### Added
13
+
14
+ - **The Azure/Foundry column exists — keyless auth and both Microsoft inference
15
+ doors, cloud and on-device.** AWS and GCP each had a full adapter column;
16
+ Azure had a static api-key string and nothing else. This release fills the
17
+ auth + inference tier, and every piece is a vendor adapter over ports that
18
+ did not change — the same seams the AWS and GCP columns already use.
19
+
20
+ **`foundry()` — the project-endpoint provider** (`agentfootprint/providers`).
21
+ The JS answer to Microsoft's `FoundryChatClient(project_endpoint, model,
22
+ credential)`: point it at a Foundry project endpoint (or let the hosted
23
+ platform's auto-injected `FOUNDRY_PROJECT_ENDPOINT` supply it), name the
24
+ deployment (`AZURE_AI_MODEL_DEPLOYMENT_NAME ?? MODEL_NAME`), and auth is an
25
+ Entra `TokenCredential`, an api key, or — given neither — the platform's own
26
+ blessed default, `DefaultAzureCredential` from the optional `@azure/identity`
27
+ peer. Inference rides the GA api-version-free `/openai/v1` route derived from
28
+ the project endpoint; the deployment name travels as the `model` field; the
29
+ token scope is `https://ai.azure.com/.default` (the management audience is a
30
+ different token — the docs say so out loud). Rotating tokens reuse the
31
+ per-request key-callback seam `openai()` has had since 9.29.0: the client is
32
+ rebuilt only when the token string actually changes.
33
+
34
+ ```ts
35
+ import { foundry } from 'agentfootprint/providers';
36
+ // In a Foundry hosted container: zero config — endpoint injected, identity ambient.
37
+ const llm = foundry();
38
+ // Locally: az login, then name the project.
39
+ const local = foundry({
40
+ projectEndpoint: 'https://acct.services.ai.azure.com/api/projects/my-project',
41
+ deployment: 'gpt-4.1-mini',
42
+ });
43
+ ```
44
+
45
+ **`foundryLocal()` — the on-device door.** Fetch-only, zero dependencies, the
46
+ `ollama()` discipline applied to Foundry Local's OpenAI-compatible `/v1`
47
+ wire: an alias like `qwen2.5-0.5b` resolves to a concrete variant through the
48
+ service's own catalog (`GET /foundry/list`, priority order, first wins;
49
+ cached per provider), a full variant id skips the catalog entirely, and the
50
+ typed `FoundryLocalUnavailableError` tells the truth a local runtime needs
51
+ telling — `foundry server start` to start it, `foundry server status` to find
52
+ the dynamic port, `foundry model run <alias>` when the model is the missing
53
+ piece, with the machine's actual model list attached when the service could
54
+ answer. No API key is sent because none exists. Streamed usage is read off
55
+ the final empty-choices frame — the exact bug class 9.73.0 fixed, pinned here
56
+ from day one.
57
+
58
+ **`entraIdentity()` — Azure credentials for tools**
59
+ (`agentfootprint/security`). The `googleIdentity` anatomy, law for law:
60
+ vends the deployment's identity as a bearer via any `TokenCredential`
61
+ (default: `DefaultAzureCredential`'s chain — env service principal, workload
62
+ identity, managed identity, VS Code, az CLI), scopes the request's way or
63
+ `AZURE_AI_SCOPE` by default, refuses `mode: 'user'` and per-request user
64
+ tokens BY NAME until an OBO surface exists, refuses services outside the
65
+ allowlist, and never lets an SDK's failure text — which echoes request
66
+ detail — reach a thrown message. `AZURE_AI_SCOPE` and
67
+ `AZURE_MANAGEMENT_SCOPE` are both exported because the audiences are not
68
+ interchangeable, and pretending there is one "azure scope" would be a lie
69
+ that 401s at runtime.
70
+
71
+ **`azureOpenai({ credential })` — the existing Azure door goes keyless.** An
72
+ Entra credential now rides the SDK's `azureADTokenProvider`; the static
73
+ api-key path is byte-identical to before; both given at once is refused by
74
+ name as the config bug it is. The docs' old caveat — "azureOpenai is the
75
+ wrong door for bearer auth" — is retired. Each keyless door defaults to the
76
+ audience ITS route documents: this one asks for
77
+ `https://cognitiveservices.azure.com/.default` (the classic deployment-scoped
78
+ route's own documented audience, `AZURE_COGNITIVE_SERVICES_SCOPE`), while
79
+ `foundry()` asks for `https://ai.azure.com/.default` (`AZURE_AI_SCOPE`, the
80
+ v1/project route's) — both overridable via `scope`, both pinned on the wire
81
+ by tests that record what the credential was actually asked for.
82
+
83
+ **`openai({ legacyEndpoint })` — the dialect dial goes public.** `baseURL`
84
+ has always implied the legacy dialect (`max_tokens`, no `stream_options`)
85
+ because most OpenAI-compatible servers are behind; `legacyEndpoint: false`
86
+ now declares "this baseURL speaks the current dialect" — which is exactly
87
+ what the Azure v1 route is. Default unchanged: `!!baseURL`.
88
+
89
+ **`providerFromEnv` learns both doors — upgrade-safely.** `FOUNDRY_LOCAL_MODEL`
90
+ slots directly after `OLLAMA_MODEL` (a model name you typed for a local
91
+ runtime — the same name-beats-leftover-credential law), and
92
+ `FOUNDRY_PROJECT_ENDPOINT` + `AZURE_AI_MODEL_DEPLOYMENT_NAME` — a pair of
93
+ product-specific spellings nobody exports by accident — outranks the
94
+ lingering-credential arms. Two guards keep an upgrade from breaking a
95
+ working environment, because the hosted platform auto-injects the endpoint
96
+ into every container, Foundry-bound or not: an endpoint with **no**
97
+ deployment named HOLDS its refusal (every arm below answers exactly as it
98
+ did before this arm existed, and the held refusal is raised only when
99
+ nothing else resolves), and the generic `MODEL_NAME` alone never carries the
100
+ endpoint past a bootable Azure config — choosing Foundry over working Azure
101
+ takes the deliberate spelling. Every existing precedence is pinned
102
+ untouched; each guard has its own test.
103
+
104
+ New from `agentfootprint/providers`: `foundry`, `foundryInferenceUrl`,
105
+ `FoundryProviderOptions`, `foundryLocal`, `FoundryLocalProvider`,
106
+ `FoundryLocalUnavailableError`, `FoundryLocalProviderOptions`,
107
+ `TokenCredentialLike`, `AccessTokenLike`. New from `agentfootprint/security`:
108
+ `entraIdentity`, `AZURE_AI_SCOPE`, `AZURE_MANAGEMENT_SCOPE`,
109
+ `AZURE_COGNITIVE_SERVICES_SCOPE`, `EntraIdentityOptions`,
110
+ `TokenCredentialLike`, `AccessTokenLike`, `AzureIdentitySdkModule`. New
111
+ optional peer: `@azure/identity`. Pinned by 178 new test cases (counted in
112
+ the diff, not the runner) across
113
+ `test/adapters/identity/entra-identity.test.ts`,
114
+ `test/adapters/unit/FoundryProvider.test.ts`,
115
+ `test/adapters/unit/FoundryLocalProvider.test.ts`,
116
+ `test/adapters/integration/foundry-wire.test.ts` (a real `openai` SDK against
117
+ a local fake of the `/openai/v1` wire — Bearer header, `max_completion_tokens`,
118
+ token rotation, and the audience each door asks its credential for, all
119
+ asserted on the wire, not assumed), the extended Azure env/wire suites, and
120
+ the fix pins from the adversarial review below. Deliberately NOT in this train: the App Insights sink,
121
+ Azure AI Search, the Toolbox MCP transport (next trains), and every
122
+ preview-only surface (A2A door, browser/computer-use, managed memory) —
123
+ refusing to ship against previews we cannot verify is a feature.
124
+
125
+ The whole train was adversarially reviewed before release (five lenses, every
126
+ serious finding independently re-verified with a reproduction): 1 blocker and
127
+ 9 should-fix findings were confirmed and every one is fixed and pinned in
128
+ this release — including a mid-stream failure frame that `foundryLocal()`
129
+ would have reported as a clean stop, an abort signal that never reached a
130
+ streaming body, and an env-detection arm that would have broken working
131
+ deployments on upgrade.
132
+
133
+ ### Fixed
134
+
135
+ - **`ollama()` inherited four stream/abort defects — found by reviewing the new
136
+ Foundry Local adapter, fixed at the root.** The adversarial review of
137
+ `foundryLocal()` proved its four streaming defects were byte-twin shapes
138
+ copied from `OllamaProvider`, which had shipped them for months: a caller's
139
+ already-aborted `AbortSignal` still sent the request; an abort after headers
140
+ never reached the streaming body (generation ran on, un-stoppable); an early
141
+ `break` leaked the response body (the reader was never cancelled); and a
142
+ mid-stream `{"error": …}` frame was silently dropped, reporting a failed
143
+ generation as a clean stop. All four now match the fixed Foundry Local
144
+ shapes — same helper names, Ollama's NDJSON wire. 10 of the 12 new pinning
145
+ tests fail against the previous source (proven by restoring it); no public
146
+ API change; `OllamaUnavailableError` is byte-identical.
147
+
148
+
10
149
  ## [9.73.0] - 2026-08-27
11
150
 
12
151
  ### Fixed
@@ -0,0 +1,306 @@
1
+ "use strict";
2
+ /**
3
+ * entraIdentity — the {@link CredentialProvider} port over Microsoft Entra ID
4
+ * (peer-dep `@azure/identity`).
5
+ *
6
+ * import { entraIdentity } from 'agentfootprint/security';
7
+ * const credentials = entraIdentity();
8
+ *
9
+ * ── What it is, and what it deliberately is not ─────────────────────────────
10
+ * This is the **narrow** adapter: it vends *Entra* access tokens for *Azure*
11
+ * APIs, from whatever credential the environment already has — the
12
+ * DefaultAzureCredential chain walks environment service principal, workload
13
+ * identity, managed identity, VS Code, Azure CLI, Azure PowerShell and the
14
+ * Azure Developer CLI, in that order. That is one job and it is done
15
+ * completely.
16
+ *
17
+ * It is **not** a user-delegation surface. Entra's on-behalf-of flow (and any
18
+ * 3-legged consent dance) needs a confidential client app registration that
19
+ * this adapter does not hold, so `mode: 'user'` is **refused by name** rather
20
+ * than quietly served with a machine token. A machine token returned where a
21
+ * user token was asked for is the exact silent downgrade the port exists to
22
+ * prevent: the call succeeds, the data comes back, and it was the agent's
23
+ * access rather than the person's. OBO is a later train; when it lands it will
24
+ * be its own provider, not a flag here.
25
+ *
26
+ * ── The audience split, and where it bites ──────────────────────────────────
27
+ * Azure tokens are minted for ONE audience. {@link AZURE_AI_SCOPE}
28
+ * (`https://ai.azure.com/.default`) is the data plane — every Foundry and
29
+ * Azure OpenAI inference call takes it. {@link AZURE_MANAGEMENT_SCOPE}
30
+ * (`https://management.azure.com/.default`) is the ARM control plane —
31
+ * listing deployments, creating resources. A token for one audience is a 401
32
+ * on the other, which is why BOTH are exported by name instead of leaving the
33
+ * caller to guess a string. The default here is the data-plane scope, because
34
+ * vending inference credentials is what an agent runtime does all day.
35
+ *
36
+ * ── Caching: the credential, never a token ──────────────────────────────────
37
+ * One `DefaultAzureCredential` is constructed for the life of the provider and
38
+ * every `getToken` call goes through it. MSAL — the machinery underneath
39
+ * `@azure/identity` — caches and proactively refreshes tokens internally, so
40
+ * caching a token HERE would mean owning an expiry this adapter did not
41
+ * compute and cannot see revoked. Unlike the Google adapter (where scopes are
42
+ * fixed at client construction and a different scope set needs its own
43
+ * client), Azure scopes travel per `getToken` call, so the ONE cached
44
+ * credential serves every scope set.
45
+ *
46
+ * ── Secrets ─────────────────────────────────────────────────────────────────
47
+ * The `sdkFailure` law, same as every other credential-touching adapter here:
48
+ * the library's own message never comes through, because auth libraries echo
49
+ * request detail into 401/403 text and a message thrown from a
50
+ * `CredentialProvider` reaches the LLM as a tool result AND rides
51
+ * `agentfootprint.credential.failed` to every sink. What comes through is the
52
+ * operation that failed and the error's NAME. The original is not attached as
53
+ * `cause` — a cause travels into every serializer that walks own properties,
54
+ * which would undo all of it in one `JSON.stringify`.
55
+ *
56
+ * Pattern: Adapter (GoF) + lazy peer-dep load — `@azure/identity` is required
57
+ * the first time `getCredential` runs, or never if you inject a credential.
58
+ */
59
+ Object.defineProperty(exports, "__esModule", { value: true });
60
+ exports.entraIdentity = exports.AZURE_COGNITIVE_SERVICES_SCOPE = exports.AZURE_MANAGEMENT_SCOPE = exports.AZURE_AI_SCOPE = void 0;
61
+ const lazyRequire_js_1 = require("../../lib/lazyRequire.js");
62
+ const kinds_js_1 = require("../../identity/kinds.js");
63
+ const ADAPTER = 'entraIdentity';
64
+ /**
65
+ * The data-plane scope for ALL Foundry / Azure OpenAI inference
66
+ * (`https://ai.azure.com/.default`). This is the default scope this provider
67
+ * requests.
68
+ *
69
+ * The audience split matters: a token minted for this scope does NOT work on
70
+ * the ARM control plane, and a {@link AZURE_MANAGEMENT_SCOPE} token does not
71
+ * work here — Azure validates the audience on every call. Both are exported by
72
+ * name so nobody has to remember which string is which.
73
+ */
74
+ exports.AZURE_AI_SCOPE = 'https://ai.azure.com/.default';
75
+ /**
76
+ * The ARM control-plane scope (`https://management.azure.com/.default`) —
77
+ * listing deployments, managing resources. A DIFFERENT audience from
78
+ * {@link AZURE_AI_SCOPE}: a token for one is a 401 on the other, which is why
79
+ * both are named rather than leaving the caller to guess.
80
+ */
81
+ exports.AZURE_MANAGEMENT_SCOPE = 'https://management.azure.com/.default';
82
+ /**
83
+ * The CLASSIC Azure OpenAI data-plane scope
84
+ * (`https://cognitiveservices.azure.com/.default`) — the audience Microsoft's
85
+ * own keyless guidance names for the older deployment-scoped route
86
+ * (`{endpoint}/openai/deployments/{d}/…`), which is the route `azureOpenai()`
87
+ * builds and therefore its default. Current resources widely accept
88
+ * {@link AZURE_AI_SCOPE} too, but an older `*.openai.azure.com` resource may
89
+ * not — and a door should default to the audience ITS route documents, not the
90
+ * one its sibling uses. (Azure Government spells this
91
+ * `https://cognitiveservices.azure.us/.default`.)
92
+ */
93
+ exports.AZURE_COGNITIVE_SERVICES_SCOPE = 'https://cognitiveservices.azure.com/.default';
94
+ /**
95
+ * Vend Entra access tokens from whatever credential this environment has —
96
+ * the DefaultAzureCredential chain: environment service principal, workload
97
+ * identity, managed identity, VS Code, Azure CLI, Azure PowerShell, Azure
98
+ * Developer CLI. (`AZURE_TOKEN_CREDENTIALS` can restrict the chain; that is
99
+ * the SDK's own dial and this adapter does not second-guess it.)
100
+ *
101
+ * @throws when `mode: 'user'` is requested — no user-delegation surface is
102
+ * wired for Entra yet (on-behalf-of is a later train), and a machine token
103
+ * returned in its place would be a silent downgrade.
104
+ * @throws when `services` is configured and the request names another one.
105
+ *
106
+ * @example A tool that calls an Azure API with the deployment's own identity
107
+ * const agent = Agent.create({ provider, credentials: entraIdentity() })
108
+ * .tool(defineTool({
109
+ * name: 'ask_foundry',
110
+ * needs: [{ credential: 'azure-ai' }],
111
+ * execute: async (args, ctx) =>
112
+ * fetch(url, { headers: ctx.credential!.toHeaders() }).then((r) => r.text()),
113
+ * }))
114
+ * .build();
115
+ *
116
+ * @example A control-plane token, without touching the data-plane default
117
+ * entraIdentity({ scopes: [AZURE_MANAGEMENT_SCOPE] });
118
+ */
119
+ function entraIdentity(options = {}) {
120
+ const defaultScopes = options.scopes ?? [exports.AZURE_AI_SCOPE];
121
+ const allowed = options.services === undefined ? undefined : new Set(options.services);
122
+ const cache = {};
123
+ const resolveCredential = () => {
124
+ if (options._credential)
125
+ return options._credential;
126
+ // ONE credential for the life of the provider — scopes ride on getToken
127
+ // per call (unlike Google, where they are fixed at client construction),
128
+ // so no per-scope keying is needed. MSAL underneath caches and refreshes
129
+ // tokens on its own; we cache the CREDENTIAL, never a token.
130
+ if (cache.credential)
131
+ return cache.credential;
132
+ const mod = loadIdentitySdk(options._sdk);
133
+ if (typeof mod.DefaultAzureCredential !== 'function') {
134
+ throw new Error(`${ADAPTER}: \`@azure/identity\` is installed but exports no ` +
135
+ `\`DefaultAzureCredential\`. This adapter is built against the 4.x package — ` +
136
+ `update it, or pass \`_credential\`.`);
137
+ }
138
+ cache.credential = new mod.DefaultAzureCredential();
139
+ return cache.credential;
140
+ };
141
+ return {
142
+ id: options.id ?? 'entra-identity',
143
+ async getCredential(req) {
144
+ if (req.mode === 'user') {
145
+ throw new Error(`${ADAPTER}: a \`mode: 'user'\` request arrived for '${req.service}', and this ` +
146
+ `provider cannot serve one.\n` +
147
+ ` It vends the DEPLOYMENT's Entra credential (the DefaultAzureCredential ` +
148
+ `chain — environment service principal, workload identity, managed identity, ` +
149
+ `or a developer's \`az login\`). No user-delegation surface is wired for Entra ` +
150
+ `yet — the on-behalf-of flow is a later train.\n` +
151
+ ` Returning a machine token here would succeed and be wrong: the call would ` +
152
+ `run with the AGENT's access rather than the person's, and nothing downstream ` +
153
+ `could tell.\n` +
154
+ ` Fix: declare \`mode: 'machine'\` if the deployment's own identity is really ` +
155
+ `what you want, or vend the user's token from a provider that holds one.`);
156
+ }
157
+ if (req.userToken !== undefined) {
158
+ // A user's signed token handed to a provider that cannot exchange it.
159
+ // Named rather than ignored: without a confidential client app
160
+ // registration there is no on-behalf-of exchange to perform, and
161
+ // silently dropping somebody's proof and vending machine access is
162
+ // the same downgrade in a quieter costume.
163
+ throw new Error(`${ADAPTER}: a \`userToken\` arrived for '${req.service}', but this provider has ` +
164
+ `nothing to exchange it against — the on-behalf-of flow needs a confidential ` +
165
+ `client app registration, and this provider vends the deployment's own Entra ` +
166
+ `credential.\n` +
167
+ ` Ignoring it would hand back agent-scoped access while holding the user's ` +
168
+ `proof. Drop the token, or use a provider that can exchange one.`);
169
+ }
170
+ if (allowed !== undefined && !allowed.has(req.service)) {
171
+ throw new Error(`${ADAPTER}: this provider is configured for [${[...allowed].join(', ')}] and was ` +
172
+ `asked for '${req.service}'.\n` +
173
+ ` It vends ENTRA access tokens; handing one to a tool that wanted a different ` +
174
+ `service's credential would fail downstream as a puzzling 401 instead of here ` +
175
+ `as a wiring error.\n` +
176
+ ` Fix: add '${req.service}' to 'services', or attach a provider that serves it.`);
177
+ }
178
+ let credential;
179
+ try {
180
+ credential = resolveCredential();
181
+ }
182
+ catch (err) {
183
+ // A refusal this adapter authored (a missing peer dependency, a
184
+ // too-old SDK) is already the right diagnosis; rewriting it through
185
+ // sdkFailure would send the reader chasing Entra sign-in logs for a
186
+ // problem `npm install` fixes. Wrong diagnoses are their own kind of
187
+ // silently-wrong.
188
+ if (isOwnRefusal(err))
189
+ throw err;
190
+ throw sdkFailure('new DefaultAzureCredential', err);
191
+ }
192
+ // A request's own non-empty scopes win over the provider's default.
193
+ // The array goes to getToken as-is — Azure scopes are per-call, and
194
+ // `.default` scopes are single-element by convention anyway.
195
+ const scopes = req.scopes !== undefined && req.scopes.length > 0 ? req.scopes : defaultScopes;
196
+ let answer;
197
+ try {
198
+ answer = await credential.getToken(scopes);
199
+ }
200
+ catch (err) {
201
+ throw sdkFailure('getToken', err);
202
+ }
203
+ const token = answer?.token;
204
+ if (typeof token !== 'string' || token.trim() === '') {
205
+ // The SCOPES are quoted; nothing else is. An audience URI is public,
206
+ // and it is the datum most often wrong here — "could not mint for the
207
+ // requested scope" is useless when the reader cannot see WHICH scope
208
+ // was requested (a tool that declares `needs` without `scopes` gets
209
+ // this provider's default, which it never typed anywhere). The token
210
+ // response's own fields stay withheld: every one of them is a secret.
211
+ // Same conclusion `entraBearerToken` reached out loud in
212
+ // src/adapters/llm/OpenAIProvider.ts — the two siblings now agree.
213
+ throw new Error(`${ADAPTER}: the credential resolved but vended no access token for ` +
214
+ `'${req.service}' at scope${scopes.length === 1 ? '' : 's'} ` +
215
+ `[${scopes.join(', ')}] — ${answer === null
216
+ ? '`getToken` returned null, the SDK\'s spelling of "no token available"'
217
+ : "the response's `token` field was empty"}.\n` +
218
+ ` No token value is quoted here on purpose — every field of a token response is ` +
219
+ `a secret. This usually means the chain found a credential source that could not ` +
220
+ `actually mint for THAT audience.\n` +
221
+ ` Fix: check the audience first — inference is ${exports.AZURE_AI_SCOPE} and the ARM ` +
222
+ `control plane is ${exports.AZURE_MANAGEMENT_SCOPE}; name the right one in the request's ` +
223
+ `\`scopes\` or this provider's \`scopes\` option. If it is already right, sign in as ` +
224
+ `an identity that can mint for it (\`az login\`, or a managed identity with the role).`);
225
+ }
226
+ // The SDK records expiry in unix MILLISECONDS; the port reports unix
227
+ // SECONDS. Reported when known and omitted when not — an invented
228
+ // expiry is worse than none, because a caller would cache against it.
229
+ // (`answer` cannot be null past the token guard; the optional chain is
230
+ // for the compiler, which does not carry the narrowing across fields.)
231
+ const expiryMs = answer?.expiresOnTimestamp;
232
+ const expiresAt = typeof expiryMs === 'number' && Number.isFinite(expiryMs) && expiryMs > 0
233
+ ? Math.floor(expiryMs / 1000)
234
+ : undefined;
235
+ return {
236
+ status: 'issued',
237
+ credential: (0, kinds_js_1.bearer)(token),
238
+ ...(expiresAt !== undefined && { expiresAt }),
239
+ };
240
+ },
241
+ };
242
+ }
243
+ exports.entraIdentity = entraIdentity;
244
+ // ─── Internals ───────────────────────────────────────────────────────
245
+ function loadIdentitySdk(injected) {
246
+ if (injected)
247
+ return injected;
248
+ try {
249
+ return (0, lazyRequire_js_1.lazyRequire)('@azure/identity');
250
+ }
251
+ catch {
252
+ throw new Error(`${ADAPTER} requires the \`@azure/identity\` peer dependency.\n` +
253
+ ` Install: npm install @azure/identity\n` +
254
+ ` It is optional and loaded only when this provider first vends, so nothing else ` +
255
+ `in this library pays for it.`);
256
+ }
257
+ }
258
+ /**
259
+ * "There is no usable credential here" — the most common real failure, given
260
+ * the fix instead of the library's own text. `@azure/identity` names it
261
+ * `CredentialUnavailableError` (one rung) or `AggregateAuthenticationError`
262
+ * (the whole chain came up empty); both mean the same thing to the reader.
263
+ */
264
+ function credentialsUnavailable(name) {
265
+ const failure = new Error(`${ADAPTER}: could not acquire an Entra token in this environment — ${name}.\n` +
266
+ ` The underlying message is withheld: auth libraries echo request detail into ` +
267
+ `failure text, and this message reaches the model as a tool result.\n` +
268
+ ` Every rung of the DefaultAzureCredential chain was tried: environment service ` +
269
+ `principal, workload identity, managed identity, VS Code, Azure CLI, Azure ` +
270
+ `PowerShell, Azure Developer CLI.\n` +
271
+ ` Fix: run \`az login\`, or set AZURE_CLIENT_ID / AZURE_TENANT_ID / ` +
272
+ `AZURE_CLIENT_SECRET, or run where a managed identity exists, or pass \`_credential\`.`);
273
+ failure.name = 'AzureCredentialsUnavailableError';
274
+ return failure;
275
+ }
276
+ /** Re-raise without the library's text. See the module header for why. */
277
+ function sdkFailure(operation, err) {
278
+ const name = errorName(err);
279
+ // The no-credential names get the fix, not just the diagnosis — but ONLY
280
+ // those names. Everything else is reported as what it is.
281
+ if (name === 'CredentialUnavailableError' || name === 'AggregateAuthenticationError') {
282
+ return credentialsUnavailable(name);
283
+ }
284
+ const failure = new Error(`${ADAPTER}: ${operation} failed — ${name}.\n` +
285
+ ` The underlying message is withheld: this call handles an access token, and auth ` +
286
+ `libraries echo request detail into failure text. Check the Entra sign-in logs for ` +
287
+ `the full error.`);
288
+ failure.name = 'AzureCredentialError';
289
+ return failure;
290
+ }
291
+ /**
292
+ * Did THIS adapter write this error?
293
+ *
294
+ * Every refusal authored here opens with the adapter's own name — both as the
295
+ * marker and because it is what makes the message readable ("entraIdentity:
296
+ * …", "entraIdentity requires …"). A library's own failure never does, so the
297
+ * prefix is a reliable discriminator without an error subclass per refusal.
298
+ */
299
+ function isOwnRefusal(err) {
300
+ return err instanceof Error && err.message.startsWith(ADAPTER);
301
+ }
302
+ function errorName(err) {
303
+ const name = err?.name;
304
+ return typeof name === 'string' && name.length > 0 ? name : 'an unnamed failure';
305
+ }
306
+ //# 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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"}