/**
 * OpenAIProvider — wraps the `openai` SDK as an `LLMProvider`.
 *
 * Pattern: Adapter (GoF) + Ports-and-Adapters (Cockburn 2005).
 * Role:    Outer ring — translates `LLMRequest`/`LLMResponse` to/from
 *          OpenAI's Chat Completions API. Knows nothing about agents,
 *          recorders, or compositions.
 * Emits:   N/A.
 *
 * ─── Limitations ────────────────────────────────────────────────────
 *
 * • Multi-modal NOT supported  (`LLMMessage.content` is
 *   `string`). May extend in a future release.
 * • `responseFormat` (JSON-mode) NOT exposed  — pass schema
 *   instructions via `systemPrompt` for now.
 *
 * The `baseURL` option enables OpenAI-compatible APIs (Ollama, Together,
 * Groq, vLLM, LM Studio) without a separate adapter — see the `ollama()`
 * convenience factory below.
 */
import type { LLMCallHooks, LLMChunk, LLMProvider, LLMRequest, LLMResponse, WireRole } from '../../provider-contract/types.js';
import type { ThinkingHandler } from '../../thinking/types.js';
import type { TokenCredentialLike } from '../identity/azure.js';
interface OpenAIClient {
    chat: {
        completions: {
            create(params: OpenAICreateParams, options?: OpenAIRequestOptions): Promise<OpenAIChatCompletion> | AsyncIterable<OpenAIStreamChunk>;
        };
    };
}
/** The SDK's per-request options — the one this adapter sends is the signal,
 *  so an aborted run cancels the HTTP request itself (`lib/abort.ts`). */
