"use strict";
/**
 * Module: llm/gen/openai-compatible
 *
 * OpenAI Chat Completions compatible integration implementing streaming and batch generation.
 *
 * Rationale:
 * - Many "OpenAI-compatible" providers implement the Chat Completions API but not the newer
 *   Responses API. Dominds' `apiType: openai` uses the Responses API; this generator targets
 *   chat-completions-only providers and explicit provider quirks built on that transport shape.
 * - Isolation principle: this wrapper owns the `model_params.openai-compatible.*` namespace and
 *   must not inherit OpenAI Responses or Codex-specific request meanings.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenAiCompatibleGen = void 0;
exports.resolveOpenAiCompatibleToolChoice = resolveOpenAiCompatibleToolChoice;
exports.buildOpenAiCompatibleExtraParamsForTest = buildOpenAiCompatibleExtraParamsForTest;
exports.wrapOpenAiCompatibleRejectedRequestErrorForTest = wrapOpenAiCompatibleRejectedRequestErrorForTest;
exports.buildOpenAiCompatibleRequestMessagesWrapper = buildOpenAiCompatibleRequestMessagesWrapper;
exports.consumeOpenAiCompatibleChatCompletionStreamForTest = consumeOpenAiCompatibleChatCompletionStreamForTest;
exports.chatCompletionToChatMessagesForTest = chatCompletionToChatMessagesForTest;
exports.chatCompletionToBatchOutputsForTest = chatCompletionToBatchOutputsForTest;
const events_1 = require("events");
const fs_1 = require("fs");
const promises_1 = __importDefault(require("fs/promises"));
const openai_1 = __importDefault(require("openai"));
const path_1 = __importDefault(require("path"));
const log_1 = require("../../log");
const i18n_text_1 = require("../../runtime/i18n-text");
const work_language_1 = require("../../runtime/work-language");
const dominds_running_version_1 = require("../../server/dominds-running-version");
const api_quirks_1 = require("../api-quirks");
const gen_1 = require("../gen");
const stop_reason_i18n_1 = require("../stop-reason-i18n");
const artifacts_1 = require("./artifacts");
const failure_classifier_1 = require("./failure-classifier");
const tool_call_context_1 = require("./tool-call-context");
const tool_output_limit_1 = require("./tool-output-limit");
const tool_result_image_ingest_1 = require("./tool-result-image-ingest");
const log = (0, log_1.createLogger)('llm/openai-compatible');
const OPENAI_COMPAT_CAPTURE_SSE_ENV = 'DOMINDS_OPENAI_COMPAT_CAPTURE_SSE';
const OPENAI_COMPAT_CAPTURE_DIR_ENV = 'DOMINDS_OPENAI_COMPAT_CAPTURE_DIR';
const OPENAI_COMPAT_REJECTED_DIR_ENV = 'DOMINDS_OPENAI_COMPAT_REJECTED_DIR';
const OPENAI_COMPATIBLE_MALFORMED_BATCH_TOOL_CALL_ERROR_CODE = 'OPENAI_COMPATIBLE_MALFORMED_BATCH_TOOL_CALL';
const OPENAI_COMPATIBLE_REJECTED_REQUEST_ERROR_CODE = 'OPENAI_COMPATIBLE_REJECTED_REQUEST';
const OPENAI_COMPATIBLE_REASONING_DETAILS_PROVIDER_DATA_KEY = 'openai_compatible_reasoning_details';
const KIMI_CODE_REASONING_EFFORTS = new Set(['low', 'medium', 'high']);
const KIMI_CLI_CLOAK_API_QUIRK = 'kimi-cli-cloak';
const DISABLE_ASSISTANT_TOOL_CALL_REASONING_CONTENT_API_QUIRK = 'disable-assistant-tool-call-reasoning-content';
const JSON_SCHEMA_COMBINATOR_KEYS = new Set([
    'anyOf',
    'oneOf',
    'allOf',
    'not',
    'if',
    'then',
    'else',
    '$ref',
]);
const JSON_SCHEMA_BRANCH_ARRAY_KEYS = ['anyOf', 'oneOf', 'allOf'];
const JSON_SCHEMA_OBJECT_KEYS = new Set([
    'properties',
    'additionalProperties',
    'patternProperties',
    'propertyNames',
    'required',
    'minProperties',
    'maxProperties',
]);
const JSON_SCHEMA_ARRAY_KEYS = new Set([
    'items',
    'prefixItems',
    'minItems',
    'maxItems',
    'uniqueItems',
    'contains',
]);
const JSON_SCHEMA_STRING_KEYS = new Set(['minLength', 'maxLength', 'pattern', 'format']);
const JSON_SCHEMA_NUMERIC_KEYS = new Set([
    'minimum',
    'maximum',
    'multipleOf',
    'exclusiveMinimum',
    'exclusiveMaximum',
]);
function resolveOpenAiCompatibleToolChoice(funcTools, requestContext, modelInfo) {
    const requirement = requestContext.toolUseRequirement ?? 'auto';
    if (funcTools.length === 0) {
        if (requirement === 'required') {
            throw new Error(`OpenAI-compatible request invariant violation: toolUseRequirement=required but no tools are available (dialog=${requestContext.dialogSelfId})`);
        }
        return modelInfo?.supports_tool_choice === false ? undefined : 'none';
    }
    if (modelInfo?.supports_tool_choice === false)
        return undefined;
    if (requirement === 'none')
        return 'none';
    if (requirement === 'required')
        return 'required';
    return 'auto';
}
function resolveOpenAiCompatibleRequestTools(funcTools, requestContext) {
    return (requestContext.toolUseRequirement ?? 'auto') === 'none' ? [] : [...funcTools];
}
function resolveOpenAiCompatibleRequestModelInfo(providerConfig, agent, requestContext) {
    const requestModelKey = requestContext.modelKey;
    const modelKey = typeof requestModelKey === 'string' && requestModelKey.trim() !== ''
        ? requestModelKey
        : agent.model;
    if (typeof modelKey !== 'string' || modelKey.trim() === '')
        return undefined;
    return providerConfig.models[modelKey];
}
function resolveOpenAiCompatibleParallelToolCalls(args) {
    if (args.openAiParams.parallel_tool_calls !== undefined) {
        return args.openAiParams.parallel_tool_calls;
    }
    return true;
}
function isOpenAiCompatibleSseCaptureEnabled() {
    const configured = process.env[OPENAI_COMPAT_CAPTURE_SSE_ENV]?.trim().toLowerCase();
    return configured === '1' || configured === 'true' || configured === 'yes' || configured === 'on';
}
function resolveOpenAiCompatibleCaptureDir() {
    const configured = process.env[OPENAI_COMPAT_CAPTURE_DIR_ENV]?.trim();
    if (configured && configured.length > 0)
        return path_1.default.resolve(configured);
    return path_1.default.resolve(process.cwd(), '.dialogs', 'debug', 'openai-compatible-sse');
}
function resolveOpenAiCompatibleRejectedDir() {
    const configured = process.env[OPENAI_COMPAT_REJECTED_DIR_ENV]?.trim();
    if (configured && configured.length > 0)
        return path_1.default.resolve(configured);
    return path_1.default.resolve(process.cwd(), '.dialogs', 'debug', 'openai-compatible-rejected');
}
function sanitizeCapturePathPart(value) {
    const sanitized = value.replace(/[^a-zA-Z0-9_.-]+/g, '_').replace(/^_+|_+$/g, '');
    if (sanitized.length === 0)
        return 'unknown';
    return sanitized.slice(0, 96);
}
function formatRejectedCaptureFailureDetail(error) {
    const raw = error instanceof Error ? error.message : String(error);
    const compact = raw.replace(/\s+/g, ' ').trim();
    if (compact.length === 0)
        return 'unknown debug capture failure';
    return compact.slice(0, 500);
}
function buildOpenAiCompatibleCaptureId(context) {
    const now = new Date().toISOString().replace(/[:.]/g, '-');
    const suffix = Math.random().toString(36).slice(2, 10);
    return [
        now,
        sanitizeCapturePathPart(context.providerKey ?? context.providerName),
        sanitizeCapturePathPart(context.model),
        sanitizeCapturePathPart(context.dialogSelfId),
        context.requestKind,
        suffix,
    ].join('__');
}
function buildOpenAiCompatibleRejectedCaptureId(context) {
    const now = new Date().toISOString().replace(/[:.]/g, '-');
    const suffix = Math.random().toString(36).slice(2, 10);
    return [
        now,
        sanitizeCapturePathPart(context.providerKey ?? context.providerName),
        sanitizeCapturePathPart(context.model),
        sanitizeCapturePathPart(context.dialogSelfId),
        `g${String(context.genseq)}`,
        context.requestKind,
        suffix,
    ].join('__');
}
function redactHttpHeader(name, value) {
    const normalized = name.toLowerCase();
    if (normalized === 'authorization' ||
        normalized === 'x-api-key' ||
        normalized === 'api-key' ||
        normalized.includes('token') ||
        normalized.includes('secret')) {
        return '<redacted>';
    }
    return value;
}
function headersToRedactedRecord(headers) {
    const entries = {};
    headers.forEach((value, name) => {
        entries[name] = redactHttpHeader(name, value);
    });
    return entries;
}
function mergeRequestHeaders(input, init) {
    const headers = new Headers(input instanceof Request ? input.headers : undefined);
    if (init?.headers !== undefined) {
        new Headers(init.headers).forEach((value, name) => headers.set(name, value));
    }
    return headers;
}
function requestMethod(input, init) {
    if (init?.method !== undefined)
        return init.method;
    if (input instanceof Request)
        return input.method;
    return 'GET';
}
function requestUrl(input) {
    if (typeof input === 'string')
        return input;
    if (input instanceof URL)
        return input.toString();
    return input.url;
}
function decodeArrayBufferView(view) {
    return new TextDecoder().decode(view);
}
async function readCaptureRequestBody(input, init) {
    const body = init?.body;
    if (typeof body === 'string')
        return body;
    if (body instanceof URLSearchParams)
        return body.toString();
    if (body instanceof Blob)
        return await body.text();
    if (body instanceof ArrayBuffer)
        return new TextDecoder().decode(body);
    if (ArrayBuffer.isView(body))
        return decodeArrayBufferView(body);
    if (body === null)
        return undefined;
    if (body !== undefined)
        return `[unreadable RequestInit body: ${Object.prototype.toString.call(body)}]`;
    if (input instanceof Request) {
        try {
            return await input.clone().text();
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return `[failed to clone/read Request body: ${message}]`;
        }
    }
    return undefined;
}
async function writeCaptureJson(pathname, value) {
    await promises_1.default.writeFile(pathname, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
}
async function startOpenAiCompatibleCapture(context, input, init) {
    const captureRoot = resolveOpenAiCompatibleCaptureDir();
    const id = buildOpenAiCompatibleCaptureId(context);
    const dir = path_1.default.join(captureRoot, id);
    await promises_1.default.mkdir(dir, { recursive: true });
    const record = {
        id,
        dir,
        metaPath: path_1.default.join(dir, 'meta.json'),
        requestBodyPath: path_1.default.join(dir, 'request-body.txt'),
        responseBodyPath: path_1.default.join(dir, 'response-body.raw'),
        framesPath: path_1.default.join(dir, 'sse-frames.jsonl'),
        summaryPath: path_1.default.join(dir, 'summary.json'),
        context,
    };
    const bodyText = await readCaptureRequestBody(input, init);
    await promises_1.default.writeFile(record.requestBodyPath, bodyText ?? '', 'utf-8');
    await writeCaptureJson(record.metaPath, {
        id: record.id,
        capturedAt: new Date().toISOString(),
        context,
        env: {
            enabledBy: OPENAI_COMPAT_CAPTURE_SSE_ENV,
            captureDirEnv: process.env[OPENAI_COMPAT_CAPTURE_DIR_ENV]
                ? OPENAI_COMPAT_CAPTURE_DIR_ENV
                : undefined,
        },
        request: {
            method: requestMethod(input, init),
            url: requestUrl(input),
            headers: headersToRedactedRecord(mergeRequestHeaders(input, init)),
            bodyPath: record.requestBodyPath,
        },
        response: {
            bodyPath: record.responseBodyPath,
            framesPath: record.framesPath,
            summaryPath: record.summaryPath,
        },
    });
    log.info('OPENAI compatible SSE capture started', undefined, {
        captureDir: record.dir,
        providerKey: context.providerKey,
        providerName: context.providerName,
        model: context.model,
        rootId: context.dialogRootId,
        selfId: context.dialogSelfId,
        requestKind: context.requestKind,
    });
    return record;
}
async function writeOpenAiCompatibleRejectedCapture(args) {
    const captureRoot = resolveOpenAiCompatibleRejectedDir();
    const id = buildOpenAiCompatibleRejectedCaptureId(args.context);
    const dir = path_1.default.join(captureRoot, id);
    await promises_1.default.mkdir(dir, { recursive: true });
    const record = {
        id,
        dir,
        metaPath: path_1.default.join(dir, 'meta.json'),
        requestPayloadPath: path_1.default.join(dir, 'request-payload.json'),
    };
    await writeCaptureJson(record.requestPayloadPath, args.payload);
    await writeCaptureJson(record.metaPath, {
        id,
        capturedAt: new Date().toISOString(),
        context: args.context,
        request: {
            payloadPath: record.requestPayloadPath,
        },
        error: {
            name: args.error instanceof Error ? args.error.name : undefined,
            message: args.context.upstreamMessage,
            status: args.context.status,
            code: args.context.code,
        },
    });
    return record;
}
async function tryWriteOpenAiCompatibleRejectedCapture(args) {
    try {
        return { kind: 'captured', record: await writeOpenAiCompatibleRejectedCapture(args) };
    }
    catch (error) {
        const detail = formatRejectedCaptureFailureDetail(error);
        log.error('OPENAI-COMPATIBLE rejected request debug capture failed', error, {
            providerKey: args.context.providerKey,
            providerName: args.context.providerName,
            model: args.context.model,
            rootId: args.context.dialogRootId,
            selfId: args.context.dialogSelfId,
            genseq: args.context.genseq,
            requestKind: args.context.requestKind,
        });
        return { kind: 'capture_failed', detail };
    }
}
function parseSseFrameData(frame) {
    const lines = frame.split(/\r?\n/);
    const eventLine = lines.find((line) => line.startsWith('event:'));
    const eventName = eventLine ? eventLine.slice('event:'.length).trim() : '';
    const dataLines = lines
        .filter((line) => line.startsWith('data:'))
        .map((line) => (line.startsWith('data: ') ? line.slice(6) : line.slice(5)));
    if (dataLines.length === 0)
        return { eventName, data: undefined };
    return { eventName, data: dataLines.join('\n') };
}
function parseSseFrameJson(frame) {
    const { data } = parseSseFrameData(frame);
    if (data === undefined)
        return { kind: 'no_data' };
    if (data === '[DONE]')
        return { kind: 'done' };
    try {
        JSON.parse(data);
        return { kind: 'json_ok' };
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        return { kind: 'invalid_json', message, data };
    }
}
function describeCaptureError(error) {
    return error instanceof Error ? error.message : String(error);
}
function buildSseFrameLogLine(frame, state) {
    const { eventName, data } = parseSseFrameData(frame);
    const result = parseSseFrameJson(frame);
    if (result.kind === 'done')
        state.doneSeen = true;
    if (result.kind === 'json_ok')
        state.jsonFrameCount += 1;
    if (result.kind === 'invalid_json') {
        state.invalidJsonFrameCount += 1;
        state.invalidFrames.push({
            frameIndex: state.frameCount,
            eventName,
            message: result.message ?? 'unknown parse error',
            data: result.data ?? '',
        });
    }
    return `${JSON.stringify({
        frameIndex: state.frameCount,
        eventName,
        dataBytes: data === undefined ? 0 : Buffer.byteLength(data),
        parse: result.kind,
        error: result.kind === 'invalid_json' ? result.message : undefined,
        data: result.kind === 'invalid_json' ? result.data : undefined,
    })}\n`;
}
async function writeAndDrain(stream, chunk) {
    if (!stream.write(chunk)) {
        await Promise.race([
            (0, events_1.once)(stream, 'drain'),
            (0, events_1.once)(stream, 'error').then(([error]) => {
                throw error;
            }),
        ]);
    }
}
async function endWriteStream(stream) {
    await new Promise((resolve, reject) => {
        stream.once('error', reject);
        stream.end(resolve);
    });
}
async function cancelReadableStreamReader(reader, context) {
    try {
        await reader.cancel();
    }
    catch (error) {
        log.warn('OPENAI compatible response reader cancel failed during cleanup', error, context);
    }
}
async function captureOpenAiCompatibleResponseBody(record, response) {
    const responseClone = response.clone();
    const rawStream = (0, fs_1.createWriteStream)(record.responseBodyPath);
    const framesStream = (0, fs_1.createWriteStream)(record.framesPath);
    const decoder = new TextDecoder();
    const parseAsSse = record.context.requestKind === 'stream';
    let captureError;
    const state = {
        buffer: '',
        responseBytes: 0,
        frameCount: 0,
        doneSeen: false,
        jsonFrameCount: 0,
        invalidJsonFrameCount: 0,
        invalidFrames: [],
    };
    try {
        if (responseClone.body) {
            const reader = responseClone.body.getReader();
            let done = false;
            try {
                for (;;) {
                    const readResult = await reader.read();
                    if (readResult.done) {
                        done = true;
                        break;
                    }
                    state.responseBytes += readResult.value.byteLength;
                    await writeAndDrain(rawStream, readResult.value);
                    if (parseAsSse) {
                        state.buffer += decoder.decode(readResult.value, { stream: true });
                        for (;;) {
                            const separator = state.buffer.match(/\r?\n\r?\n/);
                            if (!separator || separator.index === undefined)
                                break;
                            const frame = state.buffer.slice(0, separator.index);
                            state.buffer = state.buffer.slice(separator.index + separator[0].length);
                            if (frame.trim().length === 0)
                                continue;
                            state.frameCount += 1;
                            await writeAndDrain(framesStream, buildSseFrameLogLine(frame, state));
                        }
                    }
                }
                if (parseAsSse) {
                    const rest = decoder.decode();
                    if (rest.length > 0)
                        state.buffer += rest;
                    if (state.buffer.trim().length > 0) {
                        state.frameCount += 1;
                        await writeAndDrain(framesStream, buildSseFrameLogLine(state.buffer, state));
                    }
                }
            }
            finally {
                if (!done) {
                    await cancelReadableStreamReader(reader, {
                        captureDir: record.dir,
                        providerKey: record.context.providerKey,
                        model: record.context.model,
                    });
                }
                reader.releaseLock();
            }
        }
    }
    catch (error) {
        captureError = describeCaptureError(error);
    }
    finally {
        await Promise.all([endWriteStream(rawStream), endWriteStream(framesStream)]);
    }
    await writeCaptureJson(record.summaryPath, {
        id: record.id,
        completedAt: new Date().toISOString(),
        status: response.status,
        ok: response.ok,
        statusText: response.statusText,
        requestKind: record.context.requestKind,
        captureError,
        headers: headersToRedactedRecord(response.headers),
        responseBytes: state.responseBytes,
        frameCount: state.frameCount,
        doneSeen: state.doneSeen,
        jsonFrameCount: state.jsonFrameCount,
        invalidJsonFrameCount: state.invalidJsonFrameCount,
        invalidFrames: state.invalidFrames,
    });
    if (state.invalidJsonFrameCount > 0) {
        log.warn('OPENAI compatible SSE capture found invalid JSON data frame', undefined, {
            captureDir: record.dir,
            invalidJsonFrameCount: state.invalidJsonFrameCount,
            invalidFrames: state.invalidFrames,
            providerKey: record.context.providerKey,
            model: record.context.model,
            rootId: record.context.dialogRootId,
            selfId: record.context.dialogSelfId,
        });
    }
    else if (captureError === undefined) {
        log.info('OPENAI compatible SSE capture completed', undefined, {
            captureDir: record.dir,
            responseBytes: state.responseBytes,
            frameCount: state.frameCount,
            doneSeen: state.doneSeen,
            providerKey: record.context.providerKey,
            model: record.context.model,
            rootId: record.context.dialogRootId,
            selfId: record.context.dialogSelfId,
        });
    }
    if (captureError !== undefined) {
        throw new Error(`OPENAI compatible SSE capture failed while reading response clone: ${captureError}`);
    }
}
function buildOpenAiCompatibleCaptureFetch(context) {
    return async (input, init) => {
        const record = await startOpenAiCompatibleCapture(context, input, init);
        try {
            const response = await fetch(input, init);
            void captureOpenAiCompatibleResponseBody(record, response).catch((error) => {
                log.error('OPENAI compatible SSE capture failed while reading response clone', error, {
                    captureDir: record.dir,
                    providerKey: context.providerKey,
                    model: context.model,
                    rootId: context.dialogRootId,
                    selfId: context.dialogSelfId,
                });
            });
            return response;
        }
        catch (error) {
            await writeCaptureJson(record.summaryPath, {
                id: record.id,
                completedAt: new Date().toISOString(),
                fetchError: error instanceof Error ? error.message : String(error),
            });
            throw error;
        }
    };
}
function createOpenAiCompatibleClient(args) {
    const providerKey = typeof args.requestContext.providerKey === 'string' &&
        args.requestContext.providerKey.trim().length > 0
        ? args.requestContext.providerKey
        : args.providerConfig.name;
    const modelKey = typeof args.requestContext.modelKey === 'string' &&
        args.requestContext.modelKey.trim().length > 0
        ? args.requestContext.modelKey
        : (args.agent.model ?? 'unknown');
    const options = {
        apiKey: args.apiKey,
        baseURL: args.providerConfig.baseUrl,
    };
    if (isKimiCliCloakProvider(args.providerConfig)) {
        options.defaultHeaders = {
            'User-Agent': `KimiCLI/Dominds/${dominds_running_version_1.DOMINDS_RUNNING_VERSION}`,
        };
    }
    else if (isKimiCodeProvider(args.providerConfig)) {
        options.defaultHeaders = {
            'User-Agent': `Dominds/${dominds_running_version_1.DOMINDS_RUNNING_VERSION || 'dev'}`,
        };
    }
    if (args.providerConfig.apiType === 'openai-compatible' &&
        isOpenAiCompatibleSseCaptureEnabled()) {
        options.fetch = buildOpenAiCompatibleCaptureFetch({
            providerKey,
            providerName: args.providerConfig.name.trim().length > 0 ? args.providerConfig.name : providerKey,
            model: modelKey,
            dialogRootId: args.requestContext.dialogRootId,
            dialogSelfId: args.requestContext.dialogSelfId,
            requestKind: args.requestKind,
        });
    }
    return new openai_1.default(options);
}
function limitOpenAiCompatibleToolOutputText(text, msg, limitChars) {
    const limited = (0, tool_output_limit_1.truncateProviderToolOutputText)(text, limitChars);
    if (limited.truncated) {
        log.warn('OPENAI-COMPATIBLE tool output truncated before provider request', undefined, {
            callId: msg.id,
            toolName: msg.name,
            originalChars: limited.originalChars,
            limitChars: limited.limitChars,
        });
    }
    return limited.text;
}
function isRecord(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function isJsonPrimitive(value) {
    return (typeof value === 'string' ||
        (typeof value === 'number' && Number.isFinite(value)) ||
        typeof value === 'boolean' ||
        value === null);
}
function isJsonValue(value) {
    if (isJsonPrimitive(value))
        return true;
    if (Array.isArray(value))
        return value.every((item) => isJsonValue(item));
    if (!isRecord(value))
        return false;
    return Object.values(value).every((item) => isJsonValue(item));
}
function isJsonObject(value) {
    return isRecord(value) && Object.values(value).every((item) => isJsonValue(item));
}
function isKimiCodeProvider(providerConfig) {
    return (0, api_quirks_1.normalizeProviderApiQuirks)(providerConfig).has(api_quirks_1.KIMI_CODE_API_QUIRK);
}
function isKimiCliCloakProvider(providerConfig) {
    return (0, api_quirks_1.normalizeProviderApiQuirks)(providerConfig).has(KIMI_CLI_CLOAK_API_QUIRK);
}
function isMiniMaxReasoningDetailsProvider(providerConfig) {
    if (providerConfig === undefined)
        return false;
    return (0, api_quirks_1.normalizeProviderApiQuirks)(providerConfig).has(api_quirks_1.MINIMAX_REASONING_DETAILS_API_QUIRK);
}
function isKimiCodeReasoningEffort(value) {
    return typeof value === 'string' && KIMI_CODE_REASONING_EFFORTS.has(value);
}
function isKimiCodeThinkingMode(value) {
    return (value === 'auto' || value === 'off' || value === 'low' || value === 'medium' || value === 'high');
}
function cloneJsonSchemaValue(value) {
    if (Array.isArray(value))
        return value.map((item) => cloneJsonSchemaValue(item));
    if (isRecord(value)) {
        const out = {};
        for (const [key, child] of Object.entries(value)) {
            out[key] = cloneJsonSchemaValue(child);
        }
        return out;
    }
    return value;
}
function hasAnyOwnKey(value, keys) {
    for (const key of keys) {
        if (Object.prototype.hasOwnProperty.call(value, key))
            return true;
    }
    return false;
}
function inferJsonSchemaTypeFromValues(values) {
    const inferred = new Set();
    for (const value of values) {
        if (typeof value === 'boolean')
            inferred.add('boolean');
        else if (typeof value === 'number')
            inferred.add(Number.isInteger(value) ? 'integer' : 'number');
        else if (typeof value === 'string')
            inferred.add('string');
        else if (value === null)
            inferred.add('null');
        else if (Array.isArray(value))
            inferred.add('array');
        else if (isRecord(value))
            inferred.add('object');
        else
            return 'string';
    }
    if (inferred.size === 1) {
        const only = [...inferred][0];
        return only ?? 'string';
    }
    if (inferred.size === 2 && inferred.has('integer') && inferred.has('number')) {
        return 'number';
    }
    return 'string';
}
function inferJsonSchemaTypeFromStructure(value) {
    if (hasAnyOwnKey(value, JSON_SCHEMA_OBJECT_KEYS))
        return 'object';
    if (hasAnyOwnKey(value, JSON_SCHEMA_ARRAY_KEYS))
        return 'array';
    if (hasAnyOwnKey(value, JSON_SCHEMA_STRING_KEYS))
        return 'string';
    if (hasAnyOwnKey(value, JSON_SCHEMA_NUMERIC_KEYS))
        return 'number';
    return 'string';
}
function normalizeOpenAiCompatibleKimiCodeJsonSchemaProperty(value) {
    if (!isRecord(value))
        return;
    if (!Object.prototype.hasOwnProperty.call(value, 'type') &&
        !hasAnyOwnKey(value, JSON_SCHEMA_COMBINATOR_KEYS)) {
        const enumValues = value.enum;
        if (Array.isArray(enumValues) && enumValues.length > 0) {
            value.type = inferJsonSchemaTypeFromValues(enumValues);
        }
        else if (Object.prototype.hasOwnProperty.call(value, 'const')) {
            value.type = inferJsonSchemaTypeFromValues([value.const]);
        }
        else {
            value.type = inferJsonSchemaTypeFromStructure(value);
        }
    }
    normalizeOpenAiCompatibleKimiCodeJsonSchemaContainer(value);
}
function normalizeOpenAiCompatibleKimiCodeJsonSchemaContainer(value) {
    if (!isRecord(value))
        return;
    const properties = value.properties;
    if (isRecord(properties)) {
        for (const property of Object.values(properties)) {
            normalizeOpenAiCompatibleKimiCodeJsonSchemaProperty(property);
        }
    }
    const items = value.items;
    if (isRecord(items)) {
        normalizeOpenAiCompatibleKimiCodeJsonSchemaProperty(items);
    }
    else if (Array.isArray(items)) {
        for (const item of items) {
            normalizeOpenAiCompatibleKimiCodeJsonSchemaProperty(item);
        }
    }
    const additionalProperties = value.additionalProperties;
    if (isRecord(additionalProperties)) {
        normalizeOpenAiCompatibleKimiCodeJsonSchemaProperty(additionalProperties);
    }
    for (const key of JSON_SCHEMA_BRANCH_ARRAY_KEYS) {
        const branches = value[key];
        if (!Array.isArray(branches))
            continue;
        for (const branch of branches) {
            normalizeOpenAiCompatibleKimiCodeJsonSchemaProperty(branch);
        }
    }
}
function normalizeOpenAiCompatibleKimiCodeJsonSchema(value) {
    const cloned = cloneJsonSchemaValue(value);
    normalizeOpenAiCompatibleKimiCodeJsonSchemaContainer(cloned);
    return cloned;
}
function isLlmRequestContext(value) {
    return (isRecord(value) &&
        typeof value.dialogSelfId === 'string' &&
        typeof value.dialogRootId === 'string' &&
        typeof value.providerKey === 'string' &&
        typeof value.modelKey === 'string');
}
function tryExtractChatUsage(usage) {
    // NOTE: External API payload; a runtime check is unavoidable.
    if (!isRecord(usage))
        return { kind: 'unavailable' };
    const prompt = usage.prompt_tokens;
    const completion = usage.completion_tokens;
    const total = usage.total_tokens;
    if (typeof prompt !== 'number' || typeof completion !== 'number')
        return { kind: 'unavailable' };
    return {
        kind: 'available',
        promptTokens: prompt,
        completionTokens: completion,
        totalTokens: typeof total === 'number' ? total : prompt + completion,
    };
}
function buildChatCompletionResponseFormat(openAiParams) {
    const textFormat = openAiParams.text_format;
    if (textFormat === 'text' || textFormat === 'json_object') {
        return { type: textFormat };
    }
    if (textFormat !== 'json_schema')
        return undefined;
    const schemaName = openAiParams.text_format_json_schema_name?.trim();
    const rawSchema = openAiParams.text_format_json_schema?.trim();
    if (!schemaName || !rawSchema) {
        throw new Error('Invalid openai-compatible text_format=json_schema: text_format_json_schema_name and text_format_json_schema are required.');
    }
    let parsedSchema;
    try {
        parsedSchema = JSON.parse(rawSchema);
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        throw new Error(`Invalid openai-compatible text_format_json_schema: ${message}`);
    }
    if (!isRecord(parsedSchema)) {
        throw new Error('Invalid openai-compatible text_format_json_schema: expected a JSON object at the top level.');
    }
    return {
        type: 'json_schema',
        json_schema: {
            name: schemaName,
            schema: parsedSchema,
            ...(openAiParams.text_format_json_schema_strict !== undefined
                ? { strict: openAiParams.text_format_json_schema_strict }
                : {}),
        },
    };
}
function buildReasoningPayloadFromText(text) {
    if (text.trim().length === 0)
        return undefined;
    return {
        summary: [{ type: 'summary_text', text }],
    };
}
function buildOpenAiCompatibleExtraParams(args) {
    if (args.providerConfig !== undefined && isKimiCodeProvider(args.providerConfig)) {
        return buildKimiCodeOpenAiCompatibleExtraParams(args);
    }
    const model = args.agent.model ?? '';
    const thinking = args.openAiParams.thinking;
    if (typeof thinking === 'string') {
        throw new Error(`Invalid openai-compatible model_params: string thinking mode '${thinking}' requires apiQuirks: ${api_quirks_1.KIMI_CODE_API_QUIRK} for model '${model}'.`);
    }
    const reasoningEffort = args.openAiParams.reasoning_effort;
    const thinkingDisabled = thinking === false || (isRecord(thinking) && thinking.type === 'disabled');
    if (thinkingDisabled && reasoningEffort !== undefined) {
        throw new Error(`Invalid openai-compatible model_params: thinking disabled conflicts with reasoning_effort=${reasoningEffort} for model '${model}'.`);
    }
    const reasoningSplit = args.openAiParams.reasoning_split ??
        (isMiniMaxReasoningDetailsProvider(args.providerConfig) ? true : undefined);
    if (thinking === undefined && reasoningEffort === undefined && reasoningSplit === undefined) {
        return {};
    }
    const thinkingPayload = typeof thinking === 'boolean' ? { type: thinking ? 'enabled' : 'disabled' } : thinking;
    return {
        ...(thinkingPayload !== undefined ? { thinking: thinkingPayload } : {}),
        ...(reasoningEffort !== undefined ? { reasoning_effort: reasoningEffort } : {}),
        ...(reasoningSplit !== undefined ? { reasoning_split: reasoningSplit } : {}),
    };
}
function buildKimiCodeOpenAiCompatibleExtraParams(args) {
    const model = args.agent.model ?? '';
    const thinking = args.openAiParams.thinking;
    const reasoningEffort = args.openAiParams.reasoning_effort;
    const promptCacheKey = args.requestContext?.promptCacheKey?.trim();
    const reasoningSplit = args.openAiParams.reasoning_split ??
        (isMiniMaxReasoningDetailsProvider(args.providerConfig) ? true : undefined);
    if (reasoningEffort !== undefined && !isKimiCodeReasoningEffort(reasoningEffort)) {
        throw new Error(`Invalid Kimi Code openai-compatible model_params: reasoning_effort=${reasoningEffort} is not supported for model '${model}'; expected low|medium|high.`);
    }
    const base = promptCacheKey !== undefined && promptCacheKey.length > 0
        ? { prompt_cache_key: promptCacheKey }
        : {};
    if (reasoningSplit !== undefined)
        base.reasoning_split = reasoningSplit;
    if (thinking === undefined) {
        return {
            ...base,
            ...(reasoningEffort !== undefined
                ? { thinking: { type: 'enabled' }, reasoning_effort: reasoningEffort }
                : {}),
        };
    }
    if (thinking === 'auto' || thinking === 'off') {
        if (reasoningEffort !== undefined) {
            throw new Error(`Invalid Kimi Code openai-compatible model_params: thinking=${thinking} conflicts with reasoning_effort=${reasoningEffort} for model '${model}'.`);
        }
        if (thinking === 'auto')
            return base;
        return {
            ...base,
            thinking: { type: 'disabled' },
        };
    }
    if (isKimiCodeThinkingMode(thinking)) {
        if (reasoningEffort !== undefined && reasoningEffort !== thinking) {
            throw new Error(`Invalid Kimi Code openai-compatible model_params: thinking=${thinking} conflicts with reasoning_effort=${reasoningEffort} for model '${model}'.`);
        }
        return {
            ...base,
            thinking: { type: 'enabled' },
            reasoning_effort: thinking,
        };
    }
    const thinkingDisabled = thinking === false || (isRecord(thinking) && thinking.type === 'disabled');
    if (thinkingDisabled && reasoningEffort !== undefined) {
        throw new Error(`Invalid Kimi Code openai-compatible model_params: thinking disabled conflicts with reasoning_effort=${reasoningEffort} for model '${model}'.`);
    }
    const thinkingPayload = typeof thinking === 'boolean' ? { type: thinking ? 'enabled' : 'disabled' } : thinking;
    return {
        ...base,
        ...(thinkingPayload !== undefined ? { thinking: thinkingPayload } : {}),
        ...(reasoningEffort !== undefined ? { reasoning_effort: reasoningEffort } : {}),
    };
}
async function wrapOpenAiCompatibleRejectedRequestError(args) {
    const status = (0, failure_classifier_1.readErrorStatus)(args.error);
    if (status !== 400)
        return args.error;
    const providerKey = args.requestContext.providerKey ?? args.providerConfig.name;
    const modelKey = args.requestContext.modelKey ?? args.agent.model ?? 'unknown';
    const code = (0, failure_classifier_1.readErrorCode)(args.error);
    const upstreamMessage = args.error instanceof Error ? args.error.message : String(args.error);
    const captureContext = {
        providerKey,
        providerName: args.providerConfig.name.trim().length > 0 ? args.providerConfig.name : providerKey,
        model: modelKey,
        dialogRootId: args.requestContext.dialogRootId,
        dialogSelfId: args.requestContext.dialogSelfId,
        requestKind: args.requestKind,
        genseq: args.genseq,
        status,
        ...(code !== undefined && { code }),
        upstreamMessage,
    };
    const capture = await tryWriteOpenAiCompatibleRejectedCapture({
        context: captureContext,
        payload: args.payload,
        error: args.error,
    });
    const captureMessageLines = capture.kind === 'captured'
        ? [
            `debugPath=${capture.record.dir}`,
            `requestPayloadPath=${capture.record.requestPayloadPath}`,
        ]
        : [`debugCaptureError=${capture.detail}`];
    const message = [
        `OPENAI-compatible provider rejected ${args.requestKind} request with HTTP 400.`,
        `provider=${providerKey}`,
        `model=${modelKey}`,
        `rootId=${args.requestContext.dialogRootId}`,
        `selfId=${args.requestContext.dialogSelfId}`,
        `genseq=${String(args.genseq)}`,
        `upstream=${upstreamMessage}`,
        ...captureMessageLines,
    ].join('\n');
    const wrapped = new Error(message);
    wrapped.name = 'OpenAiCompatibleRejectedRequestError';
    wrapped.cause = args.error;
    wrapped.status = status;
    wrapped.statusCode = status;
    wrapped.code = code ?? OPENAI_COMPATIBLE_REJECTED_REQUEST_ERROR_CODE;
    if (capture.kind === 'captured') {
        wrapped.debugPath = capture.record.dir;
        wrapped.requestPayloadPath = capture.record.requestPayloadPath;
    }
    else {
        wrapped.debugCaptureError = capture.detail;
    }
    return wrapped;
}
function buildOpenAiCompatibleExtraParamsForTest(args) {
    return buildOpenAiCompatibleExtraParams(args);
}
async function wrapOpenAiCompatibleRejectedRequestErrorForTest(args) {
    return await wrapOpenAiCompatibleRejectedRequestError(args);
}
function extractThinkingReasoningText(msg) {
    const fromSummary = msg.reasoning?.summary.map((part) => part.text).join('') ?? '';
    const fromContent = msg.reasoning?.content?.map((part) => part.text).join('') ?? '';
    const combined = `${fromSummary}${fromContent}`;
    return combined.length > 0 ? combined : msg.content;
}
function extractReasoningContentField(value) {
    if (!isRecord(value))
        return undefined;
    const content = value.reasoning_content;
    if (typeof content !== 'string')
        return undefined;
    return content.length > 0 ? content : undefined;
}
function extractReasoningDetailsField(value) {
    if (!isRecord(value))
        return undefined;
    const details = value.reasoning_details;
    if (details === undefined)
        return undefined;
    if (!Array.isArray(details)) {
        throw new Error('Invalid openai-compatible reasoning_details: expected array');
    }
    if (!details.every((item) => isJsonValue(item))) {
        throw new Error('Invalid openai-compatible reasoning_details: expected JSON values');
    }
    if (details.length === 0)
        return undefined;
    return details.map((item) => item);
}
function extractReasoningTextFromDetails(details) {
    let out = '';
    for (const detail of details) {
        if (!isJsonObject(detail))
            continue;
        const text = detail.text;
        if (typeof text === 'string')
            out += text;
    }
    return out.length > 0 ? out : undefined;
}
function appendReasoningDetails(existing, next) {
    return existing === undefined ? next : [...existing, ...next];
}
function buildOpenAiCompatibleReasoningProviderData(reasoningDetails) {
    if (reasoningDetails === undefined)
        return undefined;
    return {
        [OPENAI_COMPATIBLE_REASONING_DETAILS_PROVIDER_DATA_KEY]: reasoningDetails,
    };
}
function extractOpenAiCompatibleReasoningDetailsProviderData(providerData) {
    const value = providerData?.[OPENAI_COMPATIBLE_REASONING_DETAILS_PROVIDER_DATA_KEY];
    if (value === undefined)
        return undefined;
    if (!Array.isArray(value)) {
        throw new Error(`Invalid provider_data.${OPENAI_COMPATIBLE_REASONING_DETAILS_PROVIDER_DATA_KEY}: expected array`);
    }
    if (!value.every((item) => isJsonValue(item))) {
        throw new Error(`Invalid provider_data.${OPENAI_COMPATIBLE_REASONING_DETAILS_PROVIDER_DATA_KEY}: expected JSON values`);
    }
    if (value.length === 0)
        return undefined;
    return value;
}
function attachReasoningFields(message, pending, useReasoningDetails) {
    if (pending === undefined)
        return message;
    if (!isRecord(message) || message.role !== 'assistant')
        return message;
    if (useReasoningDetails && pending.reasoningDetails !== undefined) {
        return {
            ...message,
            reasoning_details: pending.reasoningDetails,
        };
    }
    if (pending.content.length === 0)
        return message;
    return {
        ...message,
        reasoning_content: pending.content,
    };
}
function shouldAttachReasoningContentToAssistantToolCalls(providerConfig) {
    if (providerConfig === undefined)
        return true;
    return !(0, api_quirks_1.normalizeProviderApiQuirks)(providerConfig).has(DISABLE_ASSISTANT_TOOL_CALL_REASONING_CONTENT_API_QUIRK);
}
function funcToolToChatCompletionTool(funcTool, providerConfig) {
    // MCP schemas are passed through to providers. Chat Completions expects a narrower JSON schema
    // shape; runtime compatibility is handled by provider validation + the driver stop policy.
    const rawParameters = funcTool.parameters;
    const parameters = (providerConfig !== undefined && isKimiCodeProvider(providerConfig)
        ? normalizeOpenAiCompatibleKimiCodeJsonSchema(rawParameters)
        : rawParameters);
    const description = (0, i18n_text_1.getTextForLanguage)({ i18n: funcTool.descriptionI18n, fallback: funcTool.description }, (0, work_language_1.getWorkLanguage)());
    return {
        type: 'function',
        function: {
            name: funcTool.name,
            description,
            parameters,
            strict: false,
        },
    };
}
function chatMessageToChatCompletionMessage(msg) {
    switch (msg.type) {
        case 'environment_msg':
        case 'prompting_msg':
        case 'tellask_result_msg':
        case 'tellask_carryover_msg':
            return { role: 'user', content: msg.content };
        case 'transient_guide_msg':
        case 'saying_msg':
        case 'thinking_msg':
            return { role: 'assistant', content: msg.content };
        case 'func_call_msg':
            return {
                role: 'assistant',
                tool_calls: [
                    {
                        id: msg.id,
                        type: 'function',
                        function: { name: msg.name, arguments: msg.arguments },
                    },
                ],
            };
        case 'func_result_msg':
            return { role: 'tool', tool_call_id: msg.id, content: msg.content };
        default: {
            const _exhaustive = msg;
            return _exhaustive;
        }
    }
}
async function userLikeMessageToChatCompletionMessageWithImages(msg, requestContext, providerConfig, allowedImageKeys, onUserImageIngest) {
    const items = msg.contentItems;
    if (!Array.isArray(items) || items.length === 0) {
        return chatMessageToChatCompletionMessage(msg);
    }
    const content = [{ type: 'text', text: msg.content }];
    const supportsImageInput = (0, tool_result_image_ingest_1.resolveModelImageInputSupport)(requestContext.modelKey === undefined
        ? undefined
        : providerConfig?.models[requestContext.modelKey], false);
    for (const [itemIndex, item] of items.entries()) {
        if (item.type === 'input_text') {
            content.push({ type: 'text', text: item.text });
            continue;
        }
        if (item.type === 'input_image') {
            if (!supportsImageInput) {
                if (onUserImageIngest) {
                    await onUserImageIngest((0, tool_result_image_ingest_1.buildUserImageIngest)({
                        requestContext,
                        ...(msg.type === 'prompting_msg' ? { msgId: msg.msgId } : {}),
                        artifact: item.artifact,
                        disposition: 'filtered_model_unsupported',
                        providerPathLabel: 'OpenAI-compatible Chat Completions path',
                    }));
                }
                content.push({
                    type: 'text',
                    text: `[image not sent: current model does not support image input]`,
                });
                continue;
            }
            if (!(0, artifacts_1.isVisionImageMimeType)(item.mimeType)) {
                if (onUserImageIngest) {
                    await onUserImageIngest((0, tool_result_image_ingest_1.buildUserImageIngest)({
                        requestContext,
                        ...(msg.type === 'prompting_msg' ? { msgId: msg.msgId } : {}),
                        artifact: item.artifact,
                        disposition: 'filtered_mime_unsupported',
                        mimeType: item.mimeType,
                        providerPathLabel: 'OpenAI-compatible Chat Completions path',
                    }));
                }
                content.push({
                    type: 'text',
                    text: `[image not sent: unsupported mimeType=${item.mimeType}]`,
                });
                continue;
            }
            if (!allowedImageKeys.has((0, tool_result_image_ingest_1.buildImageBudgetKeyForContentItem)({ msg, itemIndex, artifact: item.artifact }))) {
                if (onUserImageIngest) {
                    await onUserImageIngest((0, tool_result_image_ingest_1.buildUserImageIngest)({
                        requestContext,
                        ...(msg.type === 'prompting_msg' ? { msgId: msg.msgId } : {}),
                        artifact: item.artifact,
                        disposition: 'filtered_size_limit',
                        detail: (0, tool_result_image_ingest_1.buildImageBudgetLimitDetail)({
                            byteLength: item.byteLength,
                            budgetBytes: tool_result_image_ingest_1.OPENAI_COMPATIBLE_TOOL_RESULT_IMAGE_BUDGET_BYTES,
                        }),
                        providerPathLabel: 'OpenAI-compatible Chat Completions path',
                    }));
                }
                content.push({
                    type: 'text',
                    text: `[image not sent: request image budget exceeded bytes=${String(item.byteLength)} budget=${String(tool_result_image_ingest_1.OPENAI_COMPATIBLE_TOOL_RESULT_IMAGE_BUDGET_BYTES)}]`,
                });
                continue;
            }
            const bytesResult = await (0, tool_result_image_ingest_1.readToolResultImageBytesSafe)(item.artifact);
            if (bytesResult.kind === 'missing') {
                if (onUserImageIngest) {
                    await onUserImageIngest((0, tool_result_image_ingest_1.buildUserImageIngest)({
                        requestContext,
                        ...(msg.type === 'prompting_msg' ? { msgId: msg.msgId } : {}),
                        artifact: item.artifact,
                        disposition: 'filtered_missing',
                        providerPathLabel: 'OpenAI-compatible Chat Completions path',
                    }));
                }
                content.push({ type: 'text', text: `[image missing: ${item.artifact.relPath}]` });
                continue;
            }
            if (bytesResult.kind === 'read_failed') {
                if (onUserImageIngest) {
                    await onUserImageIngest((0, tool_result_image_ingest_1.buildUserImageIngest)({
                        requestContext,
                        ...(msg.type === 'prompting_msg' ? { msgId: msg.msgId } : {}),
                        artifact: item.artifact,
                        disposition: 'filtered_read_failed',
                        detail: bytesResult.detail,
                        providerPathLabel: 'OpenAI-compatible Chat Completions path',
                    }));
                }
                content.push({ type: 'text', text: `[image unreadable: ${item.artifact.relPath}]` });
                continue;
            }
            if (onUserImageIngest) {
                await onUserImageIngest((0, tool_result_image_ingest_1.buildUserImageIngest)({
                    requestContext,
                    ...(msg.type === 'prompting_msg' ? { msgId: msg.msgId } : {}),
                    artifact: item.artifact,
                    disposition: 'fed_provider_transformed',
                    providerPathLabel: 'OpenAI-compatible Chat Completions path',
                }));
            }
            content.push({
                type: 'image_url',
                image_url: {
                    url: (0, artifacts_1.bytesToDataUrl)({ mimeType: item.mimeType, bytes: bytesResult.bytes }),
                    detail: 'auto',
                },
            });
            continue;
        }
        const _exhaustive = item;
        throw new Error(`Unsupported user content item: ${String(_exhaustive)}`);
    }
    return { role: 'user', content };
}
async function funcResultToChatCompletionMessages(msg, limitChars, requestContext, providerConfig, allowedImageKeys, onToolResultImageIngest) {
    const items = msg.contentItems;
    if (!Array.isArray(items) || items.length === 0) {
        return [
            {
                role: 'tool',
                tool_call_id: msg.id,
                content: limitOpenAiCompatibleToolOutputText(msg.content, msg, limitChars),
            },
        ];
    }
    const out = [];
    out.push({
        role: 'tool',
        tool_call_id: msg.id,
        content: limitOpenAiCompatibleToolOutputText(msg.content, msg, limitChars),
    });
    const parts = [];
    let sawImageUrl = false;
    let sawAnyImage = false;
    parts.push({
        type: 'text',
        text: `Tool output images (${msg.name}, call_id=${msg.id}):`,
    });
    const modelKey = typeof requestContext.modelKey === 'string' ? requestContext.modelKey.trim() : '';
    const modelInfo = modelKey.length > 0 && providerConfig ? providerConfig.models[modelKey] : undefined;
    const supportsImageInput = (0, tool_result_image_ingest_1.resolveModelImageInputSupport)(modelInfo, false);
    const imageUnsupportedDisposition = modelInfo?.['supports_image_input'] === false
        ? 'filtered_model_unsupported'
        : 'filtered_provider_unsupported';
    for (const [itemIndex, item] of items.entries()) {
        if (item.type === 'input_text')
            continue;
        if (item.type === 'input_image') {
            sawAnyImage = true;
            if (!supportsImageInput) {
                if (onToolResultImageIngest) {
                    await onToolResultImageIngest((0, tool_result_image_ingest_1.buildToolResultImageIngest)({
                        requestContext,
                        toolCallId: msg.id,
                        toolName: msg.name,
                        artifact: item.artifact,
                        disposition: imageUnsupportedDisposition,
                        providerPathLabel: 'OpenAI-compatible path',
                    }));
                }
                parts.push({
                    type: 'text',
                    text: `[image not sent: current openai-compatible image input is disabled for model=${typeof requestContext.modelKey === 'string' && requestContext.modelKey.trim().length > 0 ? requestContext.modelKey.trim() : 'unknown'}]`,
                });
                continue;
            }
            if (!(0, artifacts_1.isVisionImageMimeType)(item.mimeType)) {
                if (onToolResultImageIngest) {
                    await onToolResultImageIngest((0, tool_result_image_ingest_1.buildToolResultImageIngest)({
                        requestContext,
                        toolCallId: msg.id,
                        toolName: msg.name,
                        artifact: item.artifact,
                        disposition: 'filtered_mime_unsupported',
                        mimeType: item.mimeType,
                        providerPathLabel: 'OpenAI-compatible path',
                    }));
                }
                parts.push({
                    type: 'text',
                    text: `[image omitted: unsupported mimeType=${item.mimeType}]`,
                });
                continue;
            }
            if (!allowedImageKeys.has((0, tool_result_image_ingest_1.buildImageBudgetKeyForContentItem)({ msg, itemIndex, artifact: item.artifact }))) {
                if (onToolResultImageIngest) {
                    await onToolResultImageIngest((0, tool_result_image_ingest_1.buildToolResultImageIngest)({
                        requestContext,
                        toolCallId: msg.id,
                        toolName: msg.name,
                        artifact: item.artifact,
                        disposition: 'filtered_size_limit',
                        detail: (0, tool_result_image_ingest_1.buildImageBudgetLimitDetail)({
                            byteLength: item.byteLength,
                            budgetBytes: tool_result_image_ingest_1.OPENAI_COMPATIBLE_TOOL_RESULT_IMAGE_BUDGET_BYTES,
                        }),
                        providerPathLabel: 'OpenAI-compatible path',
                    }));
                }
                parts.push({
                    type: 'text',
                    text: `[image omitted: request image budget exceeded bytes=${String(item.byteLength)} budget=${String(tool_result_image_ingest_1.OPENAI_COMPATIBLE_TOOL_RESULT_IMAGE_BUDGET_BYTES)}]`,
                });
                continue;
            }
            const bytesResult = await (0, tool_result_image_ingest_1.readToolResultImageBytesSafe)(item.artifact);
            if (bytesResult.kind === 'missing') {
                if (onToolResultImageIngest) {
                    await onToolResultImageIngest((0, tool_result_image_ingest_1.buildToolResultImageIngest)({
                        requestContext,
                        toolCallId: msg.id,
                        toolName: msg.name,
                        artifact: item.artifact,
                        disposition: 'filtered_missing',
                        providerPathLabel: 'OpenAI-compatible path',
                    }));
                }
                parts.push({
                    type: 'text',
                    text: `[image missing: ${item.artifact.relPath}]`,
                });
                continue;
            }
            if (bytesResult.kind === 'read_failed') {
                if (onToolResultImageIngest) {
                    await onToolResultImageIngest((0, tool_result_image_ingest_1.buildToolResultImageIngest)({
                        requestContext,
                        toolCallId: msg.id,
                        toolName: msg.name,
                        artifact: item.artifact,
                        disposition: 'filtered_read_failed',
                        detail: bytesResult.detail,
                        providerPathLabel: 'OpenAI-compatible path',
                    }));
                }
                parts.push({
                    type: 'text',
                    text: `[image unreadable: ${item.artifact.relPath}]`,
                });
                continue;
            }
            if (onToolResultImageIngest) {
                await onToolResultImageIngest((0, tool_result_image_ingest_1.buildToolResultImageIngest)({
                    requestContext,
                    toolCallId: msg.id,
                    toolName: msg.name,
                    artifact: item.artifact,
                    disposition: 'fed_provider_transformed',
                    providerPathLabel: 'OpenAI-compatible path',
                }));
            }
            const bytes = bytesResult.bytes;
            parts.push({
                type: 'image_url',
                image_url: {
                    url: (0, artifacts_1.bytesToDataUrl)({ mimeType: item.mimeType, bytes }),
                    detail: 'auto',
                },
            });
            sawImageUrl = true;
            continue;
        }
        const _exhaustive = item;
        parts.push({ type: 'text', text: `[unknown content item: ${String(_exhaustive)}]` });
    }
    if (sawAnyImage) {
        if (sawImageUrl) {
            out.push({ role: 'user', content: parts });
        }
        else {
            const text = parts
                .filter((p) => p.type === 'text')
                .map((p) => p.text)
                .join('\n')
                .trim();
            if (text.length > 0) {
                out.push({ role: 'user', content: text });
            }
        }
    }
    return out;
}
function mergeAdjacentMessages(input) {
    // Some proxies behave poorly with long runs of same-role messages (Dominds persists thinking/saying
    // as separate msgs). Merge adjacent user/assistant/system messages where safe.
    const merged = [];
    for (const item of input) {
        if (!isRecord(item)) {
            merged.push(item);
            continue;
        }
        const role = item.role;
        const content = item.content;
        const hasToolCalls = 'tool_calls' in item && Array.isArray(item.tool_calls);
        const hasReasoningContent = 'reasoning_content' in item && typeof item.reasoning_content === 'string';
        const hasReasoningDetails = 'reasoning_details' in item && Array.isArray(item.reasoning_details);
        if ((role !== 'user' && role !== 'assistant' && role !== 'system') ||
            typeof content !== 'string' ||
            hasToolCalls ||
            hasReasoningContent ||
            hasReasoningDetails) {
            merged.push(item);
            continue;
        }
        const prev = merged.length > 0 ? merged[merged.length - 1] : null;
        if (prev &&
            isRecord(prev) &&
            prev.role === role &&
            typeof prev.content === 'string' &&
            !('tool_calls' in prev && Array.isArray(prev.tool_calls)) &&
            !('reasoning_content' in prev && typeof prev.reasoning_content === 'string') &&
            !('reasoning_details' in prev && Array.isArray(prev.reasoning_details))) {
            prev.content = `${prev.content}\n${content}`;
            continue;
        }
        merged.push(item);
    }
    return merged;
}
async function buildChatCompletionMessages(systemPrompt, context, requestContext, options) {
    const normalized = (0, tool_call_context_1.normalizeToolCallPairs)(context);
    const violation = (0, tool_call_context_1.findFirstToolCallAdjacencyViolation)(normalized);
    if (violation) {
        const detail = (0, tool_call_context_1.formatToolCallAdjacencyViolation)(violation, 'OPENAI-COMPATIBLE provider projection');
        log.error(detail, new Error('openai_compatible_tool_call_adjacency_violation'), {
            callId: violation.callId,
            toolName: violation.toolName,
            violationKind: violation.kind,
            index: violation.index,
        });
        throw new Error(detail);
    }
    const input = [];
    const toolResultMaxChars = (0, tool_output_limit_1.resolveProviderToolResultMaxChars)(options?.providerConfig);
    const allowedImageKeys = (0, tool_result_image_ingest_1.selectLatestImagesWithinBudget)(normalized, tool_result_image_ingest_1.OPENAI_COMPATIBLE_TOOL_RESULT_IMAGE_BUDGET_BYTES);
    const attachToolCallReasoning = shouldAttachReasoningContentToAssistantToolCalls(options?.providerConfig);
    const attachReasoningDetails = isMiniMaxReasoningDetailsProvider(options?.providerConfig);
    let pendingReasoningFields;
    let assistantTurnReasoningFields;
    const takePendingReasoningFields = () => {
        const current = pendingReasoningFields;
        pendingReasoningFields = undefined;
        return current;
    };
    const appendReasoningFields = (value, providerData) => {
        const reasoningDetails = extractOpenAiCompatibleReasoningDetailsProviderData(providerData);
        const content = value.length > 0
            ? value
            : reasoningDetails !== undefined
                ? (extractReasoningTextFromDetails(reasoningDetails) ?? '')
                : '';
        if (value.length === 0 && reasoningDetails === undefined)
            return;
        pendingReasoningFields =
            pendingReasoningFields === undefined
                ? { content, ...(reasoningDetails !== undefined ? { reasoningDetails } : {}) }
                : {
                    content: pendingReasoningFields.content.length > 0 && content.length > 0
                        ? `${pendingReasoningFields.content}\n${content}`
                        : pendingReasoningFields.content.length > 0
                            ? pendingReasoningFields.content
                            : content,
                    ...(reasoningDetails !== undefined
                        ? { reasoningDetails }
                        : pendingReasoningFields.reasoningDetails !== undefined
                            ? { reasoningDetails: pendingReasoningFields.reasoningDetails }
                            : {}),
                };
    };
    const noteAssistantTurnReasoningFields = (value) => {
        if (!value)
            return;
        assistantTurnReasoningFields = value;
    };
    const flushPendingReasoningAsAssistantMessage = () => {
        const reasoningFields = takePendingReasoningFields();
        if (!reasoningFields)
            return;
        noteAssistantTurnReasoningFields(reasoningFields);
        input.push(attachReasoningFields({
            role: 'assistant',
            content: '',
        }, reasoningFields, attachReasoningDetails));
    };
    if (systemPrompt.trim().length > 0) {
        input.push({ role: 'system', content: systemPrompt.trim() });
    }
    for (const msg of normalized) {
        if (msg.type === 'thinking_msg') {
            appendReasoningFields(extractThinkingReasoningText(msg), msg.provider_data);
            continue;
        }
        if (msg.type === 'func_call_msg') {
            const mapped = chatMessageToChatCompletionMessage(msg);
            const pending = takePendingReasoningFields();
            noteAssistantTurnReasoningFields(pending);
            const reasoningFields = attachToolCallReasoning
                ? (pending ?? assistantTurnReasoningFields)
                : undefined;
            input.push(attachReasoningFields(mapped, reasoningFields, attachReasoningDetails));
            continue;
        }
        if (msg.type === 'func_result_msg') {
            flushPendingReasoningAsAssistantMessage();
            input.push(...(await funcResultToChatCompletionMessages(msg, toolResultMaxChars, requestContext, options?.providerConfig, allowedImageKeys, options?.onToolResultImageIngest)));
            continue;
        }
        if (msg.type === 'environment_msg' ||
            msg.type === 'prompting_msg' ||
            msg.type === 'tellask_result_msg' ||
            msg.type === 'tellask_carryover_msg') {
            assistantTurnReasoningFields = undefined;
        }
        const mapped = (msg.type === 'prompting_msg' ||
            msg.type === 'tellask_result_msg' ||
            msg.type === 'tellask_carryover_msg') &&
            Array.isArray(msg.contentItems) &&
            msg.contentItems.length > 0
            ? await userLikeMessageToChatCompletionMessageWithImages(msg, requestContext, options?.providerConfig, allowedImageKeys, options?.onUserImageIngest)
            : chatMessageToChatCompletionMessage(msg);
        const reasoningFields = takePendingReasoningFields();
        noteAssistantTurnReasoningFields(reasoningFields);
        input.push(attachReasoningFields(mapped, reasoningFields, attachReasoningDetails));
    }
    flushPendingReasoningAsAssistantMessage();
    return mergeAdjacentMessages(input);
}
async function buildOpenAiCompatibleRequestMessagesWrapper(systemPrompt, context, requestContextOrOptions, optionsMaybe) {
    const requestContext = isLlmRequestContext(requestContextOrOptions)
        ? requestContextOrOptions
        : {
            dialogSelfId: '',
            dialogRootId: '',
            providerKey: 'openai-compatible',
            modelKey: 'unknown',
        };
    const options = isLlmRequestContext(requestContextOrOptions)
        ? optionsMaybe
        : requestContextOrOptions;
    return await buildChatCompletionMessages(systemPrompt, context, requestContext, options);
}
function applyArgsDelta(state, chunk) {
    if (chunk.length === 0)
        return;
    if (state.argsJson.length === 0) {
        state.argsJson = chunk;
        return;
    }
    // Support both delta and cumulative streaming implementations.
    if (chunk.startsWith(state.argsJson)) {
        state.argsJson = chunk;
        return;
    }
    if (state.argsJson.startsWith(chunk)) {
        return;
    }
    state.argsJson += chunk;
}
function synthesizeCallId(genseq, index) {
    // Some OpenAI-compatible proxies omit `id` in tool call deltas. Dominds requires a stable call id
    // for correlating tool outputs across turns; synthesize one that is stable for this generation.
    return `toolcall_${genseq}_${index}`;
}
function buildOpenAiCompatibleStreamError(args) {
    return new gen_1.LlmStreamErrorEmittedError({
        detail: args.detail,
        i18nStopReason: (0, stop_reason_i18n_1.buildHumanSystemStopReasonTextI18n)({
            detail: args.detail,
            kind: args.kind,
        }),
    });
}
function throwOpenAiCompatibleMalformedBatchToolCall(detail) {
    const message = `OPENAI-COMPATIBLE malformed batch tool call: ${detail}`;
    const error = new Error(message);
    error.code = OPENAI_COMPATIBLE_MALFORMED_BATCH_TOOL_CALL_ERROR_CODE;
    log.error(message, error);
    throw error;
}
function buildInvalidStreamedToolFunctionNameCall(state) {
    return {
        provider: 'openai-compatible',
        callId: state.callId,
        detail: `OPENAI-COMPATIBLE missing streamed tool function name for callId=${state.callId}`,
        toolCallIndex: state.index,
        rawArgumentsText: state.argsJson,
    };
}
function buildInvalidToolFunctionNameCall(args) {
    return {
        provider: 'openai-compatible',
        callId: args.callId,
        detail: `OPENAI-COMPATIBLE missing tool function name for callId=${args.callId}`,
        toolCallIndex: args.toolCallIndex,
        rawArgumentsText: args.rawArgumentsText,
    };
}
async function maybeEmitFuncCall(state, receiver, genseq) {
    if (state.emitted)
        return;
    if (state.callId.trim().length === 0) {
        state.callId = synthesizeCallId(genseq, state.index);
    }
    if (state.name.trim().length === 0) {
        if (state.argsJson.trim().length === 0) {
            log.warn('OPENAI-COMPATIBLE ignored empty streamed tool call placeholder', undefined, {
                callId: state.callId,
                index: state.index,
            });
            state.emitted = true;
            return;
        }
        const invalidCall = buildInvalidStreamedToolFunctionNameCall(state);
        const detail = invalidCall.detail;
        log.error(detail, new Error('openai_compatible_missing_tool_call_name'), {
            callId: state.callId,
            index: state.index,
        });
        if (!receiver.invalidFuncCall) {
            if (receiver.streamError) {
                await receiver.streamError(detail);
            }
            throw buildOpenAiCompatibleStreamError({
                detail,
                kind: 'invalid_tool_call',
            });
        }
        state.emitted = true;
        await receiver.invalidFuncCall(invalidCall);
        return;
    }
    state.emitted = true;
    const args = state.argsJson.trim().length > 0 ? state.argsJson : '{}';
    await receiver.funcCall(state.callId, state.name, args);
}
async function emitOpenAiCompatibleStreamError(args) {
    if (args.logMeta === undefined) {
        log.error(args.detail, new Error(args.errorCode));
    }
    else {
        log.error(args.detail, new Error(args.errorCode), args.logMeta);
    }
    if (args.receiver.streamError) {
        await args.receiver.streamError(args.detail);
    }
    throw buildOpenAiCompatibleStreamError({
        detail: args.detail,
        kind: args.kind,
    });
}
async function consumeOpenAiCompatibleChatCompletionStream(args) {
    let sayingStarted = false;
    let thinkingStarted = false;
    let currentThinkingContent = '';
    let currentReasoningDetails;
    let currentReasoningDetailsText = '';
    let activeStream = 'idle';
    let usage = { kind: 'unavailable' };
    let returnedModel;
    const activeCallsByIndex = new Map();
    const finishThinkingSegment = async () => {
        if (!thinkingStarted)
            return;
        await args.receiver.thinkingFinish(buildReasoningPayloadFromText(currentThinkingContent), buildOpenAiCompatibleReasoningProviderData(currentReasoningDetails));
        thinkingStarted = false;
        currentThinkingContent = '';
        currentReasoningDetails = undefined;
        currentReasoningDetailsText = '';
        if (activeStream === 'thinking')
            activeStream = 'idle';
    };
    const finishSayingSegment = async () => {
        if (!sayingStarted)
            return;
        await args.receiver.sayingFinish();
        sayingStarted = false;
        if (activeStream === 'saying')
            activeStream = 'idle';
    };
    const ensureCanEnterThinking = async () => {
        if (activeStream !== 'saying')
            return;
        await finishSayingSegment();
    };
    const ensureCanEnterSaying = async () => {
        if (activeStream !== 'thinking')
            return;
        await finishThinkingSegment();
    };
    try {
        for await (const chunk of args.stream) {
            if (args.abortSignal?.aborted)
                throw new Error('AbortError');
            if (returnedModel === undefined &&
                typeof chunk.model === 'string' &&
                chunk.model.length > 0) {
                returnedModel = chunk.model;
            }
            if (chunk.usage) {
                usage = tryExtractChatUsage(chunk.usage);
            }
            const choice = chunk.choices && chunk.choices.length > 0 ? chunk.choices[0] : undefined;
            if (!choice)
                continue;
            const delta = choice.delta;
            let reasoningDetails;
            try {
                reasoningDetails = extractReasoningDetailsField(delta);
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                await emitOpenAiCompatibleStreamError({
                    receiver: args.receiver,
                    detail: `OPENAI-COMPATIBLE invalid reasoning_details stream payload: ${message}`,
                    kind: 'malformed_stream',
                    errorCode: 'openai_compatible_invalid_reasoning_details',
                });
            }
            const reasoningDetailsText = reasoningDetails !== undefined
                ? extractReasoningTextFromDetails(reasoningDetails)
                : undefined;
            const reasoningDelta = reasoningDetailsText !== undefined
                ? reasoningDetailsText.startsWith(currentReasoningDetailsText)
                    ? reasoningDetailsText.slice(currentReasoningDetailsText.length)
                    : reasoningDetailsText
                : extractReasoningContentField(delta);
            if (reasoningDetails !== undefined) {
                currentReasoningDetails =
                    reasoningDetailsText !== undefined &&
                        reasoningDetailsText.startsWith(currentReasoningDetailsText)
                        ? reasoningDetails
                        : appendReasoningDetails(currentReasoningDetails, reasoningDetails);
                currentReasoningDetailsText =
                    reasoningDetailsText !== undefined &&
                        reasoningDetailsText.startsWith(currentReasoningDetailsText)
                        ? reasoningDetailsText
                        : `${currentReasoningDetailsText}${reasoningDelta ?? ''}`;
            }
            if (typeof reasoningDelta === 'string' && reasoningDelta.length > 0) {
                await ensureCanEnterThinking();
                if (!thinkingStarted) {
                    thinkingStarted = true;
                    currentThinkingContent = '';
                    await args.receiver.thinkingStart();
                    activeStream = 'thinking';
                }
                currentThinkingContent += reasoningDelta;
                await args.receiver.thinkingChunk(reasoningDelta);
            }
            else if (reasoningDetails !== undefined && !thinkingStarted) {
                await ensureCanEnterThinking();
                thinkingStarted = true;
                currentThinkingContent = '';
                await args.receiver.thinkingStart();
                activeStream = 'thinking';
            }
            const content = delta.content;
            if (typeof content === 'string' && content.length > 0) {
                await ensureCanEnterSaying();
                if (!sayingStarted) {
                    sayingStarted = true;
                    await args.receiver.sayingStart();
                    activeStream = 'saying';
                }
                await args.receiver.sayingChunk(content);
            }
            const toolCalls = delta.tool_calls;
            if (Array.isArray(toolCalls)) {
                await finishThinkingSegment();
                await finishSayingSegment();
                for (const call of toolCalls) {
                    const rawIndex = call.index;
                    const index = typeof rawIndex === 'number' && Number.isInteger(rawIndex) && rawIndex >= 0
                        ? rawIndex
                        : undefined;
                    if (index === undefined) {
                        await emitOpenAiCompatibleStreamError({
                            receiver: args.receiver,
                            detail: `OPENAI-COMPATIBLE invalid tool call index: ${JSON.stringify(rawIndex)}`,
                            kind: 'invalid_tool_call',
                            errorCode: 'openai_compatible_invalid_tool_call_index',
                        });
                        continue;
                    }
                    const rawType = call.type;
                    if (rawType !== undefined && rawType !== 'function') {
                        const detail = `OPENAI-COMPATIBLE invalid streamed tool call type for index=${String(index)}: expected function, got ${JSON.stringify(rawType)}`;
                        await emitOpenAiCompatibleStreamError({
                            receiver: args.receiver,
                            detail,
                            kind: 'invalid_tool_call',
                            errorCode: 'openai_compatible_invalid_tool_call_type',
                        });
                    }
                    const existing = activeCallsByIndex.get(index);
                    const rawCallId = call.id;
                    if (typeof rawCallId === 'string' &&
                        rawCallId.length > 0 &&
                        existing !== undefined &&
                        existing.callId.length > 0 &&
                        existing.callId !== rawCallId) {
                        const detail = `OPENAI-COMPATIBLE conflicting streamed tool call id for index=${String(index)}: existing=${existing.callId}, next=${rawCallId}`;
                        await emitOpenAiCompatibleStreamError({
                            receiver: args.receiver,
                            detail,
                            kind: 'invalid_tool_call',
                            errorCode: 'openai_compatible_conflicting_tool_call_id',
                            logMeta: { callId: rawCallId },
                        });
                    }
                    const state = existing ??
                        {
                            index,
                            callId: '',
                            name: '',
                            argsJson: '',
                            emitted: false,
                        };
                    if (typeof call.id === 'string' && call.id.length > 0)
                        state.callId = call.id;
                    if (call.function) {
                        if (typeof call.function.name === 'string' && call.function.name.length > 0) {
                            if (state.name.length > 0 && state.name !== call.function.name) {
                                const detail = `OPENAI-COMPATIBLE conflicting streamed tool function name for callId=${state.callId}: existing=${state.name}, next=${call.function.name}`;
                                await emitOpenAiCompatibleStreamError({
                                    receiver: args.receiver,
                                    detail,
                                    kind: 'invalid_tool_call',
                                    errorCode: 'openai_compatible_conflicting_tool_call_name',
                                    logMeta: { callId: state.callId },
                                });
                            }
                            state.name = call.function.name;
                        }
                        if (typeof call.function.arguments === 'string' && call.function.arguments.length > 0) {
                            applyArgsDelta(state, call.function.arguments);
                        }
                    }
                    activeCallsByIndex.set(index, state);
                }
            }
            if (choice.finish_reason === 'tool_calls') {
                await finishThinkingSegment();
                await finishSayingSegment();
                activeStream = 'idle';
                for (const state of activeCallsByIndex.values()) {
                    await maybeEmitFuncCall(state, args.receiver, args.genseq);
                }
                activeCallsByIndex.clear();
            }
            if (choice.finish_reason === 'stop' ||
                choice.finish_reason === 'length' ||
                choice.finish_reason === 'content_filter') {
                await finishThinkingSegment();
                await finishSayingSegment();
                activeStream = 'idle';
            }
        }
        for (const state of activeCallsByIndex.values()) {
            await maybeEmitFuncCall(state, args.receiver, args.genseq);
        }
    }
    finally {
        if (thinkingStarted) {
            await args.receiver.thinkingFinish(buildReasoningPayloadFromText(currentThinkingContent), buildOpenAiCompatibleReasoningProviderData(currentReasoningDetails));
        }
        if (sayingStarted)
            await args.receiver.sayingFinish();
    }
    return { usage, ...(returnedModel ? { llmGenModel: returnedModel } : {}) };
}
async function consumeOpenAiCompatibleChatCompletionStreamForTest(args) {
    return await consumeOpenAiCompatibleChatCompletionStream({
        stream: args.stream,
        receiver: args.receiver,
        genseq: args.genseq,
        abortSignal: args.abortSignal,
    });
}
function chatCompletionToBatchOutputs(response, genseq) {
    const outputs = [];
    const choice = response.choices && response.choices.length > 0 ? response.choices[0] : undefined;
    const msg = choice ? choice.message : undefined;
    if (!msg)
        return outputs;
    const reasoningDetails = extractReasoningDetailsField(msg);
    const reasoningContent = reasoningDetails !== undefined
        ? extractReasoningTextFromDetails(reasoningDetails)
        : extractReasoningContentField(msg);
    if ((reasoningContent && reasoningContent.length > 0) || reasoningDetails !== undefined) {
        const content = reasoningContent ?? '';
        const reasoning = buildReasoningPayloadFromText(content);
        const message = {
            type: 'thinking_msg',
            role: 'assistant',
            genseq,
            content,
            ...(reasoning !== undefined ? { reasoning } : {}),
            ...(reasoningDetails !== undefined
                ? { provider_data: buildOpenAiCompatibleReasoningProviderData(reasoningDetails) }
                : {}),
        };
        outputs.push({ kind: 'message', message });
    }
    const content = typeof msg.content === 'string' ? msg.content : null;
    if (content && content.length > 0) {
        const message = { type: 'saying_msg', role: 'assistant', genseq, content };
        outputs.push({ kind: 'message', message });
    }
    const toolCalls = msg.tool_calls;
    if (Array.isArray(toolCalls)) {
        for (let index = 0; index < toolCalls.length; index += 1) {
            const call = toolCalls[index];
            if (!call) {
                throwOpenAiCompatibleMalformedBatchToolCall(`empty tool call at index=${String(index)}`);
            }
            if (call.type !== undefined && call.type !== 'function') {
                throwOpenAiCompatibleMalformedBatchToolCall(`invalid tool call type at index=${String(index)}: expected function, got ${JSON.stringify(call.type)}`);
            }
            const callId = typeof call.id === 'string' && call.id.trim().length > 0
                ? call.id
                : synthesizeCallId(genseq, index);
            const name = typeof call.function?.name === 'string' ? call.function.name : '';
            const args = typeof call.function?.arguments === 'string' ? call.function.arguments : '';
            if (name.trim().length === 0) {
                const invalidCall = buildInvalidToolFunctionNameCall({
                    callId,
                    toolCallIndex: index,
                    rawArgumentsText: args,
                });
                log.error(invalidCall.detail, new Error('openai_compatible_missing_tool_call_name'), {
                    callId,
                    index,
                });
                outputs.push({ kind: 'invalid_func_call', call: invalidCall });
                continue;
            }
            const message = {
                type: 'func_call_msg',
                role: 'assistant',
                genseq,
                id: callId,
                name,
                arguments: args,
            };
            outputs.push({ kind: 'message', message });
        }
    }
    return outputs;
}
function batchOutputsToChatMessages(outputs) {
    return outputs
        .filter((output) => {
        return output.kind === 'message';
    })
        .map((output) => output.message);
}
function chatCompletionToChatMessages(response, genseq) {
    const out = batchOutputsToChatMessages(chatCompletionToBatchOutputs(response, genseq));
    return out;
}
function chatCompletionToChatMessagesForTest(response, genseq) {
    return chatCompletionToChatMessages(response, genseq);
}
function chatCompletionToBatchOutputsForTest(response, genseq) {
    return chatCompletionToBatchOutputs(response, genseq);
}
class OpenAiCompatibleGen {
    get apiType() {
        return 'openai-compatible';
    }
    classifyFailure(error) {
        return (0, failure_classifier_1.classifyOpenAiLikeFailure)(error);
    }
    async genToReceiver(providerConfig, agent, systemPrompt, funcTools, requestContext, context, receiver, genseq, abortSignal) {
        const apiKey = process.env[providerConfig.apiKeyEnvVar];
        if (!apiKey)
            throw new Error(`Missing API key env var ${providerConfig.apiKeyEnvVar}`);
        if (!agent.model) {
            throw new Error(`Internal error: Model is undefined for agent '${agent.id}'`);
        }
        const client = createOpenAiCompatibleClient({
            apiKey,
            providerConfig,
            agent,
            requestContext,
            requestKind: 'stream',
        });
        const messages = await buildChatCompletionMessages(systemPrompt, context, requestContext, {
            providerConfig,
            onToolResultImageIngest: receiver.toolResultImageIngest,
            onUserImageIngest: receiver.userImageIngest,
        });
        const openAiParams = agent.model_params?.['openai-compatible'] || {};
        const parallelToolCalls = resolveOpenAiCompatibleParallelToolCalls({
            providerConfig,
            openAiParams,
        });
        const responseFormat = buildChatCompletionResponseFormat(openAiParams);
        const requestTools = resolveOpenAiCompatibleRequestTools(funcTools, requestContext);
        const modelInfo = resolveOpenAiCompatibleRequestModelInfo(providerConfig, agent, requestContext);
        const toolChoice = resolveOpenAiCompatibleToolChoice(requestTools, requestContext, modelInfo);
        const openAiCompatibleExtraParams = buildOpenAiCompatibleExtraParams({
            providerConfig,
            agent,
            openAiParams,
            requestContext,
        });
        const payload = {
            model: agent.model,
            messages,
            stream: true,
            stream_options: { include_usage: true },
            ...(openAiParams.service_tier !== undefined && { service_tier: openAiParams.service_tier }),
            ...(openAiParams.safety_identifier !== undefined && {
                safety_identifier: openAiParams.safety_identifier,
            }),
            ...(openAiParams.temperature !== undefined && { temperature: openAiParams.temperature }),
            ...(openAiParams.top_p !== undefined && { top_p: openAiParams.top_p }),
            ...openAiCompatibleExtraParams,
            ...(responseFormat !== undefined && { response_format: responseFormat }),
            ...(requestTools.length > 0
                ? { tools: requestTools.map((tool) => funcToolToChatCompletionTool(tool, providerConfig)) }
                : {}),
            ...(toolChoice !== undefined && { tool_choice: toolChoice }),
            ...(parallelToolCalls !== undefined && { parallel_tool_calls: parallelToolCalls }),
        };
        try {
            const stream = await client.chat.completions.create(payload, {
                ...(abortSignal ? { signal: abortSignal } : {}),
            });
            return await consumeOpenAiCompatibleChatCompletionStream({
                stream,
                receiver,
                genseq,
                abortSignal,
            });
        }
        catch (error) {
            const enrichedError = await wrapOpenAiCompatibleRejectedRequestError({
                error,
                providerConfig,
                agent,
                requestContext,
                genseq,
                requestKind: 'stream',
                payload,
            });
            log.warn('OPENAI-COMPATIBLE streaming error', enrichedError, {
                providerKey: requestContext.providerKey ?? providerConfig.name,
                model: requestContext.modelKey ?? agent.model,
                rootId: requestContext.dialogRootId,
                selfId: requestContext.dialogSelfId,
                genseq,
            });
            throw enrichedError;
        }
    }
    async genMoreMessages(providerConfig, agent, systemPrompt, funcTools, requestContext, context, genseq, abortSignal) {
        const apiKey = process.env[providerConfig.apiKeyEnvVar];
        if (!apiKey)
            throw new Error(`Missing API key env var ${providerConfig.apiKeyEnvVar}`);
        if (!agent.model) {
            throw new Error(`Internal error: Model is undefined for agent '${agent.id}'`);
        }
        const client = createOpenAiCompatibleClient({
            apiKey,
            providerConfig,
            agent,
            requestContext,
            requestKind: 'batch',
        });
        const outputs = [];
        const messages = await buildChatCompletionMessages(systemPrompt, context, requestContext, {
            providerConfig,
            onToolResultImageIngest: async (ingest) => {
                outputs.push({ kind: 'tool_result_image_ingest', ingest });
            },
            onUserImageIngest: async (ingest) => {
                outputs.push({ kind: 'user_image_ingest', ingest });
            },
        });
        const openAiParams = agent.model_params?.['openai-compatible'] || {};
        const parallelToolCalls = resolveOpenAiCompatibleParallelToolCalls({
            providerConfig,
            openAiParams,
        });
        const responseFormat = buildChatCompletionResponseFormat(openAiParams);
        const requestTools = resolveOpenAiCompatibleRequestTools(funcTools, requestContext);
        const modelInfo = resolveOpenAiCompatibleRequestModelInfo(providerConfig, agent, requestContext);
        const toolChoice = resolveOpenAiCompatibleToolChoice(requestTools, requestContext, modelInfo);
        const openAiCompatibleExtraParams = buildOpenAiCompatibleExtraParams({
            providerConfig,
            agent,
            openAiParams,
            requestContext,
        });
        const payload = {
            model: agent.model,
            messages,
            ...(openAiParams.service_tier !== undefined && { service_tier: openAiParams.service_tier }),
            ...(openAiParams.safety_identifier !== undefined && {
                safety_identifier: openAiParams.safety_identifier,
            }),
            ...(openAiParams.temperature !== undefined && { temperature: openAiParams.temperature }),
            ...(openAiParams.top_p !== undefined && { top_p: openAiParams.top_p }),
            ...openAiCompatibleExtraParams,
            ...(responseFormat !== undefined && { response_format: responseFormat }),
            ...(requestTools.length > 0 && {
                tools: requestTools.map((tool) => funcToolToChatCompletionTool(tool, providerConfig)),
            }),
            ...(toolChoice !== undefined && { tool_choice: toolChoice }),
            ...(parallelToolCalls !== undefined && { parallel_tool_calls: parallelToolCalls }),
        };
        try {
            const response = await client.chat.completions.create(payload, {
                ...(abortSignal ? { signal: abortSignal } : {}),
            });
            const batchOutputs = chatCompletionToBatchOutputs(response, genseq);
            const messagesOut = batchOutputsToChatMessages(batchOutputs);
            const orderedOutputs = outputs.length > 0
                ? [
                    ...outputs,
                    ...messagesOut.map((message) => ({ kind: 'message', message })),
                    ...batchOutputs.filter((output) => output.kind !== 'message'),
                ]
                : batchOutputs;
            const usage = response.usage
                ? tryExtractChatUsage(response.usage)
                : { kind: 'unavailable' };
            const model = typeof response.model === 'string' && response.model.length > 0
                ? response.model
                : undefined;
            return {
                messages: messagesOut,
                ...(orderedOutputs.length > 0 ? { outputs: orderedOutputs } : {}),
                usage,
                ...(model ? { llmGenModel: model } : {}),
            };
        }
        catch (error) {
            const enrichedError = await wrapOpenAiCompatibleRejectedRequestError({
                error,
                providerConfig,
                agent,
                requestContext,
                genseq,
                requestKind: 'batch',
                payload,
            });
            log.warn('OPENAI-COMPATIBLE batch error', enrichedError, {
                providerKey: requestContext.providerKey ?? providerConfig.name,
                model: requestContext.modelKey ?? agent.model,
                rootId: requestContext.dialogRootId,
                selfId: requestContext.dialogSelfId,
                genseq,
            });
            throw enrichedError;
        }
    }
}
exports.OpenAiCompatibleGen = OpenAiCompatibleGen;
