opencode-codebuddy-external-auth 1.0.0
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/README.md +106 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +9 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +205 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# OpenCode CodeBuddy External Auth Plugin
|
|
2
|
+
|
|
3
|
+
OpenCode 插件,用于 CodeBuddy External (IOA) 认证。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install opencode-codebuddy-external-auth
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 配置
|
|
12
|
+
|
|
13
|
+
编辑 `~/.config/opencode/opencode.json`:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"$schema": "https://opencode.ai/config.json",
|
|
18
|
+
"plugin": ["opencode-codebuddy-external-auth"],
|
|
19
|
+
"provider": {
|
|
20
|
+
"codebuddy-external": {
|
|
21
|
+
"npm": "@ai-sdk/anthropic",
|
|
22
|
+
"name": "CodeBuddy External (IOA)",
|
|
23
|
+
"models": {
|
|
24
|
+
"claude-4.5": { "name": "Claude Sonnet 4.5 (x2.20)", "contextLength": 176000 },
|
|
25
|
+
"claude-opus-4.5": { "name": "Claude Opus 4.5 (x3.33)", "contextLength": 176000 },
|
|
26
|
+
"claude-haiku-4.5": { "name": "Claude Haiku 4.5 (x0.67)", "contextLength": 176000 },
|
|
27
|
+
"gemini-3.0-pro": { "name": "Gemini 3.0 Pro (x1.33)", "contextLength": 400000 },
|
|
28
|
+
"gemini-3.0-flash": { "name": "Gemini 3.0 Flash (x0.33)", "contextLength": 400000 },
|
|
29
|
+
"gemini-2.5-pro": { "name": "Gemini 2.5 Pro (x0.95)", "contextLength": 400000 },
|
|
30
|
+
"gpt-5.2": { "name": "GPT-5.2 (x1.33)", "contextLength": 272000 },
|
|
31
|
+
"gpt-5.2-codex": { "name": "GPT-5.2-Codex (x1.33)", "contextLength": 272000 },
|
|
32
|
+
"gpt-5.1": { "name": "GPT-5.1 (x0.95)", "contextLength": 272000 },
|
|
33
|
+
"gpt-5.1-codex": { "name": "GPT-5.1-Codex (x0.95)", "contextLength": 272000 },
|
|
34
|
+
"gpt-5.1-codex-max": { "name": "GPT-5.1-Codex-Max (x0.95)", "contextLength": 272000 },
|
|
35
|
+
"gpt-5.1-codex-mini": { "name": "GPT-5.1-Codex-Mini (x0.19)", "contextLength": 272000 },
|
|
36
|
+
"kimi-k2-thinking": { "name": "Kimi-K2-Thinking (x0.46)", "contextLength": 256000 },
|
|
37
|
+
"glm-4.7-ioa": { "name": "GLM-4.7 [免费]", "contextLength": 200000 },
|
|
38
|
+
"glm-4.6-ioa": { "name": "GLM-4.6 [免费]", "contextLength": 168000 },
|
|
39
|
+
"glm-4.6v-ioa": { "name": "GLM-4.6V [免费]", "contextLength": 128000 },
|
|
40
|
+
"deepseek-v3-2-volc-ioa": { "name": "DeepSeek-V3.2 [免费]", "contextLength": 96000 }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"model": "codebuddy-external/claude-4.5"
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 使用
|
|
49
|
+
|
|
50
|
+
1. 启动 OpenCode: `opencode`
|
|
51
|
+
2. 选择 CodeBuddy External 模型
|
|
52
|
+
3. 选择 "IOA 登录 (浏览器)" 认证方式
|
|
53
|
+
4. 在浏览器中完成 IOA 登录
|
|
54
|
+
5. 返回终端开始使用
|
|
55
|
+
|
|
56
|
+
## 支持的模型
|
|
57
|
+
|
|
58
|
+
根据 CodeBuddy IOA 版本配置(2026-01-24):
|
|
59
|
+
|
|
60
|
+
### Claude 系列
|
|
61
|
+
| 模型 ID | 名称 | Credits | Context |
|
|
62
|
+
|---------|------|---------|---------|
|
|
63
|
+
| `claude-4.5` | Claude Sonnet 4.5 | x2.20 | 176K |
|
|
64
|
+
| `claude-opus-4.5` | Claude Opus 4.5 | x3.33 | 176K |
|
|
65
|
+
| `claude-haiku-4.5` | Claude Haiku 4.5 | x0.67 | 176K |
|
|
66
|
+
|
|
67
|
+
### Gemini 系列
|
|
68
|
+
| 模型 ID | 名称 | Credits | Context |
|
|
69
|
+
|---------|------|---------|---------|
|
|
70
|
+
| `gemini-3.0-pro` | Gemini 3.0 Pro | x1.33 | 400K |
|
|
71
|
+
| `gemini-3.0-flash` | Gemini 3.0 Flash | x0.33 | 400K |
|
|
72
|
+
| `gemini-2.5-pro` | Gemini 2.5 Pro | x0.95 | 400K |
|
|
73
|
+
|
|
74
|
+
### GPT 系列
|
|
75
|
+
| 模型 ID | 名称 | Credits | Context |
|
|
76
|
+
|---------|------|---------|---------|
|
|
77
|
+
| `gpt-5.2` | GPT-5.2 | x1.33 | 272K |
|
|
78
|
+
| `gpt-5.2-codex` | GPT-5.2-Codex | x1.33 | 272K |
|
|
79
|
+
| `gpt-5.1` | GPT-5.1 | x0.95 | 272K |
|
|
80
|
+
| `gpt-5.1-codex` | GPT-5.1-Codex | x0.95 | 272K |
|
|
81
|
+
| `gpt-5.1-codex-max` | GPT-5.1-Codex-Max | x0.95 | 272K |
|
|
82
|
+
| `gpt-5.1-codex-mini` | GPT-5.1-Codex-Mini | x0.19 | 272K |
|
|
83
|
+
|
|
84
|
+
### 其他模型
|
|
85
|
+
| 模型 ID | 名称 | Credits | Context |
|
|
86
|
+
|---------|------|---------|---------|
|
|
87
|
+
| `kimi-k2-thinking` | Kimi-K2-Thinking | x0.46 | 256K |
|
|
88
|
+
|
|
89
|
+
### 免费模型 (x0.00)
|
|
90
|
+
| 模型 ID | 名称 | Context |
|
|
91
|
+
|---------|------|---------|
|
|
92
|
+
| `glm-4.7-ioa` | GLM-4.7 | 200K |
|
|
93
|
+
| `glm-4.6-ioa` | GLM-4.6 | 168K |
|
|
94
|
+
| `glm-4.6v-ioa` | GLM-4.6V (多模态) | 128K |
|
|
95
|
+
| `deepseek-v3-2-volc-ioa` | DeepSeek-V3.2 | 96K |
|
|
96
|
+
|
|
97
|
+
## 认证流程
|
|
98
|
+
|
|
99
|
+
1. 插件请求 `/plugin/auth/state` 获取认证状态和 URL
|
|
100
|
+
2. 用户在浏览器中打开 URL 完成 IOA 登录
|
|
101
|
+
3. 插件轮询 `/plugin/auth/token` 获取访问令牌
|
|
102
|
+
4. Token 到期前自动通过 `/plugin/auth/token/refresh` 刷新
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CodeBuddyExternalAuthPlugin, default } from "./plugin.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = exports.CodeBuddyExternalAuthPlugin = void 0;
|
|
7
|
+
var plugin_js_1 = require("./plugin.js");
|
|
8
|
+
Object.defineProperty(exports, "CodeBuddyExternalAuthPlugin", { enumerable: true, get: function () { return plugin_js_1.CodeBuddyExternalAuthPlugin; } });
|
|
9
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(plugin_js_1).default; } });
|
package/dist/plugin.d.ts
ADDED
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CodeBuddyExternalAuthPlugin = void 0;
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Configuration
|
|
6
|
+
// ============================================================================
|
|
7
|
+
const PROVIDER_ID = "codebuddy-external";
|
|
8
|
+
const CONFIG = {
|
|
9
|
+
// IOA 版本使用 copilot.tencent.com
|
|
10
|
+
serverUrl: "https://copilot.tencent.com",
|
|
11
|
+
// 认证 ID(来自 product.json)
|
|
12
|
+
authId: "Tencent-Cloud.coding-copilot",
|
|
13
|
+
// API 端点
|
|
14
|
+
// 注意:Anthropic SDK 会自动追加 /messages,所以 baseURL 需要以 /v1 结尾
|
|
15
|
+
// 完整路径: https://copilot.tencent.com/plugin/v1/messages
|
|
16
|
+
apiBaseUrl: "https://copilot.tencent.com/plugin/v1",
|
|
17
|
+
// 平台标识
|
|
18
|
+
platform: "CLI",
|
|
19
|
+
appVersion: "2.37.20",
|
|
20
|
+
};
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Utility Functions
|
|
23
|
+
// ============================================================================
|
|
24
|
+
function sleep(ms) {
|
|
25
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Creates an authenticated fetch function with CodeBuddy headers
|
|
29
|
+
*/
|
|
30
|
+
function createAuthenticatedFetch(accessToken, userId) {
|
|
31
|
+
return async (url, init) => {
|
|
32
|
+
const headers = new Headers(init?.headers);
|
|
33
|
+
// Bearer token authentication
|
|
34
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
35
|
+
// User identification (URL encoded)
|
|
36
|
+
if (userId) {
|
|
37
|
+
headers.set("X-User-Id", encodeURIComponent(userId));
|
|
38
|
+
}
|
|
39
|
+
// Request tracing
|
|
40
|
+
headers.set("X-B3-TraceId", crypto.randomUUID().replace(/-/g, ""));
|
|
41
|
+
headers.set("X-B3-SpanId", crypto.randomUUID().substring(0, 16));
|
|
42
|
+
headers.set("X-B3-Sampled", "1");
|
|
43
|
+
return fetch(url, { ...init, headers });
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// OAuth Flow Implementation (External Link / IOA)
|
|
48
|
+
// ============================================================================
|
|
49
|
+
/**
|
|
50
|
+
* Start authentication by getting the auth state URL
|
|
51
|
+
*/
|
|
52
|
+
async function getAuthState() {
|
|
53
|
+
const response = await fetch(`${CONFIG.serverUrl}/plugin/auth/state?platform=${CONFIG.platform}`, {
|
|
54
|
+
method: "GET",
|
|
55
|
+
headers: {
|
|
56
|
+
"Content-Type": "application/json",
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
throw new Error(`Auth state request failed: ${response.status}`);
|
|
61
|
+
}
|
|
62
|
+
const data = await response.json();
|
|
63
|
+
if (!data.data?.state || !data.data?.url) {
|
|
64
|
+
throw new Error("Invalid auth state response");
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
state: data.data.state,
|
|
68
|
+
url: data.data.url,
|
|
69
|
+
expiresAt: Date.now() + 10 * 60 * 1000, // 10 minutes
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Poll for token after user completes browser authentication
|
|
74
|
+
*/
|
|
75
|
+
async function pollForToken(state, expiresAt, signal) {
|
|
76
|
+
const pollInterval = 3000; // 3 seconds
|
|
77
|
+
while (Date.now() < expiresAt) {
|
|
78
|
+
if (signal?.aborted) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
await sleep(pollInterval);
|
|
82
|
+
try {
|
|
83
|
+
const response = await fetch(`${CONFIG.serverUrl}/plugin/auth/token?state=${state}`, {
|
|
84
|
+
method: "GET",
|
|
85
|
+
headers: {
|
|
86
|
+
"Content-Type": "application/json",
|
|
87
|
+
},
|
|
88
|
+
signal,
|
|
89
|
+
});
|
|
90
|
+
if (response.ok) {
|
|
91
|
+
const data = await response.json();
|
|
92
|
+
if (data.data?.accessToken) {
|
|
93
|
+
return data.data;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Token not ready yet, continue polling
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
// Network error or abort, continue polling if not aborted
|
|
100
|
+
if (signal?.aborted) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Refresh the access token
|
|
109
|
+
*/
|
|
110
|
+
async function refreshAccessToken(refreshToken) {
|
|
111
|
+
try {
|
|
112
|
+
const response = await fetch(`${CONFIG.serverUrl}/plugin/auth/token/refresh`, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: {
|
|
115
|
+
"Content-Type": "application/json",
|
|
116
|
+
Authorization: `Bearer ${refreshToken}`,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
console.warn(`[codebuddy-external] Token refresh failed: ${response.status}`);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const data = await response.json();
|
|
124
|
+
return data.data || null;
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
console.warn(`[codebuddy-external] Token refresh error:`, error);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// ============================================================================
|
|
132
|
+
// Plugin Export
|
|
133
|
+
// ============================================================================
|
|
134
|
+
const CodeBuddyExternalAuthPlugin = async (_input) => {
|
|
135
|
+
return {
|
|
136
|
+
auth: {
|
|
137
|
+
provider: PROVIDER_ID,
|
|
138
|
+
async loader(getAuth) {
|
|
139
|
+
const auth = await getAuth();
|
|
140
|
+
if (auth.type === "oauth" && auth.access) {
|
|
141
|
+
return {
|
|
142
|
+
apiKey: auth.access,
|
|
143
|
+
baseURL: CONFIG.apiBaseUrl,
|
|
144
|
+
fetch: createAuthenticatedFetch(auth.access, auth.userId),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return {};
|
|
148
|
+
},
|
|
149
|
+
async refresh(auth) {
|
|
150
|
+
if (auth.type !== "oauth" || !auth.refresh) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
const tokenData = await refreshAccessToken(auth.refresh);
|
|
154
|
+
if (!tokenData) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
access: tokenData.accessToken,
|
|
159
|
+
refresh: tokenData.refreshToken || auth.refresh,
|
|
160
|
+
expires: tokenData.expiresIn
|
|
161
|
+
? Date.now() + tokenData.expiresIn * 1000
|
|
162
|
+
: Date.now() + 24 * 60 * 60 * 1000, // Default 24 hours
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
methods: [
|
|
166
|
+
{
|
|
167
|
+
label: "IOA 登录 (浏览器)",
|
|
168
|
+
type: "oauth",
|
|
169
|
+
async authorize() {
|
|
170
|
+
// Get auth state from server
|
|
171
|
+
const authState = await getAuthState();
|
|
172
|
+
return {
|
|
173
|
+
url: authState.url,
|
|
174
|
+
instructions: `请在浏览器中完成 IOA 登录`,
|
|
175
|
+
method: "auto",
|
|
176
|
+
async callback() {
|
|
177
|
+
// Poll for token
|
|
178
|
+
const tokenData = await pollForToken(authState.state, authState.expiresAt);
|
|
179
|
+
if (!tokenData) {
|
|
180
|
+
return { type: "failed" };
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
type: "success",
|
|
184
|
+
access: tokenData.accessToken,
|
|
185
|
+
refresh: tokenData.refreshToken || "",
|
|
186
|
+
expires: tokenData.expiresIn
|
|
187
|
+
? Date.now() + tokenData.expiresIn * 1000
|
|
188
|
+
: Date.now() + 24 * 60 * 60 * 1000,
|
|
189
|
+
};
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
async "chat.params"(input, output) {
|
|
197
|
+
if (input.model.providerID !== PROVIDER_ID) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
output.options.baseURL = CONFIG.apiBaseUrl;
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
exports.CodeBuddyExternalAuthPlugin = CodeBuddyExternalAuthPlugin;
|
|
205
|
+
exports.default = exports.CodeBuddyExternalAuthPlugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-codebuddy-external-auth",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenCode plugin for CodeBuddy External (IOA) authentication",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepublishOnly": "npm run build"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"opencode",
|
|
13
|
+
"plugin",
|
|
14
|
+
"codebuddy",
|
|
15
|
+
"ioa",
|
|
16
|
+
"authentication"
|
|
17
|
+
],
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@opencode-ai/plugin": "*",
|
|
22
|
+
"@opencode-ai/sdk": "*"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@opencode-ai/plugin": "^1.0.168",
|
|
26
|
+
"@opencode-ai/sdk": "^1.0.168",
|
|
27
|
+
"typescript": "^5.7.2"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md"
|
|
32
|
+
]
|
|
33
|
+
}
|