interface OpenAIRequestOptions {
    signal?: AbortSignal;
}
interface OpenAICreateParams {
    model: string;
    messages: OpenAIMessage[];
    tools?: OpenAITool[];
    /** Legacy token cap — DEPRECATED by OpenAI and REJECTED by o-series reasoning
     *  models. Kept only for custom OpenAI-compatible endpoints (Ollama/vLLM/…). */
    max_tokens?: number;
    /** Current token cap — accepted by all OpenAI/Azure chat models incl. o-series. */
    max_completion_tokens?: number;
    temperature?: number;
    stop?: string[];
    stream?: boolean;
    /** Ask OpenAI/Azure to emit a final usage chunk while streaming. */
    stream_options?: {
        include_usage: boolean;
    };
    /** v7.26 — forced choice of one named tool, OpenAI's dialect of
     *  `LLMRequest.toolChoice`. */
    tool_choice?: {
        type: 'function';
        function: {
            name: string;
        };
    };
}
interface OpenAIMessage {
    role: 'system' | 'developer' | 'user' | 'assistant' | 'tool';
    content: string | null;
    tool_calls?: OpenAIToolCall[];
    tool_call_id?: string;
}
interface OpenAIToolCall {
    id: string;
    type: 'function';
    function: {
        name: string;
        arguments: string;
    };
}
interface OpenAITool {
    type: 'function';
    function: {
        name: string;
        description: string;
        parameters: Record<string, unknown>;
    };
}
interface OpenAIChatCompletion {
    id: string;
    model: string;
    choices: Array<{
        index: number;
        message: {
            role: 'assistant';
            content: string | null;
            tool_calls?: OpenAIToolCall[];
        };
        finish_reason: 'stop' | 'tool_calls' | 'length' | 'content_filter' | string;
    }>;
    usage?: {
        prompt_tokens: number;
        completion_tokens: number;
    };
}
interface OpenAIStreamChunk {
    id: string;
    model: string;
    choices: Array<{
        index: number;
        delta: {
            role?: string;
            content?: string | null;
            tool_calls?: Array<{
                index: number;
                id?: string;
                type?: string;
                function?: {
                    name?: string;
                    arguments?: string;
                };
            }>;
        };
        finish_reason: string | null;
    }>;
    usage?: {
        prompt_tokens: number;
        completion_tokens: number;
    };
}
export interface OpenAIProviderOptions {
    /**
     * API key. Defaults to the `OPENAI_API_KEY` env var.
     *
     * **A FUNCTION here is re-read before every request (9.29.0).** That is what
     * makes this adapter usable in front of an endpoint whose credential is a
     * short-lived OAuth token rather than a key — Vertex AI's OpenAI-compatible
     * endpoint being the case that forced it. An independent field trial ran a
     * real call through that endpoint with a current token, then repeated it
     * with an expired one and got HTTP 401 with nowhere to put a fresh token:
     * *"`OpenAIProviderOptions` accepts a fixed `apiKey` and exposes no
     * credential callback"* (FINDINGS "Part 2B"). A process living longer than
     * an hour had to rebuild the provider, and usually found out it hadn't at
     * 3am.
     *
     * The boundary, stated so nobody has to guess:
     *   • called ONCE PER REQUEST, before the request is built;
     *   • the SDK client is rebuilt only when the returned string CHANGED, so a
     *     cached token costs one function call;
     *   • a stream keeps the key it started with — nothing can re-authenticate
     *     a socket that is already open;
     *   • what expiry means is the callback's business. This adapter does not
     *     inspect, decode or schedule anything; it asks every time and uses what
     *     it is given.
     *
     * ```ts
     * const auth = new GoogleAuth({ scopes: ['https://www.googleapis.com/auth/cloud-platform'] });
     * const client = await auth.getClient();
     * openai({
     *   baseURL: 'https://…/openapi',
     *   apiKey: async () => (await client.getAccessToken()).token ?? '',
     * });
     * ```
     */
    readonly apiKey?: string | (() => string | Promise<string>);
    /** Base URL — set for OpenAI-compatible APIs (Ollama, Together, vLLM). */
    readonly baseURL?: string;
    /**
     * Declare the endpoint's DIALECT instead of letting `baseURL` imply it (9.74.0).
     *
     * ── What "legacy" means here ─────────────────────────────────────────────
     * An OpenAI-compatible server that predates two of OpenAI's own moves: it
     * accepts only the deprecated `max_tokens` (never `max_completion_tokens`),
     * and it may reject `stream_options` outright. Setting `baseURL` has always
     * implied that dialect, because the compatible servers the option was built
     * for (Ollama/vLLM/Together/Groq) are exactly the ones that break on the
     * modern fields — and a hard failure is worse than a conservative request.
     *
     * That implication is a DEFAULT, not a law. `legacyEndpoint: false` declares
     * "this baseURL speaks the CURRENT dialect": send `max_completion_tokens`,
     * send `stream_options.include_usage` on streams, and declare forced tool
     * choice. The worked example is Azure's v1 inference route
     * (`https://….services.ai.azure.com/api/projects/{project}/openai/v1`) — a
     * custom `baseURL` that IS current OpenAI wire; `foundry()` sets this for
     * you. `legacyEndpoint: true` with no `baseURL` is legal and means what it
     * says, though nothing today needs it.
     *
     * `streamUsage` interplay: a non-legacy endpoint already sends
     * `stream_options`, exactly as before — `streamUsage` remains the opt-in
     * for endpoints that stay legacy, and this flag does not change what either
     * value of it does.
     *
     * Unset, nothing changes anywhere: the default is exactly `!!baseURL`.
     */
    readonly legacyEndpoint?: boolean;
    /**
     * Ask a CUSTOM endpoint for token usage while streaming (9.73.0).
     *
     * ── The silence this breaks ──────────────────────────────────────────────
     * OpenAI and Azure only report usage on a stream when asked, via
     * `stream_options: { include_usage: true }`. This adapter sends that — but
     * NOT when you set `baseURL`, because some OpenAI-compatible servers reject
     * an unknown field outright, and a hard failure is worse than a missing
     * number. The cost of that caution went unnoticed: **every local-model user
     * streaming through `openai({ baseURL })` sees zero tokens**, everywhere
     * usage is read — dashboards, cost recorders, the thinking trace's per-step
     * cost. Nothing is broken and nothing says so.
     *
     * Most current local servers do support it (llama.cpp and Ollama both send a
     * final usage chunk when asked). Set this to `true` and get the numbers
     * back; leave it out and nothing changes.
     *
     * ── When it is ignored ───────────────────────────────────────────────────
     * Ignored whenever the endpoint is treated as MODERN, because there the
     * field is already sent. "Modern" is `legacyEndpoint === false`, which is
     * `!baseURL` by default — so with no `baseURL` and no `legacyEndpoint` this
     * flag still changes nothing, exactly as it did before `legacyEndpoint`
     * existed.
     *
     * But `legacyEndpoint` is now the thing that decides, not `baseURL`:
     * `openai({ legacyEndpoint: true })` with NO `baseURL` is legal and means
     * what it says, and there `stream_options` is withheld and this flag is what
     * turns it back on. So read the pair, not `baseURL` alone.
     */
    readonly streamUsage?: boolean;
    /**
     * Default model used when `LLMRequest.model` is `'openai'` (the
     * shorthand). Full model ids pass through unchanged.
     */
    readonly defaultModel?: string;
    /** Default max tokens when the request doesn't set it. Optional. */
    readonly defaultMaxTokens?: number;
    /**
     * Treat the target as a **reasoning model** (o-series: o1 / o3 / o4-mini, or an
     * Azure reasoning deployment). Reasoning models reject `max_tokens` and an explicit
     * `temperature`, and use the `developer` role in place of `system`. Standard o-series
     * model ids are auto-detected; set this explicitly for Azure deployments whose name
     * does not reveal the underlying model.
     */
    readonly reasoning?: boolean;
    /** @internal Pre-built client for testing. Skips SDK import. */
    readonly _client?: OpenAIClient;
}
/**
 * Build an `LLMProvider` backed by OpenAI's Chat Completions API.
 *
 * @example
 *   import { Agent } from 'agentfootprint';
 *   import { openai } from 'agentfootprint/providers';
 *
 *   const agent = Agent.create({
 *     provider: openai({ defaultModel: 'gpt-4o' }),
 *     model: 'openai',
 *   })
 *     .tool(searchTool)
 *     .build();
 */
