opencode-codebuddy-external-auth 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin.js +140 -64
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -8,13 +8,22 @@ const PROVIDER_ID = "codebuddy-external";
|
|
|
8
8
|
const CONFIG = {
|
|
9
9
|
// IOA 版本使用 copilot.tencent.com 进行认证
|
|
10
10
|
serverUrl: "https://copilot.tencent.com",
|
|
11
|
-
//
|
|
12
|
-
|
|
11
|
+
// 真实对话 API 路径
|
|
12
|
+
chatCompletionsPath: "/v2/chat/completions",
|
|
13
13
|
// 平台标识
|
|
14
14
|
platform: "CLI",
|
|
15
15
|
appVersion: "2.37.20",
|
|
16
|
-
|
|
16
|
+
ideName: "CLI",
|
|
17
|
+
ideType: "CLI",
|
|
18
|
+
domain: "tencent.sso.copilot.tencent.com",
|
|
19
|
+
product: "SaaS",
|
|
20
|
+
agentIntent: "craft",
|
|
21
|
+
// 使用 HTTP Auth API 模式还是 CLI 模式
|
|
17
22
|
useHttpApi: true,
|
|
23
|
+
// 可通过环境变量注入(从零启动时必需)
|
|
24
|
+
tenantId: process.env.CODEBUDDY_TENANT_ID || "",
|
|
25
|
+
enterpriseId: process.env.CODEBUDDY_ENTERPRISE_ID || "",
|
|
26
|
+
userId: process.env.CODEBUDDY_USER_ID || "",
|
|
18
27
|
};
|
|
19
28
|
// ============================================================================
|
|
20
29
|
// Utility Functions
|
|
@@ -22,6 +31,97 @@ const CONFIG = {
|
|
|
22
31
|
function sleep(ms) {
|
|
23
32
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
24
33
|
}
|
|
34
|
+
function generateUuid() {
|
|
35
|
+
if (globalThis.crypto?.randomUUID) {
|
|
36
|
+
return globalThis.crypto.randomUUID();
|
|
37
|
+
}
|
|
38
|
+
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
39
|
+
}
|
|
40
|
+
function decodeJwtPayload(token) {
|
|
41
|
+
try {
|
|
42
|
+
const parts = token.split(".");
|
|
43
|
+
if (parts.length < 2)
|
|
44
|
+
return null;
|
|
45
|
+
const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
46
|
+
const pad = "=".repeat((4 - (payload.length % 4)) % 4);
|
|
47
|
+
const json = Buffer.from(payload + pad, "base64").toString("utf8");
|
|
48
|
+
return JSON.parse(json);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function extractTenantIdFromIss(iss) {
|
|
55
|
+
if (!iss)
|
|
56
|
+
return "";
|
|
57
|
+
const match = iss.match(/realms\/sso-([^/]+)$/);
|
|
58
|
+
return match?.[1] || "";
|
|
59
|
+
}
|
|
60
|
+
function resolveTenantId(accessToken) {
|
|
61
|
+
if (CONFIG.tenantId)
|
|
62
|
+
return CONFIG.tenantId;
|
|
63
|
+
const payload = decodeJwtPayload(accessToken);
|
|
64
|
+
return (payload?.tenant_id ||
|
|
65
|
+
payload?.tenantId ||
|
|
66
|
+
extractTenantIdFromIss(payload?.iss));
|
|
67
|
+
}
|
|
68
|
+
function resolveEnterpriseId(accessToken) {
|
|
69
|
+
if (CONFIG.enterpriseId)
|
|
70
|
+
return CONFIG.enterpriseId;
|
|
71
|
+
const payload = decodeJwtPayload(accessToken);
|
|
72
|
+
return (payload?.enterprise_id ||
|
|
73
|
+
payload?.enterpriseId ||
|
|
74
|
+
payload?.ent_id ||
|
|
75
|
+
payload?.entId ||
|
|
76
|
+
"");
|
|
77
|
+
}
|
|
78
|
+
function resolveUserId(accessToken) {
|
|
79
|
+
if (CONFIG.userId)
|
|
80
|
+
return CONFIG.userId;
|
|
81
|
+
const payload = decodeJwtPayload(accessToken);
|
|
82
|
+
return payload?.user_id || payload?.userId || payload?.uid || payload?.sub || "";
|
|
83
|
+
}
|
|
84
|
+
function buildAuthHeaders(accessToken) {
|
|
85
|
+
const tenantId = resolveTenantId(accessToken);
|
|
86
|
+
const enterpriseId = resolveEnterpriseId(accessToken);
|
|
87
|
+
const userId = resolveUserId(accessToken);
|
|
88
|
+
if (!tenantId) {
|
|
89
|
+
console.warn("[codebuddy-external] 未获取到 X-Tenant-Id,请设置 CODEBUDDY_TENANT_ID");
|
|
90
|
+
}
|
|
91
|
+
if (!enterpriseId) {
|
|
92
|
+
console.warn("[codebuddy-external] 未获取到 X-Enterprise-Id,请设置 CODEBUDDY_ENTERPRISE_ID");
|
|
93
|
+
}
|
|
94
|
+
if (!userId) {
|
|
95
|
+
console.warn("[codebuddy-external] 未获取到 X-User-Id,请设置 CODEBUDDY_USER_ID");
|
|
96
|
+
}
|
|
97
|
+
const conversationId = generateUuid();
|
|
98
|
+
const messageId = generateUuid();
|
|
99
|
+
const requestId = messageId;
|
|
100
|
+
const headers = {
|
|
101
|
+
"Accept": "application/json",
|
|
102
|
+
"Content-Type": "application/json",
|
|
103
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
104
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
105
|
+
"X-Conversation-ID": conversationId,
|
|
106
|
+
"X-Conversation-Message-ID": messageId,
|
|
107
|
+
"X-Conversation-Request-ID": requestId,
|
|
108
|
+
"X-Request-ID": requestId,
|
|
109
|
+
"X-Agent-Intent": CONFIG.agentIntent,
|
|
110
|
+
"X-IDE-Type": CONFIG.ideType,
|
|
111
|
+
"X-IDE-Name": CONFIG.ideName,
|
|
112
|
+
"X-IDE-Version": CONFIG.appVersion,
|
|
113
|
+
"X-Domain": CONFIG.domain,
|
|
114
|
+
"X-Product": CONFIG.product,
|
|
115
|
+
"User-Agent": `CLI/${CONFIG.appVersion} CodeBuddy/${CONFIG.appVersion}`,
|
|
116
|
+
};
|
|
117
|
+
if (tenantId)
|
|
118
|
+
headers["X-Tenant-Id"] = tenantId;
|
|
119
|
+
if (enterpriseId)
|
|
120
|
+
headers["X-Enterprise-Id"] = enterpriseId;
|
|
121
|
+
if (userId)
|
|
122
|
+
headers["X-User-Id"] = userId;
|
|
123
|
+
return headers;
|
|
124
|
+
}
|
|
25
125
|
/**
|
|
26
126
|
* Convert OpenAI chat messages to a single prompt string
|
|
27
127
|
*/
|
|
@@ -187,7 +287,7 @@ async function executeCodeBuddyCLI(prompt, model, systemPrompt) {
|
|
|
187
287
|
* Creates a fetch function that invokes codebuddy CLI
|
|
188
288
|
* and converts between OpenAI and CodeBuddy formats
|
|
189
289
|
*/
|
|
190
|
-
function createProxyFetch() {
|
|
290
|
+
function createProxyFetch(auth) {
|
|
191
291
|
return async (url, init) => {
|
|
192
292
|
const urlStr = url.toString();
|
|
193
293
|
// Check if this is a chat completions request
|
|
@@ -202,15 +302,13 @@ function createProxyFetch() {
|
|
|
202
302
|
});
|
|
203
303
|
}
|
|
204
304
|
const openaiRequest = JSON.parse(typeof body === "string" ? body : await new Response(body).text());
|
|
205
|
-
// Convert to CodeBuddy format
|
|
206
|
-
const { prompt, systemPrompt } = convertMessagesToPrompt(openaiRequest.messages);
|
|
207
305
|
// 根据配置选择 HTTP API 模式或 CLI 模式
|
|
208
306
|
if (CONFIG.useHttpApi) {
|
|
209
|
-
return await
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
return await executeViaCli(openaiRequest, prompt, systemPrompt);
|
|
307
|
+
return await executeViaAuthApi(openaiRequest, auth);
|
|
213
308
|
}
|
|
309
|
+
// CLI 模式:将消息拼接为 prompt
|
|
310
|
+
const { prompt, systemPrompt } = convertMessagesToPrompt(openaiRequest.messages);
|
|
311
|
+
return await executeViaCli(openaiRequest, prompt, systemPrompt);
|
|
214
312
|
}
|
|
215
313
|
catch (error) {
|
|
216
314
|
console.error(`[codebuddy-external] Error:`, error);
|
|
@@ -225,61 +323,39 @@ function createProxyFetch() {
|
|
|
225
323
|
};
|
|
226
324
|
}
|
|
227
325
|
/**
|
|
228
|
-
* Execute via HTTP API (
|
|
229
|
-
* 支持流式响应
|
|
326
|
+
* Execute via Auth HTTP API (真实对话接口)
|
|
230
327
|
*/
|
|
231
|
-
async function
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
328
|
+
async function executeViaAuthApi(openaiRequest, auth) {
|
|
329
|
+
if (auth.type !== "oauth" || !auth.access) {
|
|
330
|
+
throw new Error("缺少 access token,无法调用 CodeBuddy API");
|
|
331
|
+
}
|
|
332
|
+
let accessToken = auth.access;
|
|
333
|
+
const requestBody = {
|
|
334
|
+
...openaiRequest,
|
|
335
|
+
response_format: openaiRequest.response_format || { type: "text" },
|
|
336
|
+
stream: openaiRequest.stream ?? true,
|
|
337
|
+
};
|
|
338
|
+
const doRequest = async (token) => {
|
|
339
|
+
const response = await fetch(`${CONFIG.serverUrl}${CONFIG.chatCompletionsPath}`, {
|
|
340
|
+
method: "POST",
|
|
341
|
+
headers: buildAuthHeaders(token),
|
|
342
|
+
body: JSON.stringify(requestBody),
|
|
343
|
+
});
|
|
344
|
+
return response;
|
|
240
345
|
};
|
|
241
|
-
|
|
242
|
-
|
|
346
|
+
let response = await doRequest(accessToken);
|
|
347
|
+
if ((response.status === 401 || response.status === 403) && auth.refresh) {
|
|
348
|
+
const refreshed = await refreshAccessToken(auth.refresh);
|
|
349
|
+
if (refreshed?.accessToken) {
|
|
350
|
+
accessToken = refreshed.accessToken;
|
|
351
|
+
response = await doRequest(accessToken);
|
|
352
|
+
}
|
|
243
353
|
}
|
|
244
|
-
// 调用本地 codebuddy --serve
|
|
245
|
-
const response = await fetch(`${CONFIG.localServeUrl}/agent`, {
|
|
246
|
-
method: "POST",
|
|
247
|
-
headers: {
|
|
248
|
-
"Content-Type": "application/json",
|
|
249
|
-
},
|
|
250
|
-
body: JSON.stringify(agentRequest),
|
|
251
|
-
});
|
|
252
354
|
if (!response.ok) {
|
|
253
355
|
const errorText = await response.text();
|
|
254
|
-
throw new Error(`
|
|
356
|
+
throw new Error(`Auth API error: ${response.status} - ${errorText}`);
|
|
255
357
|
}
|
|
256
|
-
|
|
257
|
-
if (useStream && response.body) {
|
|
258
|
-
return new Response(transformCodeBuddyStreamToOpenAI(response.body, openaiRequest.model), {
|
|
259
|
-
headers: {
|
|
260
|
-
"Content-Type": "text/event-stream",
|
|
261
|
-
"Cache-Control": "no-cache",
|
|
262
|
-
"Connection": "keep-alive",
|
|
263
|
-
},
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
// 非流式响应 - 等待完整结果
|
|
267
|
-
const responseText = await response.text();
|
|
268
|
-
let resultText = responseText.trim();
|
|
269
|
-
// 返回 OpenAI 格式响应
|
|
270
|
-
// 如果客户端请求流式,返回伪流式响应
|
|
271
|
-
if (openaiRequest.stream) {
|
|
272
|
-
return new Response(createOpenAIStreamResponse(resultText, openaiRequest.model), {
|
|
273
|
-
headers: {
|
|
274
|
-
"Content-Type": "text/event-stream",
|
|
275
|
-
"Cache-Control": "no-cache",
|
|
276
|
-
"Connection": "keep-alive",
|
|
277
|
-
},
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
return new Response(createOpenAIResponse(resultText, openaiRequest.model), {
|
|
281
|
-
headers: { "Content-Type": "application/json" },
|
|
282
|
-
});
|
|
358
|
+
return response;
|
|
283
359
|
}
|
|
284
360
|
/**
|
|
285
361
|
* Transform CodeBuddy SSE stream to OpenAI SSE stream
|
|
@@ -521,9 +597,9 @@ const CodeBuddyExternalAuthPlugin = async (_input) => {
|
|
|
521
597
|
// 返回代理 fetch 函数,通过 codebuddy CLI 处理请求
|
|
522
598
|
// baseURL 可以是任意值,因为 fetch 会拦截并调用 CLI
|
|
523
599
|
return {
|
|
524
|
-
apiKey: "cli-proxy", // 占位符,实际认证由
|
|
525
|
-
baseURL:
|
|
526
|
-
fetch: createProxyFetch(),
|
|
600
|
+
apiKey: "cli-proxy", // 占位符,实际认证由 fetch 处理
|
|
601
|
+
baseURL: CONFIG.serverUrl,
|
|
602
|
+
fetch: createProxyFetch(auth),
|
|
527
603
|
};
|
|
528
604
|
}
|
|
529
605
|
return {};
|
|
@@ -580,8 +656,8 @@ const CodeBuddyExternalAuthPlugin = async (_input) => {
|
|
|
580
656
|
if (input.model.providerID !== PROVIDER_ID) {
|
|
581
657
|
return;
|
|
582
658
|
}
|
|
583
|
-
//
|
|
584
|
-
output.options.baseURL =
|
|
659
|
+
// 交由 fetch 处理,baseURL 仅用于 SDK 记录
|
|
660
|
+
output.options.baseURL = CONFIG.serverUrl;
|
|
585
661
|
},
|
|
586
662
|
};
|
|
587
663
|
};
|