import { convertMessages, streamSimpleOpenAICompletions } from '../ai/providers/openai-completions.js';
import { createShortcutXLModelCallError, ShortcutXLModelCallErrorCategory, ShortcutXLModelCallErrorEnvelope, ShortcutXLModelCallTransportKind, ShortcutXLProviderUsageStatus } from '../contracts/model-call.js';
import { toProviderUsageFromAi, toShortcutXLModelStreamEvent } from './anthropic-messages-transport.js';
function normalizeBaseUrl(baseUrl) {
    return baseUrl.replace(/\/+$/, '');
}
function normalizeAiOpenAIBaseUrl(baseUrl) {
    const normalized = normalizeBaseUrl(baseUrl);
    return normalized.endsWith('/v1') ? normalized : `${normalized}/v1`;
}
function createAuthHeader(auth) {
    if (auth.headerName === 'Authorization') {
        return {
            Authorization: auth.token.startsWith('Bearer ') ? auth.token : `Bearer ${auth.token}`
        };
    }
    return { 'x-api-key': auth.token };
}
function mapHttpStatusToErrorCategory(status) {
    if (status === 401 || status === 403)
        return ShortcutXLModelCallErrorCategory.Auth;
    if (status === 404)
        return ShortcutXLModelCallErrorCategory.Configuration;
    if (status === 408 || status === 409 || status >= 500) {
        return ShortcutXLModelCallErrorCategory.TransientNetwork;
    }
    if (status === 429)
        return ShortcutXLModelCallErrorCategory.RateLimited;
    return ShortcutXLModelCallErrorCategory.ProviderRejected;
}
function parseRetryAfterMs(header) {
    if (!header)
        return undefined;
    const seconds = Number(header);
    if (Number.isFinite(seconds))
        return Math.max(0, seconds * 1000);
    const dateMs = Date.parse(header);
    if (!Number.isNaN(dateMs))
        return Math.max(0, dateMs - Date.now());
    return undefined;
}
async function throwOpenAIHttpError(response, operation) {
    const body = await response.text().catch(() => '');
    let providerErrorType;
    let providerMessage;
    try {
        const parsed = body
            ? JSON.parse(body)
            : {};
        providerErrorType = typeof parsed.error?.type === 'string' ? parsed.error.type : undefined;
        providerMessage = typeof parsed.error?.message === 'string' ? parsed.error.message : undefined;
    }
    catch {
        providerMessage = body || undefined;
    }
    throw new ShortcutXLModelCallErrorEnvelope(createShortcutXLModelCallError(mapHttpStatusToErrorCategory(response.status), providerMessage ?? `OpenAI Chat Completions ${operation} failed: HTTP ${response.status}`, {
        providerErrorType,
        retryable: response.status === 429 || response.status === 408 || response.status >= 500,
        retryAfterMs: parseRetryAfterMs(response.headers.get('retry-after')),
        status: response.status
    }));
}
function throwInvalidResponse(message) {
    throw new ShortcutXLModelCallErrorEnvelope(createShortcutXLModelCallError(ShortcutXLModelCallErrorCategory.InvalidResponse, message));
}
function isAbortError(error) {
    return error instanceof DOMException && error.name === 'AbortError';
}
function normalizeFetchError(error, operation) {
    if (error instanceof ShortcutXLModelCallErrorEnvelope)
        throw error;
    if (isAbortError(error)) {
        throw new ShortcutXLModelCallErrorEnvelope(createShortcutXLModelCallError(ShortcutXLModelCallErrorCategory.Cancelled, `OpenAI Chat Completions ${operation} was cancelled.`));
    }
    throw new ShortcutXLModelCallErrorEnvelope(createShortcutXLModelCallError(ShortcutXLModelCallErrorCategory.TransientNetwork, error instanceof Error
        ? error.message
        : `OpenAI Chat Completions ${operation} failed before receiving a response.`, { retryable: true }));
}
function buildGatewayAiModel(request, baseUrl) {
    return {
        api: 'openai-completions',
        baseUrl: normalizeAiOpenAIBaseUrl(baseUrl),
        compat: {
            maxTokensField: 'max_completion_tokens',
            supportsDeveloperRole: false,
            supportsStore: false
        },
        contextWindow: request.model.contextWindow,
        cost: { cacheRead: 0, cacheWrite: 0, input: 0, output: 0 },
        id: request.model.id,
        input: request.model.input,
        maxTokens: request.model.maxTokens,
        name: request.model.name,
        provider: 'openai',
        reasoning: request.model.reasoning
    };
}
function buildGatewayAiStreamOptions(request, options) {
    return {
        apiKey: options.auth.token,
        cacheRetention: request.options?.cacheRetention,
        fetchOptions: options.fetchOptions,
        headers: {
            ...request.options?.headers,
            ...options.headers,
            ...createAuthHeader(options.auth)
        },
        maxRetryDelayMs: request.options?.maxRetryDelayMs,
        maxTokens: request.options?.maxTokens,
        metadata: request.options?.metadata,
        reasoning: request.options?.reasoning,
        signal: options.signal,
        temperature: request.options?.temperature,
        transport: request.options?.transport
    };
}
function toProviderUsage(usage) {
    if (!usage)
        return undefined;
    const cacheRead = usage.prompt_tokens_details?.cached_tokens ?? 0;
    const input = usage.prompt_tokens ?? 0;
    const output = usage.completion_tokens ?? 0;
    return {
        cacheReadInputTokens: cacheRead,
        inputTokens: input,
        outputTokens: output,
        totalTokens: usage.total_tokens ?? input + output
    };
}
function buildInvokeRequestBody(request, options) {
    const baseUrl = options.baseUrl;
    const model = buildGatewayAiModel(request, baseUrl);
    const toolChoice = request.options?.toolChoice;
    const disableQwenThinking = request.options?.requestType === 'suggestions' &&
        options.qwenCompatibility?.disableThinkingForSuggestions === true;
    return {
        ...(disableQwenThinking ? { enable_thinking: false } : {}),
        max_completion_tokens: request.options?.maxTokens ?? 4096,
        messages: convertMessages(model, request.context, {
            maxTokensField: 'max_completion_tokens',
            openRouterRouting: {},
            requiresAssistantAfterToolResult: false,
            requiresMistralToolIds: false,
            requiresThinkingAsText: false,
            requiresToolResultName: false,
            supportsDeveloperRole: false,
            supportsReasoningEffort: true,
            supportsStore: false,
            supportsStrictMode: true,
            supportsUsageInStreaming: true,
            thinkingFormat: 'openai',
            vercelGatewayRouting: {}
        }),
        model: model.id,
        stream: false,
        ...(request.context.tools
            ? {
                tools: request.context.tools.map((tool) => ({
                    function: {
                        description: tool.description,
                        name: tool.name,
                        parameters: tool.parameters
                    },
                    type: 'function'
                }))
            }
            : {}),
        ...(typeof toolChoice === 'object'
            ? {
                tool_choice: {
                    function: { name: toolChoice.name },
                    type: 'function'
                }
            }
            : {}),
        ...(toolChoice === 'required' || toolChoice === 'any' ? { tool_choice: 'required' } : {}),
        ...(toolChoice === 'auto' || toolChoice === 'none' ? { tool_choice: toolChoice } : {})
    };
}
function concreteHeaders(headers) {
    const result = {};
    for (const [key, value] of Object.entries(headers ?? {})) {
        if (value !== null)
            result[key] = value;
    }
    return result;
}
function parseInvokeResponse(data) {
    if (!data || typeof data !== 'object') {
        throwInvalidResponse('OpenAI Chat Completions invoke response must be a JSON object.');
    }
    const response = data;
    const choice = response.choices?.[0];
    const toolCalls = choice?.message?.tool_calls?.map((toolCall) => ({
        arguments: toolCall.function?.arguments ?? '{}',
        id: toolCall.id ?? '',
        name: toolCall.function?.name ?? ''
    }));
    return {
        stopReason: choice?.finish_reason ?? undefined,
        text: choice?.message?.content ?? undefined,
        toolCalls: toolCalls && toolCalls.length > 0 ? toolCalls : undefined,
        usageStatus: response.usage
            ? ShortcutXLProviderUsageStatus.UsageAvailable
            : ShortcutXLProviderUsageStatus.Unavailable
    };
}
export async function streamAiOpenAICompletionsModelRequest(request, options) {
    if (options.fetchImpl && options.fetchImpl !== fetch) {
        throw new Error('owned AI OpenAI Chat Completions streaming does not support per-call fetchImpl.');
    }
    const model = buildGatewayAiModel(request, options.baseUrl);
    const stream = streamSimpleOpenAICompletions(model, request.context, buildGatewayAiStreamOptions(request, options));
    let finalMessage;
    for await (const event of stream) {
        if (event.type === 'done')
            finalMessage = event.message;
        if (event.type === 'error')
            finalMessage = event.error;
        const shortcutEvent = toShortcutXLModelStreamEvent(event);
        if (shortcutEvent)
            options.onEvent(shortcutEvent);
    }
    const result = finalMessage ?? (await stream.result().catch(() => undefined));
    await options.usageStatusLogger?.recordProviderUsage({
        callId: request.requestId,
        model: model.id,
        status: result && result.usage.totalTokens > 0
            ? ShortcutXLProviderUsageStatus.UsageAvailable
            : ShortcutXLProviderUsageStatus.Unavailable,
        transport: ShortcutXLModelCallTransportKind.OpenAICompletions,
        usage: toProviderUsageFromAi(result?.usage)
    });
}
export async function invokeOpenAICompletionsModel(request, options) {
    const fetchImpl = options.fetchImpl ?? fetch;
    let response;
    try {
        response = await fetchImpl(`${normalizeBaseUrl(options.baseUrl)}/v1/chat/completions`, {
            body: JSON.stringify(buildInvokeRequestBody(request, options)),
            headers: {
                'content-type': 'application/json',
                ...concreteHeaders(options.headers),
                ...createAuthHeader(options.auth)
            },
            method: 'POST',
            credentials: options.fetchOptions?.credentials,
            signal: options.signal
        });
    }
    catch (error) {
        normalizeFetchError(error, 'invoke');
    }
    if (!response.ok) {
        await throwOpenAIHttpError(response, 'invoke');
    }
    let data;
    try {
        data = (await response.json());
    }
    catch {
        throwInvalidResponse('OpenAI Chat Completions invoke response was not valid JSON.');
    }
    const parsed = parseInvokeResponse(data);
    const usage = data.usage;
    await options.usageStatusLogger?.recordProviderUsage({
        callId: options.usageRequestId,
        model: request.model.id,
        status: parsed.usageStatus ?? ShortcutXLProviderUsageStatus.Unavailable,
        transport: ShortcutXLModelCallTransportKind.OpenAICompletions,
        usage: toProviderUsage(usage)
    });
    return parsed;
}
//# sourceMappingURL=openai-completions-transport.js.map