<?php



namespace HaoZiTeam\ChatGPT;



use Exception;

// Guzzle 相关引用已不再在运行时使用，仅保留向后兼容说明；
// 实际 HTTP 请求改为使用 WordPress HTTP API（wp_remote_request）。



class V2

{

    private $baseUrl = 'https://api.openai.com/';

    private $model;

    private $key;

    private $temperature = 1;

    private $topP = 1.0;

    private $messages = [];

    private $http; // 不再使用 Guzzle 实例，保留属性仅为兼容旧代码结构

    private $httpOptions = [];

    private $isDeepseek = false;

    private $isDoubao = false;

    private $isBaiLian = false;

    private $customHeaders = [];

    private $requestFormat = '';

    private $verify = true; // Guzzle verify: true|false|string(path)
    private $caBundle = null; // detected CA bundle path if any



    /**
     * 初始化ChatGPT对象
     * @param string $key API密钥
     * @param string|null $baseUrl 基础URL，自定义服务器可配置，DeepSeek和豆包API地址已内置固定
     * @param string|null $model 使用的模型
     * @param int|null $temperature 温度
     * @param int|null $topP Top P
     * @param int $timeout 超时时间
     * @param array $options 其他选项
     */
    public function __construct(

        string $key,

        string $baseUrl = null,

        string $model = null,

        float $temperature = null,

        float $topP = null,

        int $timeout = 360,

        array $options = []

    ) {

        // 确保key格式正确
        if (empty($key)) {
            throw new Exception('API key is empty');
        }
        
        // 如果key不以Bearer开头，则添加Bearer前缀
        if (strpos($key, 'Bearer ') !== 0 && strpos($key, 'bearer ') !== 0) {
            $this->key = 'Bearer ' . $key;
            error_log("ChatGPT-PHP: 已添加Bearer前缀到API密钥");
        } else {
            $this->key = $key;
            error_log("ChatGPT-PHP: API密钥已包含Bearer前缀");
        }

        if ($baseUrl) {
            $this->baseUrl = $baseUrl;
        }
        
        if ($model) {
            $this->model = $model; // Use the model provided
            error_log("ChatGPT-PHP: 使用指定模型: {$model}");
        } else {
            // 设置默认模型
            $this->model = 'gpt-3.5-turbo';
            error_log("ChatGPT-PHP: 未指定模型，使用默认值: gpt-3.5-turbo");
        }

        if ($temperature !== null) {
            $this->temperature = $temperature;
        }

        if ($topP !== null) {
            $this->topP = $topP;
        }

        $this->isDeepseek = $options['is_deepseek'] ?? false;
        $this->isDoubao = $options['is_doubao'] ?? false;
        $this->isBaiLian = $options['is_bai_lian'] ?? false;
        $this->customHeaders = $options['headers'] ?? [];
        $this->requestFormat = $options['request_format'] ?? '';

        // SSL verify handling: allow caller to pass verify option, otherwise try to detect CA bundle
        if (array_key_exists('verify', $options)) {
            $this->verify = $options['verify'];
            if (is_string($this->verify)) {
                $this->caBundle = $this->verify;
            }
        } else {
            $this->caBundle = $this->resolveCABundlePath();
            if ($this->caBundle && @file_exists($this->caBundle)) {
                $this->verify = $this->caBundle;
            } else {
                $this->verify = true; // fallback to default
            }
        }

        // 确保 baseUrl 末尾有斜杠，防止路径拼接问题
        if ($this->baseUrl && substr($this->baseUrl, -1) !== '/') {
            $this->baseUrl .= '/';
            error_log("ChatGPT-PHP: 已为baseUrl添加末尾斜杠: {$this->baseUrl}");
        }

        // 2.8.6 之后：不再在这里实例化 Guzzle 客户端，而是改用 WP HTTP API
        $this->http = null;
        $this->httpOptions = [
            'timeout' => $timeout,
            'verify'  => $this->verify,
        ];

        $verify_msg = is_bool($this->verify)
            ? ('verify=' . ($this->verify ? 'true' : 'false'))
            : ('verify_path=' . (string)$this->verify);
        error_log("ChatGPT-PHP: 客户端初始化完成，baseUrl: {$this->baseUrl}, 模型: {$this->model}, {$verify_msg}");
        error_log('ChatGPT-PHP: using WP HTTP API transport instead of Guzzle');
    }

