import { createOpenAI, openai } from "@ai-sdk/openai";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { BestAgentError } from "@best-agent/foundation";
import { createProviderUsageStats, decideWithAiSdk, generateTextWithProviderResilience, resolveAiSdkSystemPrompt, resolveOpenAiCompatibleMode, resolveProviderOptions, } from "./provider-core.js";
import { mapConversationMessagesToAiSdk } from "./provider-message-mapping.js";
import { getProviderCapabilities } from "./provider-metadata.js";
const COMPATIBLE_CHAT_COMPLETION_MAX_TOKENS = 32_000;
export class OpenAiSdkProvider {
    capabilities;
    config;
    constructor(config) {
        this.config = config;
        this.capabilities = getProviderCapabilities(config);
    }
    async generate(input) {
        const systemPrompt = input.systemPrompt ?? this.config.systemPrompt;
        const aiSdkSystemPrompt = resolveAiSdkSystemPrompt(this.config, systemPrompt);
        const messages = await mapConversationMessagesToAiSdk(input.messages, input.toolNameBindings ?? []);
        const result = await generateTextWithProviderResilience(this.config, {
            model: resolveOpenAiLanguageModel(this.config),
            messages,
            ...(resolveOpenAiMaxOutputTokens(this.config)
                ? { maxOutputTokens: resolveOpenAiMaxOutputTokens(this.config) }
                : {}),
            providerOptions: resolveProviderOptions(this.config, {
                cacheControl: input.cacheControl,
                includeReasoningSummary: true,
                systemPrompt,
            }),
            ...(aiSdkSystemPrompt ? { system: aiSdkSystemPrompt } : {}),
        }, input.stream);
        return {
            provider: this.config.kind,
            model: this.config.model,
            reasoningText: result.reasoningText,
            text: result.text,
            usage: createProviderUsageStats(result.totalUsage),
        };
    }
    async decide(input) {
        const effectiveConfig = this.config.maxOutputTokens == null &&
            resolveOpenAiCompatibleMode(this.config) === "compatible"
            ? {
                ...this.config,
                maxOutputTokens: COMPATIBLE_CHAT_COMPLETION_MAX_TOKENS,
            }
            : this.config;
        return decideWithAiSdk(effectiveConfig, input, (modelId) => resolveOpenAiLanguageModel(effectiveConfig, modelId));
    }
    async searchWeb(input) {
        if (!this.capabilities.supportsNativeWebSearch) {
            throw new BestAgentError("PROVIDER_WEB_SEARCH_UNSUPPORTED", `Provider ${this.config.kind} does not support native web search in the current compatibility mode.`);
        }
        const providerInstance = createOpenAiNativeProvider(this.config);
        const systemPrompt = [
            "Use the web_search tool to find external sources.",
            "Return plain text only.",
            `Format the final answer exactly as: Search results for "${input.query}" (up to ${input.limit}):`,
            "Then list each result as:",
            "1. <title>",
            "   URL: <url>",
            "   Snippet: <one short snippet>",
            "Include at most the requested number of results.",
            "If no reliable results are available, say so plainly.",
        ].join("\n");
        const aiSdkSystemPrompt = resolveAiSdkSystemPrompt(this.config, systemPrompt);
        const result = await generateTextWithProviderResilience(this.config, {
            model: resolveOpenAiLanguageModel(this.config),
            ...(aiSdkSystemPrompt ? { system: aiSdkSystemPrompt } : {}),
            messages: [
                {
                    role: "user",
                    content: buildWebSearchPrompt(input),
                },
            ],
            ...(resolveOpenAiMaxOutputTokens(this.config)
                ? { maxOutputTokens: resolveOpenAiMaxOutputTokens(this.config) }
                : {}),
            providerOptions: resolveProviderOptions(this.config, {
                systemPrompt,
            }),
            toolChoice: { type: "tool", toolName: "web_search" },
            tools: {
                web_search: providerInstance.tools.webSearch({
                    ...(input.domains && input.domains.length > 0
                        ? {
                            filters: {
                                allowedDomains: input.domains,
                            },
                        }
                        : {}),
                    searchContextSize: resolveWebSearchContextSize(input.limit),
                }),
            },
        });
        const text = result.text.trim();
        if (text) {
            return text;
        }
        const sources = result.sources ?? [];
        if (sources.length > 0) {
            return renderUrlOnlyWebSearchFallback(input.query, input.limit, sources);
        }
        return `Search results for "${input.query}" (up to ${input.limit}):\nNo reliable results were returned.`;
    }
}
function createOpenAiNativeProvider(config) {
    return config.apiKey || config.baseURL
        ? createOpenAI({
            apiKey: config.apiKey,
            baseURL: config.baseURL,
        })
        : openai;
}
function createOpenAiCompatibleProvider(config) {
    const baseURL = config.baseURL?.trim();
    if (!baseURL) {
        throw new BestAgentError("PROVIDER_BASE_URL_REQUIRED", "OpenAI-compatible chat/completions mode requires an explicit baseURL.");
    }
    return createOpenAICompatible({
        apiKey: config.apiKey,
        baseURL: baseURL.replace(/\/$/u, ""),
        includeUsage: true,
        name: "openai-compatible",
        transformRequestBody: normalizeOpenAiCompatibleRequestBody,
    });
}
function normalizeOpenAiCompatibleRequestBody(body) {
    if (!Array.isArray(body.messages)) {
        return body;
    }
    return {
        ...body,
        messages: body.messages.map((message) => {
            if (!message ||
                typeof message !== "object" ||
                message.role !== "assistant" ||
                !Array.isArray(message.tool_calls) ||
                message.content != null) {
                return message;
            }
            return {
                ...message,
                content: "",
            };
        }),
    };
}
function resolveOpenAiLanguageModel(config, modelId = config.model) {
    if (resolveOpenAiCompatibleMode(config) === "compatible") {
        return createOpenAiCompatibleProvider(config).chatModel(modelId);
    }
    return createOpenAiNativeProvider(config)(modelId);
}
function resolveOpenAiMaxOutputTokens(config) {
    if (typeof config.maxOutputTokens === "number") {
        return config.maxOutputTokens;
    }
    return resolveOpenAiCompatibleMode(config) === "compatible"
        ? COMPATIBLE_CHAT_COMPLETION_MAX_TOKENS
        : undefined;
}
function buildWebSearchPrompt(input) {
    return [
        `Query: ${input.query}`,
        `Maximum results: ${input.limit}`,
        input.domains && input.domains.length > 0
            ? `Allowed domains: ${input.domains.join(", ")}`
            : undefined,
        input.recencyDays
            ? `Prefer sources from the last ${input.recencyDays} days when possible.`
            : undefined,
    ]
        .filter((value) => Boolean(value))
        .join("\n");
}
function resolveWebSearchContextSize(limit) {
    if (limit <= 3) {
        return "low";
    }
    if (limit >= 8) {
        return "high";
    }
    return "medium";
}
function renderUrlOnlyWebSearchFallback(query, limit, sources) {
    const urlSources = sources
        .map((source) => {
        if (!source || typeof source !== "object" || !("url" in source)) {
            return undefined;
        }
        const value = source.url;
        return typeof value === "string" ? value.trim() : undefined;
    })
        .filter((source) => Boolean(source))
        .slice(0, limit);
    if (urlSources.length === 0) {
        return `Search results for "${query}" (up to ${limit}):\nNo reliable results were returned.`;
    }
    return [
        `Search results for "${query}" (up to ${limit}):`,
        ...urlSources.map((url, index) => `${index + 1}. ${url}\n   URL: ${url}\n   Snippet: Source URL returned without formatted summary.`),
    ].join("\n");
}
//# sourceMappingURL=openai-provider.js.map