export declare function openai(options?: OpenAIProviderOptions): LLMProvider;
/**
 * Class form for consumers who prefer `new OpenAIProvider(...)`.
 */
export declare class OpenAIProvider implements LLMProvider {
    readonly name = "openai";
    readonly carriesInMessages: readonly WireRole[];
    /** Read off `inner` rather than fixed here: the answer depends on whether a
     *  custom `baseURL` was given, and this class is only the thing its options
     *  made it. */
    readonly carriesForcedToolChoice: boolean;
    /** Read off `inner` for the same reason. */
    readonly promptCaching?: LLMProvider['promptCaching'];
    readonly thinkingHandler?: ThinkingHandler;
    private readonly inner;
    constructor(options?: OpenAIProviderOptions);
    complete(req: LLMRequest, hooks?: LLMCallHooks): Promise<LLMResponse>;
    stream(req: LLMRequest, hooks?: LLMCallHooks): AsyncIterable<LLMChunk>;
}
export interface AzureOpenAIProviderOptions {
    /**
     * Resource endpoint, e.g. `https://my-co.openai.azure.com`. Env fallbacks:
     * `AZURE_OPENAI_ENDPOINT`, then `OPENAI_BASE_URL` — the two spellings are
     * interchangeable HERE and produce the same URL, whichever one your gateway
     * config already uses. A value that already ends in `/openai` is taken as-is;
     * anything else gets `/openai` appended, which is the path Azure serves
     * deployments under.
     */
    readonly endpoint?: string;
    /** API key. Env fallbacks: `AZURE_OPENAI_API_KEY`, then `OPENAI_API_KEY`. */
    readonly apiKey?: string;
    /**
     * Keyless (Microsoft Entra ID) auth — pass any `@azure/identity` credential
     * (`DefaultAzureCredential`, `ManagedIdentityCredential`, …); the type is
     * duck-typed so this file never imports that SDK. The token is minted (or
     * served from MSAL's cache) on EVERY request by the underlying client, so a
     * long-lived agent process never holds an expired token.
     *
     * Mutually exclusive with `apiKey`: two credentials is a config bug, not
     * extra security, and is refused by name rather than silently ranked.
     */
    readonly credential?: TokenCredentialLike;
    /**
     * Token audience for `credential`. Default
     * {@link AZURE_COGNITIVE_SERVICES_SCOPE}
     * (`https://cognitiveservices.azure.com/.default`) — the audience
     * Microsoft's own keyless guidance names for the CLASSIC deployment-scoped
     * route this door builds
     * (`{endpoint}/openai/deployments/{d}/chat/completions?api-version=…`).
     * Each door defaults to the audience ITS route documents: `foundry()`'s
     * v1/project route is documented against {@link AZURE_AI_SCOPE}
     * (`https://ai.azure.com/.default`), and current resources widely accept
     * both — but an older `*.openai.azure.com` resource may only accept this
     * one, and a default that 401s on the oldest resources it exists to serve
     * would be the wrong default. The ARM control plane
     * (`https://management.azure.com/.default`) is a THIRD audience whose
     * tokens never work here. Azure Government spells this audience
     * `https://cognitiveservices.azure.us/.default`. Ignored without
     * `credential`.
     */
    readonly scope?: string;
    /** Azure API version, e.g. `2024-12-01-preview`. Env fallback:
     *  `AZURE_OPENAI_API_VERSION`. Required. */
    readonly apiVersion?: string;
    /** The DEPLOYMENT name (Azure's "model"), e.g. `gpt-4o-128k`. Env fallbacks:
     *  `AZURE_OPENAI_DEPLOYMENT`, then `MODEL_NAME`. Required. */
    readonly deployment?: string;
    /** Default max tokens when the request doesn't set it. Optional. */
    readonly defaultMaxTokens?: number;
    /**
     * Set when the Azure DEPLOYMENT is a **reasoning model** (o1/o3/o4-mini). Azure
     * deployment names are arbitrary, so this cannot be auto-detected — declare it to
     * omit `temperature` and send the `developer` role. (`max_completion_tokens` is used
     * for all Azure deployments regardless.)
     */
    readonly reasoning?: boolean;
    /** @internal Pre-built client for testing. Skips SDK import. */
    readonly _client?: OpenAIClient;
}
/**
 * Build an `LLMProvider` for **Azure OpenAI**.
 *
 * Azure is NOT a drop-in OpenAI-compatible URL — it uses a deployment-scoped
 * path, `api-key` header auth, and an `api-version` query param. This wraps the
 * `openai` SDK's `AzureOpenAI` client (which handles all that) and reuses the
 * exact same completion/streaming/tool-call logic as `openai()`.
 *
 * The request's `model` is the Azure **deployment** name. Pass a deployment id
 * to target it; the shorthands `'azure'` / `'azure-openai'` resolve to the
 * configured default `deployment`.
 *
 * `endpoint` is the resource ROOT (`https://my-co.openai.azure.com`), and
 * `AZURE_OPENAI_ENDPOINT` and `OPENAI_BASE_URL` are two names for it that
 * resolve to the identical final URL. Setting `OPENAI_BASE_URL` no longer
 * collides with the SDK's own reading of that variable — this factory hands the
 * SDK a `baseURL` it computed rather than an `endpoint` the SDK would have to
 * reconcile with the environment.
 *
 * @example
 *   import { azureOpenai } from 'agentfootprint/providers';
 *
 *   const agent = Agent.create({
 *     provider: azureOpenai({
 *       endpoint: process.env.OPENAI_BASE_URL,            // *.openai.azure.com
 *       apiKey: process.env.AZURE_OPENAI_API_KEY,
 *       apiVersion: process.env.AZURE_OPENAI_API_VERSION, // 2024-12-01-preview
 *       deployment: process.env.MODEL_NAME,               // gpt-4o-128k
 *     }),
 *     model: 'azure',
 *   }).build();
 */
