fix(llm): OpenCode 呼叫加逾時、失敗改丟例外以優雅降級

opencodeAxiosOptions 加 timeout(OPENCODE_TIMEOUT_MS,預設 120s),避免 server/模型停滯時請求無限等待;chat() 失敗改 throw 而非 process.exit(1),讓 Step5 的 Promise.allSettled 與去重/過濾的 try/catch fallback 能各自優雅降級,不再因單一 LLM 失敗砍掉整個流程。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeffery
2026-06-26 16:47:28 +08:00
co-authored by Claude Opus 4.8
parent 11551f8f58
commit 98da4d4143
+13 -7
View File
@@ -3,6 +3,10 @@ import { getLLMConfig, getOpenCodeHttpsAgent } from './config.js';
import { recordUsage } from './usage.js'; import { recordUsage } from './usage.js';
import { line, error } from './log.js'; import { line, error } from './log.js';
// 單次 OpenCode HTTP 請求的逾時(毫秒)。預設 120 秒,可用 OPENCODE_TIMEOUT_MS 覆寫。
// 沒有逾時時,server / 模型卡住會讓請求無限等待、整個流程停擺。
const OPENCODE_TIMEOUT_MS = Number(process.env.OPENCODE_TIMEOUT_MS) || 120000;
/** /**
* 將模型識別字串解析為 OpenCode API 所需的 provider 與 model 識別碼。 * 將模型識別字串解析為 OpenCode API 所需的 provider 與 model 識別碼。
* *
@@ -23,12 +27,14 @@ function opencodeModelConfig(model) {
* 供本模組所有 OpenCode HTTP 呼叫共用,集中管理連線設定。 * 供本模組所有 OpenCode HTTP 呼叫共用,集中管理連線設定。
* *
* @param {Record<string, string>} headers - 要附加於請求的 HTTP 標頭。 * @param {Record<string, string>} headers - 要附加於請求的 HTTP 標頭。
* @returns {{ headers: Record<string, string>, httpsAgent: import('https').Agent }} axios 請求選項物件。 * @returns {{ headers: Record<string, string>, httpsAgent: import('https').Agent, timeout: number }} axios 請求選項物件。
* @remarks 帶 `timeout``OPENCODE_TIMEOUT_MS`,預設 120s),避免 server/模型停滯時請求無限等待。
*/ */
function opencodeAxiosOptions(headers) { function opencodeAxiosOptions(headers) {
return { return {
headers, headers,
httpsAgent: getOpenCodeHttpsAgent(), httpsAgent: getOpenCodeHttpsAgent(),
timeout: OPENCODE_TIMEOUT_MS,
}; };
} }
@@ -91,13 +97,14 @@ async function chatOpenCode(baseURL, model, systemPrompt, userContent, headers)
* 對 OpenCode server 送出一次對話請求並回傳模型純文字回應。 * 對 OpenCode server 送出一次對話請求並回傳模型純文字回應。
* *
* 從設定取得 provider/baseURL/model;未設定 provider 時拋錯。成功時記錄 * 從設定取得 provider/baseURL/model;未設定 provider 時拋錯。成功時記錄
* usage 並回傳內容。OpenCode 呼叫失敗時會記錄錯誤並以 `process.exit(1)` * usage 並回傳內容。OpenCode 呼叫失敗(含逾時)時記錄錯誤並向外**拋出例外**,
* 終止整個行程(不會回傳)。 * 交由呼叫端處理——多數呼叫端(去重、誤報過濾、補行號)有 try/catch fallback
* 角色分析則在 Step5 以 `Promise.allSettled` 略過失敗的單一角色,達到優雅降級。
* *
* @param {string} systemPrompt - 系統提示詞。 * @param {string} systemPrompt - 系統提示詞。
* @param {string} userContent - 使用者輸入內容。 * @param {string} userContent - 使用者輸入內容。
* @returns {Promise<string>} 模型回應的純文字內容。 * @returns {Promise<string>} 模型回應的純文字內容。
* @throws {Error} 當未設定 OpenCode server(缺少 provider)時。 * @throws {Error} 當未設定 OpenCode server(缺少 provider)時,或 OpenCode 呼叫失敗/逾時時
*/ */
export async function chat(systemPrompt, userContent) { export async function chat(systemPrompt, userContent) {
const { provider, baseURL, model } = getLLMConfig(); const { provider, baseURL, model } = getLLMConfig();
@@ -112,10 +119,9 @@ export async function chat(systemPrompt, userContent) {
recordUsage(data); recordUsage(data);
return content; return content;
} catch (e) { } catch (e) {
line(`[LLM] OpenCode 呼叫失敗: ${e.message}`); error(`[LLM] OpenCode 呼叫失敗: ${e.message}`);
throw e;
} }
error('[LLM] OpenCode 呼叫失敗,終止流程');
process.exit(1);
} }
/** /**