    /**
     * 使用 WordPress HTTP API 发送请求，统一处理 DeepSeek/豆包/百炼/OpenAI 兼容模式。
     *
     * @param string $method HTTP 方法，如 'POST'
     * @param string $path   相对路径，例如 'chat/completions'
     * @param array  $options headers/json/stream 等选项
     * @return string 返回响应 body 字符串
     * @throws Exception
     */
    private function request(string $method, string $path, array $options = [])
    {
        $method = strtoupper($method);
        $url = rtrim($this->baseUrl, '/') . '/' . ltrim($path, '/');

        $args = [
            'method'  => $method,
            'timeout' => isset($this->httpOptions['timeout']) ? (int)$this->httpOptions['timeout'] : 360,
            'headers' => [],
        ];

        // 允许按单次请求覆盖 timeout
        if (isset($options['timeout']) && is_numeric($options['timeout'])) {
            $args['timeout'] = (int)$options['timeout'];
        }

        // SSL 校验：兼容布尔与路径两种形式
        if (is_bool($this->verify)) {
            $args['sslverify'] = $this->verify;
        } elseif (is_string($this->verify) && $this->verify !== '') {
            $args['sslverify'] = true;
            // 在新版本 WP 中可使用 sslcertificates 指定证书包路径
            $args['sslcertificates'] = $this->verify;
        }

        if (isset($options['headers']) && is_array($options['headers'])) {
            $args['headers'] = array_merge($args['headers'], $options['headers']);
        }

        $is_stream = !empty($options['stream']);

        if (isset($options['json'])) {
            // Spark X family (x2/v2) requires messages[0] to be system. Some upstream flows may
            // accidentally produce non-zero-based arrays which JSON-encode as objects, breaking
            // ordering on the server side. Enforce and reindex right before encoding.
            $isSparkXFamilyReq = is_string($url)
                && (strpos($url, 'spark-api-open.xf-yun.com/x2') !== false
                    || strpos($url, 'spark-api-open.xf-yun.com/v2') !== false);
            if ($isSparkXFamilyReq && isset($options['json']['messages']) && is_array($options['json']['messages'])) {
                $msgs = array_values($options['json']['messages']);
                $firstRole = $msgs[0]['role'] ?? '';
                $firstRole = is_string($firstRole) ? strtolower($firstRole) : '';
                if ($firstRole !== 'system') {
                    $systemIndex = -1;
                    foreach ($msgs as $idx => $m) {
                        $r = $m['role'] ?? '';
                        $r = is_string($r) ? strtolower($r) : '';
                        if ($r === 'system') {
                            $systemIndex = (int)$idx;
                            break;
                        }
                    }
                    if ($systemIndex > 0) {
                        $systemMsg = $msgs[$systemIndex];
                        array_splice($msgs, $systemIndex, 1);
                        array_unshift($msgs, $systemMsg);
                    } elseif ($systemIndex < 0) {
                        array_unshift($msgs, [
                            'role' => 'system',
                            'content' => ' ',
                        ]);
                    }
                }

                if (!isset($msgs[0]) || !is_array($msgs[0])) {
                    $msgs = [[ 'role' => 'system', 'content' => ' ' ]];
                }
                $msgs[0]['role'] = 'system';
                if (!isset($msgs[0]['content']) || !is_string($msgs[0]['content']) || trim((string)$msgs[0]['content']) === '') {
                    $msgs[0]['content'] = ' ';
                }
                $options['json']['messages'] = $msgs;
            }

            // Debug for Spark: log final message roles and a short body preview before sending
            $isSparkHost = is_string($url) && strpos($url, 'spark-api-open.xf-yun.com/') !== false;
            if ($isSparkHost) {
                $rolesPreview = [];
                $msgCount = 0;
                if (isset($options['json']['messages']) && is_array($options['json']['messages'])) {
                    $msgCount = count($options['json']['messages']);
                    $i = 0;
                    foreach ($options['json']['messages'] as $m) {
                        if ($i >= 3) {
                            break;
                        }
                        $rolesPreview[] = is_array($m) ? ($m['role'] ?? '') : '';
                        $i++;
                    }
                }
                error_log('ChatGPT-PHP: Spark request about to send, endpoint=' . $url . ', messages_count=' . $msgCount . ', roles0_2=' . implode('|', $rolesPreview));
            }

            $body = function_exists('wp_json_encode') ? wp_json_encode($options['json']) : json_encode($options['json']);
            $args['body'] = $body;

            if (!empty($isSparkHost)) {
                $preview = function_exists('mb_substr') ? mb_substr($body, 0, 300) : substr($body, 0, 300);
                error_log('ChatGPT-PHP: Spark request body preview (first 300): ' . $preview);

                // Spark 长文本偶发 504，适当延长 timeout（不影响其他供应商）
                // - WP HTTP API timeout 单位为秒
                // - 仅在未显式覆盖 timeout 时兜底提升
                if (!isset($options['timeout']) && (int)$args['timeout'] < 600) {
                    $args['timeout'] = 600;
                }
            }
            // 若未显式指定 Content-Type，则补充
            $has_ct = false;
            foreach ($args['headers'] as $hk => $hv) {
                if (strtolower($hk) === 'content-type') {
                    $has_ct = true; break;
                }
            }
            if (!$has_ct) {
                $args['headers']['Content-Type'] = 'application/json';
            }
        }

        // 发送请求
        if (!function_exists('wp_remote_request')) {
            throw new Exception('WordPress HTTP API not available');
        }

        $isSparkHost = is_string($url) && strpos($url, 'spark-api-open.xf-yun.com/') !== false;
        $maxAttempts = $isSparkHost ? 3 : 1;
        $attempt = 0;
        $lastErr = '';
        $lastCode = 0;
        $lastBody = '';

        while ($attempt < $maxAttempts) {
            $attempt++;
            if ($isSparkHost && $maxAttempts > 1) {
                error_log('ChatGPT-PHP: Spark request attempt ' . $attempt . '/' . $maxAttempts . ', timeout=' . (int)$args['timeout'] . 's');
            }

            $response = wp_remote_request($url, $args);

            if (is_wp_error($response)) {
                $msg = $response->get_error_message();
                $lastErr = 'HTTP request failed: ' . $msg;
                error_log('ChatGPT-PHP: ' . $lastErr);

                $isTimeout = stripos($msg, 'timed out') !== false || stripos($msg, 'timeout') !== false;
                if ($isSparkHost && $isTimeout && $attempt < $maxAttempts) {
                    // 简单退避，避免立即重打导致更容易 504
                    usleep(250000 * $attempt);
                    continue;
                }
                throw new Exception($lastErr);
            }

            $code = (int)wp_remote_retrieve_response_code($response);
            $body = (string)wp_remote_retrieve_body($response);
            $lastCode = $code;
            $lastBody = $body;

            if ($code >= 200 && $code < 300) {
                // 目前极光 AI 实际未真正使用流式，这里统一返回完整 body
                if ($is_stream) {
                    // 向后兼容：原来返回的是一个可读流，这里返回字符串供上层按需处理
                    return (string)$body;
                }

                return (string)$body;
            }

            error_log('ChatGPT-PHP: HTTP ' . $code . ' response: ' . substr($body, 0, 200));

            // Spark 偶发 504：重试
            if ($isSparkHost && $code === 504 && $attempt < $maxAttempts) {
                usleep(300000 * $attempt);
                continue;
            }

            throw new Exception('HTTP ' . $code . ': ' . substr($body, 0, 200));
        }

        if ($isSparkHost) {
            throw new Exception('HTTP ' . $lastCode . ': ' . substr($lastBody, 0, 200));
        }

        throw new Exception($lastErr !== '' ? $lastErr : 'HTTP request failed');

        // unreachable
    }