export declare function azureOpenai(options?: AzureOpenAIProviderOptions): LLMProvider;
/**
 * `TokenCredentialLike.getToken(scope)` → the bearer string an OpenAI-shaped
 * client can send — shared by the two Entra doors (`azureOpenai()` here and
 * `foundry()` in FoundryProvider.ts, which imports it; the dependency points
 * THIS way because FoundryProvider already composes over `openai()`).
 *
 * What it enforces are the credential-surface laws, not conveniences:
 *   • an ABSENT answer — `null`, the SDK's spelling of "no token available",
 *     and `undefined`, which is what a hand-rolled or caching credential
 *     hands back on a miss — is refused by NAME. Both are the same absence
 *     and get the same refusal: passed onward, `null` becomes a bare 401 that
 *     names nothing, and `undefined` becomes a raw `TypeError` thrown from
 *     inside this library, which names less than the 401 does.
 *   • an empty or blank `token` FIELD is refused by field name; the value
 *     itself is a secret when it is right, so no message ever quotes it.
 *   • a throwing credential is reported as operation + error NAME only (auth
 *     SDKs echo request detail into 401/403 text), with no `cause` — a cause
 *     travels into every serializer that walks own properties.
 * The scope IS quoted: it is a public audience URI, never a secret, and it is
 * the thing the consumer most likely got wrong.
 */
export declare function entraBearerToken(adapter: string, credential: TokenCredentialLike, scope: string): Promise<string>;
export {};
//# sourceMappingURL=OpenAIProvider.d.ts.map