import { MIDSCENE_LANGFUSE_DEBUG, MIDSCENE_LANGSMITH_DEBUG, MIDSCENE_MODEL_MAX_TOKENS, OPENAI_MAX_TOKENS, globalConfigManager } from "@midscene/shared/env";
import { getDebug } from "@midscene/shared/logger";
import { assert, ifInBrowser } from "@midscene/shared/utils";
import { jsonrepair } from "jsonrepair";
import openai_0 from "openai";
import { isAutoGLM, isUITars } from "../auto-glm/util.mjs";
function _define_property(obj, key, value) {
    if (key in obj) Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
    });
    else obj[key] = value;
    return obj;
}
class AIResponseParseError extends Error {
    constructor(message, rawResponse, usage){
        super(message), _define_property(this, "usage", void 0), _define_property(this, "rawResponse", void 0);
        this.name = 'AIResponseParseError';
        this.rawResponse = rawResponse;
        this.usage = usage;
    }
}
async function createChatClient({ modelConfig }) {
    const { socksProxy, httpProxy, modelName, openaiBaseURL, openaiApiKey, openaiExtraConfig, modelDescription, uiTarsModelVersion, modelFamily, createOpenAIClient, timeout } = modelConfig;
    let proxyAgent;
    const warnClient = getDebug('ai:call', {
        console: true
    });
    const debugProxy = getDebug('ai:call:proxy');
    const warnProxy = getDebug('ai:call:proxy', {
        console: true
    });
    const sanitizeProxyUrl = (url)=>{
        try {
            const parsed = new URL(url);
            if (parsed.username) {
                parsed.password = '****';
                return parsed.href;
            }
            return url;
        } catch  {
            return url;
        }
    };
    if (httpProxy) {
        debugProxy('using http proxy', sanitizeProxyUrl(httpProxy));
        if (ifInBrowser) warnProxy('HTTP proxy is configured but not supported in browser environment');
        else {
            const moduleName = 'undici';
            const { ProxyAgent } = await import(moduleName);
            proxyAgent = new ProxyAgent({
                uri: httpProxy
            });
        }
    } else if (socksProxy) {
        debugProxy('using socks proxy', sanitizeProxyUrl(socksProxy));
        if (ifInBrowser) warnProxy('SOCKS proxy is configured but not supported in browser environment');
        else try {
            const moduleName = 'fetch-socks';
            const { socksDispatcher } = await import(moduleName);
            const proxyUrl = new URL(socksProxy);
            if (!proxyUrl.hostname) throw new Error('SOCKS proxy URL must include a valid hostname');
            const port = Number.parseInt(proxyUrl.port, 10);
            if (!proxyUrl.port || Number.isNaN(port)) throw new Error('SOCKS proxy URL must include a valid port');
            const protocol = proxyUrl.protocol.replace(':', '');
            const socksType = 'socks4' === protocol ? 4 : 'socks5' === protocol ? 5 : 5;
            proxyAgent = socksDispatcher({
                type: socksType,
                host: proxyUrl.hostname,
                port,
                ...proxyUrl.username ? {
                    userId: decodeURIComponent(proxyUrl.username),
                    password: decodeURIComponent(proxyUrl.password || '')
                } : {}
            });
            debugProxy('socks proxy configured successfully', {
                type: socksType,
                host: proxyUrl.hostname,
                port: port
            });
        } catch (error) {
            warnProxy('Failed to configure SOCKS proxy:', error);
            throw new Error(`Invalid SOCKS proxy URL: ${socksProxy}. Expected format: socks4://host:port, socks5://host:port, or with authentication: socks5://user:pass@host:port`);
        }
    }
    const openAIOptions = {
        baseURL: openaiBaseURL,
        apiKey: openaiApiKey,
        ...proxyAgent ? {
            fetchOptions: {
                dispatcher: proxyAgent
            }
        } : {},
        ...openaiExtraConfig,
        ...'number' == typeof timeout ? {
            timeout
        } : {},
        dangerouslyAllowBrowser: true
    };
    const baseOpenAI = new openai_0(openAIOptions);
    let openai = baseOpenAI;
    if (openai && globalConfigManager.getEnvConfigInBoolean(MIDSCENE_LANGSMITH_DEBUG)) {
        if (ifInBrowser) throw new Error('langsmith is not supported in browser');
        warnClient('DEBUGGING MODE: langsmith wrapper enabled');
        const langsmithModule = 'langsmith/wrappers';
        const { wrapOpenAI } = await import(langsmithModule);
        openai = wrapOpenAI(openai);
    }
    if (openai && globalConfigManager.getEnvConfigInBoolean(MIDSCENE_LANGFUSE_DEBUG)) {
        if (ifInBrowser) throw new Error('langfuse is not supported in browser');
        warnClient('DEBUGGING MODE: langfuse wrapper enabled');
        const langfuseModule = '@langfuse/openai';
        const { observeOpenAI } = await import(langfuseModule);
        openai = observeOpenAI(openai);
    }
    if (createOpenAIClient) {
        const wrappedClient = await createOpenAIClient(baseOpenAI, openAIOptions);
        if (wrappedClient) openai = wrappedClient;
    }
    return {
        completion: openai.chat.completions,
        modelName,
        modelDescription,
        uiTarsModelVersion,
        modelFamily
    };
}
async function callAI(messages, modelConfig, options) {
    const { completion, modelName, modelDescription, uiTarsModelVersion, modelFamily } = await createChatClient({
        modelConfig
    });
    const maxTokens = globalConfigManager.getEnvConfigValueAsNumber(MIDSCENE_MODEL_MAX_TOKENS) ?? globalConfigManager.getEnvConfigValueAsNumber(OPENAI_MAX_TOKENS);
    const debugCall = getDebug('ai:call');
    const warnCall = getDebug('ai:call', {
        console: true
    });
    const debugProfileStats = getDebug('ai:profile:stats');
    const debugProfileDetail = getDebug('ai:profile:detail');
    const startTime = Date.now();
    const temperature = modelConfig.temperature ?? 0;
    const isStreaming = options?.stream && options?.onChunk;
    let content;
    let accumulated = '';
    let accumulatedReasoning = '';
    let usage;
    let timeCost;
    let requestId;
    const buildUsageInfo = (usageData, requestId)=>{
        if (!usageData) return;
        const cachedInputTokens = usageData?.prompt_tokens_details?.cached_tokens;
        return {
            prompt_tokens: usageData.prompt_tokens ?? 0,
            completion_tokens: usageData.completion_tokens ?? 0,
            total_tokens: usageData.total_tokens ?? 0,
            cached_input: cachedInputTokens ?? 0,
            time_cost: timeCost ?? 0,
            model_name: modelName,
            model_description: modelDescription,
            intent: modelConfig.intent,
            request_id: requestId ?? void 0
        };
    };
    const commonConfig = {
        temperature,
        stream: !!isStreaming,
        max_tokens: maxTokens,
        ...'qwen2.5-vl' === modelFamily ? {
            vl_high_resolution_images: true
        } : {}
    };
    if (isAutoGLM(modelFamily)) {
        commonConfig.top_p = 0.85;
        commonConfig.frequency_penalty = 0.2;
    }
    const { config: deepThinkConfig, debugMessage, warningMessage } = resolveDeepThinkConfig({
        deepThink: options?.deepThink,
        modelFamily
    });
    if (debugMessage) debugCall(debugMessage);
    if (warningMessage) warnCall(warningMessage);
    try {
        debugCall(`sending ${isStreaming ? 'streaming ' : ''}request to ${modelName}`);
        if (isStreaming) {
            const stream = await completion.create({
                model: modelName,
                messages,
                ...commonConfig,
                ...deepThinkConfig
            }, {
                stream: true
            });
            requestId = stream._request_id;
            for await (const chunk of stream){
                const content = chunk.choices?.[0]?.delta?.content || '';
                const reasoning_content = chunk.choices?.[0]?.delta?.reasoning_content || '';
                if (chunk.usage) usage = chunk.usage;
                if (content || reasoning_content) {
                    accumulated += content;
                    accumulatedReasoning += reasoning_content;
                    const chunkData = {
                        content,
                        reasoning_content,
                        accumulated,
                        isComplete: false,
                        usage: void 0
                    };
                    options.onChunk(chunkData);
                }
                if (chunk.choices?.[0]?.finish_reason) {
                    timeCost = Date.now() - startTime;
                    if (!usage) {
                        const estimatedTokens = Math.max(1, Math.floor(accumulated.length / 4));
                        usage = {
                            prompt_tokens: estimatedTokens,
                            completion_tokens: estimatedTokens,
                            total_tokens: 2 * estimatedTokens
                        };
                    }
                    const finalChunk = {
                        content: '',
                        accumulated,
                        reasoning_content: '',
                        isComplete: true,
                        usage: buildUsageInfo(usage, requestId)
                    };
                    options.onChunk(finalChunk);
                    break;
                }
            }
            content = accumulated;
            debugProfileStats(`streaming model, ${modelName}, mode, ${modelFamily || 'default'}, cost-ms, ${timeCost}, temperature, ${temperature ?? ''}`);
        } else {
            const retryCount = modelConfig.retryCount ?? 1;
            const retryInterval = modelConfig.retryInterval ?? 2000;
            const maxAttempts = retryCount + 1;
            let lastError;
            for(let attempt = 1; attempt <= maxAttempts; attempt++)try {
                const result = await completion.create({
                    model: modelName,
                    messages,
                    ...commonConfig,
                    ...deepThinkConfig
                });
                timeCost = Date.now() - startTime;
                debugProfileStats(`model, ${modelName}, mode, ${modelFamily || 'default'}, ui-tars-version, ${uiTarsModelVersion}, prompt-tokens, ${result.usage?.prompt_tokens || ''}, completion-tokens, ${result.usage?.completion_tokens || ''}, total-tokens, ${result.usage?.total_tokens || ''}, cost-ms, ${timeCost}, requestId, ${result._request_id || ''}, temperature, ${temperature ?? ''}`);
                debugProfileDetail(`model usage detail: ${JSON.stringify(result.usage)}`);
                if (!result.choices) throw new Error(`invalid response from LLM service: ${JSON.stringify(result)}`);
                content = result.choices[0].message.content;
                accumulatedReasoning = result.choices[0].message?.reasoning_content || '';
                usage = result.usage;
                requestId = result._request_id;
                if (!content && accumulatedReasoning && 'doubao-vision' === modelFamily) {
                    warnCall('empty content from AI model, using reasoning content');
                    content = accumulatedReasoning;
                }
                if (!content) throw new Error('empty content from AI model');
                break;
            } catch (error) {
                lastError = error;
                if (attempt < maxAttempts) {
                    warnCall(`AI call failed (attempt ${attempt}/${maxAttempts}), retrying in ${retryInterval}ms... Error: ${lastError.message}`);
                    await new Promise((resolve)=>setTimeout(resolve, retryInterval));
                }
            }
            if (!content) throw lastError;
        }
        debugCall(`response reasoning content: ${accumulatedReasoning}`);
        debugCall(`response content: ${content}`);
        if (isStreaming && !usage) {
            const estimatedTokens = Math.max(1, Math.floor((content || '').length / 4));
            usage = {
                prompt_tokens: estimatedTokens,
                completion_tokens: estimatedTokens,
                total_tokens: 2 * estimatedTokens
            };
        }
        return {
            content: content || '',
            reasoning_content: accumulatedReasoning || void 0,
            usage: buildUsageInfo(usage, requestId),
            isStreamed: !!isStreaming
        };
    } catch (e) {
        warnCall('call AI error', e);
        const newError = new Error(`failed to call ${isStreaming ? 'streaming ' : ''}AI model service (${modelName}): ${e.message}\nTrouble shooting: https://midscenejs.com/model-provider.html`, {
            cause: e
        });
        throw newError;
    }
}
async function callAIWithObjectResponse(messages, modelConfig, options) {
    const response = await callAI(messages, modelConfig, {
        deepThink: options?.deepThink
    });
    assert(response, 'empty response');
    const modelFamily = modelConfig.modelFamily;
    const jsonContent = safeParseJson(response.content, modelFamily);
    if ('object' != typeof jsonContent) throw new AIResponseParseError(`failed to parse json response from model (${modelConfig.modelName}): ${response.content}`, response.content, response.usage);
    return {
        content: jsonContent,
        contentString: response.content,
        usage: response.usage,
        reasoning_content: response.reasoning_content
    };
}
async function callAIWithStringResponse(msgs, modelConfig) {
    const { content, usage } = await callAI(msgs, modelConfig);
    return {
        content,
        usage
    };
}
function extractJSONFromCodeBlock(response) {
    try {
        const jsonMatch = response.match(/^\s*(\{[\s\S]*\})\s*$/);
        if (jsonMatch) return jsonMatch[1];
        const codeBlockMatch = response.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
        if (codeBlockMatch) return codeBlockMatch[1];
        const jsonLikeMatch = response.match(/\{[\s\S]*\}/);
        if (jsonLikeMatch) return jsonLikeMatch[0];
    } catch  {}
    return response;
}
function preprocessDoubaoBboxJson(input) {
    if (input.includes('bbox')) while(/\d+\s+\d+/.test(input))input = input.replace(/(\d+)\s+(\d+)/g, '$1,$2');
    return input;
}
function resolveDeepThinkConfig({ deepThink, modelFamily }) {
    const normalizedDeepThink = 'unset' === deepThink ? void 0 : deepThink;
    if (void 0 === normalizedDeepThink) return {
        config: {},
        debugMessage: void 0
    };
    if ('qwen3-vl' === modelFamily) return {
        config: {
            enable_thinking: normalizedDeepThink
        },
        debugMessage: `deepThink mapped to enable_thinking=${normalizedDeepThink} for qwen3-vl`
    };
    if ('doubao-vision' === modelFamily) return {
        config: {
            thinking: {
                type: normalizedDeepThink ? 'enabled' : 'disabled'
            }
        },
        debugMessage: `deepThink mapped to thinking.type=${normalizedDeepThink ? 'enabled' : 'disabled'} for doubao-vision`
    };
    if ('glm-v' === modelFamily) return {
        config: {
            thinking: {
                type: normalizedDeepThink ? 'enabled' : 'disabled'
            }
        },
        debugMessage: `deepThink mapped to thinking.type=${normalizedDeepThink ? 'enabled' : 'disabled'} for glm-v`
    };
    if ('gpt-5' === modelFamily) return {
        config: normalizedDeepThink ? {
            reasoning: {
                effort: 'high'
            }
        } : {
            reasoning: {
                effort: 'low'
            }
        },
        debugMessage: normalizedDeepThink ? 'deepThink mapped to reasoning.effort=high for gpt-5' : 'deepThink disabled for gpt-5'
    };
    return {
        config: {},
        debugMessage: `deepThink ignored: unsupported model_family "${modelFamily ?? 'default'}"`,
        warningMessage: `The "deepThink" option is not supported for model_family "${modelFamily ?? 'default'}".`
    };
}
function normalizeJsonObject(obj) {
    if (null == obj) return obj;
    if (Array.isArray(obj)) return obj.map((item)=>normalizeJsonObject(item));
    if ('object' == typeof obj) {
        const normalized = {};
        for (const [key, value] of Object.entries(obj)){
            const trimmedKey = key.trim();
            let normalizedValue = normalizeJsonObject(value);
            if ('string' == typeof normalizedValue) normalizedValue = normalizedValue.trim();
            normalized[trimmedKey] = normalizedValue;
        }
        return normalized;
    }
    if ('string' == typeof obj) return obj.trim();
    return obj;
}
function safeParseJson(input, modelFamily) {
    const cleanJsonString = extractJSONFromCodeBlock(input);
    if (cleanJsonString?.match(/\((\d+),(\d+)\)/)) return cleanJsonString.match(/\((\d+),(\d+)\)/)?.slice(1).map(Number);
    let parsed;
    let lastError;
    try {
        parsed = JSON.parse(cleanJsonString);
        return normalizeJsonObject(parsed);
    } catch (error) {
        lastError = error;
    }
    try {
        parsed = JSON.parse(jsonrepair(cleanJsonString));
        return normalizeJsonObject(parsed);
    } catch (error) {
        lastError = error;
    }
    if ('doubao-vision' === modelFamily || isUITars(modelFamily)) {
        const jsonString = preprocessDoubaoBboxJson(cleanJsonString);
        try {
            parsed = JSON.parse(jsonrepair(jsonString));
            return normalizeJsonObject(parsed);
        } catch (error) {
            lastError = error;
        }
    }
    throw Error(`failed to parse LLM response into JSON. Error - ${String(lastError ?? 'unknown error')}. Response - \n ${input}`);
}
export { AIResponseParseError, callAI, callAIWithObjectResponse, callAIWithStringResponse, extractJSONFromCodeBlock, preprocessDoubaoBboxJson, resolveDeepThinkConfig, safeParseJson };

//# sourceMappingURL=index.mjs.map