opencode-codebuddy-external-auth 1.0.21 → 1.0.23

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.
Files changed (2) hide show
  1. package/dist/plugin.js +85 -17
  2. package/package.json +1 -1
package/dist/plugin.js CHANGED
@@ -8,8 +8,8 @@ const PROVIDER_ID = "codebuddy-external";
8
8
  const CONFIG = {
9
9
  // IOA 版本使用 copilot.tencent.com 进行认证
10
10
  serverUrl: "https://copilot.tencent.com",
11
- // 真实对话 API 路径
12
- chatCompletionsPath: "/v2/chat/completions",
11
+ // 真实对话 API 路径(优先尝试 /v2/chat/completions)
12
+ chatCompletionsPaths: ["/v2/chat/completions", "/v2/plugin/chat/completions"],
13
13
  // 平台标识
14
14
  platform: "CLI",
15
15
  appVersion: "2.37.20",
@@ -39,6 +39,35 @@ function generateUuid() {
39
39
  }
40
40
  return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
41
41
  }
42
+ function generateHexId(length) {
43
+ const bytes = Math.ceil(length / 2);
44
+ const buffer = new Uint8Array(bytes);
45
+ if (globalThis.crypto?.getRandomValues) {
46
+ globalThis.crypto.getRandomValues(buffer);
47
+ }
48
+ else {
49
+ for (let i = 0; i < buffer.length; i += 1) {
50
+ buffer[i] = Math.floor(Math.random() * 256);
51
+ }
52
+ }
53
+ return Array.from(buffer)
54
+ .map((b) => b.toString(16).padStart(2, "0"))
55
+ .join("")
56
+ .slice(0, length);
57
+ }
58
+ function buildTraceHeaders() {
59
+ const traceId = generateHexId(32);
60
+ const spanId = generateHexId(16);
61
+ const parentSpanId = generateHexId(16);
62
+ const sampled = "1";
63
+ return {
64
+ "X-B3-TraceId": traceId,
65
+ "X-B3-ParentSpanId": parentSpanId,
66
+ "X-B3-SpanId": spanId,
67
+ "X-B3-Sampled": sampled,
68
+ b3: `${traceId}-${spanId}-${sampled}-${parentSpanId}`,
69
+ };
70
+ }
42
71
  function decodeJwtPayload(token) {
43
72
  try {
44
73
  const parts = token.split(".");
@@ -204,10 +233,25 @@ function pickFallbackModel(models) {
204
233
  return "glm-4.7-ioa";
205
234
  return models[0] || "";
206
235
  }
236
+ function buildEnterpriseHeaders(accessToken) {
237
+ const tenantId = cachedTenantId || resolveTenantId(accessToken);
238
+ const enterpriseId = cachedEnterpriseId || resolveEnterpriseId(accessToken);
239
+ const resolvedTenantId = tenantId || enterpriseId;
240
+ if (tenantId)
241
+ cachedTenantId = tenantId;
242
+ if (enterpriseId)
243
+ cachedEnterpriseId = enterpriseId;
244
+ const headers = {};
245
+ if (resolvedTenantId)
246
+ headers["X-Tenant-Id"] = resolvedTenantId;
247
+ if (enterpriseId)
248
+ headers["X-Enterprise-Id"] = enterpriseId;
249
+ return headers;
250
+ }
207
251
  async function buildAuthHeaders(accessToken) {
208
252
  const payload = decodeJwtPayload(accessToken);
209
253
  const roles = payload?.realm_access?.roles || payload?.resource_access?.account?.roles;
210
- const tenantId = cachedTenantId || resolveTenantId(accessToken);
254
+ let tenantId = cachedTenantId || resolveTenantId(accessToken);
211
255
  let enterpriseId = cachedEnterpriseId || resolveEnterpriseId(accessToken);
212
256
  const userId = cachedUserId || resolveUserId(accessToken);
213
257
  if (!enterpriseId) {
@@ -217,6 +261,9 @@ async function buildAuthHeaders(accessToken) {
217
261
  cachedModelIds = config.models;
218
262
  }
219
263
  }
264
+ if (!tenantId && enterpriseId) {
265
+ tenantId = enterpriseId;
266
+ }
220
267
  if (tenantId)
221
268
  cachedTenantId = tenantId;
222
269
  if (enterpriseId)
@@ -243,6 +290,8 @@ async function buildAuthHeaders(accessToken) {
243
290
  const conversationId = generateUuid();
244
291
  const messageId = generateUuid();
245
292
  const requestId = messageId;
293
+ const traceHeaders = buildTraceHeaders();
294
+ const encodedUserId = userId ? encodeURIComponent(userId) : "";
246
295
  const headers = {
247
296
  "Accept": "application/json",
248
297
  "Content-Type": "application/json",
@@ -259,13 +308,14 @@ async function buildAuthHeaders(accessToken) {
259
308
  "X-Domain": CONFIG.domain,
260
309
  "X-Product": CONFIG.product,
261
310
  "User-Agent": `CLI/${CONFIG.appVersion} CodeBuddy/${CONFIG.appVersion}`,
311
+ ...traceHeaders,
262
312
  };
263
313
  if (tenantId)
264
314
  headers["X-Tenant-Id"] = tenantId;
265
315
  if (enterpriseId)
266
316
  headers["X-Enterprise-Id"] = enterpriseId;
267
- if (userId)
268
- headers["X-User-Id"] = userId;
317
+ if (encodedUserId)
318
+ headers["X-User-Id"] = encodedUserId;
269
319
  return headers;
270
320
  }
271
321
  /**
@@ -492,21 +542,32 @@ async function executeViaAuthApi(openaiRequest, auth) {
492
542
  response_format: openaiRequest.response_format || { type: "text" },
493
543
  stream: openaiRequest.stream ?? true,
494
544
  };
495
- const doRequest = async (token) => {
545
+ const doRequest = async (token, path) => {
496
546
  const headers = await buildAuthHeaders(token);
497
- const response = await fetch(`${CONFIG.serverUrl}${CONFIG.chatCompletionsPath}`, {
547
+ const response = await fetch(`${CONFIG.serverUrl}${path}`, {
498
548
  method: "POST",
499
549
  headers,
500
550
  body: JSON.stringify(requestBody),
501
551
  });
502
552
  return response;
503
553
  };
504
- let response = await doRequest(accessToken);
554
+ const requestWithFallback = async (token) => {
555
+ let lastResponse = null;
556
+ for (const path of CONFIG.chatCompletionsPaths) {
557
+ const response = await doRequest(token, path);
558
+ if (response.status !== 404) {
559
+ return response;
560
+ }
561
+ lastResponse = response;
562
+ }
563
+ return lastResponse || doRequest(token, CONFIG.chatCompletionsPaths[0]);
564
+ };
565
+ let response = await requestWithFallback(accessToken);
505
566
  if ((response.status === 401 || response.status === 403) && auth.refresh) {
506
- const refreshed = await refreshAccessToken(auth.refresh);
567
+ const refreshed = await refreshAccessToken(auth.refresh, accessToken);
507
568
  if (refreshed?.accessToken) {
508
569
  accessToken = refreshed.accessToken;
509
- response = await doRequest(accessToken);
570
+ response = await requestWithFallback(accessToken);
510
571
  }
511
572
  }
512
573
  if (!response.ok) {
@@ -716,15 +777,22 @@ async function pollForToken(state, expiresAt, signal) {
716
777
  /**
717
778
  * Refresh the access token
718
779
  */
719
- async function refreshAccessToken(refreshToken) {
780
+ async function refreshAccessToken(refreshToken, accessToken) {
720
781
  try {
782
+ const traceHeaders = buildTraceHeaders();
783
+ const headers = {
784
+ "Content-Type": "application/json",
785
+ "Accept": "application/json",
786
+ "X-Refresh-Token": refreshToken,
787
+ ...traceHeaders,
788
+ };
789
+ if (accessToken) {
790
+ headers["Authorization"] = `Bearer ${accessToken}`;
791
+ Object.assign(headers, buildEnterpriseHeaders(accessToken));
792
+ }
721
793
  const response = await fetch(`${CONFIG.serverUrl}/v2/plugin/auth/token/refresh`, {
722
794
  method: "POST",
723
- headers: {
724
- "Content-Type": "application/json",
725
- "Accept": "application/json",
726
- "Authorization": `Bearer ${refreshToken}`,
727
- },
795
+ headers,
728
796
  });
729
797
  if (!response.ok) {
730
798
  console.warn(`[codebuddy-external] Token refresh failed: ${response.status}`);
@@ -766,7 +834,7 @@ const CodeBuddyExternalAuthPlugin = async (_input) => {
766
834
  if (auth.type !== "oauth" || !auth.refresh) {
767
835
  return null;
768
836
  }
769
- const tokenData = await refreshAccessToken(auth.refresh);
837
+ const tokenData = await refreshAccessToken(auth.refresh, auth.access);
770
838
  if (!tokenData) {
771
839
  return null;
772
840
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-codebuddy-external-auth",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "OpenCode plugin for CodeBuddy External (IOA) authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",