/**
 * Shared utilities for OpenAI Completions and Responses providers.
 *
 * This module consolidates code that is identical (or near-identical) across
 * openai-completions.ts and openai-responses.ts to reduce duplication while
 * preserving the subtle behavioural differences of each provider.
 */
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
// =============================================================================
// Lazy SDK loading
// =============================================================================
let _openAIClass;
/**
 * Lazy-load the OpenAI SDK default export.
 * Shared between Completions and Responses providers so the module is only
 * imported once regardless of which provider is used first.
 */
export async function getOpenAIClass() {
    if (!_openAIClass) {
        const mod = await import("openai");
        _openAIClass = mod.default;
    }
    return _openAIClass;
}
/**
 * Create an OpenAI SDK client instance.
 *
 * Handles:
 * - API key resolution (explicit > env)
 * - GitHub Copilot dynamic headers
 * - Options header merging
 * - Lazy SDK loading
 */
export async function createOpenAIClient(model, context, apiKey, options) {
    if (!apiKey) {
        if (!process.env.OPENAI_API_KEY) {
            throw new Error("OpenAI API key is required. Set OPENAI_API_KEY environment variable or pass it as an argument.");
        }
        apiKey = process.env.OPENAI_API_KEY;
    }
    const headers = { ...model.headers };
    if (model.provider === "github-copilot") {
        const hasImages = hasCopilotVisionInput(context.messages);
        const copilotHeaders = buildCopilotDynamicHeaders({
            messages: context.messages,
            hasImages,
        });
        Object.assign(headers, copilotHeaders);
    }
    // Merge options headers last so they can override defaults
    if (options?.optionsHeaders) {
        Object.assign(headers, options.optionsHeaders);
    }
    const OpenAIClass = await getOpenAIClass();
    return new OpenAIClass({
        apiKey,
        baseURL: model.baseUrl,
        dangerouslyAllowBrowser: true,
        defaultHeaders: headers,
        ...options?.extraClientOptions,
    });
}
// =============================================================================
// Initial output construction
// =============================================================================
/**
 * Build the initial AssistantMessage output object used by all OpenAI stream
 * handlers.  Every field is initialised to its zero/default value.
 */
export function buildInitialOutput(model) {
    return {
        role: "assistant",
        content: [],
        api: model.api,
        provider: model.provider,
        model: model.id,
        usage: {
            input: 0,
            output: 0,
            cacheRead: 0,
            cacheWrite: 0,
            totalTokens: 0,
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
        },
        stopReason: "stop",
        timestamp: Date.now(),
    };
}
// =============================================================================
// Stream lifecycle helpers
// =============================================================================
/**
 * Shared post-stream checks.  Call after the provider-specific stream loop
 * finishes successfully (before pushing the "done" event).
 *
 * Throws if the request was aborted or the output indicates an error.
 */
export function assertStreamSuccess(output, signal) {
    if (signal?.aborted) {
        throw new Error("Request was aborted");
    }
    if (output.stopReason === "aborted" || output.stopReason === "error") {
        throw new Error("An unknown error occurred");
    }
}
/**
 * Emit the "done" event and close the stream.
 */
export function finalizeStream(stream, output) {
    stream.push({ type: "done", reason: output.stopReason, message: output });
    stream.end();
}
/**
 * Handle an error during streaming.
 *
 * Cleans up any leftover `index` properties on content blocks, sets the
 * appropriate stop reason and error message, then emits the "error" event.
 */
export function handleStreamError(stream, output, error, signal, 
/** Extra error metadata to append (e.g. OpenRouter raw metadata). */
extraMessage) {
    for (const block of output.content)
        delete block.index;
    output.stopReason = signal?.aborted ? "aborted" : "error";
    output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
    if (extraMessage)
        output.errorMessage += `\n${extraMessage}`;
    stream.push({ type: "error", reason: output.stopReason, error: output });
    stream.end();
}
// =============================================================================
// Reasoning helpers
// =============================================================================
/**
 * Clamp reasoning effort for models that don't support all levels.
 * gpt-5.x models don't support "minimal" -- map to "low".
 *
 * Used by both openai-responses.ts and azure-openai-responses.ts.
 */
export function clampReasoningForModel(modelName, effort) {
    const name = modelName.includes("/") ? modelName.split("/").pop() : modelName;
    if (name.startsWith("gpt-5") && effort === "minimal")
        return "low";
    return effort;
}
//# sourceMappingURL=openai-shared.js.map