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 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
@@ -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; } });
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ export declare const CodeBuddyExternalAuthPlugin: Plugin;
3
+ export default CodeBuddyExternalAuthPlugin;
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
+ }