/** * FoundryProvider — Microsoft Foundry (Azure AI Foundry) projects over the * api-version-free v1 OpenAI-compatible inference route. * * Pattern: Adapter (GoF) + Ports-and-Adapters (Cockburn 2005), composed — * a thin vendor file over `openai()`'s machinery, the exact * `azureOpenai()` shape. * Role: Outer ring — owns EVERY Foundry spelling (the project-endpoint * shape, the env-var names, the token audience, the `/openai/v1` * derivation) so the `LLMProvider` port and the rest of the library * never learn one. Knows nothing about agents, recorders, or * compositions. * Emits: N/A. * * ─── Why this exists ───────────────────────────────────────────────── * * This is the JS answer to Microsoft's * `FoundryChatClient(project_endpoint, model, credential)`: point at a * Foundry PROJECT, name a DEPLOYMENT, hand over a credential, done. The * doc-verified facts it is built on (learn.microsoft.com, Aug 2026): * * • A Foundry project endpoint * (`https://{account}.services.ai.azure.com/api/projects/{project}`) * itself serves the v1 inference route by simple suffixing: `+ /openai/v1`. * No `api-version` query, no deployment-scoped path. * • The wire's `model` field carries the DEPLOYMENT name — Foundry's * "model" is a deployment, exactly as on classic Azure OpenAI. * • Auth is `Authorization: Bearer ` — the v1 * route accepts a key as a Bearer too. * * Because that route IS current OpenAI wire, this file is `openai({ baseURL, * legacyEndpoint: false, … })` plus Foundry's spellings — the same * composition `azureOpenai()` uses, with the modern dialect declared instead * of implied away by the custom `baseURL`. * * ─── Auth: three doors, one refused ambiguity ──────────────────────── * * • `credential` — any `@azure/identity` credential (duck-typed; the SDK * is never imported for this door). Tokens are minted per request * through `openai()`'s credential-callback seam. * • `apiKey` — a static key, or a callback re-read per request. * • NEITHER — `new DefaultAzureCredential()` via the optional * `@azure/identity` peer: the platform's own blessed zero-config pattern * inside a hosted Foundry container (the platform injects * `FOUNDRY_PROJECT_ENDPOINT` and gives the agent a managed identity, so * `foundry()` with no arguments is a complete configuration there). * • BOTH `credential` and `apiKey` — refused by name. Two credentials is a * config bug, not extra security. * * ─── Ceilings (stated, not worked around) ──────────────────────────── * * • DEPLOYMENT NAMES HIDE THE MODEL. o-series auto-detection cannot work on * an arbitrary deployment name, so `reasoning` must be declared — the same * rationale, word for word, as `azureOpenai()`. * • INFERENCE ONLY. The token audience here is the data plane * ({@link AZURE_AI_SCOPE}); the ARM control plane is a different audience * and a different job — see `entraIdentity()` in the identity adapters. * • Everything `openai()` does not do (multi-modal, JSON-mode), this does * not do either — it is the same machinery. */ import type { LLMProvider } from '../types.js'; import type { OpenAIProviderOptions } from './OpenAIProvider.js'; import type { TokenCredentialLike } from '../identity/azure.js'; export interface FoundryProviderOptions { /** * The Foundry PROJECT endpoint — * `https://{account}.services.ai.azure.com/api/projects/{project}`, copied * from the Foundry portal. Env fallback: `FOUNDRY_PROJECT_ENDPOINT`, which * hosted Foundry containers get AUTO-INJECTED by the platform — inside one, * this option can simply be omitted. Required (option or env); refused by * name otherwise. */ readonly projectEndpoint?: string; /** * The DEPLOYMENT name (Foundry's "model"). Env fallbacks: * `AZURE_AI_MODEL_DEPLOYMENT_NAME` — the `azd` scaffolding convention, so a * template-provisioned app needs no extra wiring — then `MODEL_NAME`. * Required (option or env); refused by name otherwise. */ readonly deployment?: string; /** * Keyless (Microsoft Entra ID) auth — any `@azure/identity` credential * (`DefaultAzureCredential`, `ManagedIdentityCredential`, …), duck-typed so * this file never imports that SDK. Consulted before EVERY request through * `openai()`'s credential-callback seam, so MSAL's cache does the pacing * and an expired token is a fresh token, never a 401. * * Mutually exclusive with `apiKey` — both together are refused by name. * NEITHER given constructs a `DefaultAzureCredential` (peer-dep * `@azure/identity`), the zero-config path hosted containers are built for. */ readonly credential?: TokenCredentialLike; /** * Static api key — the v1 route accepts a key as a Bearer too. A FUNCTION * here is re-read before every request (`openai()`'s 9.29.0 contract). * Mutually exclusive with `credential`. */ readonly apiKey?: string | (() => string | Promise); /** * Token audience for the credential doors. Default {@link AZURE_AI_SCOPE} * (`https://ai.azure.com/.default`) — the ONE data-plane audience every * Foundry / Azure OpenAI inference call accepts. The ARM control plane * (`https://management.azure.com/.default`) is a DIFFERENT audience whose * tokens do NOT work here: Azure validates the audience on every call. * Ignored when `apiKey` is the door in use. */ readonly scope?: string; /** * Set when the DEPLOYMENT is a **reasoning model** (o1/o3/o4-mini). * Deployment names are arbitrary and hide the underlying model, so this * cannot be auto-detected — declare it to omit `temperature` and send the * `developer` role. (Same rationale as `azureOpenai()`.) */ readonly reasoning?: boolean; /** Default max tokens when the request doesn't set it. Optional. */ readonly defaultMaxTokens?: number; /** @internal Pre-built client for testing — the same duck type `openai()` * takes, spelled as such so the two seams can never drift apart. */ readonly _client?: OpenAIProviderOptions['_client']; } /** * Project endpoint → the base URL the v1 inference route serves. * * `https://acct.services.ai.azure.com/api/projects/proj` * → `https://acct.services.ai.azure.com/api/projects/proj/openai/v1` * …with trailing slashes → the same * …already ending in `/openai/v1` → the same (idempotent — `azureUrl.ts`'s * rule, applied to Foundry's suffix) * * Validation lives HERE, beside the derivation, so there is exactly ONE owner * of what a Foundry endpoint looks like: it must be `https://` and it must * contain `/api/projects/` — anything else is some OTHER Azure endpoint * (a resource root, an ARM URL) that would 404 or 401 far from the typo. * Endpoints are NOT secrets, so a malformed one is echoed back: seeing what * arrived is the fastest fix. The one carve-out is cleartext to LOOPBACK * (127.0.0.1 / localhost / [::1]) — that is how the wire tests drive the * REAL SDK against a local fake, and refusing bytes that cannot leave the * machine would buy no safety. * * A `?` or `#` is refused too, and for a reason particular to a SUFFIXING * derivation: `…/api/projects/p?x=1` + `/openai/v1` is * `…/api/projects/p?x=1/openai/v1`, where the route lives entirely inside the * query string. That URL is well-formed, constructs without complaint, and * 404s on every request — the exact "late and far from the typo" failure this * validator exists to convert into an early named one. The v1 inference route * takes no query parameters at all (that is the point of it: no * `api-version`), so there is nothing legitimate to preserve. */ export declare function foundryInferenceUrl(projectEndpoint: string): string; /** * Build an `LLMProvider` for a **Microsoft Foundry project**. * * Inside a hosted Foundry container this is a COMPLETE configuration: * * @example * import { foundry } from 'agentfootprint/providers'; * * const agent = Agent.create({ * provider: foundry(), // endpoint injected, managed identity signs * model: 'foundry', // → the configured deployment * }).build(); * * Anywhere else, name the project and the deployment (and sign in, or pass a * credential): * * @example * const provider = foundry({ * projectEndpoint: 'https://my-acct.services.ai.azure.com/api/projects/my-proj', * deployment: 'gpt-4o-128k', * credential: new DefaultAzureCredential(), * }); * * The request's `model` is the DEPLOYMENT name: the shorthand `'foundry'` * resolves to the configured default, and a concrete deployment id passes * through untouched (so one provider can target several deployments — the * azureOpenai precedent). */ export declare function foundry(options?: FoundryProviderOptions): LLMProvider; //# sourceMappingURL=FoundryProvider.d.ts.map