feat(opencode): 新增 OpenCode server provider 串接

This commit is contained in:
2026-06-17 06:50:44 +00:00
parent ffc9038923
commit 98eaeb6050
4 changed files with 138 additions and 7 deletions
+32 -3
View File
@@ -15,6 +15,17 @@ import { step, line, ok, error } from './log.js';
const httpsAgent = GITEA_SKIP_TLS_VERIFY ? new https.Agent({ rejectUnauthorized: false }) : undefined;
const api = (path) => `${GITEA_SERVER_URL.replace(/\/$/, '')}/api/v1${path}`;
const giteaHeaders = (token) => ({ Authorization: `token ${token}`, 'Content-Type': 'application/json' });
const usesResponsesApi = (provider, model) => provider === 'openai' && /^gpt-5\.5(?:-|$)/i.test(model || '');
const opencodeModelConfig = (model) => {
const [providerID, modelID] = model.includes('/') ? model.split('/', 2) : [process.env.OPENCODE_PROVIDER || 'google', model];
return { providerID, modelID };
};
const applyOpenCodeAuth = (headers) => {
const password = process.env.OPENCODE_SERVER_PASSWORD;
if (!password) return;
const username = process.env.OPENCODE_SERVER_USERNAME || 'opencode';
headers['Authorization'] = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
};
function giteaErr(e) {
const status = e.response?.status;
@@ -63,6 +74,7 @@ export async function verifyLLM() {
if (!baseURL) return { ok: false, provider, error: `${provider} 缺少 base URL` };
const base = baseURL.replace(/\/$/, '');
const headers = { 'Content-Type': 'application/json' };
if (provider === 'ollama') {
try {
@@ -73,14 +85,31 @@ export async function verifyLLM() {
}
}
const headers = { 'Content-Type': 'application/json' };
if (provider === 'opencode') {
const { providerID, modelID } = opencodeModelConfig(model);
applyOpenCodeAuth(headers);
try {
await axios.get(`${base}/global/health`, { headers, timeout: 30000 });
const providers = await axios.get(`${base}/config/providers`, { headers, timeout: 30000 });
const configuredProvider = providers.data.providers?.find(p => p.id === providerID);
if (!configuredProvider) return { ok: false, provider, error: `OpenCode server 未設定 provider=${providerID}` };
if (!configuredProvider.models?.[modelID]) return { ok: false, provider, error: `OpenCode server provider=${providerID} 未列出 model=${modelID}` };
return { ok: true, provider };
} catch (e) {
return { ok: false, provider, error: `OpenCode server 驗證失敗: ${e.message}` };
}
}
if (provider === 'claude') headers['anthropic-version'] = '2023-06-01';
const payload = { model, messages: [{ role: 'user', content: 'ping' }], max_tokens: 1, temperature: 0 };
const endpoint = usesResponsesApi(provider, model) ? `${base}/responses` : `${base}/chat/completions`;
const payload = usesResponsesApi(provider, model)
? { model, input: 'ping', max_output_tokens: 1, temperature: 0 }
: { model, messages: [{ role: 'user', content: 'ping' }], max_tokens: 1, temperature: 0 };
for (let i = 0; i < apiKeys.length; i++) {
headers['Authorization'] = `Bearer ${apiKeys[i]}`;
try {
await axios.post(`${base}/chat/completions`, payload, { headers, timeout: 30000 });
await axios.post(endpoint, payload, { headers, timeout: 30000 });
return { ok: true, provider, keyIndex: i + 1, total: apiKeys.length };
} catch (e) {
line(`[preflight] LLM key[${i + 1}/${apiKeys.length}] 驗證失敗: ${e.message}`);