import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { ensureBridgeInstructions } from '../bridge-instructions.js';
import { evaluateResponsesHostPolicy } from './responses-host-policy.js';
import { convertBridgeInputToChatMessages } from '../bridge-message-utils.js';
import { createToolCallIdTransformer, enforceToolCallIdStyle, resolveToolCallIdStyle, sanitizeResponsesFunctionName } from '../shared/responses-tool-utils.js';
import { mapChatToolsToBridge } from '../shared/tool-mapping.js';
import { ProviderProtocolError } from '../provider-protocol-error.js';
import { readRuntimeMetadata } from '../runtime-metadata.js';
import { clampResponsesInputItemId } from '../bridge-id-utils.js';
import { isImagePath } from '../media.js';
import { captureReqInboundResponsesContextSnapshotWithNative, mapReqInboundBridgeToolsToChatWithNative } from '../../router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js';
import { buildBridgeHistoryWithNative } from '../../router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js';
// --- Utilities (ported strictly) ---
import { createBridgeActionState, runBridgeActionPipeline } from '../bridge-actions.js';
import { resolveBridgePolicy, resolvePolicyActions } from '../bridge-policies.js';
function isObject(v) {
    return !!v && typeof v === 'object' && !Array.isArray(v);
}
function filterBridgeInputForUpstream(input, options) {
    // Upstream `/v1/responses` create only accepts a subset of input item types.
    // In particular, `type:"reasoning"` entries are output-only artifacts (often
    // captured from previous responses) and OpenAI rejects them with schema errors
    // like `input[N].content: array too long (max 0)`.
    return (Array.isArray(input) ? input : []).flatMap((item) => {
        if (!item || typeof item !== 'object')
            return [];
        if (item.type === 'reasoning') {
            return [];
        }
        // OpenAI `/v1/responses` request schema uses `call_id` for tool outputs.
        // Some internal carriers may set `tool_call_id`; strip it before sending upstream
        // to avoid strict schema errors (e.g. "Unknown parameter: input[N].tool_call_id").
        const clone = { ...item };
        if (options?.allowToolCallId !== true && clone.tool_call_id !== undefined) {
            delete clone.tool_call_id;
        }
        // OpenAI /v1/responses enforces max length (64) on input item id fields.
        // Keep this as the single outbound guardrail for all bridged input item types.
        const normalizedId = clampResponsesInputItemId(clone.id);
        if (normalizedId) {
            clone.id = normalizedId;
        }
        return [clone];
    });
}
// normalizeTools unified in ../args-mapping.ts
// --- Structured self-repair helpers for tool failures (Responses path) ---
const IMAGE_MIME_BY_EXT = {
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.gif': 'image/gif',
    '.webp': 'image/webp',
    '.bmp': 'image/bmp',
    '.svg': 'image/svg+xml',
    '.tif': 'image/tiff',
    '.tiff': 'image/tiff',
    '.ico': 'image/x-icon',
    '.heic': 'image/heic',
    '.jxl': 'image/jxl'
};
function decodeEscapedPathLikeText(input) {
    if (!input || input.indexOf('\\') < 0) {
        return input;
    }
    return input
        .replace(/\\\//g, '/')
        .replace(/\\u([0-9a-fA-F]{4})/g, (_m, hex) => {
        const codepoint = Number.parseInt(hex, 16);
        return Number.isFinite(codepoint) ? String.fromCharCode(codepoint) : '';
    })
        .replace(/\\x([0-9a-fA-F]{2})/g, (_m, hex) => {
        const codepoint = Number.parseInt(hex, 16);
        return Number.isFinite(codepoint) ? String.fromCharCode(codepoint) : '';
    })
        .replace(/\\\\/g, '\\');
}
function normalizeLocalImagePath(candidate) {
    if (!candidate || typeof candidate !== 'string')
        return null;
    let value = decodeEscapedPathLikeText(candidate.trim());
    if (!value)
        return null;
    if (/^https?:\/\//i.test(value))
        return null;
    if (value.toLowerCase().startsWith('file://')) {
        try {
            const parsed = new URL(value);
            value = decodeURIComponent(parsed.pathname || '');
        }
        catch {
            return null;
        }
    }
    if (!value)
        return null;
    if (value.startsWith('~')) {
        value = path.join(os.homedir(), value.slice(1));
    }
    const isAbsolutePath = path.isAbsolute(value);
    const normalized = isAbsolutePath ? path.normalize(value) : path.resolve(process.cwd(), value);
    return isImagePath(normalized) ? normalized : null;
}
function collectLocalImagePathCandidates(text) {
    if (typeof text !== 'string' || !text.trim())
        return [];
    const candidates = new Set();
    const variants = [text, decodeEscapedPathLikeText(text)].filter(Boolean);
    const quotedPathRegex = /(["'`])((?:\\.|(?!\1).)+)\1/g;
    const barePathRegex = /(?:^|[\s(])((?:~|\/|\.\.?\/)[^\s"'`<>]+?\.(?:png|jpg|jpeg|gif|webp|bmp|svg|tiff?|ico|heic|jxl))(?=$|[\s),.;!?])/gi;
    for (const variant of variants) {
        let quotedMatch;
        quotedPathRegex.lastIndex = 0;
        while ((quotedMatch = quotedPathRegex.exec(variant)) !== null) {
            const normalized = normalizeLocalImagePath(quotedMatch[2] || '');
            if (normalized)
                candidates.add(normalized);
        }
        let bareMatch;
        barePathRegex.lastIndex = 0;
        while ((bareMatch = barePathRegex.exec(variant)) !== null) {
            const normalized = normalizeLocalImagePath(bareMatch[1] || '');
            if (normalized)
                candidates.add(normalized);
        }
    }
    return Array.from(candidates);
}
function detectImageMime(filePath) {
    const ext = path.extname(filePath).toLowerCase();
    return IMAGE_MIME_BY_EXT[ext] || 'application/octet-stream';
}
function encodeLocalImageAsDataUrl(filePath) {
    const imageBuffer = fs.readFileSync(filePath);
    const mimeType = detectImageMime(filePath);
    return `data:${mimeType};base64,${imageBuffer.toString('base64')}`;
}
function messageHasImageContent(content) {
    if (!Array.isArray(content))
        return false;
    return content.some((part) => {
        if (!part || typeof part !== 'object')
            return false;
        const type = String(part.type || '').toLowerCase();
        if (type !== 'image_url' && type !== 'input_image' && type !== 'image')
            return false;
        const imageUrl = part.image_url;
        return typeof imageUrl === 'string' || Boolean(imageUrl && typeof imageUrl === 'object' && typeof imageUrl.url === 'string');
    });
}
function appendLocalImageBlockOnLatestUserInput(messages, context) {
    if (!Array.isArray(messages) || !messages.length)
        return;
    let latestUserIndex = -1;
    for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
        const role = String(messages[idx]?.role || '').toLowerCase();
        if (role === 'user') {
            latestUserIndex = idx;
            break;
        }
    }
    if (latestUserIndex < 0)
        return;
    const latestUserMessage = messages[latestUserIndex];
    const originalContent = latestUserMessage.content;
    if (messageHasImageContent(originalContent))
        return;
    const textCandidates = [];
    if (typeof originalContent === 'string' && originalContent.trim().length) {
        textCandidates.push(originalContent);
    }
    else if (Array.isArray(originalContent)) {
        for (const part of originalContent) {
            if (!part || typeof part !== 'object')
                continue;
            const type = String(part.type || '').toLowerCase();
            if ((type === 'text' || type === 'input_text' || type === 'output_text' || type === 'commentary') &&
                typeof part.text === 'string' &&
                part.text.trim().length) {
                textCandidates.push(part.text);
            }
        }
    }
    if (!textCandidates.length)
        return;
    const imagePaths = new Set();
    for (const text of textCandidates) {
        for (const candidate of collectLocalImagePathCandidates(text)) {
            imagePaths.add(candidate);
        }
    }
    if (!imagePaths.size)
        return;
    const normalizedContent = [];
    if (typeof originalContent === 'string') {
        normalizedContent.push({ type: 'text', text: originalContent });
    }
    else if (Array.isArray(originalContent)) {
        for (const part of originalContent) {
            if (part && typeof part === 'object') {
                normalizedContent.push({ ...part });
            }
            else if (typeof part === 'string') {
                normalizedContent.push({ type: 'text', text: part });
            }
        }
    }
    const unreadableImageNotices = [];
    for (const imagePath of imagePaths) {
        let dataUrl = '';
        try {
            dataUrl = encodeLocalImageAsDataUrl(imagePath);
        }
        catch (error) {
            const reason = error instanceof Error
                ? `${error.code ?? 'READ_FAILED'}: ${error.message}`
                : String(error ?? 'READ_FAILED');
            unreadableImageNotices.push(`[local_image_unreadable] 文件不可读，已跳过该图片路径: ${imagePath} (${reason})`);
            continue;
        }
        normalizedContent.push({ type: 'image_url', image_url: { url: dataUrl } });
    }
    if (unreadableImageNotices.length) {
        normalizedContent.push({
            type: 'text',
            text: unreadableImageNotices.join('\n')
        });
    }
    if (!messageHasImageContent(normalizedContent) && !unreadableImageNotices.length) {
        return;
    }
    latestUserMessage.content = normalizedContent;
}
// NOTE: 自修复提示已移除（统一标准：不做模糊兜底）。
// --- Public bridge functions ---
export function captureResponsesContext(payload, dto) {
    const preservedInput = cloneBridgeEntries(payload.input);
    ensureBridgeInstructions(payload);
    const requestForCapture = {
        ...payload,
        ...(preservedInput ? { input: preservedInput } : {})
    };
    const captured = captureReqInboundResponsesContextSnapshotWithNative({
        rawRequest: requestForCapture,
        requestId: dto?.route?.requestId,
        toolCallIdStyle: payload?.toolCallIdStyle ?? payload?.metadata?.toolCallIdStyle
    });
    const instructionReasoning = payload?.__rcc_reasoning_instructions;
    if (instructionReasoning !== undefined && instructionReasoning !== null) {
        const segments = Array.isArray(instructionReasoning) ? instructionReasoning : [instructionReasoning];
        const normalized = segments
            .map((entry) => (typeof entry === 'string' ? entry.trim() : String(entry ?? '').trim()))
            .filter((entry) => entry.length > 0);
        if (normalized.length) {
            captured.__rcc_reasoning_instructions_segments = normalized;
        }
    }
    if (preservedInput) {
        captured.input = preservedInput;
    }
    if (!captured.systemInstruction && typeof payload.instructions === 'string' && payload.instructions.trim().length) {
        captured.systemInstruction = payload.instructions;
    }
    return captured;
}
export function buildChatRequestFromResponses(payload, context) {
    // V3: 对 Responses 路径仅做“形状转换”，不做参数解析/修复。
    // 将顶层 { type,name,description,parameters,strict } 归一为 OpenAI Chat tools 形状：
    // { type:'function', function:{ name,description,parameters,strict? } }
    const toolsNormalized = Array.isArray(context.toolsNormalized) && context.toolsNormalized.length
        ? cloneBridgeEntries(context.toolsNormalized)
        : mapReqInboundBridgeToolsToChatWithNative(payload.tools);
    // 不在 Responses 路径进行 MCP 工具注入；统一由 Chat 后半段治理注入
    let messages = convertBridgeInputToChatMessages({
        input: context.input,
        tools: toolsNormalized,
        normalizeFunctionName: sanitizeResponsesFunctionName,
        toolResultFallbackText: 'Command succeeded (no output).'
    });
    try {
        const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-responses', moduleType: 'openai-responses' });
        const policyActions = resolvePolicyActions(bridgePolicy, 'request_inbound');
        if (policyActions?.length) {
            const actionState = createBridgeActionState({
                messages,
                requiredAction: typeof payload?.required_action === 'object' ? payload.required_action : undefined,
                rawRequest: payload
            });
            runBridgeActionPipeline({
                stage: 'request_inbound',
                actions: policyActions,
                protocol: bridgePolicy?.protocol ?? 'openai-responses',
                moduleType: bridgePolicy?.moduleType ?? 'openai-responses',
                requestId: context.requestId,
                state: actionState
            });
            messages = actionState.messages;
        }
    }
    catch {
        // Policy application is best-effort; fall back to raw mapping on failure.
    }
    if (Array.isArray(context.originalSystemMessages) && context.originalSystemMessages.length) {
        const preservedSystems = context.originalSystemMessages
            .map(text => ({ role: 'system', content: text }))
            .filter(message => typeof message.content === 'string');
        if (preservedSystems.length) {
            const nonSystemMessages = messages.filter((msg) => String(msg?.role).toLowerCase() !== 'system');
            messages = [...preservedSystems, ...nonSystemMessages];
        }
    }
    appendLocalImageBlockOnLatestUserInput(messages, context);
    // 不在 Responses 路径做工具治理；统一在 Chat 后半段处理
    // No system tips for MCP on OpenAI Responses path (avoid leaking tool names)
    if (!messages.length) {
        throw new ProviderProtocolError('Responses payload produced no chat messages', {
            code: 'MALFORMED_REQUEST',
            protocol: 'openai-responses',
            providerType: 'responses',
            details: {
                context: 'buildChatRequestFromResponses',
                inputLength: Array.isArray(context.input) ? context.input.length : undefined,
                requestId: context.requestId
            }
        });
    }
    // 如果只有 system 消息且无 user/assistant/tool，后续桥接 action 会从 instructions 注入兜底 user 消息
    const result = { model: payload.model, messages };
    if (typeof payload.stream === 'boolean') {
        result.stream = payload.stream;
    }
    for (const key of ['top_p', 'tool_choice', 'parallel_tool_calls', 'user', 'logit_bias', 'seed', 'response_format']) {
        if (payload[key] !== undefined)
            result[key] = payload[key];
    }
    if (typeof payload.max_tokens === 'number' && payload.max_output_tokens === undefined) {
        payload.max_output_tokens = payload.max_tokens;
    }
    if (payload.max_output_tokens !== undefined) {
        result.max_output_tokens = payload.max_output_tokens;
    }
    if (Array.isArray(toolsNormalized) && toolsNormalized.length)
        result.tools = toolsNormalized;
    return { request: result, toolsNormalized };
}
/**
 * Chat 请求 → Responses 请求（非流），用于 V3 process=chat 且 providerWire=responses 的请求编码。
 *
 * 设计目标：
 *  - 保留 model / tools / tool_choice / parallel_tool_calls 等字段；
 *  - 将 system 消息折叠到 instructions；
 *  - 将 user/assistant/tool 消息编码为 input[] 中的 message 块，使得 mapResponsesInputToChat 能够还原为等价 Chat 请求。
 */
function normalizeBridgeHistory(seed) {
    if (!seed || typeof seed !== 'object') {
        return undefined;
    }
    const record = seed;
    if (!Array.isArray(record.input)) {
        return undefined;
    }
    const systemMessages = Array.isArray(record.originalSystemMessages)
        ? record.originalSystemMessages.filter((entry) => typeof entry === 'string' && entry.trim().length > 0)
        : [];
    return {
        input: record.input,
        combinedSystemInstruction: typeof record.combinedSystemInstruction === 'string' && record.combinedSystemInstruction.trim().length
            ? record.combinedSystemInstruction
            : undefined,
        latestUserInstruction: typeof record.latestUserInstruction === 'string' && record.latestUserInstruction.trim().length
            ? record.latestUserInstruction
            : undefined,
        originalSystemMessages: systemMessages
    };
}
function stripRoutingTagsFromText(text) {
    if (typeof text !== 'string') {
        return typeof text === 'string' ? text : String(text ?? '');
    }
    // NOTE: Do not strip <**...**> here. Routing instructions must reach the
    // virtual router so allowlist/sticky directives can be applied. The router
    // will clean them from messages after parsing.
    return text;
}
function sanitizeBridgeHistory(history) {
    if (!history || typeof history !== 'object') {
        return history;
    }
    if (Array.isArray(history.input)) {
        for (const entry of history.input) {
            if (!entry || typeof entry !== 'object')
                continue;
            const blocks = entry.content;
            if (!Array.isArray(blocks))
                continue;
            for (const block of blocks) {
                if (!block || typeof block !== 'object')
                    continue;
                const record = block;
                if (typeof record.text === 'string') {
                    record.text = stripRoutingTagsFromText(record.text);
                }
            }
        }
    }
    if (typeof history.combinedSystemInstruction === 'string') {
        history.combinedSystemInstruction = stripRoutingTagsFromText(history.combinedSystemInstruction);
    }
    if (typeof history.latestUserInstruction === 'string') {
        history.latestUserInstruction = stripRoutingTagsFromText(history.latestUserInstruction);
    }
    if (Array.isArray(history.originalSystemMessages)) {
        history.originalSystemMessages = history.originalSystemMessages.map((msg) => stripRoutingTagsFromText(msg));
    }
    return history;
}
function mergeResponsesTools(originalTools, fromChat) {
    const result = [];
    const byKey = new Map();
    const norm = (value) => {
        if (typeof value !== 'string')
            return undefined;
        const trimmed = value.trim();
        return trimmed.length ? trimmed.toLowerCase() : undefined;
    };
    const register = (tool) => {
        const fn = tool.function ?? tool.function;
        const baseName = norm(fn?.name ?? tool.name);
        const typeName = norm(tool.type);
        const key = baseName || typeName;
        if (!key || byKey.has(key))
            return;
        byKey.set(key, tool);
        result.push(tool);
    };
    if (Array.isArray(fromChat)) {
        for (const t of fromChat) {
            if (t && typeof t === 'object') {
                register(t);
            }
        }
    }
    if (Array.isArray(originalTools)) {
        for (const t of originalTools) {
            if (!t || typeof t !== 'object')
                continue;
            const typeName = norm(t.type);
            const isWebSearch = typeName === 'web_search' || (typeof typeName === 'string' && typeName.startsWith('web_search'));
            if (!isWebSearch) {
                // 目前仅恢复原始载荷中的 builtin web_search 工具，其它非函数工具保持忽略，避免意外改变行为。
                continue;
            }
            register(t);
        }
    }
    return result.length ? result : undefined;
}
export function buildResponsesRequestFromChat(payload, ctx, extras) {
    const chat = unwrapData(payload);
    const out = {};
    const forceWebSearch = (() => {
        if (!ctx || typeof ctx !== 'object') {
            return false;
        }
        const metaCarrier = ctx.metadata && typeof ctx.metadata === 'object'
            ? ctx.metadata
            : undefined;
        const rt = readRuntimeMetadata(metaCarrier ?? null);
        if (rt && typeof rt === 'object') {
            if (rt.forceWebSearch === true) {
                return true;
            }
            const webSearch = rt.webSearch;
            if (webSearch && typeof webSearch === 'object' && webSearch.force === true) {
                return true;
            }
        }
        return false;
    })();
    // 基本字段
    out.model = chat.model;
    // tools: 反向映射为 ResponsesToolDefinition 形状
    const chatTools = Array.isArray(chat.tools) ? chat.tools : [];
    // 对于 openai-responses upstream，内建 web_search 由官方服务器处理。
    // Chat 侧注入的 server-side web_search 函数（带 engine/query/recency/count）
    // 仅用于非 Responses provider 的 server-tool 回环；在这里构造真正的
    // `/v1/responses` 请求时，需要：
    //   1) 不再把函数版 web_search 透传上游；
    //   2) 若检测到 Chat 侧启用了 web_search 且原始请求中没有 builtin web_search，
    //      则补一个 `{ type: "web_search" }` 内建工具给 OpenAI Responses。
    const hasServerSideWebSearch = !forceWebSearch && chatTools.some((tool) => {
        const fn = tool && typeof tool === 'object' ? tool.function : undefined;
        const name = typeof fn?.name === 'string' ? fn.name.trim().toLowerCase() : '';
        return name === 'web_search';
    });
    const toolsForBridge = hasServerSideWebSearch
        ? chatTools.filter((tool) => {
            const fn = tool && typeof tool === 'object' ? tool.function : undefined;
            const name = typeof fn?.name === 'string' ? fn.name.trim().toLowerCase() : '';
            return name !== 'web_search';
        })
        : chatTools;
    const responsesToolsFromChat = mapChatToolsToBridge(toolsForBridge, {
        sanitizeName: sanitizeResponsesFunctionName
    });
    // Prefer Chat‑normalized tools, but if the original Responses payload carried
    // non‑function tools (such as builtin `web_search`), merge them back so that
    // upstream `/v1/responses` providers see their original tool definitions.
    const originalTools = Array.isArray(ctx?.toolsRaw) ? ctx.toolsRaw : undefined;
    let mergedTools = mergeResponsesTools(originalTools, responsesToolsFromChat);
    if (hasServerSideWebSearch) {
        const normalizeType = (value) => typeof value === 'string' ? value.trim().toLowerCase() : '';
        const hasBuiltinWebSearch = (mergedTools && mergedTools.some((tool) => normalizeType(tool.type) === 'web_search')) ||
            (originalTools && originalTools.some((tool) => normalizeType(tool.type) === 'web_search'));
        if (!hasBuiltinWebSearch) {
            const injected = { type: 'web_search' };
            mergedTools = mergedTools ? [...mergedTools, injected] : [injected];
        }
    }
    if (mergedTools?.length) {
        out.tools = mergedTools;
    }
    const passthroughKeys = [
        'temperature',
        'tool_choice',
        'parallel_tool_calls',
        'response_format',
        'user',
        'top_p',
        'prompt_cache_key',
        'reasoning',
        'logit_bias',
        'seed'
    ];
    for (const key of passthroughKeys) {
        if (chat[key] !== undefined)
            out[key] = chat[key];
    }
    let messages = Array.isArray(chat.messages) ? chat.messages : [];
    let bridgeMetadata;
    try {
        const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-responses', moduleType: 'openai-responses' });
        const policyActions = resolvePolicyActions(bridgePolicy, 'request_outbound');
        if (policyActions?.length) {
            const actionState = createBridgeActionState({
                messages,
                rawRequest: chat
            });
            runBridgeActionPipeline({
                stage: 'request_outbound',
                actions: policyActions,
                protocol: bridgePolicy?.protocol ?? 'openai-responses',
                moduleType: bridgePolicy?.moduleType ?? 'openai-responses',
                requestId: ctx?.requestId,
                state: actionState
            });
            messages = actionState.messages;
            if (actionState.metadata && Object.keys(actionState.metadata).length) {
                bridgeMetadata = actionState.metadata;
            }
        }
    }
    catch {
        // ignore policy errors
    }
    const envelopeMetadata = ctx?.metadata && typeof ctx.metadata === 'object' ? ctx.metadata : undefined;
    const metadataExtraFields = extractMetadataExtraFields(envelopeMetadata);
    const contextToolCallIdStyle = readToolCallIdStyleFromContext(ctx);
    const envelopeToolCallIdStyle = resolveToolCallIdStyle(envelopeMetadata);
    const requestMetadata = chat && typeof chat === 'object' && chat.metadata && typeof chat.metadata === 'object'
        ? chat.metadata
        : undefined;
    const routeToolCallIdStyle = requestMetadata
        ? normalizeToolCallIdStyleCandidate(requestMetadata.toolCallIdStyle)
        : undefined;
    // Route-selected toolCallIdStyle must win over captured context to prevent cross-provider leakage
    // (e.g. LM Studio "preserve" contaminating OpenAI "fc").
    const toolCallIdStyle = routeToolCallIdStyle ?? envelopeToolCallIdStyle ?? contextToolCallIdStyle;
    const fallbackHistory = ctx?.input && Array.isArray(ctx.input)
        ? {
            input: ctx.input,
            originalSystemMessages: ctx.originalSystemMessages,
            combinedSystemInstruction: ctx.systemInstruction
        }
        : undefined;
    const historySeed = sanitizeBridgeHistory(normalizeBridgeHistory(extras?.bridgeHistory)) ??
        sanitizeBridgeHistory(normalizeBridgeHistory(fallbackHistory)) ??
        sanitizeBridgeHistory(normalizeBridgeHistory(bridgeMetadata?.bridgeHistory)) ??
        sanitizeBridgeHistory(normalizeBridgeHistory(envelopeMetadata?.bridgeHistory)) ??
        sanitizeBridgeHistory(fallbackHistory);
    const history = historySeed ??
        sanitizeBridgeHistory(buildBridgeHistoryWithNative({
            messages,
            tools: Array.isArray(out.tools) ? out.tools : undefined
        }));
    const callIdTransformer = createToolCallIdTransformer(toolCallIdStyle);
    if (callIdTransformer) {
        enforceToolCallIdStyle(history.input, callIdTransformer);
    }
    const { input, combinedSystemInstruction, originalSystemMessages } = history;
    const instructionCandidates = [
        typeof ctx?.systemInstruction === 'string' && ctx.systemInstruction.trim().length ? ctx.systemInstruction.trim() : undefined,
        typeof extras?.systemInstruction === 'string' && extras.systemInstruction.trim().length ? extras.systemInstruction.trim() : undefined,
        typeof envelopeMetadata?.systemInstruction === 'string' && envelopeMetadata.systemInstruction.trim().length ? envelopeMetadata.systemInstruction.trim() : undefined,
        combinedSystemInstruction && combinedSystemInstruction.length > 0 ? combinedSystemInstruction : undefined
    ];
    let resolvedInstruction;
    for (const candidate of instructionCandidates) {
        if (candidate && candidate.length) {
            resolvedInstruction = candidate;
            break;
        }
    }
    if (resolvedInstruction && resolvedInstruction.length) {
        const reasoningSegments = (() => {
            if (!ctx || typeof ctx !== 'object')
                return [];
            const ctxAny = ctx;
            const raw = ctxAny.__rcc_reasoning_instructions_segments;
            if (!raw)
                return [];
            const segments = Array.isArray(raw) ? raw : [raw];
            return segments
                .map((entry) => (typeof entry === 'string' ? entry.trim() : String(entry ?? '').trim()))
                .filter((entry) => entry.length > 0);
        })();
        if (reasoningSegments.length) {
            const combined = `${reasoningSegments.join('\n')}\n${resolvedInstruction}`.trim();
            out.instructions = combined;
            out.instructions_is_raw = true;
        }
        else {
            out.instructions = resolvedInstruction;
        }
    }
    // 不追加 metadata，以便 roundtrip 与原始 payload 对齐；系统提示直接写入 instructions。
    const upstreamInput = filterBridgeInputForUpstream(input, { allowToolCallId: toolCallIdStyle === 'preserve' });
    if (upstreamInput.length) {
        out.input = upstreamInput;
    }
    const streamFromChat = typeof chat.stream === 'boolean' ? chat.stream : undefined;
    const streamFromParameters = chat?.parameters && typeof chat.parameters?.stream === 'boolean'
        ? chat.parameters.stream
        : undefined;
    const resolvedStream = typeof ctx?.stream === 'boolean'
        ? ctx.stream
        : (streamFromChat !== undefined ? streamFromChat : streamFromParameters);
    if (resolvedStream === true) {
        out.stream = true;
    }
    else if (resolvedStream === false) {
        out.stream = false;
    }
    if (ctx?.include !== undefined && out.include === undefined) {
        out.include = ctx.include;
    }
    else if (metadataExtraFields?.include !== undefined && out.include === undefined) {
        out.include = metadataExtraFields.include;
    }
    const stripHostFields = shouldStripHostManagedFields(ctx);
    if (stripHostFields) {
        delete out.store;
    }
    else if (ctx?.store !== undefined && out.store === undefined) {
        out.store = ctx.store;
    }
    else if (metadataExtraFields?.store !== undefined && out.store === undefined) {
        out.store = metadataExtraFields.store;
    }
    else if (out.store === undefined) {
        // Chat 入口无 store 概念，但 Responses provider 仍要求显式声明。
        out.store = false;
    }
    if (ctx?.toolChoice !== undefined && out.tool_choice === undefined) {
        out.tool_choice = ctx.toolChoice;
    }
    else if (metadataExtraFields?.tool_choice !== undefined && out.tool_choice === undefined) {
        out.tool_choice = metadataExtraFields.tool_choice;
    }
    if (typeof ctx?.parallelToolCalls === 'boolean' && out.parallel_tool_calls === undefined) {
        out.parallel_tool_calls = ctx.parallelToolCalls;
    }
    else if (typeof metadataExtraFields?.parallel_tool_calls === 'boolean' && out.parallel_tool_calls === undefined) {
        out.parallel_tool_calls = metadataExtraFields.parallel_tool_calls;
    }
    if (ctx?.responseFormat !== undefined && out.response_format === undefined) {
        out.response_format = ctx.responseFormat;
    }
    else if (metadataExtraFields?.response_format !== undefined && out.response_format === undefined) {
        out.response_format = metadataExtraFields.response_format;
    }
    if (ctx?.serviceTier !== undefined && out.service_tier === undefined) {
        out.service_tier = ctx.serviceTier;
    }
    else if (metadataExtraFields?.service_tier !== undefined && out.service_tier === undefined) {
        out.service_tier = metadataExtraFields.service_tier;
    }
    if (ctx?.truncation !== undefined && out.truncation === undefined) {
        out.truncation = ctx.truncation;
    }
    else if (metadataExtraFields?.truncation !== undefined && out.truncation === undefined) {
        out.truncation = metadataExtraFields.truncation;
    }
    if (ctx?.metadata && Object.keys(ctx.metadata).length) {
        out.metadata = { ...ctx.metadata };
    }
    else if (isPlainObject(metadataExtraFields?.metadata)) {
        out.metadata = { ...metadataExtraFields.metadata };
    }
    // Some upstream `/v1/responses` providers reject an unknown top-level `parameters` object
    // (e.g. HTTP 400: Unsupported parameter: parameters).
    // We accept `parameters` as an internal carrier (capturedChatRequest.parameters, followups),
    // but must flatten supported fields into the top-level request shape before sending upstream.
    const parametersCandidate = (ctx?.parameters && Object.keys(ctx.parameters).length ? { ...ctx.parameters } : undefined) ??
        (chat.parameters && typeof chat.parameters === 'object' && !Array.isArray(chat.parameters) ? { ...chat.parameters } : undefined) ??
        (isPlainObject(metadataExtraFields?.parameters) ? { ...metadataExtraFields.parameters } : undefined);
    if (parametersCandidate) {
        const allowed = new Set([
            // Common OpenAI Responses parameters
            'temperature',
            'top_p',
            'max_output_tokens',
            'seed',
            'logit_bias',
            'user',
            'parallel_tool_calls',
            'tool_choice',
            'response_format',
            'service_tier',
            'truncation',
            'include',
            'store',
            'prompt_cache_key',
            'reasoning',
            'stream'
        ]);
        // Back-compat: StandardizedRequest uses max_tokens; map it to Responses max_output_tokens.
        if (parametersCandidate.max_output_tokens === undefined && parametersCandidate.max_tokens !== undefined) {
            parametersCandidate.max_output_tokens = parametersCandidate.max_tokens;
        }
        for (const [key, value] of Object.entries(parametersCandidate)) {
            if (!allowed.has(key))
                continue;
            if (key === 'stream')
                continue; // handled by resolvedStream above
            if (out[key] === undefined) {
                out[key] = value;
            }
        }
    }
    ensureBridgeInstructions(out);
    return { request: out, originalSystemMessages };
}
function readToolCallIdStyleFromContext(ctx) {
    if (!ctx) {
        return undefined;
    }
    const direct = normalizeToolCallIdStyleCandidate(ctx.toolCallIdStyle);
    if (direct) {
        return direct;
    }
    if (ctx.metadata && typeof ctx.metadata === 'object') {
        const fromMetadata = normalizeToolCallIdStyleCandidate(ctx.metadata.toolCallIdStyle);
        if (fromMetadata) {
            return fromMetadata;
        }
    }
    return undefined;
}
function normalizeToolCallIdStyleCandidate(candidate) {
    if (typeof candidate !== 'string') {
        return undefined;
    }
    const normalized = candidate.trim().toLowerCase();
    if (normalized === 'fc') {
        return 'fc';
    }
    if (normalized === 'preserve') {
        return 'preserve';
    }
    return undefined;
}
function cloneBridgeEntries(entries) {
    if (!Array.isArray(entries)) {
        return undefined;
    }
    return entries.map((entry) => {
        if (!entry || typeof entry !== 'object') {
            return entry;
        }
        try {
            return JSON.parse(JSON.stringify(entry));
        }
        catch {
            return entry;
        }
    });
}
function extractMetadataExtraFields(metadata) {
    if (!metadata) {
        return undefined;
    }
    const extras = metadata.extraFields;
    if (extras && typeof extras === 'object' && !Array.isArray(extras)) {
        return extras;
    }
    return undefined;
}
function isPlainObject(value) {
    return Boolean(value && typeof value === 'object' && !Array.isArray(value));
}
function unwrapData(value) {
    let current = value;
    const seen = new Set();
    while (current && typeof current === 'object' && !Array.isArray(current) && !seen.has(current)) {
        seen.add(current);
        if ('choices' in current || 'message' in current)
            break;
        if ('data' in current && typeof current.data === 'object') {
            current = current.data;
            continue;
        }
        break;
    }
    return current;
}
function resolveSnapshotLookupKey(response, context) {
    if (typeof response?.request_id === 'string') {
        return response.request_id;
    }
    if (typeof context?.requestId === 'string') {
        return context.requestId;
    }
    if (typeof response?.id === 'string') {
        return response.id;
    }
    return undefined;
}
function shouldStripHostManagedFields(context) {
    const result = evaluateResponsesHostPolicy(context, typeof context?.targetProtocol === 'string' ? context?.targetProtocol : undefined);
    return result.shouldStripHostManagedFields;
}
export { buildResponsesPayloadFromChat, extractRequestIdFromResponse } from './responses-openai-bridge/response-payload.js';
export { buildChatResponseFromResponses } from '../shared/responses-response-utils.js';
// (imports moved to top)