    private function resolveCABundlePath()
    {
        // Priority: env -> ini -> common locations
        $candidates = [];
        $env1 = getenv('CURL_CA_BUNDLE');
        $env2 = getenv('SSL_CERT_FILE');
        if (!empty($env1)) $candidates[] = $env1;
        if (!empty($env2)) $candidates[] = $env2;
        $ini1 = ini_get('curl.cainfo');
        $ini2 = ini_get('openssl.cafile');
        if (!empty($ini1)) $candidates[] = $ini1;
        if (!empty($ini2)) $candidates[] = $ini2;
        // WordPress paths (if running inside WP)
        if (defined('WP_CONTENT_DIR')) {
            $candidates[] = WP_CONTENT_DIR . '/uploads/cacert.pem';
            $candidates[] = WP_CONTENT_DIR . '/plugins/ai-post/resources/cacert.pem';
        }
        // Common Windows BtSoft paths (best-effort)
        $candidates[] = 'D:\\BtSoft\\php\\80\\extras\\ssl\\cacert.pem';
        $candidates[] = 'D:\\BtSoft\\php\\82\\extras\\ssl\\cacert.pem';

        foreach ($candidates as $p) {
            if (!empty($p) && @file_exists($p)) {
                $size = @filesize($p);
                error_log('ChatGPT-PHP: 检测到可用 CA 路径: ' . $p . ' (size=' . ($size === false ? 'n/a' : $size) . ' bytes)');
                return $p;
            }
        }
        error_log('ChatGPT-PHP: 未检测到可用 CA 路径，使用系统默认 verify');
        return null;
    }



