改用 AI 助理 CLI 並強化 AI Code Review 執行流程 #13
+23
-6
@@ -1,7 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import { getLLMConfig, getOpenCodeHttpsAgent } from './config.js';
|
||||
import { recordUsage } from './usage.js';
|
||||
import { line, error } from './log.js';
|
||||
import { line } from './log.js';
|
||||
|
||||
/**
|
||||
* 將模型識別字串解析為 OpenCode API 所需的 provider 與 model 識別碼。
|
||||
@@ -49,6 +49,23 @@ function extractOpenCodeContent(data) {
|
||||
.join('');
|
||||
}
|
||||
|
||||
function summarizeErrorResponse(data) {
|
||||
if (data == null) return '';
|
||||
if (typeof data === 'string') return data.slice(0, 500);
|
||||
try {
|
||||
return JSON.stringify(data).slice(0, 500);
|
||||
} catch {
|
||||
return String(data).slice(0, 500);
|
||||
}
|
||||
}
|
||||
|
||||
function formatOpenCodeError(e) {
|
||||
const status = e.response?.status;
|
||||
const response = summarizeErrorResponse(e.response?.data);
|
||||
const statusText = status ? `HTTP ${status}` : e.message;
|
||||
return response ? `${statusText}: ${response}` : statusText;
|
||||
}
|
||||
|
||||
/**
|
||||
* 對 OpenCode server 執行一次完整對話:建立 session 後送出訊息並回傳結果。
|
||||
*
|
||||
@@ -91,8 +108,8 @@ async function chatOpenCode(baseURL, model, systemPrompt, userContent, headers)
|
||||
* 對 OpenCode server 送出一次對話請求並回傳模型純文字回應。
|
||||
*
|
||||
* 從設定取得 provider/baseURL/model;未設定 provider 時拋錯。成功時記錄
|
||||
* usage 並回傳內容。OpenCode 呼叫失敗時會記錄錯誤並以 `process.exit(1)`
|
||||
* 終止整個行程(不會回傳)。
|
||||
* usage 並回傳內容。OpenCode 呼叫失敗時會記錄錯誤並向外拋出,讓呼叫端
|
||||
* 決定是否降級、略過單一角色或終止整體流程。
|
||||
*
|
||||
* @param {string} systemPrompt - 系統提示詞。
|
||||
* @param {string} userContent - 使用者輸入內容。
|
||||
@@ -112,10 +129,10 @@ export async function chat(systemPrompt, userContent) {
|
||||
recordUsage(data);
|
||||
return content;
|
||||
} catch (e) {
|
||||
line(`[LLM] OpenCode 呼叫失敗: ${e.message}`);
|
||||
const message = formatOpenCodeError(e);
|
||||
line(`[LLM] OpenCode 呼叫失敗: ${message}`);
|
||||
throw new Error(message);
|
||||
}
|
||||
error('[LLM] OpenCode 呼叫失敗,終止流程');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+9
-1
@@ -122,10 +122,18 @@ async function main() {
|
||||
}
|
||||
const analyses = await Promise.allSettled(roles.map(role => analyzeWithRole(role, diff)));
|
||||
const newFindings = [];
|
||||
let fulfilledAnalyses = 0;
|
||||
for (let i = 0; i < analyses.length; i++) {
|
||||
if (analyses[i].status === 'fulfilled') newFindings.push(...analyses[i].value);
|
||||
if (analyses[i].status === 'fulfilled') {
|
||||
fulfilledAnalyses += 1;
|
||||
newFindings.push(...analyses[i].value);
|
||||
}
|
||||
else warn(`[${roles[i].name}] 分析失敗(跳過): ${analyses[i].reason?.message}`);
|
||||
}
|
||||
if (fulfilledAnalyses === 0) {
|
||||
result(false, '所有角色分析皆失敗,終止流程以避免誤判為審查通過');
|
||||
process.exit(1);
|
||||
}
|
||||
// 對只有檔名、缺行號的問題,反問原角色補上行號(最多重試數次),確保後續能行內標註
|
||||
await resolveMissingLineNumbers(newFindings, diff);
|
||||
output(`新 findings ${newFindings.length} 筆(${formatFindingsStatsLine(newFindings)})`);
|
||||
|
||||
@@ -85,14 +85,18 @@ describe('chat - OpenCode', async () => {
|
||||
assert.equal(result, 'hello world');
|
||||
});
|
||||
|
||||
it('calls process.exit(1) when OpenCode fails', async () => {
|
||||
it('throws an error when OpenCode fails instead of exiting the process', async () => {
|
||||
process.env.OPENCODE_BASE_URL = 'http://opencode.local:4096';
|
||||
mock.method(axios, 'post', async () => { throw new Error('fail'); });
|
||||
const exitMock = mock.method(process, 'exit', () => { throw new Error('exit:1'); });
|
||||
mock.method(axios, 'post', async () => {
|
||||
const err = new Error('Request failed with status code 500');
|
||||
err.response = { status: 500, data: { error: 'provider overloaded' } };
|
||||
throw err;
|
||||
});
|
||||
const exitMock = mock.method(process, 'exit', () => { throw new Error('exit should not be called'); });
|
||||
|
||||
await assert.rejects(() => chat('sys', 'user'), /exit:1/);
|
||||
await assert.rejects(() => chat('sys', 'user'), /HTTP 500.*provider overloaded/);
|
||||
|
||||
assert.equal(exitMock.mock.calls[0].arguments[0], 1);
|
||||
assert.equal(exitMock.mock.calls.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user