import { convertResponsesTools } from '../ai/providers/openai-responses-shared.js';
import { streamSimpleOpenAIResponses } from '../ai/providers/openai-responses.js';
import { createShortcutXLModelCallError, ShortcutXLModelCallErrorCategory, ShortcutXLModelCallErrorEnvelope, ShortcutXLModelCallTransportKind, ShortcutXLProviderUsageStatus } from '../contracts/model-call.js';
import { aiUserContentToOpenAIResponsesContent } from './ai-content.js';
import { toProviderUsageFromAi, toShortcutXLModelStreamEvent } from './anthropic-messages-transport.js';
function normalizeBaseUrl(baseUrl) {
    return baseUrl.replace(/\/+$/, '');
}
function normalizeAiOpenAIResponsesBaseUrl(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 throwOpenAIResponsesHttpError(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 Responses ${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 Responses ${operation} was cancelled.`));
    }
    throw new ShortcutXLModelCallErrorEnvelope(createShortcutXLModelCallError(ShortcutXLModelCallErrorCategory.TransientNetwork, error instanceof Error
        ? error.message
        : `OpenAI Responses ${operation} failed before receiving a response.`, { retryable: true }));
}
function buildGatewayAiModel(request, baseUrl) {
    return {
        api: 'openai-responses',
        baseUrl: normalizeAiOpenAIResponsesBaseUrl(baseUrl),
        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 buildInvokeAiModel(request, baseUrl) {
    return {
        api: 'openai-responses',
        baseUrl: normalizeAiOpenAIResponsesBaseUrl(baseUrl),
        contextWindow: request.model.contextWindow,
        cost: { cacheRead: 0, cacheWrite: 0, input: 0, output: 0 },
        id: request.model.id,
        input: ['text', 'image'],
        maxTokens: request.options?.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
    };
}
// Local invoke converter because upstream owned AI's Responses converter only accepts
// text/image content. The shared owned AI content helper owns the extra input_file case.
function convertResponsesInvokeMessages(request) {
    const input = [];
    if (request.context.systemPrompt) {
        input.push({
            content: request.context.systemPrompt,
            role: request.model.reasoning ? 'developer' : 'system'
        });
    }
    for (const message of request.context.messages) {
        if (message.role === 'user') {
            const content = aiUserContentToOpenAIResponsesContent(message.content);
            if (content.length > 0)
                input.push({ content, role: 'user' });
            continue;
        }
        if (message.role === 'assistant') {
            for (const block of message.content) {
                if (block.type === 'text') {
                    input.push({
                        content: [{ annotations: [], text: block.text, type: 'output_text' }],
                        role: 'assistant',
                        status: 'completed',
                        type: 'message'
                    });
                }
                else if (block.type === 'toolCall') {
                    const [callId, itemId] = block.id.split('|');
                    input.push({
                        arguments: JSON.stringify(block.arguments),
                        call_id: callId,
                        ...(itemId ? { id: itemId.startsWith('fc') ? itemId : `fc_${itemId}` } : {}),
                        name: block.name,
                        type: 'function_call'
                    });
                }
            }
            continue;
        }
        const output = message.content
            .filter((block) => block.type === 'text')
            .map((block) => block.text)
            .join('\n');
        const attachments = message.content.filter((block) => block.type === 'image' || block.type === 'document');
        const [callId] = message.toolCallId.split('|');
        input.push({
            call_id: callId,
            output: output || '(see attached file)',
            type: 'function_call_output'
        });
        if (attachments.length > 0) {
            input.push({
                content: aiUserContentToOpenAIResponsesContent([
                    { text: 'Attached file(s) from tool result:', type: 'text' },
                    ...attachments
                ]),
                role: 'user'
            });
        }
    }
    return input;
}
function buildInvokeRequestBody(request, baseUrl) {
    const model = buildInvokeAiModel(request, baseUrl);
    return {
        input: convertResponsesInvokeMessages(request),
        max_output_tokens: request.options?.maxTokens ?? 4096,
        model: model.id,
        stream: false,
        // Browser-direct enterprise gateways must not persist helper conversation
        // state by default; keep this explicit while Responses invoke is direct.
        store: false,
        ...(request.context.tools
            ? {
                tools: convertResponsesTools(request.context.tools, { strict: false })
            }
            : {}),
        ...(typeof request.options?.toolChoice === 'object'
            ? {
                tool_choice: {
                    name: request.options.toolChoice.name,
                    type: 'function'
                }
            }
            : {})
    };
}
function toProviderUsage(usage) {
    if (!usage)
        return undefined;
    const cacheRead = usage.input_tokens_details?.cached_tokens ?? 0;
    const input = usage.input_tokens ?? 0;
    const output = usage.output_tokens ?? 0;
    return {
        cacheReadInputTokens: cacheRead,
        inputTokens: input,
        outputTokens: output,
        totalTokens: usage.total_tokens ?? input + output
    };
}
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 Responses invoke response must be a JSON object.');
    }
    const response = data;
    const textParts = [];
    const toolCalls = [];
    for (const item of response.output ?? []) {
        if (item.type === 'message' && 'content' in item) {
            for (const content of item.content ?? []) {
                if (content.type === 'output_text' && typeof content.text === 'string') {
                    textParts.push(content.text);
                }
                else if (content.type === 'refusal' && typeof content.refusal === 'string') {
                    textParts.push(content.refusal);
                }
            }
        }
        else if (item.type === 'function_call' && 'arguments' in item) {
            toolCalls.push({
                arguments: item.arguments ?? '{}',
                id: item.call_id ?? item.id ?? '',
                name: item.name ?? ''
            });
        }
    }
    return {
        stopReason: response.status,
        text: textParts.length > 0 ? textParts.join('') : undefined,
        toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
        usageStatus: response.usage
            ? ShortcutXLProviderUsageStatus.UsageAvailable
            : ShortcutXLProviderUsageStatus.Unavailable
    };
}
export async function streamAiOpenAIResponsesModelRequest(request, options) {
    if (options.fetchImpl && options.fetchImpl !== fetch) {
        throw new Error('owned AI OpenAI Responses streaming does not support per-call fetchImpl.');
    }
    const model = buildGatewayAiModel(request, options.baseUrl);
    const stream = streamSimpleOpenAIResponses(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.OpenAIResponses,
        usage: toProviderUsageFromAi(result?.usage)
    });
}
export async function invokeOpenAIResponsesModel(request, options) {
    const fetchImpl = options.fetchImpl ?? fetch;
    let response;
    try {
        response = await fetchImpl(`${normalizeBaseUrl(options.baseUrl)}/v1/responses`, {
            body: JSON.stringify(buildInvokeRequestBody(request, options.baseUrl)),
            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 throwOpenAIResponsesHttpError(response, 'invoke');
    }
    let data;
    try {
        data = (await response.json());
    }
    catch {
        throwInvalidResponse('OpenAI Responses 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.OpenAIResponses,
        usage: toProviderUsage(usage)
    });
    return parsed;
}
//# sourceMappingURL=openai-responses-transport.js.map