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";
async function createChatClient({ AIActionTypeValue, modelConfig }) {
    const { socksProxy, httpProxy, modelName, openaiBaseURL, openaiApiKey, openaiExtraConfig, modelDescription, uiTarsModelVersion: uiTarsVersion, vlMode, createOpenAIClient, timeout } = modelConfig;
    let proxyAgent;
    const debugProxy = getDebug('ai:call:proxy');
    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) console.warn('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) console.warn('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) {
            console.error('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');
        console.log('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');
        console.log('DEBUGGING MODE: langfuse wrapper enabled');
        const langfuseModule = 'langfuse';
        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,
        uiTarsVersion,
        vlMode
    };
}
async function callAI(messages, AIActionTypeValue, modelConfig, options) {
    const { completion, modelName, modelDescription, uiTarsVersion, vlMode } = await createChatClient({
        AIActionTypeValue,
        modelConfig
    });
    const maxTokens = globalConfigManager.getEnvConfigValue(MIDSCENE_MODEL_MAX_TOKENS) ?? globalConfigManager.getEnvConfigValue(OPENAI_MAX_TOKENS);
    const debugCall = getDebug('ai:call');
    const debugProfileStats = getDebug('ai:profile:stats');
    const debugProfileDetail = getDebug('ai:profile:detail');
    const startTime = Date.now();
    const isStreaming = options?.stream && options?.onChunk;
    let content;
    let accumulated = '';
    let usage;
    let timeCost;
    const buildUsageInfo = (usageData)=>{
        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
        };
    };
    const commonConfig = {
        temperature: 'vlm-ui-tars' === vlMode ? 0.0 : void 0,
        stream: !!isStreaming,
        max_tokens: 'number' == typeof maxTokens ? maxTokens : void 0,
        ...'qwen2.5-vl' === vlMode ? {
            vl_high_resolution_images: true
        } : {}
    };
    try {
        debugCall(`sending ${isStreaming ? 'streaming ' : ''}request to ${modelName}`);
        if (isStreaming) {
            const stream = await completion.create({
                model: modelName,
                messages,
                ...commonConfig
            }, {
                stream: true
            });
            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;
                    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)
                    };
                    options.onChunk(finalChunk);
                    break;
                }
            }
            content = accumulated;
            debugProfileStats(`streaming model, ${modelName}, mode, ${vlMode || 'default'}, cost-ms, ${timeCost}`);
        } else {
            const result = await completion.create({
                model: modelName,
                messages,
                ...commonConfig
            });
            timeCost = Date.now() - startTime;
            debugProfileStats(`model, ${modelName}, mode, ${vlMode || 'default'}, ui-tars-version, ${uiTarsVersion}, 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 || ''}`);
            debugProfileDetail(`model usage detail: ${JSON.stringify(result.usage)}`);
            assert(result.choices, `invalid response from LLM service: ${JSON.stringify(result)}`);
            content = result.choices[0].message.content;
            usage = result.usage;
        }
        debugCall(`response: ${content}`);
        assert(content, 'empty 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 || '',
            usage: buildUsageInfo(usage),
            isStreamed: !!isStreaming
        };
    } catch (e) {
        console.error(' call AI error', e);
        const newError = new Error(`failed to call ${isStreaming ? 'streaming ' : ''}AI model service (${modelName}): ${e.message}. Trouble shooting: https://midscenejs.com/model-provider.html`, {
            cause: e
        });
        throw newError;
    }
}
async function callAIWithObjectResponse(messages, AIActionTypeValue, modelConfig) {
    const response = await callAI(messages, AIActionTypeValue, modelConfig);
    assert(response, 'empty response');
    const vlMode = modelConfig.vlMode;
    const jsonContent = safeParseJson(response.content, vlMode);
    assert('object' == typeof jsonContent, `failed to parse json response from model (${modelConfig.modelName}): ${response.content}`);
    return {
        content: jsonContent,
        contentString: response.content,
        usage: response.usage
    };
}
async function callAIWithStringResponse(msgs, AIActionTypeValue, modelConfig) {
    const { content, usage } = await callAI(msgs, AIActionTypeValue, 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 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, vlMode) {
    const cleanJsonString = extractJSONFromCodeBlock(input);
    if (cleanJsonString?.match(/\((\d+),(\d+)\)/)) return cleanJsonString.match(/\((\d+),(\d+)\)/)?.slice(1).map(Number);
    let parsed;
    try {
        parsed = JSON.parse(cleanJsonString);
        return normalizeJsonObject(parsed);
    } catch  {}
    try {
        parsed = JSON.parse(jsonrepair(cleanJsonString));
        return normalizeJsonObject(parsed);
    } catch (e) {}
    if ('doubao-vision' === vlMode || 'vlm-ui-tars' === vlMode) {
        const jsonString = preprocessDoubaoBboxJson(cleanJsonString);
        parsed = JSON.parse(jsonrepair(jsonString));
        return normalizeJsonObject(parsed);
    }
    throw Error(`failed to parse json response: ${input}`);
}
export { callAI, callAIWithObjectResponse, callAIWithStringResponse, extractJSONFromCodeBlock, preprocessDoubaoBboxJson, safeParseJson };

//# sourceMappingURL=index.mjs.map