    public function getModel()

    {

        return $this->model; // Add a method to retrieve the model if needed

    }



    /**

     * 添加消息

     * @param  string  $message

     * @param  string  $role

     * @return void

     */

    public function addMessage(string $message, string $role = 'user'): void

    {

        $this->messages[] = [

            'role' => $role,

            'content' => $message,

        ];

    }



    public function clearMessages(): void

    {

        $this->messages = [];

    }



    /**

     * 发送消息

     * @param  string  $prompt

     * @param  string|null  $user

     * @param  bool  $stream

     * @return mixed

     * @throws Exception

     */

    public function ask(string $prompt, string $user = null, bool $stream = false)

    {

        if ($this->isDeepseek) {

            error_log("DeepSeek ask method called");

            error_log("Prompt: " . $prompt);

            

            $this->addMessage($prompt);

            // Moonshot K2 系列模型：接口要求 top_p 只能为 0.95
            if (is_string($this->model) && strpos($this->model, 'kimi-k2') === 0) {
                $this->topP = 0.95;
            }

            error_log("Messages: " . json_encode($this->messages));



            $data = [

                'model' => $this->model,

                'messages' => $this->messages,

                'temperature' => 0.7,

                'max_tokens' => 2000,

                'stream' => false

            ];

            error_log("Request data: " . json_encode($data));



            try {

                // 使用rtrim移除baseUrl末尾的斜杠，然后使用正确格式拼接路径
                $apiEndpoint = rtrim($this->baseUrl, '/') . '/chat/completions';
                error_log("Sending request to: " . $apiEndpoint);
                $body = $this->request('POST', 'chat/completions', [
                    'headers' => array_merge([
                        'Authorization' => $this->key,
                        'Content-Type' => 'application/json',
                    ], $this->customHeaders),
                    'json' => $data,
                ]);

                $result = json_decode($body, true);

                error_log("API Response: " . json_encode($result));

                

                if (isset($result['error'])) {

                    error_log("API Error: " . json_encode($result['error']));

                    throw new Exception($result['error']['message'] ?? 'Unknown DeepSeek API error');

                }



                if (isset($result['choices'][0]['message']['content'])) {

                    $this->addMessage($result['choices'][0]['message']['content'], 'assistant');

                }

                

                return [

                    'answer' => $result['choices'][0]['message']['content'] ?? '',

                    'id' => $result['id'] ?? '',

                    'model' => $this->model,

                    'usage' => $result['usage'] ?? []

                ];

            } catch (Exception $e) {

                error_log("DeepSeek request failed: " . $e->getMessage());

                error_log("Stack trace: " . $e->getTraceAsString());

                throw $e;

            }

        } elseif ($this->isDoubao) {
            error_log("Doubao ask method called");
            error_log("Prompt: " . $prompt);

            $this->addMessage($prompt);
            error_log("Messages: " . json_encode($this->messages));

            $data = [
                'model' => $this->model,
                'messages' => $this->messages,
                'stream' => false
            ];
            error_log("Request data: " . json_encode($data));

            try {
                // 修正 API 路径，确保使用正确格式拼接
                $apiEndpoint = rtrim($this->baseUrl, '/') . '/chat/completions';
                error_log("Sending request to: " . $apiEndpoint);

                $body = $this->request('POST', 'chat/completions', [
                    'headers' => array_merge([
                    ], $this->customHeaders),
                    'json' => $data,
                ]);

                $result = json_decode($body, true);
                error_log("Doubao API Response: " . json_encode($result));

                if (isset($result['error'])) {
                    error_log("Doubao API Error: " . json_encode($result['error']));
                    throw new Exception($result['error']['message'] ?? 'Doubao API error');
                }

                $answer = '';
                if (isset($result['choices'][0]['message']['content'])) {
                    $answer = $result['choices'][0]['message']['content'];
                    $this->addMessage($answer, 'assistant');
                }

                return [
                    'answer' => $answer,
                    'id' => $result['id'] ?? ('doubao-' . uniqid()),
                    'model' => $this->model,
                    'usage' => $result['usage'] ?? []
                ];

            } catch (Exception $e) {
                error_log("Doubao API request failed: " . $e->getMessage());
                throw $e;
            }
        } elseif ($this->isBaiLian) {
            error_log("Bai Lian ask method called");
            error_log("Prompt: " . $prompt);
            
            $this->addMessage($prompt);
            error_log("Messages: " . json_encode($this->messages));

            // 获取传入的温度参数，如果未指定则使用默认值0.7
            $temperature = isset($this->temperature) && $this->temperature > 0 ? $this->temperature : 0.7;
            
            $data = [
                'model' => $this->model,
                'messages' => $this->messages,
                'stream' => false,
                'temperature' => $temperature
            ];
            error_log("Request data: " . json_encode($data));

            try {
                // 使用阿里百炼的兼容模式API
                $apiEndpoint = rtrim($this->baseUrl, '/') . '/chat/completions';
                error_log("Sending request to: " . $apiEndpoint);

                $body = $this->request('POST', 'chat/completions', [
                    'headers' => array_merge([
                        'Authorization' => $this->key, // 确保使用正确的授权头
                        'Content-Type' => 'application/json',
                    ], $this->customHeaders),
                    'json' => $data,
                ]);

                $result = json_decode($body, true);
                error_log("Bai Lian API Response: " . json_encode($result));

                if (isset($result['error'])) {
                    error_log("Bai Lian API Error: " . json_encode($result['error']));
                    throw new Exception($result['error']['message'] ?? 'Bai Lian API error');
                }

                $answer = '';
                if (isset($result['choices'][0]['message']['content'])) {
                    $answer = $result['choices'][0]['message']['content'];
                    $this->addMessage($answer, 'assistant');
                }

                return [
                    'answer' => $answer,
                    'id' => $result['id'] ?? ('bai-lian-' . uniqid()),
                    'model' => $this->model,
                    'usage' => $result['usage'] ?? []
                ];

            } catch (Exception $e) {
                error_log("Bai Lian API request failed: " . $e->getMessage());
                throw $e;
            }
        } else {
            error_log("Standard API ask method called");
            // Use multibyte-safe substring to prevent garbled logs when prompt contains CJK characters
            $___prompt_preview = function_exists('mb_substr') ? mb_substr($prompt, 0, 100) : substr($prompt, 0, 100);
            error_log("Prompt: " . $___prompt_preview . "...");
            error_log("Using model: " . $this->model);
            error_log("API URL: " . $this->baseUrl);
            
            // 检查密钥
            $keyHint = substr($this->key, 0, 10) . "..." . substr($this->key, -5);
            error_log("Using key: " . $keyHint);
            
            $this->addMessage($prompt);



            // 讯飞星火 X 系列（x2/v2）接口：要求 messages[0] 必须是 system，否则会报 NotFirstSystemError(10049)
            // 这里做一次兜底修正，避免上游遗漏 clearMessages 或 system 注入导致的失败。
            $isSparkXFamily = is_string($this->baseUrl)
                && (strpos($this->baseUrl, 'spark-api-open.xf-yun.com/x2') !== false
                    || strpos($this->baseUrl, 'spark-api-open.xf-yun.com/v2') !== false);
            if ($isSparkXFamily) {
                // 先重建索引，避免上游传入/splice 后出现非连续索引导致的首条判断偏差
                $this->messages = array_values($this->messages);
                $firstRole = $this->messages[0]['role'] ?? '';
                $firstRole = is_string($firstRole) ? strtolower($firstRole) : '';
                if ($firstRole !== 'system') {
                    $systemIndex = -1;
                    foreach ($this->messages as $idx => $msg) {
                        $r = $msg['role'] ?? '';
                        $r = is_string($r) ? strtolower($r) : '';
                        if ($r === 'system') {
                            $systemIndex = (int)$idx;
                            break;
                        }
                    }
                    if ($systemIndex > 0) {
                        $systemMsg = $this->messages[$systemIndex];
                        array_splice($this->messages, $systemIndex, 1);
                        array_unshift($this->messages, $systemMsg);
                    } elseif ($systemIndex < 0) {
                        array_unshift($this->messages, [
                            'role' => 'system',
                            'content' => ' ',
                        ]);
                    }
                }

                // 再兜底一次：无论如何强制首条 role 为 system，并确保 content 非空（避免部分实现把空 system 当作无效）
                if (!isset($this->messages[0]) || !is_array($this->messages[0])) {
                    $this->messages = [[ 'role' => 'system', 'content' => ' ' ]];
                }
                $this->messages[0]['role'] = 'system';
                if (!isset($this->messages[0]['content']) || !is_string($this->messages[0]['content']) || trim($this->messages[0]['content']) === '') {
                    $this->messages[0]['content'] = ' ';
                }

                $___first_role_dbg = $this->messages[0]['role'] ?? '';
                $___first_len_dbg = is_string($this->messages[0]['content'] ?? null) ? strlen((string)$this->messages[0]['content']) : 0;
                error_log('ChatGPT-PHP: Spark X family system-first enforced, first_role=' . $___first_role_dbg . ', first_content_len=' . $___first_len_dbg . ', messages_count=' . count($this->messages));

                // Spark X family strict compatibility: keep only [system, user] to avoid server-side
                // validation errors when multiple assistant/user turns are included.
                // Some endpoints return NotFirstSystemError even when first role is system if the
                // conversation contains extra roles/turns.
                try {
                    $systemMsg = [ 'role' => 'system', 'content' => ' ' ];
                    if (isset($this->messages[0]) && is_array($this->messages[0])) {
                        $r0 = $this->messages[0]['role'] ?? '';
                        $r0 = is_string($r0) ? strtolower($r0) : '';
                        if ($r0 === 'system') {
                            $c0 = $this->messages[0]['content'] ?? ' ';
                            $c0 = is_string($c0) ? $c0 : ' ';
                            $systemMsg = [ 'role' => 'system', 'content' => (trim($c0) === '' ? ' ' : $c0) ];
                        }
                    }
                    $this->messages = [
                        $systemMsg,
                        [ 'role' => 'user', 'content' => (string)$prompt ],
                    ];
                    error_log('ChatGPT-PHP: Spark X family messages trimmed to system+user, messages_count=' . count($this->messages));
                } catch (\Throwable $e) {
                    // ignore
                }
            }

            // Moonshot K2 系列模型：接口要求 top_p 只能为 0.95
            if (is_string($this->model) && strpos($this->model, 'kimi-k2') === 0) {
                $this->topP = 0.95;
            }

            $data = [
                'model' => $this->model,
                'messages' => $this->messages,
                'stream' => $stream,
                'temperature' => $this->temperature,
                'top_p' => $this->topP,
                'n' => 1,
                'user' => $user ?? 'chatgpt-php',
            ];



            // 对讯飞星火 X2/V2（spark-x）兜底补 max_tokens，避免默认输出过短
            if ($isSparkXFamily && is_string($this->model) && $this->model === 'spark-x') {
                if (!isset($data['max_tokens'])) {
                    $data['max_tokens'] = 4096;
                }
                // 官方文档示例包含 top_k；未设置时补默认值
                if (!isset($data['top_k'])) {
                    $data['top_k'] = 4;
                }
            }
            
            error_log("Request data prepared: " . json_encode([
                'model' => $this->model,
                'messages_count' => count($this->messages),
                'stream' => $stream,
                'temperature' => $this->temperature,
                'top_p' => $this->topP,
                'max_tokens' => $data['max_tokens'] ?? null,
                'top_k' => $data['top_k'] ?? null,
            ]));

            try {
                // 说明：此处的 baseUrl 已在上游规范化为以 "/v1/" 结尾
                $endpoint = rtrim($this->baseUrl, '/') . '/chat/completions';
                error_log("Sending request to: " . $endpoint);

                $body = $this->request(
                    'POST',
                    'chat/completions',
                    [
                        'json' => $data,
                        'headers' => [
                            'Authorization' => $this->key,
                        ],
                        'stream' => $stream,
                    ]
                );

                error_log("Received response body length: " . strlen($body));
            } catch (Exception $e) {
                error_log("API request failed: " . $e->getMessage());
                throw $e;
            }

            // 目前插件不真正依赖流式返回，这里统一按完整 body 处理
            if ($stream) {
                // 向后兼容：返回字符串形式的 body
                $responseBody = (string)$body;
            } else {
                $responseBody = (string)$body;
            }

            $data = json_decode($responseBody, true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                error_log("Response is not valid JSON: " . substr($responseBody, 0, 100));
                throw new Exception('Response is not json: ' . substr($responseBody, 0, 100));
            }

            if (!$this->checkFields($data)) {
                error_log("Missing fields in response: " . json_encode($data));
                throw new Exception('Field missing in API response');
            }

            $answer = $data['choices'][0]['message']['content'];
            $this->addMessage($answer, 'assistant');
            
            error_log("Successfully received response with answer length: " . strlen($answer));

            return [
                'answer' => $answer,
                'id' => $data['id'] ?? ($data['sid'] ?? ''),
                'model' => $this->model,
                'usage' => $data['usage'] ?? [],
            ];
        }
    }



    /**

     * 检查响应行是否包含必要的字段

     * @param  mixed  $line

     * @return bool

     */

    public function checkFields($line): bool

    {

        // 兼容不同 OpenAI 兼容接口实现：部分供应商可能不返回 id / usage
        return isset($line['choices'][0]['message']['content']);

    }



    /**

     * 检查流响应行是否包含必要的字段

     * @param  mixed  $line

     * @return bool

     */

    public function checkStreamFields($line): bool

    {

        return isset($line['choices'][0]['delta']['content']) && isset($line['id']);

    }



    /**

     * 格式化流消息为数组

     * @param  string  $line

     * @return mixed

     */

    public function formatStreamMessage(string $line)

    {

        preg_match('/data: (.*)/', $line, $matches);

        if (empty($matches[1])) {

            return false;

        }



        $line = $matches[1];

        $data = json_decode($line, true);



        if (json_last_error() !== JSON_ERROR_NONE) {

            return false;

        }



        return $data;

    }

}

