/** * Abstract base class for providers that talk to an OpenAI chat-completions * shaped HTTP endpoint. Owns the entire request/stream/tool-loop pipeline * so concrete providers only declare configuration + provider-specific * quirks (env var names, default model, error mapping). * * Currently extended by: * - OpenAICompatibleProvider (generic /v1/chat/completions backend) * - LiteLLMProvider (LiteLLM proxy server) * - DeepSeekProvider (api.deepseek.com) * * Subclasses provide: * - getProviderName() / getDefaultModel() / formatProviderError() (abstract) * - optional overrides: getFallbackModelName, getFallbackModels, * adjustBuildBodyOptions, onStreamStart, getAvailableModels * * Nothing here imports from "ai" or "@ai-sdk/*". The base class is a * direct HTTP client + multi-step tool-execution loop driven by SSE. */ import type { AIProviderName } from "../constants/enums.js"; import { BaseProvider } from "../core/baseProvider.js"; import type { LanguageModel, OpenAICompatBuildBodyArgs, OpenAICompatChatRequest, OpenAICompatResponseFormat, OpenAICompatStreamLifecycleListeners, Schema, StreamOptions, StreamResult, ZodUnknownSchema } from "../types/index.js"; /** * Abstract HTTP+SSE provider for OpenAI chat-completions-shaped endpoints. */ export declare abstract class OpenAIChatCompletionsProvider extends BaseProvider { protected config: { baseURL: string; apiKey: string; }; protected resolvedModel?: string; constructor(providerName: AIProviderName, modelName: string | undefined, sdk: unknown, config: { baseURL: string; apiKey: string; }); protected abstract getProviderName(): AIProviderName; protected abstract getDefaultModel(): string; protected abstract formatProviderError(error: unknown): Error; /** * Model name to return when `getDefaultModel()` is empty AND * auto-discovery via `/models` finds nothing. Default "gpt-3.5-turbo". */ protected getFallbackModelName(): string; /** * Hardcoded model names returned from `getAvailableModels()` when the * remote `/models` endpoint can't be reached. Default empty. */ protected getFallbackModels(): string[]; /** * Hook to mutate the `buildBody` options before the wire body is * constructed. Default identity. Override for model-specific quirks * (e.g. LiteLLM's Gemini 2.5 maxTokens skip). */ protected adjustBuildBodyOptions(_modelId: string, opts: OpenAICompatBuildBodyArgs["options"]): OpenAICompatBuildBodyArgs["options"]; /** * Hook to adjust the OpenAI `response_format` after it's converted from the * V3 responseFormat (non-streaming `doGenerate` path). Default identity. * Override for providers that don't support a given format type — e.g. * DeepSeek rejects `response_format: { type: "json_schema" }` ("This * response_format type is unavailable now"); the `@ai-sdk/openai-compatible` * path this replaced declared `supportsStructuredOutputs: false`, which * downgraded `json_schema` to `json_object`. Subclasses replicate that here. */ protected adjustResponseFormat(rf: OpenAICompatResponseFormat | undefined, _modelId: string): OpenAICompatResponseFormat | undefined; /** * Hook to adjust the fully-built wire request body before it is sent, on * both the streaming and non-streaming paths. Default identity. Override for * provider/model quirks that can't be expressed through buildBody options — * e.g. Azure's newer reasoning deployments (o-series, gpt-5+) reject * `max_tokens` and require `max_completion_tokens`. */ protected adjustRequestBody(body: OpenAICompatChatRequest, _modelId: string): OpenAICompatChatRequest; /** * Hook called when a request fails with HTTP 400. Return a modified body to * retry ONCE with it; return undefined (default) to propagate the error * unchanged. Applies on both the non-streaming doGenerate path and the * streaming path. NVIDIA NIM uses this to strip `chat_template` / * `chat_template_kwargs.reasoning_budget` when a model server rejects them, * restoring the pre-migration fetch-level retry behavior. */ protected adjustBodyAfter400(_body: OpenAICompatChatRequest, _error: Error & { statusCode?: number; responseBody?: string; }): OpenAICompatChatRequest | undefined; /** * Hook called once at the start of every `executeStream` invocation. * Return lifecycle listeners (onUsage / onFinish) to receive deferred * analytics events as the stream progresses. Default returns undefined * (no extra wiring). LiteLLM uses this for the OTel span wrap with cost. */ protected onStreamStart(_modelId: string): OpenAICompatStreamLifecycleListeners | undefined; /** * Returns true if `resolveModelName` should fall back to fetching * `getAvailableModels()` and picking the first one when no explicit * model is configured. Default true. Subclasses with a non-empty * `getDefaultModel()` will never hit this branch anyway. */ protected shouldAutoDiscoverModel(): boolean; /** * Builds the chat-completions request URL for a model. Default is * `${baseURL}/chat/completions`. Override for providers with a different * routing scheme (e.g. Azure's deployment-based path + api-version query). */ protected getChatCompletionsURL(_modelId: string): string; /** * Auth headers merged into every request. Default is a Bearer token. * Override for providers that authenticate differently (e.g. Azure, which * uses an `api-key` header instead of `Authorization: Bearer`). */ protected getAuthHeaders(): Record; supportsTools(): boolean; /** * Health-check hook — part of the documented public provider contract * (`docs/provider-integration/00-architecture.md`). Default returns true * when an apiKey is configured; local providers (LM Studio, llama.cpp) * override this to probe the server's `/models` endpoint. */ validateConfiguration(): Promise; /** * Snapshot of the provider's resolved configuration — part of the documented * public provider contract (`docs/provider-integration/00-architecture.md`). * Subclasses inherit this; override only to expose extra fields. */ getConfiguration(): { provider: AIProviderName; model: string; defaultModel: string; baseURL: string; }; /** * Returns a minimal V3-shaped model used by BaseProvider's `generate()` * non-streaming path. Driven by the parent's `generateText`. The * streaming path bypasses this entirely. */ protected getAISDKModel(): Promise; protected resolveModelName(): Promise; private buildDelegatingModel; /** * Streaming path — drives the chat-completions endpoint directly. No * streamText, no AI SDK orchestrator. Tool calls, multi-step loops, * telemetry, abort handling all inline. */ protected executeStream(options: StreamOptions, _analysisSchema?: ZodUnknownSchema | Schema): Promise; private runStreamLoop; private streamOneStep; private executeToolBatch; /** * Default implementation hits `${baseURL}/models`. Subclasses with a * different endpoint path, caching, or fallback strategy should override. */ getAvailableModels(): Promise; getFirstAvailableModel(): Promise; }