opencode-codebuddy-external-auth 1.0.6 → 1.0.8
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 +198 -22
- package/package.json +2 -1
package/dist/plugin.js
CHANGED
|
@@ -6,12 +6,8 @@ exports.CodeBuddyExternalAuthPlugin = void 0;
|
|
|
6
6
|
// ============================================================================
|
|
7
7
|
const PROVIDER_ID = "codebuddy-external";
|
|
8
8
|
const CONFIG = {
|
|
9
|
-
// IOA 版本使用 copilot.tencent.com
|
|
9
|
+
// IOA 版本使用 copilot.tencent.com 进行认证
|
|
10
10
|
serverUrl: "https://copilot.tencent.com",
|
|
11
|
-
// API 端点 - CodeBuddy IOA 直接使用 endpoint 作为 baseURL
|
|
12
|
-
// OpenAI SDK 会自动追加 /chat/completions
|
|
13
|
-
// 注意:不需要 /v1 前缀,CodeBuddy 服务端直接接收 /chat/completions
|
|
14
|
-
apiBaseUrl: "https://copilot.tencent.com",
|
|
15
11
|
// 平台标识
|
|
16
12
|
platform: "CLI",
|
|
17
13
|
appVersion: "2.37.20",
|
|
@@ -23,22 +19,199 @@ function sleep(ms) {
|
|
|
23
19
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
24
20
|
}
|
|
25
21
|
/**
|
|
26
|
-
*
|
|
22
|
+
* Convert OpenAI chat messages to a single prompt string
|
|
27
23
|
*/
|
|
28
|
-
function
|
|
24
|
+
function convertMessagesToPrompt(messages) {
|
|
25
|
+
let systemPrompt;
|
|
26
|
+
const conversationParts = [];
|
|
27
|
+
for (const msg of messages) {
|
|
28
|
+
if (msg.role === "system") {
|
|
29
|
+
systemPrompt = msg.content;
|
|
30
|
+
}
|
|
31
|
+
else if (msg.role === "user") {
|
|
32
|
+
conversationParts.push(`User: ${msg.content}`);
|
|
33
|
+
}
|
|
34
|
+
else if (msg.role === "assistant") {
|
|
35
|
+
conversationParts.push(`Assistant: ${msg.content}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// If there's only one user message, use it directly
|
|
39
|
+
const userMessages = messages.filter((m) => m.role === "user");
|
|
40
|
+
if (userMessages.length === 1 && conversationParts.length === 1) {
|
|
41
|
+
return {
|
|
42
|
+
prompt: userMessages[0].content,
|
|
43
|
+
systemPrompt,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
prompt: conversationParts.join("\n\n"),
|
|
48
|
+
systemPrompt,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create a streaming response in OpenAI format from CodeBuddy response
|
|
53
|
+
*/
|
|
54
|
+
function createOpenAIStreamResponse(text, model) {
|
|
55
|
+
const encoder = new TextEncoder();
|
|
56
|
+
return new ReadableStream({
|
|
57
|
+
start(controller) {
|
|
58
|
+
// Send the content in a single chunk
|
|
59
|
+
const chunk = {
|
|
60
|
+
id: `chatcmpl-${Date.now()}`,
|
|
61
|
+
object: "chat.completion.chunk",
|
|
62
|
+
created: Math.floor(Date.now() / 1000),
|
|
63
|
+
model: model,
|
|
64
|
+
choices: [
|
|
65
|
+
{
|
|
66
|
+
index: 0,
|
|
67
|
+
delta: { content: text },
|
|
68
|
+
finish_reason: null,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`));
|
|
73
|
+
// Send done signal
|
|
74
|
+
const doneChunk = {
|
|
75
|
+
id: `chatcmpl-${Date.now()}`,
|
|
76
|
+
object: "chat.completion.chunk",
|
|
77
|
+
created: Math.floor(Date.now() / 1000),
|
|
78
|
+
model: model,
|
|
79
|
+
choices: [
|
|
80
|
+
{
|
|
81
|
+
index: 0,
|
|
82
|
+
delta: {},
|
|
83
|
+
finish_reason: "stop",
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(doneChunk)}\n\n`));
|
|
88
|
+
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
|
89
|
+
controller.close();
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Create a non-streaming response in OpenAI format
|
|
95
|
+
*/
|
|
96
|
+
function createOpenAIResponse(text, model) {
|
|
97
|
+
return JSON.stringify({
|
|
98
|
+
id: `chatcmpl-${Date.now()}`,
|
|
99
|
+
object: "chat.completion",
|
|
100
|
+
created: Math.floor(Date.now() / 1000),
|
|
101
|
+
model: model,
|
|
102
|
+
choices: [
|
|
103
|
+
{
|
|
104
|
+
index: 0,
|
|
105
|
+
message: {
|
|
106
|
+
role: "assistant",
|
|
107
|
+
content: text,
|
|
108
|
+
},
|
|
109
|
+
finish_reason: "stop",
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
usage: {
|
|
113
|
+
prompt_tokens: 0,
|
|
114
|
+
completion_tokens: 0,
|
|
115
|
+
total_tokens: 0,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Execute codebuddy CLI command and return result
|
|
121
|
+
*/
|
|
122
|
+
async function executeCodeBuddyCLI(prompt, model, systemPrompt) {
|
|
123
|
+
const { spawn } = await import("child_process");
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
const args = ["-p", "--output-format", "text"];
|
|
126
|
+
if (model) {
|
|
127
|
+
args.push("--model", model);
|
|
128
|
+
}
|
|
129
|
+
if (systemPrompt) {
|
|
130
|
+
args.push("--system-prompt", systemPrompt);
|
|
131
|
+
}
|
|
132
|
+
args.push(prompt);
|
|
133
|
+
console.log(`[codebuddy-external] Executing: codebuddy ${args.join(" ").substring(0, 100)}...`);
|
|
134
|
+
const child = spawn("codebuddy", args, {
|
|
135
|
+
env: { ...process.env },
|
|
136
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
137
|
+
});
|
|
138
|
+
let stdout = "";
|
|
139
|
+
let stderr = "";
|
|
140
|
+
child.stdout.on("data", (data) => {
|
|
141
|
+
stdout += data.toString();
|
|
142
|
+
});
|
|
143
|
+
child.stderr.on("data", (data) => {
|
|
144
|
+
stderr += data.toString();
|
|
145
|
+
});
|
|
146
|
+
child.on("close", (code) => {
|
|
147
|
+
if (code === 0) {
|
|
148
|
+
resolve(stdout.trim());
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
reject(new Error(`CodeBuddy CLI exited with code ${code}: ${stderr}`));
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
child.on("error", (err) => {
|
|
155
|
+
reject(new Error(`Failed to spawn codebuddy: ${err.message}`));
|
|
156
|
+
});
|
|
157
|
+
// Timeout after 5 minutes
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
child.kill();
|
|
160
|
+
reject(new Error("CodeBuddy CLI timeout after 5 minutes"));
|
|
161
|
+
}, 5 * 60 * 1000);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Creates a fetch function that invokes codebuddy CLI
|
|
166
|
+
* and converts between OpenAI and CodeBuddy formats
|
|
167
|
+
*/
|
|
168
|
+
function createProxyFetch() {
|
|
29
169
|
return async (url, init) => {
|
|
30
|
-
const
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
170
|
+
const urlStr = url.toString();
|
|
171
|
+
// Check if this is a chat completions request
|
|
172
|
+
if (urlStr.includes("/chat/completions") || urlStr.includes("/v1/chat/completions")) {
|
|
173
|
+
try {
|
|
174
|
+
// Parse the OpenAI request
|
|
175
|
+
const body = init?.body;
|
|
176
|
+
if (!body) {
|
|
177
|
+
return new Response(JSON.stringify({ error: "Missing request body" }), {
|
|
178
|
+
status: 400,
|
|
179
|
+
headers: { "Content-Type": "application/json" },
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const openaiRequest = JSON.parse(typeof body === "string" ? body : await new Response(body).text());
|
|
183
|
+
// Convert to CodeBuddy format
|
|
184
|
+
const { prompt, systemPrompt } = convertMessagesToPrompt(openaiRequest.messages);
|
|
185
|
+
console.log(`[codebuddy-external] Invoking CodeBuddy CLI`);
|
|
186
|
+
console.log(`[codebuddy-external] Model: ${openaiRequest.model}, Prompt length: ${prompt.length}`);
|
|
187
|
+
// Execute codebuddy CLI
|
|
188
|
+
const resultText = await executeCodeBuddyCLI(prompt, openaiRequest.model, systemPrompt);
|
|
189
|
+
// Convert response to OpenAI format
|
|
190
|
+
if (openaiRequest.stream) {
|
|
191
|
+
return new Response(createOpenAIStreamResponse(resultText, openaiRequest.model), {
|
|
192
|
+
headers: {
|
|
193
|
+
"Content-Type": "text/event-stream",
|
|
194
|
+
"Cache-Control": "no-cache",
|
|
195
|
+
"Connection": "keep-alive",
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
return new Response(createOpenAIResponse(resultText, openaiRequest.model), {
|
|
201
|
+
headers: { "Content-Type": "application/json" },
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
console.error(`[codebuddy-external] CLI error:`, error);
|
|
207
|
+
return new Response(JSON.stringify({ error: `CodeBuddy CLI error: ${error}` }), {
|
|
208
|
+
status: 500,
|
|
209
|
+
headers: { "Content-Type": "application/json" },
|
|
210
|
+
});
|
|
211
|
+
}
|
|
36
212
|
}
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
headers.set("X-B3-SpanId", crypto.randomUUID().substring(0, 16));
|
|
40
|
-
headers.set("X-B3-Sampled", "1");
|
|
41
|
-
return fetch(url, { ...init, headers });
|
|
213
|
+
// For non-chat-completions requests, pass through normally
|
|
214
|
+
return fetch(url, init);
|
|
42
215
|
};
|
|
43
216
|
}
|
|
44
217
|
// ============================================================================
|
|
@@ -165,10 +338,12 @@ const CodeBuddyExternalAuthPlugin = async (_input) => {
|
|
|
165
338
|
async loader(getAuth) {
|
|
166
339
|
const auth = await getAuth();
|
|
167
340
|
if (auth.type === "oauth" && auth.access) {
|
|
341
|
+
// 返回代理 fetch 函数,通过 codebuddy CLI 处理请求
|
|
342
|
+
// baseURL 可以是任意值,因为 fetch 会拦截并调用 CLI
|
|
168
343
|
return {
|
|
169
|
-
apiKey:
|
|
170
|
-
baseURL:
|
|
171
|
-
fetch:
|
|
344
|
+
apiKey: "cli-proxy", // 占位符,实际认证由 codebuddy CLI 处理
|
|
345
|
+
baseURL: "http://localhost", // 占位符,fetch 会拦截请求
|
|
346
|
+
fetch: createProxyFetch(),
|
|
172
347
|
};
|
|
173
348
|
}
|
|
174
349
|
return {};
|
|
@@ -225,7 +400,8 @@ const CodeBuddyExternalAuthPlugin = async (_input) => {
|
|
|
225
400
|
if (input.model.providerID !== PROVIDER_ID) {
|
|
226
401
|
return;
|
|
227
402
|
}
|
|
228
|
-
|
|
403
|
+
// 占位符,实际请求由 fetch 函数拦截并通过 CLI 处理
|
|
404
|
+
output.options.baseURL = "http://localhost";
|
|
229
405
|
},
|
|
230
406
|
};
|
|
231
407
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-codebuddy-external-auth",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "OpenCode plugin for CodeBuddy External (IOA) authentication",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@opencode-ai/plugin": "^1.0.168",
|
|
26
26
|
"@opencode-ai/sdk": "^1.0.168",
|
|
27
|
+
"@types/node": "^25.0.10",
|
|
27
28
|
"typescript": "^5.7.2"
|
|
28
29
|
},
|
|
29
30
|
"files": [
|